Merge branch 'bk/refs-multi-update'
authorJunio C Hamano <gitster@pobox.com>
Fri, 20 Sep 2013 19:36:12 +0000 (12:36 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 20 Sep 2013 19:36:12 +0000 (12:36 -0700)
Give "update-refs" a "--stdin" option to read multiple update
requests and perform them in an all-or-none fashion.

* bk/refs-multi-update:
update-ref: add test cases covering --stdin signature
update-ref: support multiple simultaneous updates
refs: add update_refs for multiple simultaneous updates
refs: add function to repack without multiple refs
refs: factor delete_ref loose ref step into a helper
refs: factor update_ref steps into helpers
refs: report ref type from lock_any_ref_for_update
reset: rename update_refs to reset_refs

1  2 
branch.c
builtin/commit.c
builtin/fetch.c
builtin/receive-pack.c
builtin/reflog.c
builtin/replace.c
builtin/reset.c
builtin/tag.c
builtin/update-ref.c
fast-import.c
refs.c
diff --combined branch.c
index d31b354d487e43f710206bbafbb130ab96d87637,f2d383fa7723a1f7a77bee6c5bd9f323766f6caf..9e6c68edaf90f27cf447ad1522da6d7de7827141
+++ b/branch.c
@@@ -203,7 -203,8 +203,7 @@@ static int check_tracking_branch(struc
        struct refspec query;
        memset(&query, 0, sizeof(struct refspec));
        query.dst = tracking_branch;
 -      return !(remote_find_tracking(remote, &query) ||
 -               prefixcmp(query.src, "refs/heads/"));
 +      return !remote_find_tracking(remote, &query);
  }
  
  static int validate_remote_tracking_branch(char *ref)
@@@ -290,7 -291,7 +290,7 @@@ void create_branch(const char *head
        hashcpy(sha1, commit->object.sha1);
  
        if (!dont_change_ref) {
-               lock = lock_any_ref_for_update(ref.buf, NULL, 0);
+               lock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);
                if (!lock)
                        die_errno(_("Failed to lock ref for update"));
        }
                         start_name);
  
        if (real_ref && track)
 -              setup_tracking(ref.buf+11, real_ref, track, quiet);
 +              setup_tracking(ref.buf + 11, real_ref, track, quiet);
  
        if (!dont_change_ref)
                if (write_ref_sha1(lock, sha1, msg) < 0)
diff --combined builtin/commit.c
index d0d8bc1b30fe4d084e32beaf4b8ee9a24bb9ac94,be08f4153ee89bb382f50b0fad857d0ea2a44ee9..29d4225ff214d629eab622242ae8e0aac780ead1
@@@ -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>..."),
@@@ -203,15 -202,17 +203,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))
@@@ -297,17 -298,17 +297,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();
@@@ -598,7 -599,6 +598,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;
 +
        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);
@@@ -940,7 -935,6 +940,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);
@@@ -1101,7 -1091,7 +1101,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."));
@@@ -1192,10 -1182,6 +1192,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)
@@@ -1242,14 -1228,14 +1242,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" },
        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)
@@@ -1449,24 -1434,24 +1449,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()
        };
                                           !current_head
                                           ? NULL
                                           : current_head->object.sha1,
-                                          0);
+                                          0, NULL);
  
        nl = strchr(sb.buf, '\n');
        if (nl)
diff --combined builtin/fetch.c
index 9e654efa3bb725edd91f6eb7059b263668e622bc,28e40255beb6541350525615e6b9d57b19794bd3..bd7a10164f4fed8aeb6e8148e2ae8c0a793e7ee0
@@@ -30,18 -30,13 +30,18 @@@ enum 
        TAGS_SET = 2
  };
  
 -static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
 +static int fetch_prune_config = -1; /* unspecified */
 +static int prune = -1; /* unspecified */
 +#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
 +
 +static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
  static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  static int tags = TAGS_DEFAULT, unshallow;
  static const char *depth;
  static const char *upload_pack;
  static struct strbuf default_rla = STRBUF_INIT;
 -static struct transport *transport;
 +static struct transport *gtransport;
 +static struct transport *gsecondary;
  static const char *submodule_prefix = "";
  static const char *recurse_submodules_default;
  
