Code

Merge branch 'maint-1.6.6' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 16 Feb 2010 23:05:02 +0000 (15:05 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 16 Feb 2010 23:05:02 +0000 (15:05 -0800)
* maint-1.6.6:
  dwim_ref: fix dangling symref warning
  stash pop: remove 'apply' options during 'drop' invocation
  diff: make sure --output=/bad/path is caught
  Remove hyphen from "git-command" in two error messages

1  2 
diff.c
git.c
sha1_name.c

diff --combined diff.c
index 381cc8d4fd69ca31fb8fc8af31422160e3ec1fd3,7a321f4254cf25101f9c9ed5df1621e3bf4b2e29..68def6caed876669d38f1baafc5a0cd6318ab16a
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -194,7 -194,6 +194,7 @@@ struct emit_callback 
        struct diff_words_data *diff_words;
        int *found_changesp;
        FILE *file;
 +      struct strbuf *header;
  };
  
  static int count_lines(const char *data, int size)
@@@ -798,11 -797,6 +798,11 @@@ static void fn_out_consume(void *priv, 
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
  
 +      if (ecbdata->header) {
 +              fprintf(ecbdata->file, "%s", ecbdata->header->buf);
 +              strbuf_reset(ecbdata->header);
 +              ecbdata->header = NULL;
 +      }
        *(ecbdata->found_changesp) = 1;
  
        if (ecbdata->label_path[0]) {
@@@ -1607,7 -1601,6 +1607,7 @@@ static void builtin_diff(const char *na
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
        const char *a_prefix, *b_prefix;
        const char *textconv_one = NULL, *textconv_two = NULL;
 +      struct strbuf header = STRBUF_INIT;
  
        if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
                        (!one->mode || S_ISGITLINK(one->mode)) &&
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one ? one->path : two->path,
 -                              one->sha1, two->sha1,
 +                              one->sha1, two->sha1, two->dirty_submodule,
                                del, add, reset);
                return;
        }
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
 -      fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
 +      strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
 -              fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset);
 +              strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
 -                      fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
 +                      strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else if (lbl[1][0] == '/') {
 -              fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
 +              strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
 -                      fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
 +                      strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else {
                if (one->mode != two->mode) {
 -                      fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset);
 -                      fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset);
 +                      strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
 +                      strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
                }
                if (xfrm_msg && xfrm_msg[0])
 -                      fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
 +                      strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
 +
                /*
                 * we do not run diff between different kind
                 * of objects.
                if (complete_rewrite &&
                    (textconv_one || !diff_filespec_is_binary(one)) &&
                    (textconv_two || !diff_filespec_is_binary(two))) {
 +                      fprintf(o->file, "%s", header.buf);
 +                      strbuf_reset(&header);
                        emit_rewrite_diff(name_a, name_b, one, two,
                                                textconv_one, textconv_two, o);
                        o->found_changes = 1;
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
                        goto free_ab_and_return;
 +              fprintf(o->file, "%s", header.buf);
 +              strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
                        emit_binary_diff(o->file, &mf1, &mf2);
                else
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
  
 +              if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) {
 +                      fprintf(o->file, "%s", header.buf);
 +                      strbuf_reset(&header);
 +              }
 +
                if (textconv_one) {
                        size_t size;
                        mf1.ptr = run_textconv(textconv_one, one, &size);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
 +              ecbdata.header = header.len ? &header : NULL;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
        }
  
   free_ab_and_return:
 +      strbuf_release(&header);
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
        free(a_one);
@@@ -1997,7 -1978,7 +1997,7 @@@ static int reuse_worktree_file(const ch
         * If ce is marked as "assume unchanged", there is no
         * guarantee that work tree matches what we are looking for.
         */
 -      if (ce->ce_flags & CE_VALID)
 +      if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
                return 0;
  
        /*
@@@ -2029,14 -2010,9 +2029,14 @@@ static int populate_from_stdin(struct d
  static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
  {
        int len;
 -      char *data = xmalloc(100);
 +      char *data = xmalloc(100), *dirty = "";
 +
 +      /* Are we looking at the work tree? */
 +      if (!s->sha1_valid && s->dirty_submodule)
 +              dirty = "-dirty";
 +
        len = snprintf(data, 100,
 -              "Subproject commit %s\n", sha1_to_hex(s->sha1));
 +                     "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
        s->data = data;
        s->size = len;
        s->should_free = 1;
