sha1_name: add get_sha1_with_context()
[gitweb.git] / sha1_name.c
index 1739e9e61240c33a1e2bf110bbeeee111c380716..7fdb20277f16c05213479e3a757745f56096f3f1 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)
 {
@@ -240,7 +241,8 @@ static int ambiguous_path(const char *path, int len)
 
 /*
  * *string and *len will only be substituted, and *string returned (for
- * later free()ing) if the string passed in is of the form @{-<n>}.
+ * 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)
 {
@@ -278,8 +280,7 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
                                *ref = xstrdup(r);
                        if (!warn_ambiguous_refs)
                                break;
-               } else if ((flag & REF_ISSYMREF) &&
-                          (len != 4 || strcmp(str, "HEAD")))
+               } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD"))
                        warning("ignoring dangling symref %s.", fullref);
        }
        free(last_branch);
@@ -323,6 +324,20 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
        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)
@@ -340,8 +355,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len && str[len-1] == '}') {
                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;
                        }
                }
@@ -381,6 +398,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];
@@ -395,9 +416,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)) {
@@ -740,17 +764,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 }
 
 /*
- * This reads "@{-N}" syntax, 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.
- *
- * 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.
+ * Parse @{-N} syntax, return the number of characters parsed
+ * if successful; otherwise signal an error with negative value.
  */
-int interpret_branch_name(const char *name, struct strbuf *buf)
+static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
 {
        long nth;
        int i, retval;
@@ -836,14 +853,88 @@ int get_sha1_mb(const char *name, unsigned char *sha1)
        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"
  */
 int get_sha1(const char *name, unsigned char *sha1)
 {
-       unsigned unused;
-       return get_sha1_with_mode(name, sha1, &unused);
+       struct object_context unused;
+       return get_sha1_with_context(name, sha1, &unused);
 }
 
 /* Must be called only when object_name:filename doesn't exist. */
@@ -901,13 +992,15 @@ static void diagnose_invalid_index_path(int stage,
        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);
+       if (pos < active_nr) {
+               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);
@@ -917,13 +1010,15 @@ static void diagnose_invalid_index_path(int stage,
        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 (pos < active_nr) {
+               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);
@@ -936,12 +1031,24 @@ static void diagnose_invalid_index_path(int stage,
 
 
 int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
+{
+       struct object_context oc;
+       int ret;
+       ret = get_sha1_with_context_1(name, sha1, &oc, gently, prefix);
+       *mode = oc.mode;
+       return ret;
+}
+
+int get_sha1_with_context_1(const char *name, unsigned char *sha1,
+                           struct object_context *oc,
+                           int gently, const char *prefix)
 {
        int ret, bracket_depth;
        int namelen = strlen(name);
        const char *cp;
 
-       *mode = S_IFINVALID;
+       memset(oc, 0, sizeof(*oc));
+       oc->mode = S_IFINVALID;
        ret = get_sha1_1(name, namelen, sha1);
        if (!ret)
                return ret;
@@ -964,6 +1071,11 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
                        cp = name + 3;
                }
                namelen = namelen - (cp - name);
+
+               strncpy(oc->path, cp,
+                       sizeof(oc->path));
+               oc->path[sizeof(oc->path)-1] = '\0';
+
                if (!active_cache)
                        read_cache();
                pos = cache_name_pos(cp, namelen);
@@ -976,7 +1088,6 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
                                break;
                        if (ce_stage(ce) == stage) {
                                hashcpy(sha1, ce->sha1);
-                               *mode = ce->ce_mode;
                                return 0;
                        }
                        pos++;
@@ -1003,12 +1114,17 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
                }
                if (!get_sha1_1(name, cp-name, tree_sha1)) {
                        const char *filename = cp+1;
-                       ret = get_tree_entry(tree_sha1, filename, sha1, mode);
+                       ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
                        if (!gently) {
                                diagnose_invalid_sha1_path(prefix, filename,
                                                           tree_sha1, object_name);
                                free(object_name);
                        }
+                       hashcpy(oc->tree, tree_sha1);
+                       strncpy(oc->path, filename,
+                               sizeof(oc->path));
+                       oc->path[sizeof(oc->path)-1] = '\0';
+
                        return ret;
                } else {
                        if (!gently)