@@@ -59,39 -54,30 +59,39 @@@ static int option_parse_recurse_submodu
        return 0;
  }
  
 +static int git_fetch_config(const char *k, const char *v, void *cb)
 +{
 +      if (!strcmp(k, "fetch.prune")) {
 +              fetch_prune_config = git_config_bool(k, v);
 +              return 0;
 +      }
 +      return 0;
 +}
 +
  static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
 -      OPT_BOOLEAN(0, "all", &all,
 -                  N_("fetch from all remotes")),
 -      OPT_BOOLEAN('a', "append", &append,
 -                  N_("append to .git/FETCH_HEAD instead of overwriting")),
 +      OPT_BOOL(0, "all", &all,
 +               N_("fetch from all remotes")),
 +      OPT_BOOL('a', "append", &append,
 +               N_("append to .git/FETCH_HEAD instead of overwriting")),
        OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
                   N_("path to upload pack on remote end")),
        OPT__FORCE(&force, N_("force overwrite of local branch")),
 -      OPT_BOOLEAN('m', "multiple", &multiple,
 -                  N_("fetch from multiple remotes")),
 +      OPT_BOOL('m', "multiple", &multiple,
 +               N_("fetch from multiple remotes")),
        OPT_SET_INT('t', "tags", &tags,
                    N_("fetch all tags and associated objects"), TAGS_SET),
        OPT_SET_INT('n', NULL, &tags,
                    N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
 -      OPT_BOOLEAN('p', "prune", &prune,
 -                  N_("prune remote-tracking branches no longer on remote")),
 +      OPT_BOOL('p', "prune", &prune,
 +               N_("prune remote-tracking branches no longer on remote")),
        { OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"),
                    N_("control recursive fetching of submodules"),
                    PARSE_OPT_OPTARG, option_parse_recurse_submodules },
 -      OPT_BOOLEAN(0, "dry-run", &dry_run,
 -                  N_("dry run")),
 -      OPT_BOOLEAN('k', "keep", &keep, N_("keep downloaded pack")),
 -      OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
 +      OPT_BOOL(0, "dry-run", &dry_run,
 +               N_("dry run")),
 +      OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")),
 +      OPT_BOOL('u', "update-head-ok", &update_head_ok,
                    N_("allow updating of HEAD ref")),
        OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
        OPT_STRING(0, "depth", &depth, N_("depth"),
  
  static void unlock_pack(void)
  {
 -      if (transport)
 -              transport_unlock_pack(transport);
 +      if (gtransport)
 +              transport_unlock_pack(gtransport);
 +      if (gsecondary)
 +              transport_unlock_pack(gsecondary);
  }
  
  static void unlock_pack_on_signal(int signo)
@@@ -262,7 -246,8 +262,8 @@@ static int s_update_ref(const char *act
                rla = default_rla.buf;
        snprintf(msg, sizeof(msg), "%s: %s", rla, action);
        lock = lock_any_ref_for_update(ref->name,
-                                      check_old ? ref->old_sha1 : NULL, 0);
+                                      check_old ? ref->old_sha1 : NULL,
+                                      0, NULL);
        if (!lock)
                return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
                                          STORE_REF_ERROR_OTHER;
@@@ -736,48 -721,6 +737,48 @@@ static int truncate_fetch_head(void
        return 0;
  }
  
 +static void set_option(struct transport *transport, const char *name, const char *value)
 +{
 +      int r = transport_set_option(transport, name, value);
 +      if (r < 0)
 +              die(_("Option \"%s\" value \"%s\" is not valid for %s"),
 +                  name, value, transport->url);
 +      if (r > 0)
 +              warning(_("Option \"%s\" is ignored for %s\n"),
 +                      name, transport->url);
 +}
 +
 +static struct transport *prepare_transport(struct remote *remote)
 +{
 +      struct transport *transport;
 +      transport = transport_get(remote, NULL);
 +      transport_set_verbosity(transport, verbosity, progress);
 +      if (upload_pack)
 +              set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack);
 +      if (keep)
 +              set_option(transport, TRANS_OPT_KEEP, "yes");
 +      if (depth)
 +              set_option(transport, TRANS_OPT_DEPTH, depth);
 +      return transport;
 +}
 +
 +static void backfill_tags(struct transport *transport, struct ref *ref_map)
 +{
 +      if (transport->cannot_reuse) {
 +              gsecondary = prepare_transport(transport->remote);
 +              transport = gsecondary;
 +      }
 +
 +      transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
 +      transport_set_option(transport, TRANS_OPT_DEPTH, "0");
 +      fetch_refs(transport, ref_map);
 +
 +      if (gsecondary) {
 +              transport_disconnect(gsecondary);
 +              gsecondary = NULL;
 +      }
 +}
 +
  static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
  {
                goto cleanup;
        }
        if (prune) {
 -              /* If --tags was specified, pretend the user gave us the canonical tags refspec */
 +              /*
 +               * If --tags was specified, pretend that the user gave us
 +               * the canonical tags refspec
 +               */
                if (tags == TAGS_SET) {
                        const char *tags_str = "refs/tags/*:refs/tags/*";
                        struct refspec *tags_refspec, *refspec;
                struct ref **tail = &ref_map;
                ref_map = NULL;
                find_non_local_tags(transport, &ref_map, &tail);
 -              if (ref_map) {
 -                      transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
 -                      transport_set_option(transport, TRANS_OPT_DEPTH, "0");
 -                      fetch_refs(transport, ref_map);
 -              }
 +              if (ref_map)
 +                      backfill_tags(transport, ref_map);
                free_refs(ref_map);
        }
  
        return retcode;
  }
  
 -static void set_option(const char *name, const char *value)
 -{
 -      int r = transport_set_option(transport, name, value);
 -      if (r < 0)
 -              die(_("Option \"%s\" value \"%s\" is not valid for %s"),
 -                      name, value, transport->url);
 -      if (r > 0)
 -              warning(_("Option \"%s\" is ignored for %s\n"),
 -                      name, transport->url);
 -}
 -
  static int get_one_remote_for_fetch(struct remote *remote, void *priv)
  {
        struct string_list *list = priv;
@@@ -929,7 -883,7 +930,7 @@@ static void add_options_to_argv(struct 
  {
        if (dry_run)
                argv_array_push(argv, "--dry-run");
 -      if (prune)
 +      if (prune > 0)
                argv_array_push(argv, "--prune");
        if (update_head_ok)
                argv_array_push(argv, "--update-head-ok");
@@@ -996,17 -950,14 +997,17 @@@ static int fetch_one(struct remote *rem
                die(_("No remote repository specified.  Please, specify either a URL or a\n"
                    "remote name from which new revisions should be fetched."));
  
 -      transport = transport_get(remote, NULL);
 -      transport_set_verbosity(transport, verbosity, progress);
 -      if (upload_pack)
 -              set_option(TRANS_OPT_UPLOADPACK, upload_pack);
 -      if (keep)
 -              set_option(TRANS_OPT_KEEP, "yes");
 -      if (depth)
 -              set_option(TRANS_OPT_DEPTH, depth);
 +      gtransport = prepare_transport(remote);
 +
 +      if (prune < 0) {
 +              /* no command line request */
 +              if (0 <= gtransport->remote->prune)
 +                      prune = gtransport->remote->prune;
 +              else if (0 <= fetch_prune_config)
 +                      prune = fetch_prune_config;
 +              else
 +                      prune = PRUNE_BY_DEFAULT;
 +      }
  
        if (argc > 0) {
                int j = 0;
        sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
        refspec = parse_fetch_refspec(ref_nr, refs);
 -      exit_code = do_fetch(transport, refspec, ref_nr);
 +      exit_code = do_fetch(gtransport, refspec, ref_nr);
        free_refspec(ref_nr, refspec);
 -      transport_disconnect(transport);
 -      transport = NULL;
 +      transport_disconnect(gtransport);
 +      gtransport = NULL;
        return exit_code;
  }
  
@@@ -1057,8 -1008,6 +1058,8 @@@ int cmd_fetch(int argc, const char **ar
        for (i = 1; i < argc; i++)
                strbuf_addf(&default_rla, " %s", argv[i]);
  
 +      git_config(git_fetch_config, NULL);
 +
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
  
diff --combined builtin/receive-pack.c
index b7e71a04f61798ed72282f683a6fcd3c156007ab,a32307038e12de776fec409a6ea67c1c9dade7d0..67ce1ef105d1494e7905e66750b14676e082e11c
@@@ -8,7 -8,6 +8,7 @@@
  #include "commit.h"
  #include "object.h"
  #include "remote.h"
 +#include "connect.h"
  #include "transport.h"
  #include "string-list.h"
  #include "sha1-array.h"
@@@ -39,7 -38,6 +39,7 @@@ static int quiet
  static int prefer_ofs_delta = 1;
  static int auto_update_server_info;
  static int auto_gc = 1;
 +static int fix_thin = 1;
  static const char *head_name;
  static void *head_name_to_free;
  static int sent_capabilities;
@@@ -526,7 -524,8 +526,8 @@@ static const char *update(struct comman
                return NULL; /* good */
        }
        else {
-               lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
+               lock = lock_any_ref_for_update(namespaced_name, old_sha1,
+                                              0, NULL);
                if (!lock) {
                        rp_error("failed to lock %s", name);
                        return "failed to lock";
@@@ -871,8 -870,7 +872,8 @@@ static const char *unpack(int err_fd
                keeper[i++] = "--stdin";
                if (fsck_objects)
                        keeper[i++] = "--strict";
 -              keeper[i++] = "--fix-thin";
 +              if (fix_thin)
 +                      keeper[i++] = "--fix-thin";
                keeper[i++] = hdr_arg;
                keeper[i++] = keep_arg;
                keeper[i++] = NULL;
@@@ -978,10 -976,6 +979,10 @@@ int cmd_receive_pack(int argc, const ch
                                stateless_rpc = 1;
                                continue;
                        }
 +                      if (!strcmp(arg, "--reject-thin-pack-for-testing")) {
 +                              fix_thin = 0;
 +                              continue;
 +                      }
  
                        usage(receive_pack_usage);
                }
diff --combined builtin/reflog.c
index ba27f7cc614214eb32f60b684b3617c39a0066ef,28d756a418b27c9b9bf23805e54b74f6c3332ece..6eb24c8da27d265cad38628aeaa3e040dd298aab
@@@ -94,7 -94,8 +94,7 @@@ static int tree_is_complete(const unsig
                        complete = 0;
                }
        }
 -      free(tree->buffer);
 -      tree->buffer = NULL;
 +      free_tree_buffer(tree);
  
        if (complete)
                tree->object.flags |= SEEN;
@@@ -365,7 -366,7 +365,7 @@@ static int expire_reflog(const char *re
         * we take the lock for the ref itself to prevent it from
         * getting updated.
         */
-       lock = lock_any_ref_for_update(ref, sha1, 0);
+       lock = lock_any_ref_for_update(ref, sha1, 0, NULL);
        if (!lock)
                return error("cannot lock ref '%s'", ref);
        log_file = git_pathdup("logs/%s", ref);
diff --combined builtin/replace.c
index 11b0a55ae7f6e3d1a04eeb4026b151144d0315ba,1ecae8d076e4a58541c600e71a0680dd6f192788..301b45ce6a453c472566278521bb51fcbd91ee10
@@@ -105,7 -105,7 +105,7 @@@ static int replace_object(const char *o
        else if (!force)
                die("replace ref '%s' already exists", ref);
  
-       lock = lock_any_ref_for_update(ref, prev, 0);
+       lock = lock_any_ref_for_update(ref, prev, 0, NULL);
        if (!lock)
                die("%s: cannot lock the ref", ref);
        if (write_ref_sha1(lock, repl, NULL) < 0)
@@@ -118,9 -118,9 +118,9 @@@ int cmd_replace(int argc, const char **
  {
        int list = 0, delete = 0, force = 0;
        struct option options[] = {
 -              OPT_BOOLEAN('l', NULL, &list, N_("list replace refs")),
 -              OPT_BOOLEAN('d', NULL, &delete, N_("delete replace refs")),
 -              OPT_BOOLEAN('f', NULL, &force, N_("replace the ref if it exists")),
 +              OPT_BOOL('l', NULL, &list, N_("list replace refs")),
 +              OPT_BOOL('d', NULL, &delete, N_("delete replace refs")),
 +              OPT_BOOL('f', NULL, &force, N_("replace the ref if it exists")),
                OPT_END()
        };
  
diff --combined builtin/reset.c
index 088ccffba07a4227150290492f22dc3bbac56fdc,789ee489b772f147f254afbf7d3b0c9eecf42c7b..1a5344877212e675d685055a631aba2ac0a4c661
@@@ -133,13 -133,12 +133,13 @@@ static void update_index_from_diff(stru
        }
  }
  
 -static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
 +static int read_from_tree(const struct pathspec *pathspec,
 +                        unsigned char *tree_sha1)
  {
        struct diff_options opt;
  
        memset(&opt, 0, sizeof(opt));
 -      diff_tree_setup_paths(pathspec, &opt);
 +      copy_pathspec(&opt.pathspec, pathspec);
        opt.output_format = DIFF_FORMAT_CALLBACK;
        opt.format_callback = update_index_from_diff;
  
                return 1;
        diffcore_std(&opt);
        diff_flush(&opt);
 -      diff_tree_release_paths(&opt);
 +      free_pathspec(&opt.pathspec);
  
        return 0;
  }
@@@ -175,10 -174,7 +175,10 @@@ static void die_if_unmerged_cache(int r
  
  }
  
 -static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret)
 +static void parse_args(struct pathspec *pathspec,
 +                     const char **argv, const char *prefix,
 +                     int patch_mode,
 +                     const char **rev_ret)
  {
        const char *rev = "HEAD";
        unsigned char unused[20];
                }
        }
        *rev_ret = rev;
 -      return argv[0] ? get_pathspec(prefix, argv) : NULL;
 +      parse_pathspec(pathspec, 0,
 +                     PATHSPEC_PREFER_FULL |
 +                     (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0),
 +                     prefix, argv);
  }
  
- static int update_refs(const char *rev, const unsigned char *sha1)
+ static int reset_refs(const char *rev, const unsigned char *sha1)
  {
        int update_ref_status;
        struct strbuf msg = STRBUF_INIT;
@@@ -253,7 -246,7 +253,7 @@@ int cmd_reset(int argc, const char **ar
        int patch_mode = 0, unborn;
        const char *rev;
        unsigned char sha1[20];
 -      const char **pathspec = NULL;
 +      struct pathspec pathspec;
        const struct option options[] = {
                OPT__QUIET(&quiet, N_("be quiet, only report errors")),
                OPT_SET_INT(0, "mixed", &reset_type,
                                N_("reset HEAD, index and working tree"), MERGE),
                OPT_SET_INT(0, "keep", &reset_type,
                                N_("reset HEAD but keep local changes"), KEEP),
 -              OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")),
 +              OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
                OPT_END()
        };
  
  
        argc = parse_options(argc, argv, prefix, options, git_reset_usage,
                                                PARSE_OPT_KEEP_DASHDASH);
 -      pathspec = parse_args(argv, prefix, &rev);
 +      parse_args(&pathspec, argv, prefix, patch_mode, &rev);
  
        unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
        if (unborn) {
                /* reset on unborn branch: treat as reset to empty tree */
                hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
 -      } else if (!pathspec) {
 +      } else if (!pathspec.nr) {
                struct commit *commit;
                if (get_sha1_committish(rev, sha1))
                        die(_("Failed to resolve '%s' as a valid revision."), rev);
        if (patch_mode) {
                if (reset_type != NONE)
                        die(_("--patch is incompatible with --{hard,mixed,soft}"));
 -              return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
 +              return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", &pathspec);
        }
  
        /* git reset tree [--] paths... can be used to
         * load chosen paths from the tree into the index without
         * affecting the working tree nor HEAD. */
 -      if (pathspec) {
 +      if (pathspec.nr) {
                if (reset_type == MIXED)
                        warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
                else if (reset_type != NONE)
                die_if_unmerged_cache(reset_type);
  
        if (reset_type != SOFT) {
 -              struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 +              struct lock_file *lock = xcalloc(1, sizeof(*lock));
                int newfd = hold_locked_index(lock, 1);
                if (reset_type == MIXED) {
 -                      if (read_from_tree(pathspec, sha1))
 +                      int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
 +                      if (read_from_tree(&pathspec, sha1))
                                return 1;
 +                      refresh_index(&the_index, flags, NULL, NULL,
 +                                    _("Unstaged changes after reset:"));
                } else {
                        int err = reset_index(sha1, reset_type, quiet);
                        if (reset_type == KEEP && !err)
                                die(_("Could not reset index file to revision '%s'."), rev);
                }
  
 -              if (reset_type == MIXED) { /* Report what has not been updated. */
 -                      int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
 -                      refresh_index(&the_index, flags, NULL, NULL,
 -                                    _("Unstaged changes after reset:"));
 -              }
 -
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_locked_index(lock))
                        die(_("Could not write new index file."));
        }
  
 -      if (!pathspec && !unborn) {
 +      if (!pathspec.nr && !unborn) {
                /* Any resets without paths update HEAD to the head being
                 * switched to, saving the previous head in ORIG_HEAD before. */
-               update_ref_status = update_refs(rev, sha1);
+               update_ref_status = reset_refs(rev, sha1);
  
                if (reset_type == HARD && !update_ref_status && !quiet)
                        print_new_head_line(lookup_commit_reference(sha1));
        }
 -      if (!pathspec)
 +      if (!pathspec.nr)
                remove_branch_state();
  
        return update_ref_status;
diff --combined builtin/tag.c
index b577af562a4a554f43c64256e112fcbc423edd3d,2c867d2c0963f3187c2b77114518d9691799545a..ea55f1d1bdd524abd46e6baa7a02ef85d56d5eb2
@@@ -436,26 -436,26 +436,26 @@@ int cmd_tag(int argc, const char **argv
        struct ref_lock *lock;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
 -      int annotate = 0, force = 0, lines = -1, list = 0,
 -              delete = 0, verify = 0;
 +      int annotate = 0, force = 0, lines = -1;
 +      int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
        struct option options[] = {
 -              OPT_BOOLEAN('l', "list", &list, N_("list tag names")),
 +              OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
                { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
                                N_("print <n> lines of each tag message"),
                                PARSE_OPT_OPTARG, NULL, 1 },
 -              OPT_BOOLEAN('d', "delete", &delete, N_("delete tags")),
 -              OPT_BOOLEAN('v', "verify", &verify, N_("verify tags")),
 +              OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
 +              OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'),
  
                OPT_GROUP(N_("Tag creation options")),
 -              OPT_BOOLEAN('a', "annotate", &annotate,
 +              OPT_BOOL('a', "annotate", &annotate,
                                        N_("annotated tag, needs a message")),
                OPT_CALLBACK('m', "message", &msg, N_("message"),
                             N_("tag message"), parse_msg_arg),
                OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
 -              OPT_BOOLEAN('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
 +              OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
                OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
                        N_("how to strip spaces and #comments from message")),
                OPT_STRING('u', "local-user", &keyid, N_("key id"),
        }
        if (opt.sign)
                annotate = 1;
 -      if (argc == 0 && !(delete || verify))
 -              list = 1;
 +      if (argc == 0 && !cmdmode)
 +              cmdmode = 'l';
  
 -      if ((annotate || msg.given || msgfile || force) &&
 -          (list || delete || verify))
 +      if ((annotate || msg.given || msgfile || force) && (cmdmode != 0))
                usage_with_options(git_tag_usage, options);
  
 -      if (list + delete + verify > 1)
 -              usage_with_options(git_tag_usage, options);
        finalize_colopts(&colopts, -1);
 -      if (list && lines != -1) {
 +      if (cmdmode == 'l' && lines != -1) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and -n are incompatible"));
                colopts = 0;
        }
 -      if (list) {
 +      if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
                        struct column_options copts;
                die(_("--contains option is only allowed with -l."));
        if (points_at.nr)
                die(_("--points-at option is only allowed with -l."));
 -      if (delete)
 +      if (cmdmode == 'd')
                return for_each_tag_name(argv, delete_tag);
 -      if (verify)
 +      if (cmdmode == 'v')
                return for_each_tag_name(argv, verify_tag);
  
        if (msg.given || msgfile) {
        if (annotate)
                create_tag(object, tag, &buf, &opt, prev, object);
  
-       lock = lock_any_ref_for_update(ref.buf, prev, 0);
+       lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
        if (!lock)
                die(_("%s: cannot lock the ref"), ref.buf);
        if (write_ref_sha1(lock, object, NULL) < 0)
diff --combined builtin/update-ref.c
index 7484d36a65ba1f722f2dda33473bf509f30727c9,894f16bc59248f4c642228781a3b12bc1a82e54f..702e90db2a82a0eba2ff233b9ec46690da5afc3e
  #include "refs.h"
  #include "builtin.h"
  #include "parse-options.h"
+ #include "quote.h"
+ #include "argv-array.h"
  
  static const char * const git_update_ref_usage[] = {
        N_("git update-ref [options] -d <refname> [<oldval>]"),
        N_("git update-ref [options]    <refname> <newval> [<oldval>]"),
+       N_("git update-ref [options] --stdin [-z]"),
        NULL
  };
  
+ static int updates_alloc;
+ static int updates_count;
+ static const struct ref_update **updates;
+ static char line_termination = '\n';
+ static int update_flags;
+ static struct ref_update *update_alloc(void)
+ {
+       struct ref_update *update;
+       /* Allocate and zero-init a struct ref_update */
+       update = xcalloc(1, sizeof(*update));
+       ALLOC_GROW(updates, updates_count + 1, updates_alloc);
+       updates[updates_count++] = update;
+       /* Store and reset accumulated options */
+       update->flags = update_flags;
+       update_flags = 0;
+       return update;
+ }
+ static void update_store_ref_name(struct ref_update *update,
+                                 const char *ref_name)
+ {
+       if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
+               die("invalid ref format: %s", ref_name);
+       update->ref_name = xstrdup(ref_name);
+ }
+ static void update_store_new_sha1(struct ref_update *update,
+                                 const char *newvalue)
+ {
+       if (*newvalue && get_sha1(newvalue, update->new_sha1))
+               die("invalid new value for ref %s: %s",
+                   update->ref_name, newvalue);
+ }
+ static void update_store_old_sha1(struct ref_update *update,
+                                 const char *oldvalue)
+ {
+       if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
+               die("invalid old value for ref %s: %s",
+                   update->ref_name, oldvalue);
+       /* We have an old value if non-empty, or if empty without -z */
+       update->have_old = *oldvalue || line_termination;
+ }
+ static const char *parse_arg(const char *next, struct strbuf *arg)
+ {
+       /* Parse SP-terminated, possibly C-quoted argument */
+       if (*next != '"')
+               while (*next && !isspace(*next))
+                       strbuf_addch(arg, *next++);
+       else if (unquote_c_style(arg, next, &next))
+               die("badly quoted argument: %s", next);
+       /* Return position after the argument */
+       return next;
+ }
+ static const char *parse_first_arg(const char *next, struct strbuf *arg)
+ {
+       /* Parse argument immediately after "command SP" */
+       strbuf_reset(arg);
+       if (line_termination) {
+               /* Without -z, use the next argument */
+               next = parse_arg(next, arg);
+       } else {
+               /* With -z, use rest of first NUL-terminated line */
+               strbuf_addstr(arg, next);
+               next = next + arg->len;
+       }
+       return next;
+ }
+ static const char *parse_next_arg(const char *next, struct strbuf *arg)
+ {
+       /* Parse next SP-terminated or NUL-terminated argument, if any */
+       strbuf_reset(arg);
+       if (line_termination) {
+               /* Without -z, consume SP and use next argument */
+               if (!*next)
+                       return NULL;
+               if (*next != ' ')
+                       die("expected SP but got: %s", next);
+               next = parse_arg(next + 1, arg);
+       } else {
+               /* With -z, read the next NUL-terminated line */
+               if (*next)
+                       die("expected NUL but got: %s", next);
+               if (strbuf_getline(arg, stdin, '\0') == EOF)
+                       return NULL;
+               next = arg->buf + arg->len;
+       }
+       return next;
+ }
+ static void parse_cmd_update(const char *next)
+ {
+       struct strbuf ref = STRBUF_INIT;
+       struct strbuf newvalue = STRBUF_INIT;
+       struct strbuf oldvalue = STRBUF_INIT;
+       struct ref_update *update;
+       update = update_alloc();
+       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+               update_store_ref_name(update, ref.buf);
+       else
+               die("update line missing <ref>");
+       if ((next = parse_next_arg(next, &newvalue)) != NULL)
+               update_store_new_sha1(update, newvalue.buf);
+       else
+               die("update %s missing <newvalue>", ref.buf);
+       if ((next = parse_next_arg(next, &oldvalue)) != NULL)
+               update_store_old_sha1(update, oldvalue.buf);
+       else if(!line_termination)
+               die("update %s missing [<oldvalue>] NUL", ref.buf);
+       if (next && *next)
+               die("update %s has extra input: %s", ref.buf, next);
+ }
+ static void parse_cmd_create(const char *next)
+ {
+       struct strbuf ref = STRBUF_INIT;
+       struct strbuf newvalue = STRBUF_INIT;
+       struct ref_update *update;
+       update = update_alloc();
+       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+               update_store_ref_name(update, ref.buf);
+       else
+               die("create line missing <ref>");
+       if ((next = parse_next_arg(next, &newvalue)) != NULL)
+               update_store_new_sha1(update, newvalue.buf);
+       else
+               die("create %s missing <newvalue>", ref.buf);
+       if (is_null_sha1(update->new_sha1))
+               die("create %s given zero new value", ref.buf);
+       if (next && *next)
+               die("create %s has extra input: %s", ref.buf, next);
+ }
+ static void parse_cmd_delete(const char *next)
+ {
+       struct strbuf ref = STRBUF_INIT;
+       struct strbuf oldvalue = STRBUF_INIT;
+       struct ref_update *update;
+       update = update_alloc();
+       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+               update_store_ref_name(update, ref.buf);
+       else
+               die("delete line missing <ref>");
+       if ((next = parse_next_arg(next, &oldvalue)) != NULL)
+               update_store_old_sha1(update, oldvalue.buf);
+       else if(!line_termination)
+               die("delete %s missing [<oldvalue>] NUL", ref.buf);
+       if (update->have_old && is_null_sha1(update->old_sha1))
+               die("delete %s given zero old value", ref.buf);
+       if (next && *next)
+               die("delete %s has extra input: %s", ref.buf, next);
+ }
+ static void parse_cmd_verify(const char *next)
+ {
+       struct strbuf ref = STRBUF_INIT;
+       struct strbuf value = STRBUF_INIT;
+       struct ref_update *update;
+       update = update_alloc();
+       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+               update_store_ref_name(update, ref.buf);
+       else
+               die("verify line missing <ref>");
+       if ((next = parse_next_arg(next, &value)) != NULL) {
+               update_store_old_sha1(update, value.buf);
+               update_store_new_sha1(update, value.buf);
+       } else if(!line_termination)
+               die("verify %s missing [<oldvalue>] NUL", ref.buf);
+       if (next && *next)
+               die("verify %s has extra input: %s", ref.buf, next);
+ }
+ static void parse_cmd_option(const char *next)
+ {
+       if (!strcmp(next, "no-deref"))
+               update_flags |= REF_NODEREF;
+       else
+               die("option unknown: %s", next);
+ }
+ static void update_refs_stdin(void)
+ {
+       struct strbuf cmd = STRBUF_INIT;
+       /* Read each line dispatch its command */
+       while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
+               if (!cmd.buf[0])
+                       die("empty command in input");
+               else if (isspace(*cmd.buf))
+                       die("whitespace before command: %s", cmd.buf);
+               else if (!prefixcmp(cmd.buf, "update "))
+                       parse_cmd_update(cmd.buf + 7);
+               else if (!prefixcmp(cmd.buf, "create "))
+                       parse_cmd_create(cmd.buf + 7);
+               else if (!prefixcmp(cmd.buf, "delete "))
+                       parse_cmd_delete(cmd.buf + 7);
+               else if (!prefixcmp(cmd.buf, "verify "))
+                       parse_cmd_verify(cmd.buf + 7);
+               else if (!prefixcmp(cmd.buf, "option "))
+                       parse_cmd_option(cmd.buf + 7);
+               else
+                       die("unknown command: %s", cmd.buf);
+       strbuf_release(&cmd);
+ }
  int cmd_update_ref(int argc, const char **argv, const char *prefix)
  {
        const char *refname, *oldval, *msg = NULL;
        unsigned char sha1[20], oldsha1[20];
-       int delete = 0, no_deref = 0, flags = 0;
+       int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
        struct option options[] = {
                OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
 -              OPT_BOOLEAN('d', NULL, &delete, N_("delete the reference")),
 -              OPT_BOOLEAN('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
 -              OPT_BOOLEAN( 0 , "no-deref", &no_deref,
 +              OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
 +              OPT_BOOL( 0 , "no-deref", &no_deref,
                                        N_("update <refname> not the one it points to")),
 -              OPT_BOOLEAN( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
++              OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
++              OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
                OPT_END(),
        };
  
        if (msg && !*msg)
                die("Refusing to perform update with empty message.");
  
+       if (read_stdin) {
+               if (delete || no_deref || argc > 0)
+                       usage_with_options(git_update_ref_usage, options);
+               if (end_null)
+                       line_termination = '\0';
+               update_refs_stdin();
+               return update_refs(msg, updates, updates_count, DIE_ON_ERR);
+       }
+       if (end_null)
+               usage_with_options(git_update_ref_usage, options);
        if (delete) {
                if (argc < 1 || argc > 2)
                        usage_with_options(git_update_ref_usage, options);
diff --combined fast-import.c
index 45c87648677a90b293c633b02232f9a490be6833,5c329f60095b1e5649b06399587e900bf8b9567a..f4d9969e5c81f9c1415f0908f090bf62b156e179
@@@ -22,8 -22,8 +22,8 @@@ Format of STDIN stream
      ('author' (sp name)? sp '<' email '>' sp when lf)?
      'committer' (sp name)? sp '<' email '>' sp when lf
      commit_msg
 -    ('from' sp committish lf)?
 -    ('merge' sp committish lf)*
 +    ('from' sp commit-ish lf)?
 +    ('merge' sp commit-ish lf)*
      (file_change | ls)*
      lf?;
    commit_msg ::= data;
    file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
    file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
      data;
 -  note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf;
 -  note_inm ::= 'N' sp 'inline' sp committish lf
 +  note_obm ::= 'N' sp (hexsha1 | idnum) sp commit-ish lf;
 +  note_inm ::= 'N' sp 'inline' sp commit-ish lf
      data;
  
    new_tag ::= 'tag' sp tag_str lf
 -    'from' sp committish lf
 +    'from' sp commit-ish lf
      ('tagger' (sp name)? sp '<' email '>' sp when lf)?
      tag_msg;
    tag_msg ::= data;
  
    reset_branch ::= 'reset' sp ref_str lf
 -    ('from' sp committish lf)?
 +    ('from' sp commit-ish lf)?
      lf?;
  
    checkpoint ::= 'checkpoint' lf
@@@ -93,7 -93,7 +93,7 @@@
       # stream formatting is: \, " and LF.  Otherwise these values
       # are UTF8.
       #
 -  committish  ::= (ref_str | hexsha1 | sha1exp_str | idnum);
 +  commit-ish  ::= (ref_str | hexsha1 | sha1exp_str | idnum);
    ref_str     ::= ref;
    sha1exp_str ::= sha1exp;
    tag_str     ::= tag;
@@@ -1568,8 -1568,7 +1568,8 @@@ static int tree_content_set
  static int tree_content_remove(
        struct tree_entry *root,
        const char *p,
 -      struct tree_entry *backup_leaf)
 +      struct tree_entry *backup_leaf,
 +      int allow_root)
  {
        struct tree_content *t;
        const char *slash1;
  
        if (!root->tree)
                load_tree(root);
 +
 +      if (!*p && allow_root) {
 +              e = root;
 +              goto del_entry;
 +      }
 +
        t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
                                goto del_entry;
                        if (!e->tree)
                                load_tree(e);
 -                      if (tree_content_remove(e, slash1 + 1, backup_leaf)) {
 +                      if (tree_content_remove(e, slash1 + 1, backup_leaf, 0)) {
                                for (n = 0; n < e->tree->entry_count; n++) {
                                        if (e->tree->entries[n]->versions[1].mode) {
                                                hashclr(root->versions[1].sha1);
@@@ -1636,8 -1629,7 +1636,8 @@@ del_entry
  static int tree_content_get(
        struct tree_entry *root,
        const char *p,
 -      struct tree_entry *leaf)
 +      struct tree_entry *leaf,
 +      int allow_root)
  {
        struct tree_content *t;
        const char *slash1;
                n = slash1 - p;
        else
                n = strlen(p);
 -      if (!n)
 +      if (!n && !allow_root)
                die("Empty path component found in input");
  
        if (!root->tree)
                load_tree(root);
 +
 +      if (!n) {
 +              e = root;
 +              goto found_entry;
 +      }
 +
        t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
                if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
 -                      if (!slash1) {
 -                              memcpy(leaf, e, sizeof(*leaf));
 -                              if (e->tree && is_null_sha1(e->versions[1].sha1))
 -                                      leaf->tree = dup_tree_content(e->tree);
 -                              else
 -                                      leaf->tree = NULL;
 -                              return 1;
 -                      }
 +                      if (!slash1)
 +                              goto found_entry;
                        if (!S_ISDIR(e->versions[1].mode))
                                return 0;
                        if (!e->tree)
                                load_tree(e);
 -                      return tree_content_get(e, slash1 + 1, leaf);
 +                      return tree_content_get(e, slash1 + 1, leaf, 0);
                }
        }
        return 0;
 +
 +found_entry:
 +      memcpy(leaf, e, sizeof(*leaf));
 +      if (e->tree && is_null_sha1(e->versions[1].sha1))
 +              leaf->tree = dup_tree_content(e->tree);
 +      else
 +              leaf->tree = NULL;
 +      return 1;
  }
  
  static int update_branch(struct branch *b)
                return 0;
        if (read_ref(b->name, old_sha1))
                hashclr(old_sha1);
-       lock = lock_any_ref_for_update(b->name, old_sha1, 0);
+       lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);
        if (!lock)
                return error("Unable to lock %s", b->name);
        if (!force_update && !is_null_sha1(old_sha1)) {
@@@ -2195,7 -2179,7 +2195,7 @@@ static uintmax_t do_change_note_fanout
                        }
  
                        /* Rename fullpath to realpath */
 -                      if (!tree_content_remove(orig_root, fullpath, &leaf))
 +                      if (!tree_content_remove(orig_root, fullpath, &leaf, 0))
                                die("Failed to remove path %s", fullpath);
                        tree_content_set(orig_root, realpath,
                                leaf.versions[1].sha1,
@@@ -2330,7 -2314,7 +2330,7 @@@ static void file_change_m(struct branc
  
        /* Git does not track empty, non-toplevel directories. */
        if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) {
 -              tree_content_remove(&b->branch_tree, p, NULL);
 +              tree_content_remove(&b->branch_tree, p, NULL, 0);
                return;
        }
  
@@@ -2391,7 -2375,7 +2391,7 @@@ static void file_change_d(struct branc
                        die("Garbage after path in: %s", command_buf.buf);
                p = uq.buf;
        }
 -      tree_content_remove(&b->branch_tree, p, NULL);
 +      tree_content_remove(&b->branch_tree, p, NULL, 1);
  }
  
  static void file_change_cr(struct branch *b, int rename)
  
        memset(&leaf, 0, sizeof(leaf));
        if (rename)
 -              tree_content_remove(&b->branch_tree, s, &leaf);
 +              tree_content_remove(&b->branch_tree, s, &leaf, 1);
        else
 -              tree_content_get(&b->branch_tree, s, &leaf);
 +              tree_content_get(&b->branch_tree, s, &leaf, 1);
        if (!leaf.versions[1].mode)
                die("Path %s not in branch", s);
        if (!*d) {      /* C "path/to/subdir" "" */
@@@ -2494,7 -2478,7 +2494,7 @@@ static void note_change_n(struct branc
        assert(*p == ' ');
        p++;  /* skip space */
  
 -      /* <committish> */
 +      /* <commit-ish> */
        s = lookup_branch(p);
        if (s) {
                if (is_null_sha1(s->sha1))
        }
  
        construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path);
 -      if (tree_content_remove(&b->branch_tree, path, NULL))
 +      if (tree_content_remove(&b->branch_tree, path, NULL, 0))
                b->num_notes--;
  
        if (is_null_sha1(sha1))
@@@ -2973,7 -2957,7 +2973,7 @@@ static struct object_entry *dereference
        case OBJ_TAG:
                break;
        default:
 -              die("Not a treeish: %s", command_buf.buf);
 +              die("Not a tree-ish: %s", command_buf.buf);
        }
  
        if (oe->pack_id != MAX_PACK_ID) {       /* in a pack being written */
@@@ -3057,7 -3041,7 +3057,7 @@@ static void parse_ls(struct branch *b
        struct tree_entry *root = NULL;
        struct tree_entry leaf = {NULL};
  
 -      /* ls SP (<treeish> SP)? <path> */
 +      /* ls SP (<tree-ish> SP)? <path> */
        p = command_buf.buf + strlen("ls ");
        if (*p == '"') {
                if (!b)
                struct object_entry *e = parse_treeish_dataref(&p);
                root = new_tree_entry();
                hashcpy(root->versions[1].sha1, e->idx.sha1);
 +              if (!is_null_sha1(root->versions[1].sha1))
 +                      root->versions[1].mode = S_IFDIR;
                load_tree(root);
                if (*p++ != ' ')
                        die("Missing space after tree-ish: %s", command_buf.buf);
                        die("Garbage after path in: %s", command_buf.buf);
                p = uq.buf;
        }
 -      tree_content_get(root, p, &leaf);
 +      tree_content_get(root, p, &leaf, 1);
        /*
         * A directory in preparation would have a sha1 of zero
         * until it is saved.  Save, for simplicity.
diff --combined refs.c
index d78860c46d95ea9785c83c08824355275393ed99,46177ad256c7991df327ea0d2fba31eb10bbdaab..6383813627ece441aa780c49f71feeaa41145c5c
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -2121,11 -2121,12 +2121,12 @@@ struct ref_lock *lock_ref_sha1(const ch
  }
  
  struct ref_lock *lock_any_ref_for_update(const char *refname,
-                                        const unsigned char *old_sha1, int flags)
+                                        const unsigned char *old_sha1,
+                                        int flags, int *type_p)
  {
        if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                return NULL;
-       return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
+       return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
  }
  
  /*
@@@ -2413,60 -2414,82 +2414,82 @@@ static int curate_packed_ref_fn(struct 
        return 0;
  }
  
- static int repack_without_ref(const char *refname)
+ static int repack_without_refs(const char **refnames, int n)
  {
        struct ref_dir *packed;
        struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
        struct string_list_item *ref_to_delete;
+       int i, removed = 0;
+       /* Look for a packed ref */
+       for (i = 0; i < n; i++)
+               if (get_packed_ref(refnames[i]))
+                       break;
  
-       if (!get_packed_ref(refname))
-               return 0; /* refname does not exist in packed refs */
+       /* Avoid locking if we have nothing to do */
+       if (i == n)
+               return 0; /* no refname exists in packed refs */
  
        if (lock_packed_refs(0)) {
                unable_to_lock_error(git_path("packed-refs"), errno);
-               return error("cannot delete '%s' from packed refs", refname);
+               return error("cannot delete '%s' from packed refs", refnames[i]);
        }
        packed = get_packed_refs(&ref_cache);
  
-       /* Remove refname from the cache: */
-       if (remove_entry(packed, refname) == -1) {
+       /* Remove refnames from the cache */
+       for (i = 0; i < n; i++)
+               if (remove_entry(packed, refnames[i]) != -1)
+                       removed = 1;
+       if (!removed) {
                /*
-                * The packed entry disappeared while we were
+                * All packed entries disappeared while we were
                 * acquiring the lock.
                 */
                rollback_packed_refs();
                return 0;
        }
  
-       /* Remove any other accumulated cruft: */
+       /* Remove any other accumulated cruft */
        do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
        for_each_string_list_item(ref_to_delete, &refs_to_delete) {
                if (remove_entry(packed, ref_to_delete->string) == -1)
                        die("internal error");
        }
  
-       /* Write what remains: */
+       /* Write what remains */
        return commit_packed_refs();
  }
  
int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
static int repack_without_ref(const char *refname)
  {
-       struct ref_lock *lock;
-       int err, i = 0, ret = 0, flag = 0;
+       return repack_without_refs(&refname, 1);
+ }
  
-       lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
-       if (!lock)
-               return 1;
+ static int delete_ref_loose(struct ref_lock *lock, int flag)
+ {
        if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
                /* loose */
-               i = strlen(lock->lk->filename) - 5; /* .lock */
+               int err, i = strlen(lock->lk->filename) - 5; /* .lock */
                lock->lk->filename[i] = 0;
                err = unlink_or_warn(lock->lk->filename);
-               if (err && errno != ENOENT)
-                       ret = 1;
                lock->lk->filename[i] = '.';
+               if (err && errno != ENOENT)
+                       return 1;
        }
+       return 0;
+ }
+ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
+ {
+       struct ref_lock *lock;
+       int ret = 0, flag = 0;
+       lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
+       if (!lock)
+               return 1;
+       ret |= delete_ref_loose(lock, flag);
        /* removing the loose one could have resurrected an earlier
         * packed one.  Also, if it was not loose we need to repack
         * without it.
@@@ -3169,12 -3192,13 +3192,13 @@@ int for_each_reflog(each_ref_fn fn, voi
        return retval;
  }
  
- int update_ref(const char *action, const char *refname,
-               const unsigned char *sha1, const unsigned char *oldval,
-               int flags, enum action_on_err onerr)
+ static struct ref_lock *update_ref_lock(const char *refname,
+                                       const unsigned char *oldval,
+                                       int flags, int *type_p,
+                                       enum action_on_err onerr)
  {
-       static struct ref_lock *lock;
-       lock = lock_any_ref_for_update(refname, oldval, flags);
+       struct ref_lock *lock;
+       lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
        if (!lock) {
                const char *str = "Cannot lock the ref '%s'.";
                switch (onerr) {
                case DIE_ON_ERR: die(str, refname); break;
                case QUIET_ON_ERR: break;
                }
-               return 1;
        }
+       return lock;
+ }
+ static int update_ref_write(const char *action, const char *refname,
+                           const unsigned char *sha1, struct ref_lock *lock,
+                           enum action_on_err onerr)
+ {
        if (write_ref_sha1(lock, sha1, action) < 0) {
                const char *str = "Cannot update the ref '%s'.";
                switch (onerr) {
        return 0;
  }
  
 -struct ref *find_ref_by_name(const struct ref *list, const char *name)
 -{
 -      for ( ; list; list = list->next)
 -              if (!strcmp(list->name, name))
 -                      return (struct ref *)list;
 -      return NULL;
 -}
 -
+ int update_ref(const char *action, const char *refname,
+              const unsigned char *sha1, const unsigned char *oldval,
+              int flags, enum action_on_err onerr)
+ {
+       struct ref_lock *lock;
+       lock = update_ref_lock(refname, oldval, flags, 0, onerr);
+       if (!lock)
+               return 1;
+       return update_ref_write(action, refname, sha1, lock, onerr);
+ }
+ static int ref_update_compare(const void *r1, const void *r2)
+ {
+       const struct ref_update * const *u1 = r1;
+       const struct ref_update * const *u2 = r2;
+       return strcmp((*u1)->ref_name, (*u2)->ref_name);
+ }
+ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
+                                       enum action_on_err onerr)
+ {
+       int i;
+       for (i = 1; i < n; i++)
+               if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
+                       const char *str =
+                               "Multiple updates for ref '%s' not allowed.";
+                       switch (onerr) {
+                       case MSG_ON_ERR:
+                               error(str, updates[i]->ref_name); break;
+                       case DIE_ON_ERR:
+                               die(str, updates[i]->ref_name); break;
+                       case QUIET_ON_ERR:
+                               break;
+                       }
+                       return 1;
+               }
+       return 0;
+ }
+ int update_refs(const char *action, const struct ref_update **updates_orig,
+               int n, enum action_on_err onerr)
+ {
+       int ret = 0, delnum = 0, i;
+       struct ref_update **updates;
+       int *types;
+       struct ref_lock **locks;
+       const char **delnames;
+       if (!updates_orig || !n)
+               return 0;
+       /* Allocate work space */
+       updates = xmalloc(sizeof(*updates) * n);
+       types = xmalloc(sizeof(*types) * n);
+       locks = xcalloc(n, sizeof(*locks));
+       delnames = xmalloc(sizeof(*delnames) * n);
+       /* Copy, sort, and reject duplicate refs */
+       memcpy(updates, updates_orig, sizeof(*updates) * n);
+       qsort(updates, n, sizeof(*updates), ref_update_compare);
+       ret = ref_update_reject_duplicates(updates, n, onerr);
+       if (ret)
+               goto cleanup;
+       /* Acquire all locks while verifying old values */
+       for (i = 0; i < n; i++) {
+               locks[i] = update_ref_lock(updates[i]->ref_name,
+                                          (updates[i]->have_old ?
+                                           updates[i]->old_sha1 : NULL),
+                                          updates[i]->flags,
+                                          &types[i], onerr);
+               if (!locks[i]) {
+                       ret = 1;
+                       goto cleanup;
+               }
+       }
+       /* Perform updates first so live commits remain referenced */
+       for (i = 0; i < n; i++)
+               if (!is_null_sha1(updates[i]->new_sha1)) {
+                       ret = update_ref_write(action,
+                                              updates[i]->ref_name,
+                                              updates[i]->new_sha1,
+                                              locks[i], onerr);
+                       locks[i] = NULL; /* freed by update_ref_write */
+                       if (ret)
+                               goto cleanup;
+               }
+       /* Perform deletes now that updates are safely completed */
+       for (i = 0; i < n; i++)
+               if (locks[i]) {
+                       delnames[delnum++] = locks[i]->ref_name;
+                       ret |= delete_ref_loose(locks[i], types[i]);
+               }
+       ret |= repack_without_refs(delnames, delnum);
+       for (i = 0; i < delnum; i++)
+               unlink_or_warn(git_path("logs/%s", delnames[i]));
+       clear_loose_ref_cache(&ref_cache);
+ cleanup:
+       for (i = 0; i < n; i++)
+               if (locks[i])
+                       unlock_ref(locks[i]);
+       free(updates);
+       free(types);
+       free(locks);
+       free(delnames);
+       return ret;
+ }
  /*
   * generate a format suitable for scanf from a ref_rev_parse_rules
   * rule, that is replace the "%.*s" spec with a "%s" spec