Merge branch 'maint'
[gitweb.git] / sha1_name.c
index 722fc35a6d98e1c260c6e14738ad3b2d41d924aa..77299257bf3aa91079d5b883c6676afa6fd2d01c 100644 (file)
@@ -5,6 +5,7 @@
 #include "blob.h"
 #include "tree-walk.h"
 #include "refs.h"
+#include "remote.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -238,8 +239,29 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is a magic short-hand form
+ * to name a branch.
+ */
+static char *substitute_branch_name(const char **string, int *len)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret = interpret_branch_name(*string, &buf);
+
+       if (ret == *len) {
+               size_t size;
+               *string = strbuf_detach(&buf, &size);
+               *len = size;
+               return (char *)*string;
+       }
+
+       return NULL;
+}
+
 int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 {
+       char *last_branch = substitute_branch_name(&str, &len);
        const char **p, *r;
        int refs_found = 0;
 
@@ -248,22 +270,27 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
                char fullref[PATH_MAX];
                unsigned char sha1_from_ref[20];
                unsigned char *this_result;
+               int flag;
 
                this_result = refs_found ? sha1_from_ref : sha1;
                mksnpath(fullref, sizeof(fullref), *p, len, str);
-               r = resolve_ref(fullref, this_result, 1, NULL);
+               r = resolve_ref(fullref, this_result, 1, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
                        if (!warn_ambiguous_refs)
                                break;
-               }
+               } else if ((flag & REF_ISSYMREF) &&
+                          (len != 4 || strcmp(str, "HEAD")))
+                       warning("ignoring dangling symref %s.", fullref);
        }
+       free(last_branch);
        return refs_found;
 }
 
 int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 {
+       char *last_branch = substitute_branch_name(&str, &len);
        const char **p;
        int logs_found = 0;
 
@@ -294,9 +321,26 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
                if (!warn_ambiguous_refs)
                        break;
        }
+       free(last_branch);
        return logs_found;
 }
 
+static inline int upstream_mark(const char *string, int len)
+{
+       const char *suffix[] = { "@{upstream}", "@{u}" };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+               int suffix_len = strlen(suffix[i]);
+               if (suffix_len <= len
+                   && !memcmp(string, suffix[i], suffix_len))
+                       return suffix_len;
+       }
+       return 0;
+}
+
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
        static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,13 +351,15 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
 
-       /* basic@{time or number} format to query ref-log */
+       /* basic@{time or number or -number} format to query ref-log */
        reflog_len = at = 0;
        if (len && str[len-1] == '}') {
-               for (at = 0; at < len - 1; at++) {
+               for (at = len-2; at >= 0; at--) {
                        if (str[at] == '@' && str[at+1] == '{') {
-                               reflog_len = (len-1) - (at+2);
-                               len = at;
+                               if (!upstream_mark(str + at, len - at)) {
+                                       reflog_len = (len-1) - (at+2);
+                                       len = at;
+                               }
                                break;
                        }
                }
@@ -324,6 +370,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                return -1;
 
        if (!len && reflog_len) {
+               struct strbuf buf = STRBUF_INIT;
+               int ret;
+               /* try the @{-N} syntax for n-th checkout */
+               ret = interpret_branch_name(str+at, &buf);
+               if (ret > 0) {
+                       /* substitute this branch name and restart */
+                       return get_sha1_1(buf.buf, buf.len, sha1);
+               } else if (ret == 0) {
+                       return -1;
+               }
                /* allow "@{...}" to mean the current branch reflog */
                refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
        } else if (reflog_len)
@@ -343,6 +399,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                unsigned long co_time;
                int co_tz, co_cnt;
 
+               /* a @{-N} placed anywhere except the start is an error */
+               if (str[at+2] == '-')
+                       return -1;
+
                /* Is it asking for N-th entry, or approxidate? */
                for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
                        char ch = str[at+2+i];
@@ -357,9 +417,12 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                } else if (0 <= nth)
                        at_time = 0;
                else {
+                       int errors = 0;
                        char *tmp = xstrndup(str + at + 2, reflog_len);
-                       at_time = approxidate(tmp);
+                       at_time = approxidate_careful(tmp, &errors);
                        free(tmp);
+                       if (errors)
+                               return -1;
                }
                if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
                                &co_time, &co_tz, &co_cnt)) {
@@ -379,8 +442,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        return 0;
 }
 
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
 static int get_parent(const char *name, int len,
                      unsigned char *result, int idx)
 {
@@ -674,6 +735,199 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
        return retval;
 }
 
