Merge branch 'jk/robustify-parse-commit'
authorJunio C Hamano <gitster@pobox.com>
Thu, 5 Dec 2013 20:54:01 +0000 (12:54 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 5 Dec 2013 20:54:01 +0000 (12:54 -0800)
* jk/robustify-parse-commit:
checkout: do not die when leaving broken detached HEAD
use parse_commit_or_die instead of custom message
use parse_commit_or_die instead of segfaulting
assume parse_commit checks for NULL commit
assume parse_commit checks commit->object.parsed
log_tree_diff: die when we fail to parse a commit

14 files changed:
1  2 
builtin/blame.c
builtin/branch.c
builtin/checkout.c
builtin/commit.c
builtin/fast-export.c
builtin/name-rev.c
builtin/show-branch.c
commit.c
commit.h
fetch-pack.c
log-tree.c
sha1_name.c
shallow.c
upload-pack.c
diff --combined builtin/blame.c
index 1407ae7eb291ab7c0d00984d18820c955703e17f,4d254665094322196e6dbb8a3da3009b4b3966db..4916eb2bd26bed670aa3662e790abad9b336ebe3
@@@ -22,7 -22,6 +22,7 @@@
  #include "utf8.h"
  #include "userdiff.h"
  #include "line-range.h"
 +#include "line-log.h"
  
  static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
  
@@@ -409,9 -408,7 +409,9 @@@ static struct origin *find_origin(struc
        paths[0] = origin->path;
        paths[1] = NULL;
  
 -      diff_tree_setup_paths(paths, &diff_opts);
 +      parse_pathspec(&diff_opts.pathspec,
 +                     PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
 +                     PATHSPEC_LITERAL_PATH, "", paths);
        diff_setup_done(&diff_opts);
  
        if (is_null_sha1(origin->commit->object.sha1))
                }
        }
        diff_flush(&diff_opts);
 -      diff_tree_release_paths(&diff_opts);
 +      free_pathspec(&diff_opts.pathspec);
        if (porigin) {
                /*
                 * Create a freestanding copy that is not part of
@@@ -489,12 -486,15 +489,12 @@@ static struct origin *find_rename(struc
        struct origin *porigin = NULL;
        struct diff_options diff_opts;
        int i;
 -      const char *paths[2];
  
        diff_setup(&diff_opts);
        DIFF_OPT_SET(&diff_opts, RECURSIVE);
        diff_opts.detect_rename = DIFF_DETECT_RENAME;
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = origin->path;
 -      paths[0] = NULL;
 -      diff_tree_setup_paths(paths, &diff_opts);
        diff_setup_done(&diff_opts);
  
        if (is_null_sha1(origin->commit->object.sha1))
                }
        }
        diff_flush(&diff_opts);
 -      diff_tree_release_paths(&diff_opts);
 +      free_pathspec(&diff_opts.pathspec);
        return porigin;
  }
  
@@@ -1064,6 -1064,7 +1064,6 @@@ static int find_copy_in_parent(struct s
                               int opt)
  {
        struct diff_options diff_opts;
 -      const char *paths[1];
        int i, j;
        int retval;
        struct blame_list *blame_list;
        DIFF_OPT_SET(&diff_opts, RECURSIVE);
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
  
 -      paths[0] = NULL;
 -      diff_tree_setup_paths(paths, &diff_opts);
        diff_setup_done(&diff_opts);
  
        /* Try "find copies harder" on new path if requested;
        }
        reset_scanned_flag(sb);
        diff_flush(&diff_opts);
 -      diff_tree_release_paths(&diff_opts);
 +      free_pathspec(&diff_opts.pathspec);
        return retval;
  }
  
@@@ -1551,8 -1554,7 +1551,7 @@@ static void assign_blame(struct scorebo
                 */
                origin_incref(suspect);
                commit = suspect->commit;
-               if (!commit->object.parsed)
-                       parse_commit(commit);
+               parse_commit(commit);
                if (reverse ||
                    (!(commit->object.flags & UNINTERESTING) &&
                     !(revs->max_age != -1 && commit->date < revs->max_age)))
@@@ -1934,6 -1936,18 +1933,6 @@@ static const char *add_prefix(const cha
        return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
  }
  
 -/*
 - * Parsing of -L option
 - */
 -static void prepare_blame_range(struct scoreboard *sb,
 -                              const char *bottomtop,
 -                              long lno,
 -                              long *bottom, long *top)
 -{
 -      if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
 -              usage(blame_usage);
 -}
 -
  static int git_blame_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "blame.showroot")) {
@@@ -2230,27 -2244,38 +2229,27 @@@ static int blame_move_callback(const st
        return 0;
  }
  
 -static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
 -{
 -      const char **bottomtop = option->value;
 -      if (!arg)
 -              return -1;
 -      if (*bottomtop)
 -              die("More than one '-L n,m' option given");
 -      *bottomtop = arg;
 -      return 0;
 -}
 -
  int cmd_blame(int argc, const char **argv, const char *prefix)
  {
        struct rev_info revs;
        const char *path;
        struct scoreboard sb;
        struct origin *o;
 -      struct blame_entry *ent;
 -      long dashdash_pos, bottom, top, lno;
 +      struct blame_entry *ent = NULL;
 +      long dashdash_pos, lno;
        const char *final_commit_name = NULL;
        enum object_type type;
  
 -      static const char *bottomtop = NULL;
 +      static struct string_list range_list;
        static int output_option = 0, opt = 0;
        static int show_stats = 0;
        static const char *revs_file = NULL;
        static const char *contents_from = NULL;
        static const struct option options[] = {
 -              OPT_BOOLEAN(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
 -              OPT_BOOLEAN('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
 -              OPT_BOOLEAN(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
 -              OPT_BOOLEAN(0, "show-stats", &show_stats, N_("Show work cost statistics")),
 +              OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
 +              OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
 +              OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
 +              OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")),
                OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE),
                OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
                OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
                OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
                { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
                { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
 -              OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback),
 +              OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
                OPT__ABBREV(&abbrev),
                OPT_END()
        };
  
        struct parse_opt_ctx_t ctx;
        int cmd_is_annotate = !strcmp(argv[0], "annotate");
 +      struct range_set ranges;
 +      unsigned int range_i;
 +      long anchor;
  
        git_config(git_blame_config, NULL);
        init_revisions(&revs, NULL);
@@@ -2469,48 -2491,22 +2468,48 @@@ parse_done
        num_read_blob++;
        lno = prepare_lines(&sb);
  
 -      bottom = top = 0;
 -      if (bottomtop)
 -              prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
 -      if (bottom < 1)
 -              bottom = 1;
 -      if (top < 1)
 -              top = lno;
 -      bottom--;
 -      if (lno < top || lno < bottom)
 -              die("file %s has only %lu lines", path, lno);
 -
 -      ent = xcalloc(1, sizeof(*ent));
 -      ent->lno = bottom;
 -      ent->num_lines = top - bottom;
 -      ent->suspect = o;
 -      ent->s_lno = bottom;
 +      if (lno && !range_list.nr)
 +              string_list_append(&range_list, xstrdup("1"));
 +
 +      anchor = 1;
 +      range_set_init(&ranges, range_list.nr);
 +      for (range_i = 0; range_i < range_list.nr; ++range_i) {
 +              long bottom, top;
 +              if (parse_range_arg(range_list.items[range_i].string,
 +                                  nth_line_cb, &sb, lno, anchor,
 +                                  &bottom, &top, sb.path))
 +                      usage(blame_usage);
 +              if (lno < top || ((lno || bottom) && lno < bottom))
 +                      die("file %s has only %lu lines", path, lno);
 +              if (bottom < 1)
 +                      bottom = 1;
 +              if (top < 1)
 +                      top = lno;
 +              bottom--;
 +              range_set_append_unsafe(&ranges, bottom, top);
 +              anchor = top + 1;
 +      }
 +      sort_and_merge_range_set(&ranges);
 +
 +      for (range_i = ranges.nr; range_i > 0; --range_i) {
 +              const struct range *r = &ranges.ranges[range_i - 1];
 +              long bottom = r->start;
 +              long top = r->end;
 +              struct blame_entry *next = ent;
 +              ent = xcalloc(1, sizeof(*ent));
 +              ent->lno = bottom;
 +              ent->num_lines = top - bottom;
 +              ent->suspect = o;
 +              ent->s_lno = bottom;
 +              ent->next = next;
 +              if (next)
 +                      next->prev = ent;
 +              origin_incref(o);
 +      }
 +      origin_decref(o);
 +
 +      range_set_release(&ranges);
 +      string_list_clear(&range_list, 0);
  
        sb.ent = ent;
        sb.path = path;
diff --combined builtin/branch.c
index f157f92f485230c2412454def728d374550497be,8db095f5c28ab6706456fdd21c214f340e1f6707..636a16ea4e3af71aef80ba42639f0933c286f485
@@@ -423,20 -423,19 +423,20 @@@ static void fill_tracking_info(struct s
        char *ref = NULL;
        struct branch *branch = branch_get(branch_name);
        struct strbuf fancy = STRBUF_INIT;
 +      int upstream_is_gone = 0;
 +      int added_decoration = 1;
  
 -      if (!stat_tracking_info(branch, &ours, &theirs)) {
 -              if (branch && branch->merge && branch->merge[0]->dst &&
 -                  show_upstream_ref) {
 -                      ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
 -                      if (want_color(branch_use_color))
 -                              strbuf_addf(stat, "[%s%s%s] ",
 -                                              branch_get_color(BRANCH_COLOR_UPSTREAM),
 -                                              ref, branch_get_color(BRANCH_COLOR_RESET));
 -                      else
 -                              strbuf_addf(stat, "[%s] ", ref);
 -              }
 +      switch (stat_tracking_info(branch, &ours, &theirs)) {
 +      case 0:
 +              /* no base */
                return;
 +      case -1:
 +              /* with "gone" base */
 +              upstream_is_gone = 1;
 +              break;
 +      default:
 +              /* with base */
 +              break;
        }
  
        if (show_upstream_ref) {
                        strbuf_addstr(&fancy, ref);
        }
  
 -      if (!ours) {
 -              if (ref)
 +      if (upstream_is_gone) {
 +              if (show_upstream_ref)
 +                      strbuf_addf(stat, _("[%s: gone]"), fancy.buf);
 +              else
 +                      added_decoration = 0;
 +      } else if (!ours && !theirs) {
 +              if (show_upstream_ref)
 +                      strbuf_addf(stat, _("[%s]"), fancy.buf);
 +              else
 +                      added_decoration = 0;
 +      } else if (!ours) {
 +              if (show_upstream_ref)
                        strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs);
                else
                        strbuf_addf(stat, _("[behind %d]"), theirs);
  
        } else if (!theirs) {
 -              if (ref)
 +              if (show_upstream_ref)
                        strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
                else
                        strbuf_addf(stat, _("[ahead %d]"), ours);
        } else {
 -              if (ref)
 +              if (show_upstream_ref)
                        strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
                                    fancy.buf, ours, theirs);
                else
                                    ours, theirs);
        }
        strbuf_release(&fancy);
 -      strbuf_addch(stat, ' ');
 +      if (added_decoration)
 +              strbuf_addch(stat, ' ');
        free(ref);
  }
  
@@@ -502,7 -490,7 +502,7 @@@ static void add_verbose_info(struct str
        const char *sub = _(" **** invalid ref ****");
        struct commit *commit = item->commit;
  
-       if (commit && !parse_commit(commit)) {
+       if (!parse_commit(commit)) {
                pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject);
                sub = subject.buf;
        }
@@@ -809,7 -797,7 +809,7 @@@ int cmd_branch(int argc, const char **a
                OPT_SET_INT( 0, "set-upstream",  &track, N_("change upstream info"),
                        BRANCH_TRACK_OVERRIDE),
                OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"),
 -              OPT_BOOLEAN(0, "unset-upstream", &unset_upstream, "Unset the upstream info"),
 +              OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"),
                OPT__COLOR(&branch_use_color, N_("use colored output")),
                OPT_SET_INT('r', "remotes",     &kinds, N_("act on remote-tracking branches"),
                        REF_REMOTE_BRANCH),
                OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
                OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
                OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
 -              OPT_BOOLEAN(0, "list", &list, N_("list branch names")),
 -              OPT_BOOLEAN('l', "create-reflog", &reflog, N_("create the branch's reflog")),
 -              OPT_BOOLEAN(0, "edit-description", &edit_description,
 -                          N_("edit the description for the branch")),
 +              OPT_BOOL(0, "list", &list, N_("list branch names")),
 +              OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")),
 +              OPT_BOOL(0, "edit-description", &edit_description,
 +                       N_("edit the description for the branch")),
                OPT__FORCE(&force_create, N_("force creation (when already exists)")),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
        if (with_commit || merge_filter != NO_FILTER)
                list = 1;
  
 -      if (!!delete + !!rename + !!force_create + !!list + !!new_upstream + !!unset_upstream > 1)
 +      if (!!delete + !!rename + !!force_create + !!new_upstream +
 +          list + unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
  
        if (abbrev == -1)
                        die(_("no such branch '%s'"), argv[0]);
                }
  
 -              if (!branch_has_merge_config(branch)) {
 +              if (!branch_has_merge_config(branch))
                        die(_("Branch '%s' has no upstream information"), branch->name);
 -              }
  
                strbuf_addf(&buf, "branch.%s.remote", branch->name);
                git_config_set_multivar(buf.buf, NULL, NULL, 1);
diff --combined builtin/checkout.c
index 54f80bd38a7413390ed2093a626b129a1a3e8e7f,4d6bb0c5d0e915a4622be656ddc2875b999a9e22..904fd715f0f27af0ac3694209ba3ffe61f174fa8
@@@ -46,7 -46,7 +46,7 @@@ struct checkout_opts 
  
        int branch_exists;
        const char *prefix;
 -      const char **pathspec;
 +      struct pathspec pathspec;
        struct tree *source_tree;
  };
  
@@@ -83,9 -83,12 +83,9 @@@ static int update_some(const unsigned c
        return 0;
  }
  
 -static int read_tree_some(struct tree *tree, const char **pathspec)
 +static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
  {
 -      struct pathspec ps;
 -      init_pathspec(&ps, pathspec);
 -      read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL);
 -      free_pathspec(&ps);
 +      read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
  
        /* update the index with the given tree's info
         * for all args, expanding wildcards, and exit
@@@ -225,6 -228,8 +225,6 @@@ static int checkout_paths(const struct 
        int flag;
        struct commit *head;
        int errs = 0;
 -      int stage = opts->writeout_stage;
 -      int merge = opts->merge;
        int newfd;
        struct lock_file *lock_file;
  
  
        if (opts->patch_mode)
                return run_add_interactive(revision, "--patch=checkout",
 -                                         opts->pathspec);
 +                                         &opts->pathspec);
  
        lock_file = xcalloc(1, sizeof(struct lock_file));
  
        newfd = hold_locked_index(lock_file, 1);
 -      if (read_cache_preload(opts->pathspec) < 0)
 +      if (read_cache_preload(&opts->pathspec) < 0)
                return error(_("corrupt index file"));
  
        if (opts->source_tree)
 -              read_tree_some(opts->source_tree, opts->pathspec);
 +              read_tree_some(opts->source_tree, &opts->pathspec);
  
 -      for (pos = 0; opts->pathspec[pos]; pos++)
 -              ;
 -      ps_matched = xcalloc(1, pos);
 +      ps_matched = xcalloc(1, opts->pathspec.nr);
  
        /*
         * Make sure all pathspecs participated in locating the paths
                 * match_pathspec() for _all_ entries when
                 * opts->source_tree != NULL.
                 */
 -              if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce),
 +              if (match_pathspec_depth(&opts->pathspec, ce->name, ce_namelen(ce),
                                   0, ps_matched))
                        ce->ce_flags |= CE_MATCHED;
        }
  
 -      if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) {
 +      if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
                free(ps_matched);
                return 1;
        }
                                continue;
                        if (opts->force) {
                                warning(_("path '%s' is unmerged"), ce->name);
 -                      } else if (stage) {
 -                              errs |= check_stage(stage, ce, pos);
 +                      } else if (opts->writeout_stage) {
 +                              errs |= check_stage(opts->writeout_stage, ce, pos);
                        } else if (opts->merge) {
                                errs |= check_stages((1<<2) | (1<<3), ce, pos);
                        } else {
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
                        }
 -                      if (stage)
 -                              errs |= checkout_stage(stage, ce, pos, &state);
 -                      else if (merge)
 +                      if (opts->writeout_stage)
 +                              errs |= checkout_stage(opts->writeout_stage, ce, pos, &state);
 +                      else if (opts->merge)
                                errs |= checkout_merged(pos, &state);
                        pos = skip_same_name(ce, pos) - 1;
                }
@@@ -380,8 -387,8 +380,8 @@@ static void show_local_changes(struct o
  static void describe_detached_head(const char *msg, struct commit *commit)
  {
        struct strbuf sb = STRBUF_INIT;
-       parse_commit(commit);
-       pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
+       if (!parse_commit(commit))
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
        fprintf(stderr, "%s %s... %s\n", msg,
                find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
        strbuf_release(&sb);
@@@ -677,12 -684,12 +677,12 @@@ static int add_pending_uninteresting_re
  
  static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
  {
-       parse_commit(commit);
        strbuf_addstr(sb, "  ");
        strbuf_addstr(sb,
                find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
        strbuf_addch(sb, ' ');
-       pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
+       if (!parse_commit(commit))
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
        strbuf_addch(sb, '\n');
  }
  
@@@ -789,7 -796,7 +789,7 @@@ static int switch_branches(const struc
                new->commit = old.commit;
                if (!new->commit)
                        die(_("You are on a branch yet to be born"));
-               parse_commit(new->commit);
+               parse_commit_or_die(new->commit);
        }
  
        ret = merge_working_tree(opts, &old, new, &writeout_error);
@@@ -873,9 -880,7 +873,9 @@@ static int parse_branchname_arg(int arg
        int argcount = 0;
        unsigned char branch_rev[20];
        const char *arg;
 -      int has_dash_dash;
 +      int dash_dash_pos;
 +      int has_dash_dash = 0;
 +      int i;
  
        /*
         * case 1: git checkout <ref> -- [<paths>]
         *
         *   everything after the '--' must be paths.
         *
 -       * case 3: git checkout <something> [<paths>]
 +       * case 3: git checkout <something> [--]
         *
 -       *   With no paths, if <something> is a commit, that is to
 -       *   switch to the branch or detach HEAD at it.  As a special case,
 -       *   if <something> is A...B (missing A or B means HEAD but you can
 -       *   omit at most one side), and if there is a unique merge base
 -       *   between A and B, A...B names that merge base.
 +       *   (a) If <something> is a commit, that is to
 +       *       switch to the branch or detach HEAD at it.  As a special case,
 +       *       if <something> is A...B (missing A or B means HEAD but you can
 +       *       omit at most one side), and if there is a unique merge base
 +       *       between A and B, A...B names that merge base.
         *
 -       *   With no paths, if <something> is _not_ a commit, no -t nor -b
 -       *   was given, and there is a tracking branch whose name is
 -       *   <something> in one and only one remote, then this is a short-hand
 -       *   to fork local <something> from that remote-tracking branch.
 +       *   (b) If <something> is _not_ a commit, either "--" is present
 +       *       or <something> is not a path, no -t nor -b was given, and
 +       *       and there is a tracking branch whose name is <something>
 +       *       in one and only one remote, then this is a short-hand to
 +       *       fork local <something> from that remote-tracking branch.
         *
 -       *   Otherwise <something> shall not be ambiguous.
 +       *   (c) Otherwise, if "--" is present, treat it like case (1).
 +       *
 +       *   (d) Otherwise :
 +       *       - if it's a reference, treat it like case (1)
 +       *       - else if it's a path, treat it like case (2)
 +       *       - else: fail.
 +       *
 +       * case 4: git checkout <something> <paths>
 +       *
 +       *   The first argument must not be ambiguous.
         *   - If it's *only* a reference, treat it like case (1).
         *   - If it's only a path, treat it like case (2).
         *   - else: fail.
        if (!argc)
                return 0;
  
 -      if (!strcmp(argv[0], "--"))     /* case (2) */
 -              return 1;
 -
        arg = argv[0];
 -      has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
 +      dash_dash_pos = -1;
 +      for (i = 0; i < argc; i++) {
 +              if (!strcmp(argv[i], "--")) {
 +                      dash_dash_pos = i;
 +                      break;
 +              }
 +      }
 +      if (dash_dash_pos == 0)
 +              return 1; /* case (2) */
 +      else if (dash_dash_pos == 1)
 +              has_dash_dash = 1; /* case (3) or (1) */
 +      else if (dash_dash_pos >= 2)
 +              die(_("only one reference expected, %d given."), dash_dash_pos);
  
        if (!strcmp(arg, "-"))
                arg = "@{-1}";
  
        if (get_sha1_mb(arg, rev)) {
 -              if (has_dash_dash)          /* case (1) */
 -                      die(_("invalid reference: %s"), arg);
 -              if (dwim_new_local_branch_ok &&
 -                  !check_filename(NULL, arg) &&
 -                  argc == 1) {
 +              /*
 +               * Either case (3) or (4), with <something> not being
 +               * a commit, or an attempt to use case (1) with an
 +               * invalid ref.
 +               *
 +               * It's likely an error, but we need to find out if
 +               * we should auto-create the branch, case (3).(b).
 +               */
 +              int recover_with_dwim = dwim_new_local_branch_ok;
 +
 +              if (check_filename(NULL, arg) && !has_dash_dash)
 +                      recover_with_dwim = 0;
 +              /*
 +               * Accept "git checkout foo" and "git checkout foo --"
 +               * as candidates for dwim.
 +               */
 +              if (!(argc == 1 && !has_dash_dash) &&
 +                  !(argc == 2 && has_dash_dash))
 +                      recover_with_dwim = 0;
 +
 +              if (recover_with_dwim) {
                        const char *remote = unique_tracking_name(arg, rev);
 -                      if (!remote)
 -                              return argcount;
 -                      *new_branch = arg;
 -                      arg = remote;
 -                      /* DWIMmed to create local branch */
 -              } else {
 +                      if (remote) {
 +                              *new_branch = arg;
 +                              arg = remote;
 +                              /* DWIMmed to create local branch, case (3).(b) */
 +                      } else {
 +                              recover_with_dwim = 0;
 +                      }
 +              }
 +
 +              if (!recover_with_dwim) {
 +                      if (has_dash_dash)
 +                              die(_("invalid reference: %s"), arg);
                        return argcount;
                }
        }
                /* not a commit */
                *source_tree = parse_tree_indirect(rev);
        } else {
-               parse_commit(new->commit);
+               parse_commit_or_die(new->commit);
                *source_tree = new->commit->tree;
        }
  
        if (!*source_tree)                   /* case (1): want a tree */
                die(_("reference is not a tree: %s"), arg);
 -      if (!has_dash_dash) {/* case (3 -> 1) */
 +      if (!has_dash_dash) {/* case (3).(d) -> (1) */
                /*
                 * Do not complain the most common case
                 *      git checkout branch
@@@ -1038,7 -1002,7 +1038,7 @@@ static int switch_unborn_to_new_branch(
  static int checkout_branch(struct checkout_opts *opts,
                           struct branch_info *new)
  {
 -      if (opts->pathspec)
 +      if (opts->pathspec.nr)
                die(_("paths cannot be used with switching branches"));
  
        if (opts->patch_mode)
@@@ -1092,8 -1056,8 +1092,8 @@@ int cmd_checkout(int argc, const char *
                           N_("create and checkout a new branch")),
                OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
                           N_("create/reset and checkout a branch")),
 -              OPT_BOOLEAN('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
 -              OPT_BOOLEAN(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")),
 +              OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
 +              OPT_BOOL(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")),
                OPT_SET_INT('t', "track",  &opts.track, N_("set upstream info for new branch"),
                        BRANCH_TRACK_EXPLICIT),
                OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new branch"), N_("new unparented branch")),
                OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"),
                            3),
                OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)")),
 -              OPT_BOOLEAN('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
 -              OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")),
 +              OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
 +              OPT_BOOL(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")),
                OPT_STRING(0, "conflict", &conflict_style, N_("style"),
                           N_("conflict style (merge or diff3)")),
 -              OPT_BOOLEAN('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
 +              OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
                OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
                         N_("do not limit pathspecs to sparse entries only")),
 -              { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
 -                N_("second guess 'git checkout no-such-branch'"),
 -                PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
 +              OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 +                              N_("second guess 'git checkout no-such-branch'")),
                OPT_END(),
        };
  
        }
  
        if (argc) {
 -              opts.pathspec = get_pathspec(prefix, argv);
 +              parse_pathspec(&opts.pathspec, 0,
 +                             opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
 +                             prefix, argv);
  
 -              if (!opts.pathspec)
 +              if (!opts.pathspec.nr)
                        die(_("invalid path specification"));
  
                /*
                strbuf_release(&buf);
        }
  
 -      if (opts.patch_mode || opts.pathspec)
 +      if (opts.patch_mode || opts.pathspec.nr)
                return checkout_paths(&opts, new.name);
        else
                return checkout_branch(&opts, &new);
diff --combined builtin/commit.c
index 6ab4605cf5c2e6ef5efb37518c9c215bf8895f97,89f65f2b8213f456975082d707e2069f70c67dd9..e89c519192121d5386ba42b88a0157b31d57c690
@@@ -30,7 -30,6 +30,7 @@@
  #include "column.h"
  #include "sequencer.h"
  #include "notes-utils.h"
 +#include "mailmap.h"
  
  static const char * const builtin_commit_usage[] = {
        N_("git commit [options] [--] <pathspec>..."),
@@@ -164,15 -163,6 +164,15 @@@ static void determine_whence(struct wt_
                s->whence = whence;
  }
  
 +static void status_init_config(struct wt_status *s, config_fn_t fn)
 +{
 +      wt_status_prepare(s);
 +      gitmodules_config();
 +      git_config(fn, s);
 +      determine_whence(s);
 +      s->hints = advice_status_hints; /* must come after git_config() */
 +}
 +
  static void rollback_index_files(void)
  {
        switch (commit_style) {
@@@ -212,15 -202,17 +212,15 @@@ static int commit_index_files(void
   * and return the paths that match the given pattern in list.
   */
  static int list_paths(struct string_list *list, const char *with_tree,
 -                    const char *prefix, const char **pattern)
 +                    const char *prefix, const struct pathspec *pattern)
  {
        int i;
        char *m;
  
 -      if (!pattern)
 +      if (!pattern->nr)
                return 0;
  
 -      for (i = 0; pattern[i]; i++)
 -              ;
 -      m = xcalloc(1, i);
 +      m = xcalloc(1, pattern->nr);
  
        if (with_tree) {
                char *max_prefix = common_prefix(pattern);
  
                if (ce->ce_flags & CE_UPDATE)
                        continue;
 -              if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
 +              if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m))
                        continue;
                item = string_list_insert(list, ce->name);
                if (ce_skip_worktree(ce))
@@@ -306,17 -298,17 +306,17 @@@ static char *prepare_index(int argc, co
  {
        int fd;
        struct string_list partial;
 -      const char **pathspec = NULL;
 +      struct pathspec pathspec;
        char *old_index_env = NULL;
        int refresh_flags = REFRESH_QUIET;
  
        if (is_status)
                refresh_flags |= REFRESH_UNMERGED;
 +      parse_pathspec(&pathspec, 0,
 +                     PATHSPEC_PREFER_FULL,
 +                     prefix, argv);
  
 -      if (*argv)
 -              pathspec = get_pathspec(prefix, argv);
 -
 -      if (read_cache_preload(pathspec) < 0)
 +      if (read_cache_preload(&pathspec) < 0)
                die(_("index file corrupt"));
  
        if (interactive) {
         * (A) if all goes well, commit the real index;
         * (B) on failure, rollback the real index.
         */
 -      if (all || (also && pathspec && *pathspec)) {
 +      if (all || (also && pathspec.nr)) {
                fd = hold_locked_index(&index_lock, 1);
 -              add_files_to_cache(also ? prefix : NULL, pathspec, 0);
 +              add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
                refresh_cache_or_die(refresh_flags);
                update_main_cache_tree(WRITE_TREE_SILENT);
                if (write_cache(fd, active_cache, active_nr) ||
         * and create commit from the_index.
         * We still need to refresh the index here.
         */
 -      if (!only && (!pathspec || !*pathspec)) {
 +      if (!only && !pathspec.nr) {
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
  
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
 -      if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
 +      if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
                exit(1);
  
        discard_cache();
@@@ -607,7 -599,6 +607,7 @@@ static int prepare_to_commit(const cha
        const char *hook_arg2 = NULL;
        int ident_shown = 0;
        int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
 +      int old_display_comment_prefix;
  
        /* This checks and barfs if author is badly specified */
        determine_author_info(author_ident);
        if (s->fp == NULL)
                die_errno(_("could not open '%s'"), git_path(commit_editmsg));
  
 +      /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */
 +      old_display_comment_prefix = s->display_comment_prefix;
 +      s->display_comment_prefix = 1;
 +
 +      /*
 +       * Most hints are counter-productive when the commit has
 +       * already started.
 +       */
 +      s->hints = 0;
 +
        if (clean_message_contents)
                stripspace(&sb, 0);
  
         */
        if (!commitable && whence != FROM_MERGE && !allow_empty &&
            !(amend && is_a_merge(current_head))) {
 +              s->display_comment_prefix = old_display_comment_prefix;
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(_(empty_amend_advice), stderr);
@@@ -955,7 -935,6 +955,7 @@@ static const char *find_author_by_nickn
        struct rev_info revs;
        struct commit *commit;
        struct strbuf buf = STRBUF_INIT;
 +      struct string_list mailmap = STRING_LIST_INIT_NODUP;
        const char *av[20];
        int ac = 0;
  
        av[++ac] = buf.buf;
        av[++ac] = NULL;
        setup_revisions(ac, av, &revs, NULL);
 +      revs.mailmap = &mailmap;
 +      read_mailmap(revs.mailmap, NULL);
 +
        prepare_revision_walk(&revs);
        commit = get_revision(&revs);
        if (commit) {
                struct pretty_print_context ctx = {0};
                ctx.date_mode = DATE_NORMAL;
                strbuf_release(&buf);
 -              format_commit_message(commit, "%an <%ae>", &buf, &ctx);
 +              format_commit_message(commit, "%aN <%aE>", &buf, &ctx);
 +              clear_mailmap(&mailmap);
                return strbuf_detach(&buf, NULL);
        }
        die(_("No existing author found with '%s'"), name);
@@@ -1116,7 -1091,7 +1116,7 @@@ static int parse_and_validate_options(i
        if (patch_interactive)
                interactive = 1;
  
 -      if (!!also + !!only + !!all + !!interactive > 1)
 +      if (also + only + all + interactive > 1)
                die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
        if (argc == 0 && (also || (only && !amend)))
                die(_("No paths with --include/--only does not make sense."));
@@@ -1207,10 -1182,6 +1207,10 @@@ static int git_status_config(const cha
                s->use_color = git_config_colorbool(k, v);
                return 0;
        }
 +      if (!strcmp(k, "status.displaycommentprefix")) {
 +              s->display_comment_prefix = git_config_bool(k, v);
 +              return 0;
 +      }
        if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
                int slot = parse_status_slot(k, 13);
                if (slot < 0)
@@@ -1257,14 -1228,14 +1257,14 @@@ int cmd_status(int argc, const char **a
                OPT_SET_INT(0, "long", &status_format,
                            N_("show status in long format (default)"),
                            STATUS_FORMAT_LONG),
 -              OPT_BOOLEAN('z', "null", &s.null_termination,
 -                          N_("terminate entries with NUL")),
 +              OPT_BOOL('z', "null", &s.null_termination,
 +                       N_("terminate entries with NUL")),
                { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
                  N_("mode"),
                  N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 -              OPT_BOOLEAN(0, "ignored", &show_ignored_in_status,
 -                          N_("show ignored files")),
 +              OPT_BOOL(0, "ignored", &show_ignored_in_status,
 +                       N_("show ignored files")),
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
                  N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_status_usage, builtin_status_options);
  
 -      wt_status_prepare(&s);
 -      gitmodules_config();
 -      git_config(git_status_config, &s);
 -      determine_whence(&s);
 +      status_init_config(&s, git_status_config);
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
        handle_untracked_files_arg(&s);
        if (show_ignored_in_status)
                s.show_ignored_files = 1;
 -      if (*argv)
 -              s.pathspec = get_pathspec(prefix, argv);
 +      parse_pathspec(&s.pathspec, 0,
 +                     PATHSPEC_PREFER_FULL,
 +                     prefix, argv);
  
 -      read_cache_preload(s.pathspec);
 -      refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
 +      read_cache_preload(&s.pathspec);
 +      refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
  
        fd = hold_locked_index(&index_lock, 0);
        if (0 <= fd)
@@@ -1338,7 -1311,7 +1338,7 @@@ static void print_summary(const char *p
        commit = lookup_commit(sha1);
        if (!commit)
                die(_("couldn't look up newly created commit"));
-       if (!commit || parse_commit(commit))
+       if (parse_commit(commit))
                die(_("could not parse newly created commit"));
  
        strbuf_addstr(&format, "format:%h] %s");
@@@ -1461,24 -1434,24 +1461,24 @@@ int cmd_commit(int argc, const char **a
                OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
                OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
                OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 -              OPT_BOOLEAN(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
 -              OPT_BOOLEAN('s', "signoff", &signoff, N_("add Signed-off-by:")),
 +              OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
 +              OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")),
                OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
                OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
                OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")),
 -              OPT_BOOLEAN(0, "status", &include_status, N_("include status in commit message template")),
 +              OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")),
                { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"),
                  N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
                /* end commit message options */
  
                OPT_GROUP(N_("Commit contents options")),
 -              OPT_BOOLEAN('a', "all", &all, N_("commit all changed files")),
 -              OPT_BOOLEAN('i', "include", &also, N_("add specified files to index for commit")),
 -              OPT_BOOLEAN(0, "interactive", &interactive, N_("interactively add files")),
 -              OPT_BOOLEAN('p', "patch", &patch_interactive, N_("interactively add changes")),
 -              OPT_BOOLEAN('o', "only", &only, N_("commit only specified files")),
 -              OPT_BOOLEAN('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
 -              OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")),
 +              OPT_BOOL('a', "all", &all, N_("commit all changed files")),
 +              OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
 +              OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
 +              OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
 +              OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 +              OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
 +              OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
                OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
                            STATUS_FORMAT_SHORT),
                OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
                OPT_SET_INT(0, "long", &status_format,
                            N_("show status in long format (default)"),
                            STATUS_FORMAT_LONG),
 -              OPT_BOOLEAN('z', "null", &s.null_termination,
 -                          N_("terminate entries with NUL")),
 -              OPT_BOOLEAN(0, "amend", &amend, N_("amend previous commit")),
 -              OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
 +              OPT_BOOL('z', "null", &s.null_termination,
 +                       N_("terminate entries with NUL")),
 +              OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
 +              OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
                { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
                /* end commit contents options */
  
 -              { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
 -                N_("ok to record an empty change"),
 -                PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
 -              { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL,
 -                N_("ok to record a change with an empty message"),
 -                PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
 +              OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
 +                              N_("ok to record an empty change")),
 +              OPT_HIDDEN_BOOL(0, "allow-empty-message", &allow_empty_message,
 +                              N_("ok to record a change with an empty message")),
  
                OPT_END()
        };
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
  
 -      wt_status_prepare(&s);
 -      gitmodules_config();
 -      git_config(git_commit_config, &s);
 +      status_init_config(&s, git_commit_config);
        status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
 -      determine_whence(&s);
        s.colopts = 0;
  
        if (get_sha1("HEAD", sha1))
                current_head = NULL;
        else {
                current_head = lookup_commit_or_die(sha1, "HEAD");
-               if (!current_head || parse_commit(current_head))
+               if (parse_commit(current_head))
                        die(_("could not parse HEAD commit"));
        }
        argc = parse_and_validate_options(argc, argv, builtin_commit_options,
                                           !current_head
                                           ? NULL
                                           : current_head->object.sha1,
 -                                         0);
 +                                         0, NULL);
  
        nl = strchr(sb.buf, '\n');
        if (nl)
diff --combined builtin/fast-export.c
index 78250eab08abf692344277e01cb94fc5f5c2f807,7785c22178b717d8d0a1275b44d27085958a51ee..ea6305258d4d63e58f2453736cfc5aa38ec37282
@@@ -30,7 -30,6 +30,7 @@@ static int fake_missing_tagger
  static int use_done_feature;
  static int no_data;
  static int full_tree;
 +static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
  
  static int parse_opt_signed_tag_mode(const struct option *opt,
                                     const char *arg, int unset)
@@@ -287,7 -286,7 +287,7 @@@ static void handle_commit(struct commi
  
        rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
  
-       parse_commit(commit);
+       parse_commit_or_die(commit);
        author = strstr(commit->buffer, "\nauthor ");
        if (!author)
                die ("Could not find author in commit %s",
        if (commit->parents &&
            get_object_mark(&commit->parents->item->object) != 0 &&
            !full_tree) {
-               parse_commit(commit->parents->item);
+               parse_commit_or_die(commit->parents->item);
                diff_tree_sha1(commit->parents->item->tree->object.sha1,
                               commit->tree->object.sha1, "", &rev->diffopt);
        }
@@@ -485,32 -484,10 +485,32 @@@ static void handle_tag(const char *name
               (int)message_size, (int)message_size, message ? message : "");
  }
  
 -static void get_tags_and_duplicates(struct rev_cmdline_info *info,
 -                                  struct string_list *extra_refs)
 +static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)
 +{
 +      switch (e->item->type) {
 +      case OBJ_COMMIT:
 +              return (struct commit *)e->item;
 +      case OBJ_TAG: {
 +              struct tag *tag = (struct tag *)e->item;
 +
 +              /* handle nested tags */
 +              while (tag && tag->object.type == OBJ_TAG) {
 +                      parse_object(tag->object.sha1);
 +                      string_list_append(&extra_refs, full_name)->util = tag;
 +                      tag = (struct tag *)tag->tagged;
 +              }
 +              if (!tag)
 +                      die("Tag %s points nowhere?", e->name);
 +              return (struct commit *)tag;
 +              break;
 +      }
 +      default:
 +              return NULL;
 +      }
 +}
 +
 +static void get_tags_and_duplicates(struct rev_cmdline_info *info)
  {
 -      struct tag *tag;
        int i;
  
        for (i = 0; i < info->nr; i++) {
                if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
                        continue;
  
 -              switch (e->item->type) {
 -              case OBJ_COMMIT:
 -                      commit = (struct commit *)e->item;
 -                      break;
 -              case OBJ_TAG:
 -                      tag = (struct tag *)e->item;
 -
 -                      /* handle nested tags */
 -                      while (tag && tag->object.type == OBJ_TAG) {
 -                              parse_object(tag->object.sha1);
 -                              string_list_append(extra_refs, full_name)->util = tag;
 -                              tag = (struct tag *)tag->tagged;
 -                      }
 -                      if (!tag)
 -                              die ("Tag %s points nowhere?", e->name);
 -                      switch(tag->object.type) {
 -                      case OBJ_COMMIT:
 -                              commit = (struct commit *)tag;
 -                              break;
 -                      case OBJ_BLOB:
 -                              export_blob(tag->object.sha1);
 -                              continue;
 -                      default: /* OBJ_TAG (nested tags) is already handled */
 -                              warning("Tag points to object of unexpected type %s, skipping.",
 -                                      typename(tag->object.type));
 -                              continue;
 -                      }
 -                      break;
 -              default:
 +              commit = get_commit(e, full_name);
 +              if (!commit) {
                        warning("%s: Unexpected object of type %s, skipping.",
                                e->name,
                                typename(e->item->type));
                        continue;
                }
  
 +              switch(commit->object.type) {
 +              case OBJ_COMMIT:
 +                      break;
 +              case OBJ_BLOB:
 +                      export_blob(commit->object.sha1);
 +                      continue;
 +              default: /* OBJ_TAG (nested tags) is already handled */
 +                      warning("Tag points to object of unexpected type %s, skipping.",
 +                              typename(commit->object.type));
 +                      continue;
 +              }
 +
                /*
                 * This ref will not be updated through a commit, lets make
                 * sure it gets properly updated eventually.
                 */
                if (commit->util || commit->object.flags & SHOWN)
 -                      string_list_append(extra_refs, full_name)->util = commit;
 +                      string_list_append(&extra_refs, full_name)->util = commit;
                if (!commit->util)
                        commit->util = full_name;
        }
  }
  
 -static void handle_tags_and_duplicates(struct string_list *extra_refs)
 +static void handle_tags_and_duplicates(void)
  {
        struct commit *commit;
        int i;
  
 -      for (i = extra_refs->nr - 1; i >= 0; i--) {
 -              const char *name = extra_refs->items[i].string;
 -              struct object *object = extra_refs->items[i].util;
 +      for (i = extra_refs.nr - 1; i >= 0; i--) {
 +              const char *name = extra_refs.items[i].string;
 +              struct object *object = extra_refs.items[i].util;
                switch (object->type) {
                case OBJ_TAG:
                        handle_tag(name, (struct tag *)object);
@@@ -665,6 -657,7 +665,6 @@@ int cmd_fast_export(int argc, const cha
  {
        struct rev_info revs;
        struct object_array commits = OBJECT_ARRAY_INIT;
 -      struct string_list extra_refs = STRING_LIST_INIT_NODUP;
        struct commit *commit;
        char *export_filename = NULL, *import_filename = NULL;
        uint32_t lastimportid;
                             N_("Dump marks to this file")),
                OPT_STRING(0, "import-marks", &import_filename, N_("file"),
                             N_("Import marks from this file")),
 -              OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
 -                           N_("Fake a tagger when tags lack one")),
 -              OPT_BOOLEAN(0, "full-tree", &full_tree,
 -                           N_("Output full tree for each commit")),
 -              OPT_BOOLEAN(0, "use-done-feature", &use_done_feature,
 +              OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger,
 +                       N_("Fake a tagger when tags lack one")),
 +              OPT_BOOL(0, "full-tree", &full_tree,
 +                       N_("Output full tree for each commit")),
 +              OPT_BOOL(0, "use-done-feature", &use_done_feature,
                             N_("Use the done feature to terminate the stream")),
                OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")),
                OPT_END()
        if (import_filename && revs.prune_data.nr)
                full_tree = 1;
  
 -      get_tags_and_duplicates(&revs.cmdline, &extra_refs);
 +      get_tags_and_duplicates(&revs.cmdline);
  
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
                }
        }
  
 -      handle_tags_and_duplicates(&extra_refs);
 +      handle_tags_and_duplicates();
  
        if (export_filename && lastimportid != last_idnum)
                export_marks(export_filename);
diff --combined builtin/name-rev.c
index 20fcf8c696700993617649da77e5bbf6b416498a,26f40330e30842ffb9995f32be9604bec9e1f940..23daaa7d99b40e1bbd38a2743bc0d9ed0c38eda9
@@@ -27,8 -27,7 +27,7 @@@ static void name_rev(struct commit *com
        struct commit_list *parents;
        int parent_number = 1;
  
-       if (!commit->object.parsed)
-               parse_commit(commit);
+       parse_commit(commit);
  
        if (commit->date < cutoff)
                return;
@@@ -310,15 -309,15 +309,15 @@@ int cmd_name_rev(int argc, const char *
        int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
        struct name_ref_data data = { 0, 0, NULL };
        struct option opts[] = {
 -              OPT_BOOLEAN(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
 -              OPT_BOOLEAN(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
 +              OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
 +              OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
                OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"),
                                   N_("only use refs matching <pattern>")),
                OPT_GROUP(""),
 -              OPT_BOOLEAN(0, "all", &all, N_("list all commits reachable from all refs")),
 -              OPT_BOOLEAN(0, "stdin", &transform_stdin, N_("read from stdin")),
 -              OPT_BOOLEAN(0, "undefined", &allow_undefined, N_("allow to print `undefined` names")),
 -              OPT_BOOLEAN(0, "always",     &always,
 +              OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
 +              OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
 +              OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")),
 +              OPT_BOOL(0, "always",     &always,
                           N_("show abbreviated commit object as fallback")),
                {
                        /* A Hidden OPT_BOOL */
  
        git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
 -      if (!!all + !!transform_stdin + !!argc > 1) {
 +      if (all + transform_stdin + !!argc > 1) {
                error("Specify either a list, or --all, not both!");
                usage_with_options(name_rev_usage, opts);
        }
diff --combined builtin/show-branch.c
index 001f29ca1b9ef8bfe2a06cda4d98c704584812fe,3afc79b2c3062ccd6ce3e61dadaac9fe33ff9575..46902c3de40b05b19ff1b5b0127b9f1cd11f6cde
@@@ -227,8 -227,7 +227,7 @@@ static void join_revs(struct commit_lis
                        parents = parents->next;
                        if ((this_flag & flags) == flags)
                                continue;
-                       if (!p->object.parsed)
-                               parse_commit(p);
+                       parse_commit(p);
                        if (mark_seen(p, seen_p) && !still_interesting)
                                extra--;
                        p->object.flags |= flags;
@@@ -646,30 -645,30 +645,30 @@@ int cmd_show_branch(int ac, const char 
        int dense = 1;
        const char *reflog_base = NULL;
        struct option builtin_show_branch_options[] = {
 -              OPT_BOOLEAN('a', "all", &all_heads,
 -                          N_("show remote-tracking and local branches")),
 -              OPT_BOOLEAN('r', "remotes", &all_remotes,
 -                          N_("show remote-tracking branches")),
 +              OPT_BOOL('a', "all", &all_heads,
 +                       N_("show remote-tracking and local branches")),
 +              OPT_BOOL('r', "remotes", &all_remotes,
 +                       N_("show remote-tracking branches")),
                OPT__COLOR(&showbranch_use_color,
                            N_("color '*!+-' corresponding to the branch")),
                { OPTION_INTEGER, 0, "more", &extra, N_("n"),
                            N_("show <n> more commits after the common ancestor"),
                            PARSE_OPT_OPTARG, NULL, (intptr_t)1 },
                OPT_SET_INT(0, "list", &extra, N_("synonym to more=-1"), -1),
 -              OPT_BOOLEAN(0, "no-name", &no_name, N_("suppress naming strings")),
 -              OPT_BOOLEAN(0, "current", &with_current_branch,
 -                          N_("include the current branch")),
 -              OPT_BOOLEAN(0, "sha1-name", &sha1_name,
 -                          N_("name commits with their object names")),
 -              OPT_BOOLEAN(0, "merge-base", &merge_base,
 -                          N_("show possible merge bases")),
 -              OPT_BOOLEAN(0, "independent", &independent,
 +              OPT_BOOL(0, "no-name", &no_name, N_("suppress naming strings")),
 +              OPT_BOOL(0, "current", &with_current_branch,
 +                       N_("include the current branch")),
 +              OPT_BOOL(0, "sha1-name", &sha1_name,
 +                       N_("name commits with their object names")),
 +              OPT_BOOL(0, "merge-base", &merge_base,
 +                       N_("show possible merge bases")),
 +              OPT_BOOL(0, "independent", &independent,
                            N_("show refs unreachable from any other ref")),
                OPT_SET_INT(0, "topo-order", &sort_order,
                            N_("show commits in topological order"),
                            REV_SORT_IN_GRAPH_ORDER),
 -              OPT_BOOLEAN(0, "topics", &topics,
 -                          N_("show only commits not on the first branch")),
 +              OPT_BOOL(0, "topics", &topics,
 +                       N_("show only commits not on the first branch")),
                OPT_SET_INT(0, "sparse", &dense,
                            N_("show merges reachable from only one tip"), 0),
                OPT_SET_INT(0, "date-order", &sort_order,
diff --combined commit.c
index de16a3c0a2d6693173678247ba0755e4d7483a1c,8535e5cf35bfabe4a9dd0dc19e1caf6fec93531a..11509ffc471c548a1367068b3d7c2f15d19361f4
+++ b/commit.c
@@@ -79,7 -79,7 +79,7 @@@ struct commit *lookup_commit_reference_
        if (get_sha1_committish(name, sha1))
                return NULL;
        commit = lookup_commit_reference(sha1);
-       if (!commit || parse_commit(commit))
+       if (parse_commit(commit))
                return NULL;
        return commit;
  }
@@@ -341,6 -341,13 +341,13 @@@ int parse_commit(struct commit *item
        return ret;
  }
  
+ void parse_commit_or_die(struct commit *item)
+ {
+       if (parse_commit(item))
+               die("unable to parse commit %s",
+                   item ? sha1_to_hex(item->object.sha1) : "(null)");
+ }
  int find_commit_subject(const char *commit_buffer, const char **subject)
  {
        const char *eol;
@@@ -377,22 -384,6 +384,22 @@@ unsigned commit_list_count(const struc
        return c;
  }
  
 +struct commit_list *copy_commit_list(struct commit_list *list)
 +{
 +      struct commit_list *head = NULL;
 +      struct commit_list **pp = &head;
 +      while (list) {
 +              struct commit_list *new;
 +              new = xmalloc(sizeof(struct commit_list));
 +              new->item = list->item;
 +              new->next = NULL;
 +              *pp = new;
 +              pp = &new->next;
 +              list = list->next;
 +      }
 +      return head;
 +}
 +
  void free_commit_list(struct commit_list *list)
  {
        while (list) {
diff --combined commit.h
index bd841f4d0c5e2b7fb8e83d55ba0d0b7e2839bf8a,a3645dabf83bd25b1a8f66993bfbbfd2d8f922b0..934af889f7d8f1aa9fba0e9ff900b1f1132a44a7
+++ b/commit.h
@@@ -49,6 -49,7 +49,7 @@@ struct commit *lookup_commit_or_die(con
  
  int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
  int parse_commit(struct commit *item);
+ void parse_commit_or_die(struct commit *item);
  
  /* Find beginning and length of commit subject. */
  int find_commit_subject(const char *commit_buffer, const char **subject);
@@@ -62,9 -63,6 +63,9 @@@ struct commit_list *commit_list_insert_
                                    struct commit_list **list);
  void commit_list_sort_by_date(struct commit_list **list);
  
 +/* Shallow copy of the input list */
 +struct commit_list *copy_commit_list(struct commit_list *list);
 +
  void free_commit_list(struct commit_list *list);
  
  /* Commit formats */
@@@ -201,10 -199,6 +202,10 @@@ extern struct commit_list *get_shallow_
                int depth, int shallow_flag, int not_shallow_flag);
  extern void check_shallow_file_for_update(void);
  extern void set_alternate_shallow_file(const char *path);
 +extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol);
 +extern void setup_alternate_shallow(struct lock_file *shallow_lock,
 +                                  const char **alternate_shallow_file);
 +extern char *setup_temporary_shallow(void);
  
  int is_descendant_of(struct commit *, struct commit_list *);
  int in_merge_bases(struct commit *, struct commit *);
@@@ -212,7 -206,7 +213,7 @@@ int in_merge_bases_many(struct commit *
  
  extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
  extern int run_add_interactive(const char *revision, const char *patch_mode,
 -                             const char **pathspec);
 +                             const struct pathspec *pathspec);
  
  static inline int single_parent(struct commit *commit)
  {
diff --combined fetch-pack.c
index 1042448fa0b321e8826f0fdb69c89810c731cf5d,86b69775e329565fe1acf995aa122030e1b5e90f..5a1200f1a06f0ba3a9366eb9221d982d423d8da0
@@@ -9,7 -9,6 +9,7 @@@
  #include "fetch-pack.h"
  #include "remote.h"
  #include "run-command.h"
 +#include "connect.h"
  #include "transport.h"
  #include "version.h"
  #include "prio-queue.h"
@@@ -47,9 -46,8 +47,8 @@@ static void rev_list_push(struct commi
        if (!(commit->object.flags & mark)) {
                commit->object.flags |= mark;
  
-               if (!(commit->object.parsed))
-                       if (parse_commit(commit))
-                               return;
+               if (parse_commit(commit))
+                       return;
  
                prio_queue_put(&rev_list, commit);
  
@@@ -128,8 -126,7 +127,7 @@@ static const unsigned char *get_rev(voi
                        return NULL;
  
                commit = prio_queue_get(&rev_list);
-               if (!commit->object.parsed)
-                       parse_commit(commit);
+               parse_commit(commit);
                parents = commit->parents;
  
                commit->object.flags |= POPPED;
@@@ -185,6 -182,36 +183,6 @@@ static void consume_shallow_list(struc
        }
  }
  
 -struct write_shallow_data {
 -      struct strbuf *out;
 -      int use_pack_protocol;
 -      int count;
 -};
 -
 -static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
 -{
 -      struct write_shallow_data *data = cb_data;
 -      const char *hex = sha1_to_hex(graft->sha1);
 -      data->count++;
 -      if (data->use_pack_protocol)
 -              packet_buf_write(data->out, "shallow %s", hex);
 -      else {
 -              strbuf_addstr(data->out, hex);
 -              strbuf_addch(data->out, '\n');
 -      }
 -      return 0;
 -}
 -
 -static int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
 -{
 -      struct write_shallow_data data;
 -      data.out = out;
 -      data.use_pack_protocol = use_pack_protocol;
 -      data.count = 0;
 -      for_each_commit_graft(write_one_shallow, &data);
 -      return data.count;
 -}
 -
  static enum ack_type get_ack(int fd, unsigned char *result_sha1)
  {
        int len;
@@@ -659,7 -686,7 +657,7 @@@ static int get_pack(struct fetch_pack_a
        const char *argv[22];
        char keep_arg[256];
        char hdr_arg[256];
 -      const char **av;
 +      const char **av, *cmd_name;
        int do_keep = args->keep_pack;
        struct child_process cmd;
        int ret;
        if (do_keep) {
                if (pack_lockfile)
                        cmd.out = -1;
 -              *av++ = "index-pack";
 +              *av++ = cmd_name = "index-pack";
                *av++ = "--stdin";
                if (!args->quiet && !args->no_progress)
                        *av++ = "-v";
                        *av++ = "--check-self-contained-and-connected";
        }
        else {
 -              *av++ = "unpack-objects";
 +              *av++ = cmd_name = "unpack-objects";
                if (args->quiet || args->no_progress)
                        *av++ = "-q";
                args->check_self_contained_and_connected = 0;
        cmd.in = demux.out;
        cmd.git_cmd = 1;
        if (start_command(&cmd))
 -              die("fetch-pack: unable to fork off %s", argv[0]);
 +              die("fetch-pack: unable to fork off %s", cmd_name);
        if (do_keep && pack_lockfile) {
                *pack_lockfile = index_pack_lockfile(cmd.out);
                close(cmd.out);
        }
  
 +      if (!use_sideband)
 +              /* Closed by start_command() */
 +              xd[0] = -1;
 +
        ret = finish_command(&cmd);
        if (!ret || (args->check_self_contained_and_connected && ret == 1))
                args->self_contained_and_connected =
                        args->check_self_contained_and_connected &&
                        ret == 0;
        else
 -              die("%s failed", argv[0]);
 +              die("%s failed", cmd_name);
        if (use_sideband && finish_async(&demux))
                die("error in sideband demultiplexer");
        return 0;
@@@ -770,6 -793,27 +768,6 @@@ static int cmp_ref_by_name(const void *
        return strcmp(a->name, b->name);
  }
  
 -static void setup_alternate_shallow(void)
 -{
 -      struct strbuf sb = STRBUF_INIT;
 -      int fd;
 -
 -      check_shallow_file_for_update();
 -      fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"),
 -                                     LOCK_DIE_ON_ERROR);
 -      if (write_shallow_commits(&sb, 0)) {
 -              if (write_in_full(fd, sb.buf, sb.len) != sb.len)
 -                      die_errno("failed to write to %s", shallow_lock.filename);
 -              alternate_shallow_file = shallow_lock.filename;
 -      } else
 -              /*
 -               * is_repository_shallow() sees empty string as "no
 -               * shallow file".
 -               */
 -              alternate_shallow_file = "";
 -      strbuf_release(&sb);
 -}
 -
  static struct ref *do_fetch_pack(struct fetch_pack_args *args,
                                 int fd[2],
                                 const struct ref *orig_ref,
        if (args->stateless_rpc)
                packet_flush(fd[1]);
        if (args->depth > 0)
 -              setup_alternate_shallow();
 +              setup_alternate_shallow(&shallow_lock, &alternate_shallow_file);
 +      else
 +              alternate_shallow_file = NULL;
        if (get_pack(args, fd, pack_lockfile))
                die("git fetch-pack: fetch failed.");
  
@@@ -943,7 -985,7 +941,7 @@@ struct ref *fetch_pack(struct fetch_pac
        }
        ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile);
  
 -      if (alternate_shallow_file) {
 +      if (args->depth > 0 && alternate_shallow_file) {
                if (*alternate_shallow_file == '\0') { /* --unshallow */
                        unlink_or_warn(git_path("shallow"));
                        rollback_lock_file(&shallow_lock);
diff --combined log-tree.c
index 8534d91826f3cf05f810a28728ee8bd51649d819,11604b1e5e1f657903b2837a5fadfa72c610f849..e958d0748f11d82995f63d06b7e6ed05b2e7e4f1
@@@ -734,11 -734,11 +734,11 @@@ static int log_tree_diff(struct rev_inf
        if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS))
                return 0;
  
-       parse_commit(commit);
+       parse_commit_or_die(commit);
        sha1 = commit->tree->object.sha1;
  
        /* Root commit? */
 -      parents = commit->parents;
 +      parents = get_saved_parents(opt, commit);
        if (!parents) {
                if (opt->show_root_diff) {
                        diff_root_tree_sha1(sha1, "", &opt->diffopt);
                         * parent, showing summary diff of the others
                         * we merged _in_.
                         */
-                       parse_commit(parents->item);
+                       parse_commit_or_die(parents->item);
                        diff_tree_sha1(parents->item->tree->object.sha1,
                                       sha1, "", &opt->diffopt);
                        log_tree_diff_flush(opt);
        for (;;) {
                struct commit *parent = parents->item;
  
-               parse_commit(parent);
+               parse_commit_or_die(parent);
                diff_tree_sha1(parent->tree->object.sha1,
                               sha1, "", &opt->diffopt);
                log_tree_diff_flush(opt);
diff --combined sha1_name.c
index e9c299943b817b5b3b5b700ac3979211d5688e2e,729ab14a87007b2f64c2bd22cb669075cafb39b6..2f37488b96c0a8485e90d87befa997d75ba4b7b1
@@@ -343,6 -343,7 +343,6 @@@ static int get_short_sha1(const char *n
        return status;
  }
  
 -
  int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data)
  {
        char hex_pfx[40];
@@@ -581,8 -582,6 +581,6 @@@ static int get_parent(const char *name
        if (ret)
                return ret;
        commit = lookup_commit_reference(sha1);
-       if (!commit)
-               return -1;
        if (parse_commit(commit))
                return -1;
        if (!idx) {
@@@ -676,13 -675,11 +674,13 @@@ static int peel_onion(const char *name
                return -1;
  
        sp++; /* beginning of type name, or closing brace for empty */
 -      if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
 +      if (!prefixcmp(sp, "commit}"))
                expected_type = OBJ_COMMIT;
 -      else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
 +      else if (!prefixcmp(sp, "tag}"))
 +              expected_type = OBJ_TAG;
 +      else if (!prefixcmp(sp, "tree}"))
                expected_type = OBJ_TREE;
 -      else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
 +      else if (!prefixcmp(sp, "blob}"))
                expected_type = OBJ_BLOB;
        else if (!prefixcmp(sp, "object}"))
                expected_type = OBJ_ANY;
@@@ -1005,28 -1002,6 +1003,28 @@@ int get_sha1_mb(const char *name, unsig
        return st;
  }
  
 +/* parse @something syntax, when 'something' is not {.*} */
 +static int interpret_empty_at(const char *name, int namelen, int len, struct strbuf *buf)
 +{
 +      const char *next;
 +
 +      if (len || name[1] == '{')
 +              return -1;
 +
 +      /* make sure it's a single @, or @@{.*}, not @foo */
 +      next = strchr(name + len + 1, '@');
 +      if (next && next[1] != '{')
 +              return -1;
 +      if (!next)
 +              next = name + namelen;
 +      if (next != name + 1)
 +              return -1;
 +
 +      strbuf_reset(buf);
 +      strbuf_add(buf, "HEAD", 4);
 +      return 1;
 +}
 +
  static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
  {
        /* we have extra data, which might need further processing */
        int ret;
  
        strbuf_add(buf, name + len, namelen - len);
 -      ret = interpret_branch_name(buf->buf, &tmp);
 +      ret = interpret_branch_name(buf->buf, buf->len, &tmp);
        /* that data was not interpreted, remove our cruft */
        if (ret < 0) {
                strbuf_setlen(buf, used);
   * 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)
 +int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
  {
        char *cp;
        struct branch *upstream;
 -      int namelen = strlen(name);
        int len = interpret_nth_prior_checkout(name, buf);
        int tmp_len;
  
 +      if (!namelen)
 +              namelen = strlen(name);
 +
        if (!len) {
                return len; /* syntax Ok, not enough switches */
        } else if (len > 0) {
        cp = strchr(name, '@');
        if (!cp)
                return -1;
 +
 +      len = interpret_empty_at(name, namelen, cp - name, buf);
 +      if (len > 0)
 +              return reinterpret(name, namelen, len, buf);
 +
        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);
  int strbuf_branchname(struct strbuf *sb, const char *name)
  {
        int len = strlen(name);
 -      int used = interpret_branch_name(name, sb);
 +      int used = interpret_branch_name(name, len, sb);
  
        if (used == len)
                return 0;
@@@ -1161,13 -1128,13 +1159,13 @@@ int get_sha1(const char *name, unsigne
  }
  
  /*
 - * Many callers know that the user meant to name a committish by
 + * Many callers know that the user meant to name a commit-ish by
   * syntactical positions where the object name appears.  Calling this
   * function allows the machinery to disambiguate shorter-than-unique
 - * abbreviated object names between committish and others.
 + * abbreviated object names between commit-ish and others.
   *
   * Note that this does NOT error out when the named object is not a
 - * committish. It is merely to give a hint to the disambiguation
 + * commit-ish. It is merely to give a hint to the disambiguation
   * machinery.
   */
  int get_sha1_committish(const char *name, unsigned char *sha1)
diff --combined shallow.c
index cdf37d694de175adcce10fa2e2a79c6579603b0b,a273685e75098440548b78c6445c1f2efeb0ad2b..961cf6f02480e04d1baebb8f508aa17c5fea6355
+++ b/shallow.c
@@@ -1,7 -1,6 +1,7 @@@
  #include "cache.h"
  #include "commit.h"
  #include "tag.h"
 +#include "pkt-line.h"
  
  static int is_shallow = -1;
  static struct stat shallow_stat;
@@@ -90,8 -89,7 +90,7 @@@ struct commit_list *get_shallow_commits
                                cur_depth = *(int *)commit->util;
                        }
                }
-               if (parse_commit(commit))
-                       die("invalid commit");
+               parse_commit_or_die(commit);
                cur_depth++;
                if (cur_depth >= depth) {
                        commit_list_insert(commit, &result);
@@@ -142,81 -140,3 +141,81 @@@ void check_shallow_file_for_update(void
                   )
                die("shallow file was changed during fetch");
  }
 +
 +struct write_shallow_data {
 +      struct strbuf *out;
 +      int use_pack_protocol;
 +      int count;
 +};
 +
 +static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
 +{
 +      struct write_shallow_data *data = cb_data;
 +      const char *hex = sha1_to_hex(graft->sha1);
 +      if (graft->nr_parent != -1)
 +              return 0;
 +      data->count++;
 +      if (data->use_pack_protocol)
 +              packet_buf_write(data->out, "shallow %s", hex);
 +      else {
 +              strbuf_addstr(data->out, hex);
 +              strbuf_addch(data->out, '\n');
 +      }
 +      return 0;
 +}
 +
 +int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
 +{
 +      struct write_shallow_data data;
 +      data.out = out;
 +      data.use_pack_protocol = use_pack_protocol;
 +      data.count = 0;
 +      for_each_commit_graft(write_one_shallow, &data);
 +      return data.count;
 +}
 +
 +char *setup_temporary_shallow(void)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int fd;
 +
 +      if (write_shallow_commits(&sb, 0)) {
 +              struct strbuf path = STRBUF_INIT;
 +              strbuf_addstr(&path, git_path("shallow_XXXXXX"));
 +              fd = xmkstemp(path.buf);
 +              if (write_in_full(fd, sb.buf, sb.len) != sb.len)
 +                      die_errno("failed to write to %s",
 +                                path.buf);
 +              close(fd);
 +              strbuf_release(&sb);
 +              return strbuf_detach(&path, NULL);
 +      }
 +      /*
 +       * is_repository_shallow() sees empty string as "no shallow
 +       * file".
 +       */
 +      return xstrdup("");
 +}
 +
 +void setup_alternate_shallow(struct lock_file *shallow_lock,
 +                           const char **alternate_shallow_file)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int fd;
 +
 +      check_shallow_file_for_update();
 +      fd = hold_lock_file_for_update(shallow_lock, git_path("shallow"),
 +                                     LOCK_DIE_ON_ERROR);
 +      if (write_shallow_commits(&sb, 0)) {
 +              if (write_in_full(fd, sb.buf, sb.len) != sb.len)
 +                      die_errno("failed to write to %s",
 +                                shallow_lock->filename);
 +              *alternate_shallow_file = shallow_lock->filename;
 +      } else
 +              /*
 +               * is_repository_shallow() sees empty string as "no
 +               * shallow file".
 +               */
 +              *alternate_shallow_file = "";
 +      strbuf_release(&sb);
 +}
diff --combined upload-pack.c
index c989a737f975b3aef41ffb9f6ec669ce3b2f2c2b,c107686ceaa91a615256bb406bf4e402ac7f1ecb..d30f339e707fe3263b9c186e54b6b3317afc986c
@@@ -10,7 -10,6 +10,7 @@@
  #include "revision.h"
  #include "list-objects.h"
  #include "run-command.h"
 +#include "connect.h"
  #include "sigchain.h"
  #include "version.h"
  #include "string-list.h"
@@@ -41,7 -40,6 +41,7 @@@ static struct object_array have_obj
  static struct object_array want_obj;
  static struct object_array extra_edge_obj;
  static unsigned int timeout;
 +static int keepalive = 5;
  /* 0 for no sideband,
   * otherwise maximum packet size (up to 65520 bytes).
   */
@@@ -70,28 -68,87 +70,28 @@@ static ssize_t send_client_data(int fd
        return sz;
  }
  
 -static FILE *pack_pipe = NULL;
 -static void show_commit(struct commit *commit, void *data)
 -{
 -      if (commit->object.flags & BOUNDARY)
 -              fputc('-', pack_pipe);
 -      if (fputs(sha1_to_hex(commit->object.sha1), pack_pipe) < 0)
 -              die("broken output pipe");
 -      fputc('\n', pack_pipe);
 -      fflush(pack_pipe);
 -      free(commit->buffer);
 -      commit->buffer = NULL;
 -}
 -
 -static void show_object(struct object *obj,
 -                      const struct name_path *path, const char *component,
 -                      void *cb_data)
 -{
 -      show_object_with_name(pack_pipe, obj, path, component);
 -}
 -
 -static void show_edge(struct commit *commit)
 -{
 -      fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1));
 -}
 -
 -static int do_rev_list(int in, int out, void *user_data)
 -{
 -      int i;
 -      struct rev_info revs;
 -
 -      pack_pipe = xfdopen(out, "w");
 -      init_revisions(&revs, NULL);
 -      revs.tag_objects = 1;
 -      revs.tree_objects = 1;
 -      revs.blob_objects = 1;
 -      if (use_thin_pack)
 -              revs.edge_hint = 1;
 -
 -      for (i = 0; i < want_obj.nr; i++) {
 -              struct object *o = want_obj.objects[i].item;
 -              /* why??? */
 -              o->flags &= ~UNINTERESTING;
 -              add_pending_object(&revs, o, NULL);
 -      }
 -      for (i = 0; i < have_obj.nr; i++) {
 -              struct object *o = have_obj.objects[i].item;
 -              o->flags |= UNINTERESTING;
 -              add_pending_object(&revs, o, NULL);
 -      }
 -      setup_revisions(0, NULL, &revs, NULL);
 -      if (prepare_revision_walk(&revs))
 -              die("revision walk setup failed");
 -      mark_edges_uninteresting(revs.commits, &revs, show_edge);
 -      if (use_thin_pack)
 -              for (i = 0; i < extra_edge_obj.nr; i++)
 -                      fprintf(pack_pipe, "-%s\n", sha1_to_hex(
 -                                      extra_edge_obj.objects[i].item->sha1));
 -      traverse_commit_list(&revs, show_commit, show_object, NULL);
 -      fflush(pack_pipe);
 -      fclose(pack_pipe);
 -      return 0;
 -}
 -
  static void create_pack_file(void)
  {
 -      struct async rev_list;
        struct child_process pack_objects;
        char data[8193], progress[128];
        char abort_msg[] = "aborting due to possible repository "
                "corruption on the remote side.";
        int buffered = -1;
        ssize_t sz;
 -      const char *argv[10];
 -      int arg = 0;
 +      const char *argv[12];
 +      int i, arg = 0;
 +      FILE *pipe_fd;
 +      char *shallow_file = NULL;
  
 -      argv[arg++] = "pack-objects";
 -      if (!shallow_nr) {
 -              argv[arg++] = "--revs";
 -              if (use_thin_pack)
 -                      argv[arg++] = "--thin";
 +      if (shallow_nr) {
 +              shallow_file = setup_temporary_shallow();
 +              argv[arg++] = "--shallow-file";
 +              argv[arg++] = shallow_file;
        }
 +      argv[arg++] = "pack-objects";
 +      argv[arg++] = "--revs";
 +      if (use_thin_pack)
 +              argv[arg++] = "--thin";
  
        argv[arg++] = "--stdout";
        if (!no_progress)
        if (start_command(&pack_objects))
                die("git upload-pack: unable to fork git-pack-objects");
  
 -      if (shallow_nr) {
 -              memset(&rev_list, 0, sizeof(rev_list));
 -              rev_list.proc = do_rev_list;
 -              rev_list.out = pack_objects.in;
 -              if (start_async(&rev_list))
 -                      die("git upload-pack: unable to fork git-rev-list");
 -      }
 -      else {
 -              FILE *pipe_fd = xfdopen(pack_objects.in, "w");
 -              int i;
 -
 -              for (i = 0; i < want_obj.nr; i++)
 -                      fprintf(pipe_fd, "%s\n",
 -                              sha1_to_hex(want_obj.objects[i].item->sha1));
 -              fprintf(pipe_fd, "--not\n");
 -              for (i = 0; i < have_obj.nr; i++)
 -                      fprintf(pipe_fd, "%s\n",
 -                              sha1_to_hex(have_obj.objects[i].item->sha1));
 -              fprintf(pipe_fd, "\n");
 -              fflush(pipe_fd);
 -              fclose(pipe_fd);
 -      }
 -
 +      pipe_fd = xfdopen(pack_objects.in, "w");
 +
 +      for (i = 0; i < want_obj.nr; i++)
 +              fprintf(pipe_fd, "%s\n",
 +                      sha1_to_hex(want_obj.objects[i].item->sha1));
 +      fprintf(pipe_fd, "--not\n");
 +      for (i = 0; i < have_obj.nr; i++)
 +              fprintf(pipe_fd, "%s\n",
 +                      sha1_to_hex(have_obj.objects[i].item->sha1));
 +      for (i = 0; i < extra_edge_obj.nr; i++)
 +              fprintf(pipe_fd, "%s\n",
 +                      sha1_to_hex(extra_edge_obj.objects[i].item->sha1));
 +      fprintf(pipe_fd, "\n");
 +      fflush(pipe_fd);
 +      fclose(pipe_fd);
  
        /* We read from pack_objects.err to capture stderr output for
         * progress bar, and pack_objects.out to capture the pack data.
        while (1) {
                struct pollfd pfd[2];
                int pe, pu, pollsize;
 +              int ret;
  
                reset_timeout();
  
                if (!pollsize)
                        break;
  
 -              if (poll(pfd, pollsize, -1) < 0) {
 +              ret = poll(pfd, pollsize, 1000 * keepalive);
 +              if (ret < 0) {
                        if (errno != EINTR) {
                                error("poll failed, resuming: %s",
                                      strerror(errno));
                        if (sz < 0)
                                goto fail;
                }
 +
 +              /*
 +               * We hit the keepalive timeout without saying anything; send
 +               * an empty message on the data sideband just to let the other
 +               * side know we're still working on it, but don't have any data
 +               * yet.
 +               *
 +               * If we don't have a sideband channel, there's no room in the
 +               * protocol to say anything, so those clients are just out of
 +               * luck.
 +               */
 +              if (!ret && use_sideband) {
 +                      static const char buf[] = "0005\1";
 +                      write_or_die(1, buf, 5);
 +              }
        }
  
        if (finish_command(&pack_objects)) {
                error("git upload-pack: git-pack-objects died with error.");
                goto fail;
        }
 -      if (shallow_nr && finish_async(&rev_list))
 -              goto fail;      /* error was already reported */
 +      if (shallow_file) {
 +              if (*shallow_file)
 +                      unlink(shallow_file);
 +              free(shallow_file);
 +      }
  
        /* flush the data */
        if (0 <= buffered) {
@@@ -649,8 -694,7 +649,7 @@@ static void receive_needs(void
                                /* make sure the real parents are parsed */
                                unregister_shallow(object->sha1);
                                object->parsed = 0;
-                               if (parse_commit((struct commit *)object))
-                                       die("invalid commit");
+                               parse_commit_or_die((struct commit *)object);
                                parents = ((struct commit *)object)->parents;
                                while (parents) {
                                        add_object_array(&parents->item->object,
@@@ -689,16 -733,6 +688,16 @@@ static int mark_our_ref(const char *ref
        return 0;
  }
  
 +static void format_symref_info(struct strbuf *buf, struct string_list *symref)
 +{
 +      struct string_list_item *item;
 +
 +      if (!symref->nr)
 +              return;
 +      for_each_string_list_item(item, symref)
 +              strbuf_addf(buf, " symref=%s:%s", item->string, (char *)item->util);
 +}
 +
  static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
  {
        static const char *capabilities = "multi_ack thin-pack side-band"
        const char *refname_nons = strip_namespace(refname);
        unsigned char peeled[20];
  
 -      if (mark_our_ref(refname, sha1, flag, cb_data))
 +      if (mark_our_ref(refname, sha1, flag, NULL))
                return 0;
  
 -      if (capabilities)
 -              packet_write(1, "%s %s%c%s%s%s agent=%s\n",
 +      if (capabilities) {
 +              struct strbuf symref_info = STRBUF_INIT;
 +
 +              format_symref_info(&symref_info, cb_data);
 +              packet_write(1, "%s %s%c%s%s%s%s agent=%s\n",
                             sha1_to_hex(sha1), refname_nons,
                             0, capabilities,
                             allow_tip_sha1_in_want ? " allow-tip-sha1-in-want" : "",
                             stateless_rpc ? " no-done" : "",
 +                           symref_info.buf,
                             git_user_agent_sanitized());
 -      else
 +              strbuf_release(&symref_info);
 +      } else {
                packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
 +      }
        capabilities = NULL;
        if (!peel_ref(refname, peeled))
                packet_write(1, "%s %s^{}\n", sha1_to_hex(peeled), refname_nons);
        return 0;
  }
  
 +static int find_symref(const char *refname, const unsigned char *sha1, int flag,
 +                     void *cb_data)
 +{
 +      const char *symref_target;
 +      struct string_list_item *item;
 +      unsigned char unused[20];
 +
 +      if ((flag & REF_ISSYMREF) == 0)
 +              return 0;
 +      symref_target = resolve_ref_unsafe(refname, unused, 0, &flag);
 +      if (!symref_target || (flag & REF_ISSYMREF) == 0)
 +              die("'%s' is a symref but it is not?", refname);
 +      item = string_list_append(cb_data, refname);
 +      item->util = xstrdup(symref_target);
 +      return 0;
 +}
 +
  static void upload_pack(void)
  {
 +      struct string_list symref = STRING_LIST_INIT_DUP;
 +
 +      head_ref_namespaced(find_symref, &symref);
 +
        if (advertise_refs || !stateless_rpc) {
                reset_timeout();
 -              head_ref_namespaced(send_ref, NULL);
 -              for_each_namespaced_ref(send_ref, NULL);
 +              head_ref_namespaced(send_ref, &symref);
 +              for_each_namespaced_ref(send_ref, &symref);
                packet_flush(1);
        } else {
                head_ref_namespaced(mark_our_ref, NULL);
                for_each_namespaced_ref(mark_our_ref, NULL);
        }
 +      string_list_clear(&symref, 1);
        if (advertise_refs)
                return;
  
@@@ -778,11 -784,6 +777,11 @@@ static int upload_pack_config(const cha
  {
        if (!strcmp("uploadpack.allowtipsha1inwant", var))
                allow_tip_sha1_in_want = git_config_bool(var, value);
 +      else if (!strcmp("uploadpack.keepalive", var)) {
 +              keepalive = git_config_int(var, value);
 +              if (!keepalive)
 +                      keepalive = -1;
 +      }
        return parse_hide_refs_config(var, value, "uploadpack");
  }