@@@ -2299,7 -2275,7 +2299,7 @@@ static void run_external_diff(const cha
        }
        *arg = NULL;
        fflush(NULL);
 -      retval = run_command_v_opt(spawn_arg, 0);
 +      retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
        remove_tempfile();
        if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@@ -2575,20 -2551,6 +2575,20 @@@ int diff_setup_done(struct diff_option
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
  
 +      /*
 +       * Most of the time we can say "there are changes"
 +       * only by checking if there are changed paths, but
 +       * --ignore-whitespace* options force us to look
 +       * inside contents.
 +       */
 +
 +      if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
 +          DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
 +          DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
 +              DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
 +      else
 +              DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
 +
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
  
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
 -      if (DIFF_OPT_TST(options, QUIET)) {
 +      if (DIFF_OPT_TST(options, QUICK)) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
@@@ -2840,7 -2802,7 +2840,7 @@@ int diff_opt_parse(struct diff_options 
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
 -              DIFF_OPT_SET(options, QUIET);
 +              DIFF_OPT_SET(options, QUICK);
        else if (!strcmp(arg, "--ext-diff"))
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
                ;
        else if (!prefixcmp(arg, "--output=")) {
                options->file = fopen(arg + strlen("--output="), "w");
+               if (!options->file)
+                       die_errno("Could not open '%s'", arg + strlen("--output="));
                options->close_file = 1;
        } else
                return 0;
@@@ -3547,18 -3511,6 +3549,18 @@@ free_queue
        q->nr = q->alloc = 0;
        if (options->close_file)
                fclose(options->file);
 +
 +      /*
 +       * Report the content-level differences with HAS_CHANGES;
 +       * diff_addremove/diff_change does not set the bit when
 +       * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
 +       */
 +      if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
 +              if (options->found_changes)
 +                      DIFF_OPT_SET(options, HAS_CHANGES);
 +              else
 +                      DIFF_OPT_CLR(options, HAS_CHANGES);
 +      }
  }
  
  static void diffcore_apply_filter(const char *filter)
@@@ -3678,23 -3630,6 +3680,23 @@@ static void diffcore_skip_stat_unmatch(
        *q = outq;
  }
  
 +static int diffnamecmp(const void *a_, const void *b_)
 +{
 +      const struct diff_filepair *a = *((const struct diff_filepair **)a_);
 +      const struct diff_filepair *b = *((const struct diff_filepair **)b_);
 +      const char *name_a, *name_b;
 +
 +      name_a = a->one ? a->one->path : a->two->path;
 +      name_b = b->one ? b->one->path : b->two->path;
 +      return strcmp(name_a, name_b);
 +}
 +
 +void diffcore_fix_diff_index(struct diff_options *options)
 +{
 +      struct diff_queue_struct *q = &diff_queued_diff;
 +      qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
 +}
 +
  void diffcore_std(struct diff_options *options)
  {
        if (options->skip_stat_unmatch)
        diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
  
 -      if (diff_queued_diff.nr)
 +      if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
@@@ -3736,7 -3671,7 +3738,7 @@@ int diff_result_code(struct diff_option
  void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
 -                  const char *concatpath)
 +                  const char *concatpath, unsigned dirty_submodule)
  {
        struct diff_filespec *one, *two;
  
  
        if (addremove != '+')
                fill_filespec(one, sha1, mode);
 -      if (addremove != '-')
 +      if (addremove != '-') {
                fill_filespec(two, sha1, mode);
 +              two->dirty_submodule = dirty_submodule;
 +      }
  
        diff_queue(&diff_queued_diff, one, two);
 -      DIFF_OPT_SET(options, HAS_CHANGES);
 +      if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
 +              DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
 -               const char *concatpath)
 +               const char *concatpath,
 +               unsigned old_dirty_submodule, unsigned new_dirty_submodule)
  {
        struct diff_filespec *one, *two;
  
                const unsigned char *tmp_c;
                tmp = old_mode; old_mode = new_mode; new_mode = tmp;
                tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
 +              tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
 +                      new_dirty_submodule = tmp;
        }
  
        if (options->prefix &&
        two = alloc_filespec(concatpath);
        fill_filespec(one, old_sha1, old_mode);
        fill_filespec(two, new_sha1, new_mode);
 +      one->dirty_submodule = old_dirty_submodule;
 +      two->dirty_submodule = new_dirty_submodule;
  
        diff_queue(&diff_queued_diff, one, two);
 -      DIFF_OPT_SET(options, HAS_CHANGES);
 +      if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
 +              DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  void diff_unmerge(struct diff_options *options,
@@@ -3847,7 -3773,6 +3849,7 @@@ static char *run_textconv(const char *p
        *arg = NULL;
  
        memset(&child, 0, sizeof(child));
 +      child.use_shell = 1;
        child.argv = argv;
        child.out = -1;
        if (start_command(&child) != 0 ||
diff --combined git.c
index 4c3028c098c03c842d2a635d054d3c27653dd511,a83cab7002205f56b454e49c389271fb963c5c67..c445d7bcc256c68fe53df4886bd7b7af81640056
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -318,9 -318,7 +318,9 @@@ static void handle_internal_command(in
                { "gc", cmd_gc, RUN_SETUP },
                { "get-tar-commit-id", cmd_get_tar_commit_id },
                { "grep", cmd_grep, RUN_SETUP | USE_PAGER },
 +              { "hash-object", cmd_hash_object },
                { "help", cmd_help },
 +              { "index-pack", cmd_index_pack },
                { "init", cmd_init_db },
                { "init-db", cmd_init_db },
                { "log", cmd_log, RUN_SETUP | USE_PAGER },
                { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
                { "merge-base", cmd_merge_base, RUN_SETUP },
                { "merge-file", cmd_merge_file },
 +              { "merge-index", cmd_merge_index, RUN_SETUP },
                { "merge-ours", cmd_merge_ours, RUN_SETUP },
                { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 +              { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 +              { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
                { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 +              { "merge-tree", cmd_merge_tree, RUN_SETUP },
 +              { "mktag", cmd_mktag, RUN_SETUP },
                { "mktree", cmd_mktree, RUN_SETUP },
                { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
 +              { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
 +              { "patch-id", cmd_patch_id },
                { "peek-remote", cmd_ls_remote },
                { "pickaxe", cmd_blame, RUN_SETUP },
                { "prune", cmd_prune, RUN_SETUP },
                { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
                { "tag", cmd_tag, RUN_SETUP },
                { "tar-tree", cmd_tar_tree },
 +              { "unpack-file", cmd_unpack_file, RUN_SETUP },
                { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
                { "update-index", cmd_update_index, RUN_SETUP },
                { "update-ref", cmd_update_ref, RUN_SETUP },
                { "update-server-info", cmd_update_server_info, RUN_SETUP },
                { "upload-archive", cmd_upload_archive },
 +              { "var", cmd_var },
                { "verify-tag", cmd_verify_tag, RUN_SETUP },
                { "version", cmd_version },
                { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
@@@ -527,7 -516,7 +527,7 @@@ int main(int argc, const char **argv
                        break;
                if (was_alias) {
                        fprintf(stderr, "Expansion of alias '%s' failed; "
-                               "'%s' is not a git-command\n",
+                               "'%s' is not a git command\n",
                                cmd, argv[0]);
                        exit(1);
                }
diff --combined sha1_name.c
index 77299257bf3aa91079d5b883c6676afa6fd2d01c,7013c53ca6f083b6887f2d8567c72c5a63054c1c..43884c69b350426b46ed73ffcfd5fc29cf1da1b1
@@@ -5,7 -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)
  {
@@@ -241,8 -240,7 +241,8 @@@ static int ambiguous_path(const char *p
  
  /*
   * *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)
  {
@@@ -280,8 -278,7 +280,7 @@@ int dwim_ref(const char *str, int len, 
                                *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);
@@@ -325,20 -322,6 +324,20 @@@ int dwim_log(const char *str, int len, 
        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)
        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;
                        }
                }
                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];
@@@ -765,10 -742,17 +764,10 @@@ static int grab_nth_branch_switch(unsig
  }
  
  /*
 - * 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;
@@@ -812,122 -796,6 +811,122 @@@ release_return
        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"
@@@ -938,96 -806,7 +937,96 @@@ int get_sha1(const char *name, unsigne
        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);
                        }
                        pos++;
                }
 +              if (!gently)
 +                      diagnose_invalid_index_path(stage, prefix, cp);
                return -1;
        }
        for (cp = name, bracket_depth = 0; *cp; cp++) {
        }
        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;
  }