+struct grab_nth_branch_switch_cbdata {
+       long cnt, alloc;
+       struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+                                 const char *email, unsigned long timestamp, int tz,
+                                 const char *message, void *cb_data)
+{
+       struct grab_nth_branch_switch_cbdata *cb = cb_data;
+       const char *match = NULL, *target = NULL;
+       size_t len;
+       int nth;
+
+       if (!prefixcmp(message, "checkout: moving from ")) {
+               match = message + strlen("checkout: moving from ");
+               target = strstr(match, " to ");
+       }
+
+       if (!match || !target)
+               return 0;
+
+       len = target - match;
+       nth = cb->cnt++ % cb->alloc;
+       strbuf_reset(&cb->buf[nth]);
+       strbuf_add(&cb->buf[nth], match, len);
+       return 0;
+}
+
+/*
+ * Parse @{-N} syntax, return the number of characters parsed
+ * if successful; otherwise signal an error with negative value.
+ */
+static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
+{
+       long nth;
+       int i, retval;
+       struct grab_nth_branch_switch_cbdata cb;
+       const char *brace;
+       char *num_end;
+
+       if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+               return -1;
+       brace = strchr(name, '}');
+       if (!brace)
+               return -1;
+       nth = strtol(name+3, &num_end, 10);
+       if (num_end != brace)
+               return -1;
+       if (nth <= 0)
+               return -1;
+       cb.alloc = nth;
+       cb.buf = xmalloc(nth * sizeof(struct strbuf));
+       for (i = 0; i < nth; i++)
+               strbuf_init(&cb.buf[i], 20);
+       cb.cnt = 0;
+       retval = 0;
+       for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
+       if (cb.cnt < nth) {
+               cb.cnt = 0;
+               for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+       }
+       if (cb.cnt < nth)
+               goto release_return;
+       i = cb.cnt % nth;
+       strbuf_reset(buf);
+       strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+       retval = brace-name+1;
+
+release_return:
+       for (i = 0; i < nth; i++)
+               strbuf_release(&cb.buf[i]);
+       free(cb.buf);
+
+       return retval;
+}
+
+int get_sha1_mb(const char *name, unsigned char *sha1)
+{
+       struct commit *one, *two;
+       struct commit_list *mbs;
+       unsigned char sha1_tmp[20];
+       const char *dots;
+       int st;
+
+       dots = strstr(name, "...");
+       if (!dots)
+               return get_sha1(name, sha1);
+       if (dots == name)
+               st = get_sha1("HEAD", sha1_tmp);
+       else {
+               struct strbuf sb;
+               strbuf_init(&sb, dots - name);
+               strbuf_add(&sb, name, dots - name);
+               st = get_sha1(sb.buf, sha1_tmp);
+               strbuf_release(&sb);
+       }
+       if (st)
+               return st;
+       one = lookup_commit_reference_gently(sha1_tmp, 0);
+       if (!one)
+               return -1;
+
+       if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
+               return -1;
+       two = lookup_commit_reference_gently(sha1_tmp, 0);
+       if (!two)
+               return -1;
+       mbs = get_merge_bases(one, two, 1);
+       if (!mbs || mbs->next)
+               st = -1;
+       else {
+               st = 0;
+               hashcpy(sha1, mbs->item->object.sha1);
+       }
+       free_commit_list(mbs);
+       return st;
+}
+
+/*
+ * This reads short-hand syntax that not only evaluates to a commit
+ * object name, but also can act as if the end user spelled the name
+ * of the branch from the command line.
+ *
+ * - "@{-N}" finds the name of the Nth previous branch we were on, and
+ *   places the name of the branch in the given buf and returns the
+ *   number of characters parsed if successful.
+ *
+ * - "<branch>@{upstream}" finds the name of the other ref that
+ *   <branch> is configured to merge with (missing <branch> defaults
+ *   to the current branch), and places the name of the branch in the
+ *   given buf and returns the number of characters parsed if
+ *   successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_branch_name(const char *name, struct strbuf *buf)
+{
+       char *cp;
+       struct branch *upstream;
+       int namelen = strlen(name);
+       int len = interpret_nth_prior_checkout(name, buf);
+       int tmp_len;
+
+       if (!len)
+               return len; /* syntax Ok, not enough switches */
+       if (0 < len && len == namelen)
+               return len; /* consumed all */
+       else if (0 < len) {
+               /* we have extra data, which might need further processing */
+               struct strbuf tmp = STRBUF_INIT;
+               int used = buf->len;
+               int ret;
+
+               strbuf_add(buf, name + len, namelen - len);
+               ret = interpret_branch_name(buf->buf, &tmp);
+               /* that data was not interpreted, remove our cruft */
+               if (ret < 0) {
+                       strbuf_setlen(buf, used);
+                       return len;
+               }
+               strbuf_reset(buf);
+               strbuf_addbuf(buf, &tmp);
+               strbuf_release(&tmp);
+               /* tweak for size of {-N} versus expanded ref name */
+               return ret - used + len;
+       }
+
+       cp = strchr(name, '@');
+       if (!cp)
+               return -1;
+       tmp_len = upstream_mark(cp, namelen - (cp - name));
+       if (!tmp_len)
+               return -1;
+       len = cp + tmp_len - name;
+       cp = xstrndup(name, cp - name);
+       upstream = branch_get(*cp ? cp : NULL);
+       if (!upstream
+           || !upstream->merge
+           || !upstream->merge[0]->dst)
+               return error("No upstream branch found for '%s'", cp);
+       free(cp);
+       cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
+       strbuf_reset(buf);
+       strbuf_addstr(buf, cp);
+       free(cp);
+       return len;
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
@@ -684,7 +938,96 @@ int get_sha1(const char *name, unsigned char *sha1)
        return get_sha1_with_mode(name, sha1, &unused);
 }
 
