Merge branch 'nd/warn-ambiguous-object-name'
authorJunio C Hamano <gitster@pobox.com>
Tue, 11 Jun 2013 20:31:07 +0000 (13:31 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 11 Jun 2013 20:31:07 +0000 (13:31 -0700)
"git cmd <name>", when <name> happens to be a 40-hex string,
directly uses the 40-hex string as an object name, even if a ref
"refs/<some hierarchy>/<name>" exists. This disambiguation order
is unlikely to change, but we should warn about the ambiguity just
like we warn when more than one refs/ hierachies share the same
name.

* nd/warn-ambiguous-object-name:
get_sha1: warn about full or short object names that look like refs

1  2 
advice.c
advice.h
sha1_name.c
diff --combined advice.c
index a8deee6e6419a1e867e06e90a70300cd2e01b556,22abde9bc4edc5d90fa8e12e5e27654948955b23..54315cbd0a554bdeb1ace160a5df7cf6c485e559
+++ b/advice.c
@@@ -8,12 -8,11 +8,13 @@@ int advice_push_already_exists = 1
  int advice_push_fetch_first = 1;
  int advice_push_needs_force = 1;
  int advice_status_hints = 1;
 +int advice_status_u_option = 1;
  int advice_commit_before_merge = 1;
  int advice_resolve_conflict = 1;
  int advice_implicit_identity = 1;
  int advice_detached_head = 1;
 +int advice_set_upstream_failure = 1;
+ int advice_object_name_warning = 1;
  
  static struct {
        const char *name;
        { "pushfetchfirst", &advice_push_fetch_first },
        { "pushneedsforce", &advice_push_needs_force },
        { "statushints", &advice_status_hints },
 +      { "statusuoption", &advice_status_u_option },
        { "commitbeforemerge", &advice_commit_before_merge },
        { "resolveconflict", &advice_resolve_conflict },
        { "implicitidentity", &advice_implicit_identity },
        { "detachedhead", &advice_detached_head },
 +      { "setupstreamfailure", &advice_set_upstream_failure },
+       { "object_name_warning", &advice_object_name_warning },
  
        /* make this an alias for backward compatibility */
        { "pushnonfastforward", &advice_push_update_rejected }
diff --combined advice.h
index 94caa32f9213f59a627176ec8b4aea022f6b0f8f,24d5420b7add56e6b19ce58918bf8d2fbde8ed8d..fefe39ac5caf5a1e8b95683a4c8dcc443df16d32
+++ b/advice.h
@@@ -11,12 -11,11 +11,13 @@@ extern int advice_push_already_exists
  extern int advice_push_fetch_first;
  extern int advice_push_needs_force;
  extern int advice_status_hints;
 +extern int advice_status_u_option;
  extern int advice_commit_before_merge;
  extern int advice_resolve_conflict;
  extern int advice_implicit_identity;
  extern int advice_detached_head;
 +extern int advice_set_upstream_failure;
+ extern int advice_object_name_warning;
  
  int git_default_advice_config(const char *var, const char *value);
  void advise(const char *advice, ...);
diff --combined sha1_name.c
index b3a90e6f051e8b75458055144829ea72bbe29970,502d107a5e427f61374a97d36ebc392f9619f3ad..a695d16d8c244700e28d220061818e59ee187000
@@@ -435,12 -435,31 +435,31 @@@ static int get_sha1_1(const char *name
  static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
  {
        static const char *warn_msg = "refname '%.*s' is ambiguous.";
+       static const char *object_name_msg = N_(
+       "Git normally never creates a ref that ends with 40 hex characters\n"
+       "because it will be ignored when you just specify 40-hex. These refs\n"
+       "may be created by mistake. For example,\n"
+       "\n"
+       "  git checkout -b $br $(git rev-parse ...)\n"
+       "\n"
+       "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
+       "examine these refs and maybe delete them. Turn this message off by\n"
+       "running \"git config advice.object_name_warning false\"");
+       unsigned char tmp_sha1[20];
        char *real_ref = NULL;
        int refs_found = 0;
        int at, reflog_len;
  
-       if (len == 40 && !get_sha1_hex(str, sha1))
+       if (len == 40 && !get_sha1_hex(str, sha1)) {
+               refs_found = dwim_ref(str, len, tmp_sha1, &real_ref);
+               if (refs_found > 0 && warn_ambiguous_refs) {
+                       warning(warn_msg, len, str);
+                       if (advice_object_name_warning)
+                               fprintf(stderr, "%s\n", _(object_name_msg));
+               }
+               free(real_ref);
                return 0;
+       }
  
        /* basic@{time or number or -number} format to query ref-log */
        reflog_len = at = 0;
        if (!refs_found)
                return -1;
  
-       if (warn_ambiguous_refs && refs_found > 1)
+       if (warn_ambiguous_refs &&
+           (refs_found > 1 ||
+            !get_short_sha1(str, len, tmp_sha1, GET_SHA1_QUIETLY)))
                warning(warn_msg, len, str);
  
        if (reflog_len) {
                }
                if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
                                &co_time, &co_tz, &co_cnt)) {
 +                      if (!len) {
 +                              if (!prefixcmp(real_ref, "refs/heads/")) {
 +                                      str = real_ref + 11;
 +                                      len = strlen(real_ref + 11);
 +                              } else {
 +                                      /* detached HEAD */
 +                                      str = "HEAD";
 +                                      len = 4;
 +                              }
 +                      }
                        if (at_time)
                                warning("Log for '%.*s' only goes "
                                        "back to %s.", len, str,
                                        show_date(co_time, co_tz, DATE_RFC2822));
                        else {
 -                              free(real_ref);
                                die("Log for '%.*s' only has %d entries.",
                                    len, str, co_cnt);
                        }
@@@ -603,7 -615,7 +624,7 @@@ struct object *peel_to_type(const char 
        while (1) {
                if (!o || (!o->parsed && !parse_object(o->sha1)))
                        return NULL;
 -              if (o->type == expected_type)
 +              if (expected_type == OBJ_ANY || o->type == expected_type)
                        return o;
                if (o->type == OBJ_TAG)
                        o = ((struct tag*) o)->tagged;
@@@ -654,8 -666,6 +675,8 @@@ static int peel_onion(const char *name
                expected_type = OBJ_TREE;
        else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
                expected_type = OBJ_BLOB;
 +      else if (!prefixcmp(sp, "object}"))
 +              expected_type = OBJ_ANY;
        else if (sp[0] == '}')
                expected_type = OBJ_NONE;
        else if (sp[0] == '/')
  
        if (expected_type == OBJ_COMMIT)
                lookup_flags = GET_SHA1_COMMITTISH;
 +      else if (expected_type == OBJ_TREE)
 +              lookup_flags = GET_SHA1_TREEISH;
  
        if (get_sha1_1(name, sp - name - 2, outer, lookup_flags))
                return -1;
@@@ -869,8 -877,8 +890,8 @@@ static int get_sha1_oneline(const char 
  }
  
  struct grab_nth_branch_switch_cbdata {
 -      long cnt, alloc;
 -      struct strbuf *buf;
 +      int remaining;
 +      struct strbuf buf;
  };
  
  static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
        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 ");
  
        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);
 +      if (--(cb->remaining) == 0) {
 +              len = target - match;
 +              strbuf_reset(&cb->buf);
 +              strbuf_add(&cb->buf, match, len);
 +              return 1; /* we are done */
 +      }
        return 0;
  }
  
  static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
  {
        long nth;
 -      int i, retval;
 +      int retval;
        struct grab_nth_branch_switch_cbdata cb;
        const char *brace;
        char *num_end;
        brace = strchr(name, '}');
        if (!brace)
                return -1;
 -      nth = strtol(name+3, &num_end, 10);
 +      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;
 +      cb.remaining = nth;
 +      strbuf_init(&cb.buf, 20);
 +
        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 (0 < for_each_reflog_ent_reverse("HEAD", grab_nth_branch_switch, &cb)) {
 +              strbuf_reset(buf);
 +              strbuf_add(buf, cb.buf.buf, cb.buf.len);
 +              retval = brace - name + 1;
        }
 -      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);
  
 +      strbuf_release(&cb.buf);
        return retval;
  }
  
@@@ -1042,15 -1062,14 +1063,15 @@@ int interpret_branch_name(const char *n
         * points to something different than a branch.
         */
        if (!upstream)
 -              return error(_("HEAD does not point to a branch"));
 +              die(_("HEAD does not point to a branch"));
        if (!upstream->merge || !upstream->merge[0]->dst) {
                if (!ref_exists(upstream->refname))
 -                      return error(_("No such branch: '%s'"), cp);
 -              if (!upstream->merge)
 -                      return error(_("No upstream configured for branch '%s'"),
 -                                   upstream->name);
 -              return error(
 +                      die(_("No such branch: '%s'"), cp);
 +              if (!upstream->merge) {
 +                      die(_("No upstream configured for branch '%s'"),
 +                              upstream->name);
 +              }
 +              die(
                        _("Upstream branch '%s' not stored as a remote-tracking branch"),
                        upstream->merge[0]->src);
        }
  int strbuf_branchname(struct strbuf *sb, const char *name)
  {
        int len = strlen(name);
 -      if (interpret_branch_name(name, sb) == len)
 +      int used = interpret_branch_name(name, sb);
 +
 +      if (used == len)
                return 0;
 -      strbuf_add(sb, name, len);
 +      if (used < 0)
 +              used = 0;
 +      strbuf_add(sb, name + used, len - used);
        return len;
  }
  
@@@ -1143,8 -1158,7 +1164,8 @@@ int get_sha1_blob(const char *name, uns
  static void diagnose_invalid_sha1_path(const char *prefix,
                                       const char *filename,
                                       const unsigned char *tree_sha1,
 -                                     const char *object_name)
 +                                     const char *object_name,
 +                                     int object_name_len)
  {
        struct stat st;
        unsigned char sha1[20];
                prefix = "";
  
        if (!lstat(filename, &st))
 -              die("Path '%s' exists on disk, but not in '%s'.",
 -                  filename, object_name);
 +              die("Path '%s' exists on disk, but not in '%.*s'.",
 +                  filename, object_name_len, object_name);
        if (errno == ENOENT || errno == ENOTDIR) {
                char *fullname = xmalloc(strlen(filename)
                                             + strlen(prefix) + 1);
                if (!get_tree_entry(tree_sha1, fullname,
                                    sha1, &mode)) {
                        die("Path '%s' exists, but not '%s'.\n"
 -                          "Did you mean '%s:%s' aka '%s:./%s'?",
 +                          "Did you mean '%.*s:%s' aka '%.*s:./%s'?",
                            fullname,
                            filename,
 -                          object_name,
 +                          object_name_len, object_name,
                            fullname,
 -                          object_name,
 +                          object_name_len, object_name,
                            filename);
                }
 -              die("Path '%s' does not exist in '%s'",
 -                  filename, object_name);
 +              die("Path '%s' does not exist in '%.*s'",
 +                  filename, object_name_len, object_name);
        }
  }
  
@@@ -1339,8 -1353,13 +1360,8 @@@ static int get_sha1_with_context_1(cons
        }
        if (*cp == ':') {
                unsigned char tree_sha1[20];
 -              char *object_name = NULL;
 -              if (only_to_die) {
 -                      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, GET_SHA1_TREEISH)) {
 +              int len = cp - name;
 +              if (!get_sha1_1(name, len, tree_sha1, GET_SHA1_TREEISH)) {
                        const char *filename = cp+1;
                        char *new_filename = NULL;
  
                        ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
                        if (ret && only_to_die) {
                                diagnose_invalid_sha1_path(prefix, filename,
 -                                                         tree_sha1, object_name);
 -                              free(object_name);
 +                                                         tree_sha1,
 +                                                         name, len);
                        }
                        hashcpy(oc->tree, tree_sha1);
                        strncpy(oc->path, filename,
                        return ret;
                } else {
                        if (only_to_die)
 -                              die("Invalid object name '%s'.", object_name);
 +                              die("Invalid object name '%.*s'.", len, name);
                }
        }
        return ret;