-int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
+/* Must be called only when object_name:filename doesn't exist. */
+static void diagnose_invalid_sha1_path(const char *prefix,
+                                      const char *filename,
+                                      const unsigned char *tree_sha1,
+                                      const char *object_name)
+{
+       struct stat st;
+       unsigned char sha1[20];
+       unsigned mode;
+
+       if (!prefix)
+               prefix = "";
+
+       if (!lstat(filename, &st))
+               die("Path '%s' exists on disk, but not in '%s'.",
+                   filename, object_name);
+       if (errno == ENOENT || errno == ENOTDIR) {
+               char *fullname = xmalloc(strlen(filename)
+                                            + strlen(prefix) + 1);
+               strcpy(fullname, prefix);
+               strcat(fullname, filename);
+
+               if (!get_tree_entry(tree_sha1, fullname,
+                                   sha1, &mode)) {
+                       die("Path '%s' exists, but not '%s'.\n"
+                           "Did you mean '%s:%s'?",
+                           fullname,
+                           filename,
+                           object_name,
+                           fullname);
+               }
+               die("Path '%s' does not exist in '%s'",
+                   filename, object_name);
+       }
+}
+
+/* Must be called only when :stage:filename doesn't exist. */
+static void diagnose_invalid_index_path(int stage,
+                                       const char *prefix,
+                                       const char *filename)
+{
+       struct stat st;
+       struct cache_entry *ce;
+       int pos;
+       unsigned namelen = strlen(filename);
+       unsigned fullnamelen;
+       char *fullname;
+
+       if (!prefix)
+               prefix = "";
+
+       /* Wrong stage number? */
+       pos = cache_name_pos(filename, namelen);
+       if (pos < 0)
+               pos = -pos - 1;
+       ce = active_cache[pos];
+       if (ce_namelen(ce) == namelen &&
+           !memcmp(ce->name, filename, namelen))
+               die("Path '%s' is in the index, but not at stage %d.\n"
+                   "Did you mean ':%d:%s'?",
+                   filename, stage,
+                   ce_stage(ce), filename);
+
+       /* Confusion between relative and absolute filenames? */
+       fullnamelen = namelen + strlen(prefix);
+       fullname = xmalloc(fullnamelen + 1);
+       strcpy(fullname, prefix);
+       strcat(fullname, filename);
+       pos = cache_name_pos(fullname, fullnamelen);
+       if (pos < 0)
+               pos = -pos - 1;
+       ce = active_cache[pos];
+       if (ce_namelen(ce) == fullnamelen &&
+           !memcmp(ce->name, fullname, fullnamelen))
+               die("Path '%s' is in the index, but not '%s'.\n"
+                   "Did you mean ':%d:%s'?",
+                   fullname, filename,
+                   ce_stage(ce), fullname);
+
+       if (!lstat(filename, &st))
+               die("Path '%s' exists on disk, but not in the index.", filename);
+       if (errno == ENOENT || errno == ENOTDIR)
+               die("Path '%s' does not exist (neither on disk nor in the index).",
+                   filename);
+
+       free(fullname);
+}
+
+
+int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
 {
        int ret, bracket_depth;
        int namelen = strlen(name);
@@ -730,6 +1073,8 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
                        }
                        pos++;
                }
+               if (!gently)
+                       diagnose_invalid_index_path(stage, prefix, cp);
                return -1;
        }
        for (cp = name, bracket_depth = 0; *cp; cp++) {
@@ -742,9 +1087,25 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
        }
        if (*cp == ':') {
                unsigned char tree_sha1[20];
-               if (!get_sha1_1(name, cp-name, tree_sha1))
-                       return get_tree_entry(tree_sha1, cp+1, sha1,
-                                             mode);
+               char *object_name = NULL;
+               if (!gently) {
+                       object_name = xmalloc(cp-name+1);
+                       strncpy(object_name, name, cp-name);
+                       object_name[cp-name] = '\0';
+               }
+               if (!get_sha1_1(name, cp-name, tree_sha1)) {
+                       const char *filename = cp+1;
+                       ret = get_tree_entry(tree_sha1, filename, sha1, mode);
+                       if (!gently) {
+                               diagnose_invalid_sha1_path(prefix, filename,
+                                                          tree_sha1, object_name);
+                               free(object_name);
+                       }
+                       return ret;
+               } else {
+                       if (!gently)
+                               die("Invalid object name '%s'.", object_name);
+               }
        }
        return ret;
 }