Merge branch 'rs/ref-transaction-1'
authorJunio C Hamano <gitster@pobox.com>
Thu, 11 Sep 2014 17:33:30 +0000 (10:33 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 11 Sep 2014 17:33:31 +0000 (10:33 -0700)
The second batch of the transactional ref update series.

* rs/ref-transaction-1: (22 commits)
update-ref --stdin: pass transaction around explicitly
update-ref --stdin: narrow scope of err strbuf
refs.c: make delete_ref use a transaction
refs.c: make prune_ref use a transaction to delete the ref
refs.c: remove lock_ref_sha1
refs.c: remove the update_ref_write function
refs.c: remove the update_ref_lock function
refs.c: make lock_ref_sha1 static
walker.c: use ref transaction for ref updates
fast-import.c: use a ref transaction when dumping tags
receive-pack.c: use a reference transaction for updating the refs
refs.c: change update_ref to use a transaction
branch.c: use ref transaction for all ref updates
fast-import.c: change update_branch to use ref transactions
sequencer.c: use ref transactions for all ref updates
commit.c: use ref transactions for updates
replace.c: use the ref transaction functions for updates
tag.c: use ref transactions when doing updates
refs.c: add transaction.status and track OPEN/CLOSED
refs.c: make ref_transaction_begin take an err argument
...

1  2 
branch.c
builtin/commit.c
builtin/receive-pack.c
builtin/replace.c
builtin/tag.c
fast-import.c
refs.c
refs.h
sequencer.c
walker.c
diff --combined branch.c
index df6b1203de75618fd7aaff28f7f38ecbcd82066e,37ac555324e4358c300e57f8a398b12250bd5835..9a2228ebb46df721741db2249ab25cce5dfb4282
+++ b/branch.c
@@@ -50,11 -50,11 +50,11 @@@ static int should_setup_rebase(const ch
  
  void install_branch_config(int flag, const char *local, const char *origin, const char *remote)
  {
 -      const char *shortname = skip_prefix(remote, "refs/heads/");
 +      const char *shortname = NULL;
        struct strbuf key = STRBUF_INIT;
        int rebasing = should_setup_rebase(origin);
  
 -      if (shortname
 +      if (skip_prefix(remote, "refs/heads/", &shortname)
            && !strcmp(local, shortname)
            && !origin) {
                warning(_("Not setting branch %s as its own upstream."),
@@@ -140,17 -140,33 +140,17 @@@ static int setup_tracking(const char *n
        return 0;
  }
  
 -struct branch_desc_cb {
 -      const char *config_name;
 -      const char *value;
 -};
 -
 -static int read_branch_desc_cb(const char *var, const char *value, void *cb)
 -{
 -      struct branch_desc_cb *desc = cb;
 -      if (strcmp(desc->config_name, var))
 -              return 0;
 -      free((char *)desc->value);
 -      return git_config_string(&desc->value, var, value);
 -}
 -
  int read_branch_desc(struct strbuf *buf, const char *branch_name)
  {
 -      struct branch_desc_cb cb;
 +      char *v = NULL;
        struct strbuf name = STRBUF_INIT;
        strbuf_addf(&name, "branch.%s.description", branch_name);
 -      cb.config_name = name.buf;
 -      cb.value = NULL;
 -      if (git_config(read_branch_desc_cb, &cb) < 0) {
 +      if (git_config_get_string(name.buf, &v)) {
                strbuf_release(&name);
                return -1;
        }
 -      if (cb.value)
 -              strbuf_addstr(buf, cb.value);
 +      strbuf_addstr(buf, v);
 +      free(v);
        strbuf_release(&name);
        return 0;
  }
@@@ -210,7 -226,6 +210,6 @@@ void create_branch(const char *head
                   int force, int reflog, int clobber_head,
                   int quiet, enum branch_track track)
  {
-       struct ref_lock *lock = NULL;
        struct commit *commit;
        unsigned char sha1[20];
        char *real_ref, msg[PATH_MAX + 20];
                die(_("Not a valid branch point: '%s'."), start_name);
        hashcpy(sha1, commit->object.sha1);
  
-       if (!dont_change_ref) {
-               lock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);
-               if (!lock)
-                       die_errno(_("Failed to lock ref for update"));
-       }
-       if (reflog)
-               log_all_ref_updates = 1;
        if (forcing)
                snprintf(msg, sizeof msg, "branch: Reset to %s",
                         start_name);
                snprintf(msg, sizeof msg, "branch: Created from %s",
                         start_name);
  
+       if (reflog)
+               log_all_ref_updates = 1;
+       if (!dont_change_ref) {
+               struct ref_transaction *transaction;
+               struct strbuf err = STRBUF_INIT;
+               transaction = ref_transaction_begin(&err);
+               if (!transaction ||
+                   ref_transaction_update(transaction, ref.buf, sha1,
+                                          null_sha1, 0, !forcing, &err) ||
+                   ref_transaction_commit(transaction, msg, &err))
+                       die("%s", err.buf);
+               ref_transaction_free(transaction);
+               strbuf_release(&err);
+       }
        if (real_ref && track)
                setup_tracking(ref.buf + 11, real_ref, track, quiet);
  
-       if (!dont_change_ref)
-               if (write_ref_sha1(lock, sha1, msg) < 0)
-                       die_errno(_("Failed to write ref"));
        strbuf_release(&ref);
        free(real_ref);
  }
diff --combined builtin/commit.c
index ec0048e8b05d5a54e1507b9a8f3e73f06be0fa39,9bf1003c0afd15c039efd1f7aeb872a3ce9a98ff..5911447486f5b2325dff4747872a25dd4325c1e2
@@@ -42,20 -42,7 +42,20 @@@ static const char * const builtin_statu
        NULL
  };
  
 -static const char implicit_ident_advice[] =
 +static const char implicit_ident_advice_noconfig[] =
 +N_("Your name and email address were configured automatically based\n"
 +"on your username and hostname. Please check that they are accurate.\n"
 +"You can suppress this message by setting them explicitly. Run the\n"
 +"following command and follow the instructions in your editor to edit\n"
 +"your configuration file:\n"
 +"\n"
 +"    git config --global --edit\n"
 +"\n"
 +"After doing this, you may fix the identity used for this commit with:\n"
 +"\n"
 +"    git commit --amend --reset-author\n");
 +
 +static const char implicit_ident_advice_config[] =
  N_("Your name and email address were configured automatically based\n"
  "on your username and hostname. Please check that they are accurate.\n"
  "You can suppress this message by setting them explicitly:\n"
@@@ -318,6 -305,7 +318,6 @@@ static void refresh_cache_or_die(int re
  static char *prepare_index(int argc, const char **argv, const char *prefix,
                           const struct commit *current_head, int is_status)
  {
 -      int fd;
        struct string_list partial;
        struct pathspec pathspec;
        int refresh_flags = REFRESH_QUIET;
  
        if (interactive) {
                char *old_index_env = NULL;
 -              fd = hold_locked_index(&index_lock, 1);
 +              hold_locked_index(&index_lock, 1);
  
                refresh_cache_or_die(refresh_flags);
  
 -              if (write_cache(fd, active_cache, active_nr) ||
 -                  close_lock_file(&index_lock))
 +              if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                        die(_("unable to create temporary index"));
  
                old_index_env = getenv(INDEX_ENVIRONMENT);
         * (B) on failure, rollback the real index.
         */
        if (all || (also && pathspec.nr)) {
 -              fd = hold_locked_index(&index_lock, 1);
 +              hold_locked_index(&index_lock, 1);
                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) ||
 -                  close_lock_file(&index_lock))
 +              if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                        die(_("unable to write new_index file"));
                commit_style = COMMIT_NORMAL;
                return index_lock.filename;
         * We still need to refresh the index here.
         */
        if (!only && !pathspec.nr) {
 -              fd = hold_locked_index(&index_lock, 1);
 +              hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
                        update_main_cache_tree(WRITE_TREE_SILENT);
 -                      if (write_cache(fd, active_cache, active_nr) ||
 -                          commit_locked_index(&index_lock))
 +                      if (write_locked_index(&the_index, &index_lock,
 +                                             COMMIT_LOCK))
                                die(_("unable to write new_index file"));
                } else {
                        rollback_lock_file(&index_lock);
                        die(_("cannot do a partial commit during a cherry-pick."));
        }
  
 -      memset(&partial, 0, sizeof(partial));
 -      partial.strdup_strings = 1;
 +      string_list_init(&partial, 1);
        if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
                exit(1);
  
        if (read_cache() < 0)
                die(_("cannot read the index"));
  
 -      fd = hold_locked_index(&index_lock, 1);
 +      hold_locked_index(&index_lock, 1);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
 -      if (write_cache(fd, active_cache, active_nr) ||
 -          close_lock_file(&index_lock))
 +      if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                die(_("unable to write new_index file"));
  
 -      fd = hold_lock_file_for_update(&false_lock,
 -                                     git_path("next-index-%"PRIuMAX,
 -                                              (uintmax_t) getpid()),
 -                                     LOCK_DIE_ON_ERROR);
 +      hold_lock_file_for_update(&false_lock,
 +                                git_path("next-index-%"PRIuMAX,
 +                                         (uintmax_t) getpid()),
 +                                LOCK_DIE_ON_ERROR);
  
        create_base_index(current_head);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
  
 -      if (write_cache(fd, active_cache, active_nr) ||
 -          close_lock_file(&false_lock))
 +      if (write_locked_index(&the_index, &false_lock, CLOSE_LOCK))
                die(_("unable to write temporary index file"));
  
        discard_cache();
@@@ -714,7 -707,7 +714,7 @@@ static int prepare_to_commit(const cha
                char *buffer;
                buffer = strstr(use_message_buffer, "\n\n");
                if (buffer)
 -                      strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
 +                      strbuf_addstr(&sb, buffer + 2);
                hook_arg1 = "commit";
                hook_arg2 = use_message;
        } else if (fixup_message) {
@@@ -1027,7 -1020,7 +1027,7 @@@ static int message_is_empty(struct strb
  static int template_untouched(struct strbuf *sb)
  {
        struct strbuf tmpl = STRBUF_INIT;
 -      char *start;
 +      const char *start;
  
        if (cleanup_mode == CLEANUP_NONE && sb->len)
                return 0;
                return 0;
  
        stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
 -      start = (char *)skip_prefix(sb->buf, tmpl.buf);
 -      if (!start)
 +      if (!skip_prefix(sb->buf, tmpl.buf, &start))
                start = sb->buf;
        strbuf_release(&tmpl);
        return rest_is_empty(sb, start - sb->buf);
@@@ -1061,8 -1055,7 +1061,8 @@@ static const char *find_author_by_nickn
        revs.mailmap = &mailmap;
        read_mailmap(revs.mailmap, NULL);
  
 -      prepare_revision_walk(&revs);
 +      if (prepare_revision_walk(&revs))
 +              die(_("revision walk setup failed"));
        commit = get_revision(&revs);
        if (commit) {
                struct pretty_print_context ctx = {0};
@@@ -1416,24 -1409,6 +1416,24 @@@ int cmd_status(int argc, const char **a
        return 0;
  }
  
 +static const char *implicit_ident_advice(void)
 +{
 +      char *user_config = NULL;
 +      char *xdg_config = NULL;
 +      int config_exists;
 +
 +      home_config_paths(&user_config, &xdg_config, "config");
 +      config_exists = file_exists(user_config) || file_exists(xdg_config);
 +      free(user_config);
 +      free(xdg_config);
 +
 +      if (config_exists)
 +              return _(implicit_ident_advice_config);
 +      else
 +              return _(implicit_ident_advice_noconfig);
 +
 +}
 +
  static void print_summary(const char *prefix, const unsigned char *sha1,
                          int initial_commit)
  {
                strbuf_addbuf_percentquote(&format, &committer_ident);
                if (advice_implicit_identity) {
                        strbuf_addch(&format, '\n');
 -                      strbuf_addstr(&format, _(implicit_ident_advice));
 +                      strbuf_addstr(&format, implicit_ident_advice());
                }
        }
        strbuf_release(&author_ident);
@@@ -1540,7 -1515,7 +1540,7 @@@ static int run_rewrite_hook(const unsig
  {
        /* oldsha1 SP newsha1 LF NUL */
        static char buf[2*40 + 3];
 -      struct child_process proc;
 +      struct child_process proc = CHILD_PROCESS_INIT;
        const char *argv[3];
        int code;
        size_t n;
        argv[1] = "amend";
        argv[2] = NULL;
  
 -      memset(&proc, 0, sizeof(proc));
        proc.argv = argv;
        proc.in = -1;
        proc.stdout_to_stderr = 1;
@@@ -1651,11 -1627,12 +1651,12 @@@ int cmd_commit(int argc, const char **a
        const char *index_file, *reflog_msg;
        char *nl;
        unsigned char sha1[20];
-       struct ref_lock *ref_lock;
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
        struct commit *current_head = NULL;
        struct commit_extra_header *extra = NULL;
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
                append_merge_tag_headers(parents, &tail);
        }
  
 -      if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
 -                               author_ident.buf, sign_commit, extra)) {
 +      if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1,
 +                       parents, sha1, author_ident.buf, sign_commit, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
        strbuf_release(&author_ident);
        free_commit_extra_headers(extra);
  
-       ref_lock = lock_any_ref_for_update("HEAD",
-                                          !current_head
-                                          ? NULL
-                                          : current_head->object.sha1,
-                                          0, NULL);
-       if (!ref_lock) {
-               rollback_index_files();
-               die(_("cannot lock HEAD ref"));
-       }
        nl = strchr(sb.buf, '\n');
        if (nl)
                strbuf_setlen(&sb, nl + 1 - sb.buf);
        strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
        strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
  
-       if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
+       transaction = ref_transaction_begin(&err);
+       if (!transaction ||
+           ref_transaction_update(transaction, "HEAD", sha1,
+                                  current_head
+                                  ? current_head->object.sha1 : NULL,
+                                  0, !!current_head, &err) ||
+           ref_transaction_commit(transaction, sb.buf, &err)) {
                rollback_index_files();
-               die(_("cannot update HEAD ref"));
+               die("%s", err.buf);
        }
+       ref_transaction_free(transaction);
  
        unlink(git_path("CHERRY_PICK_HEAD"));
        unlink(git_path("REVERT_HEAD"));
        if (!quiet)
                print_summary(prefix, sha1, !current_head);
  
+       strbuf_release(&err);
        return 0;
  }
diff --combined builtin/receive-pack.c
index 5ad9075b3c4b378366f5146ea204160b76859d46,224fadccc31108104d727ec2a3961304a78b779f..afb8d9926496de2bed8bcab793dd79562e575e18
@@@ -255,7 -255,7 +255,7 @@@ static int copy_to_sideband(int in, in
  typedef int (*feed_fn)(void *, const char **, size_t *);
  static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
  {
 -      struct child_process proc;
 +      struct child_process proc = CHILD_PROCESS_INIT;
        struct async muxer;
        const char *argv[2];
        int code;
  
        argv[1] = NULL;
  
 -      memset(&proc, 0, sizeof(proc));
        proc.argv = argv;
        proc.in = -1;
        proc.stdout_to_stderr = 1;
@@@ -349,7 -350,7 +349,7 @@@ static int run_receive_hook(struct comm
  static int run_update_hook(struct command *cmd)
  {
        const char *argv[5];
 -      struct child_process proc;
 +      struct child_process proc = CHILD_PROCESS_INIT;
        int code;
  
        argv[0] = find_hook("update");
        argv[3] = sha1_to_hex(cmd->new_sha1);
        argv[4] = NULL;
  
 -      memset(&proc, 0, sizeof(proc));
        proc.no_stdin = 1;
        proc.stdout_to_stderr = 1;
        proc.err = use_sideband ? -1 : 0;
@@@ -436,7 -438,7 +436,7 @@@ static int update_shallow_ref(struct co
        uint32_t mask = 1 << (cmd->index % 32);
        int i;
  
 -      trace_printf_key("GIT_TRACE_SHALLOW",
 +      trace_printf_key(&trace_shallow,
                         "shallow: update_shallow_ref %s\n", cmd->ref_name);
        for (i = 0; i < si->shallow->nr; i++)
                if (si->used_shallow[i] &&
@@@ -473,7 -475,6 +473,6 @@@ static const char *update(struct comman
        const char *namespaced_name;
        unsigned char *old_sha1 = cmd->old_sha1;
        unsigned char *new_sha1 = cmd->new_sha1;
-       struct ref_lock *lock;
  
        /* only refs/... are allowed */
        if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
                return NULL; /* good */
        }
        else {
+               struct strbuf err = STRBUF_INIT;
+               struct ref_transaction *transaction;
                if (shallow_update && si->shallow_ref[cmd->index] &&
                    update_shallow_ref(cmd, si))
                        return "shallow error";
  
-               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";
-               }
-               if (write_ref_sha1(lock, new_sha1, "push")) {
-                       return "failed to write"; /* error() already called */
+               transaction = ref_transaction_begin(&err);
+               if (!transaction ||
+                   ref_transaction_update(transaction, namespaced_name,
+                                          new_sha1, old_sha1, 0, 1, &err) ||
+                   ref_transaction_commit(transaction, "push", &err)) {
+                       ref_transaction_free(transaction);
+                       rp_error("%s", err.buf);
+                       strbuf_release(&err);
+                       return "failed to update ref";
                }
+               ref_transaction_free(transaction);
+               strbuf_release(&err);
                return NULL; /* good */
        }
  }
@@@ -596,7 -605,7 +603,7 @@@ static void run_update_post_hook(struc
        struct command *cmd;
        int argc;
        const char **argv;
 -      struct child_process proc;
 +      struct child_process proc = CHILD_PROCESS_INIT;
        char *hook;
  
        hook = find_hook("post-update");
        argv[0] = hook;
  
        for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
 -              char *p;
                if (cmd->error_string || cmd->did_not_exist)
                        continue;
 -              p = xmalloc(strlen(cmd->ref_name) + 1);
 -              strcpy(p, cmd->ref_name);
 -              argv[argc] = p;
 +              argv[argc] = xstrdup(cmd->ref_name);
                argc++;
        }
        argv[argc] = NULL;
  
 -      memset(&proc, 0, sizeof(proc));
        proc.no_stdin = 1;
        proc.stdout_to_stderr = 1;
        proc.err = use_sideband ? -1 : 0;
@@@ -908,7 -921,7 +915,7 @@@ static const char *unpack(int err_fd, s
        const char *hdr_err;
        int status;
        char hdr_arg[38];
 -      struct child_process child;
 +      struct child_process child = CHILD_PROCESS_INIT;
        int fsck_objects = (receive_fsck_objects >= 0
                            ? receive_fsck_objects
                            : transfer_fsck_objects >= 0
                argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL);
        }
  
 -      memset(&child, 0, sizeof(child));
        if (ntohl(hdr.hdr_entries) < unpack_limit) {
                argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL);
                if (quiet)
@@@ -1118,7 -1132,7 +1125,7 @@@ int cmd_receive_pack(int argc, const ch
        int advertise_refs = 0;
        int stateless_rpc = 0;
        int i;
 -      char *dir = NULL;
 +      const char *dir = NULL;
        struct command *commands;
        struct sha1_array shallow = SHA1_ARRAY_INIT;
        struct sha1_array ref = SHA1_ARRAY_INIT;
                }
                if (dir)
                        usage(receive_pack_usage);
 -              dir = xstrdup(arg);
 +              dir = arg;
        }
        if (!dir)
                usage(receive_pack_usage);
diff --combined builtin/replace.c
index d2aac642606e1f1614b5b6c7f83707b4fedac87d,1fcd06db256df65d1e0d7a0316c2de7922e8eae4..8020db850092691f55df3815173a6eefce0ca6a0
  #include "refs.h"
  #include "parse-options.h"
  #include "run-command.h"
 +#include "tag.h"
  
  static const char * const git_replace_usage[] = {
        N_("git replace [-f] <object> <replacement>"),
        N_("git replace [-f] --edit <object>"),
 +      N_("git replace [-f] --graft <commit> [<parent>...]"),
        N_("git replace -d <object>..."),
        N_("git replace [--format=<format>] [-l [<pattern>]]"),
        NULL
  };
  
  enum replace_format {
 -      REPLACE_FORMAT_SHORT,
 -      REPLACE_FORMAT_MEDIUM,
 -      REPLACE_FORMAT_LONG
 +      REPLACE_FORMAT_SHORT,
 +      REPLACE_FORMAT_MEDIUM,
 +      REPLACE_FORMAT_LONG
  };
  
  struct show_data {
@@@ -155,7 -153,8 +155,8 @@@ static int replace_object_sha1(const ch
        unsigned char prev[20];
        enum object_type obj_type, repl_type;
        char ref[PATH_MAX];
-       struct ref_lock *lock;
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
  
        obj_type = sha1_object_info(object, NULL);
        repl_type = sha1_object_info(repl, NULL);
  
        check_ref_valid(object, prev, ref, sizeof(ref), force);
  
-       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)
-               die("%s: cannot update the ref", ref);
+       transaction = ref_transaction_begin(&err);
+       if (!transaction ||
+           ref_transaction_update(transaction, ref, repl, prev, 0, 1, &err) ||
+           ref_transaction_commit(transaction, NULL, &err))
+               die("%s", err.buf);
  
+       ref_transaction_free(transaction);
        return 0;
  }
  
@@@ -190,32 -190,27 +192,32 @@@ static int replace_object(const char *o
  }
  
  /*
 - * Write the contents of the object named by "sha1" to the file "filename",
 - * pretty-printed for human editing based on its type.
 + * Write the contents of the object named by "sha1" to the file "filename".
 + * If "raw" is true, then the object's raw contents are printed according to
 + * "type". Otherwise, we pretty-print the contents for human editing.
   */
 -static void export_object(const unsigned char *sha1, const char *filename)
 +static void export_object(const unsigned char *sha1, enum object_type type,
 +                        int raw, const char *filename)
  {
 -      const char *argv[] = { "--no-replace-objects", "cat-file", "-p", NULL, NULL };
 -      struct child_process cmd = { argv };
 +      struct child_process cmd = CHILD_PROCESS_INIT;
        int fd;
  
        fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd < 0)
                die_errno("unable to open %s for writing", filename);
  
 -      argv[3] = sha1_to_hex(sha1);
 +      argv_array_push(&cmd.args, "--no-replace-objects");
 +      argv_array_push(&cmd.args, "cat-file");
 +      if (raw)
 +              argv_array_push(&cmd.args, typename(type));
 +      else
 +              argv_array_push(&cmd.args, "-p");
 +      argv_array_push(&cmd.args, sha1_to_hex(sha1));
        cmd.git_cmd = 1;
        cmd.out = fd;
  
        if (run_command(&cmd))
                die("cat-file reported failure");
 -
 -      close(fd);
  }
  
  /*
   * The sha1 of the written object is returned via sha1.
   */
  static void import_object(unsigned char *sha1, enum object_type type,
 -                        const char *filename)
 +                        int raw, const char *filename)
  {
        int fd;
  
        if (fd < 0)
                die_errno("unable to open %s for reading", filename);
  
 -      if (type == OBJ_TREE) {
 +      if (!raw && type == OBJ_TREE) {
                const char *argv[] = { "mktree", NULL };
 -              struct child_process cmd = { argv };
 +              struct child_process cmd = CHILD_PROCESS_INIT;
                struct strbuf result = STRBUF_INIT;
  
                cmd.argv = argv;
         */
  }
  
 -static int edit_and_replace(const char *object_ref, int force)
 +static int edit_and_replace(const char *object_ref, int force, int raw)
  {
        char *tmpfile = git_pathdup("REPLACE_EDITOBJ");
        enum object_type type;
  
        check_ref_valid(old, prev, ref, sizeof(ref), force);
  
 -      export_object(old, tmpfile);
 +      export_object(old, type, raw, tmpfile);
        if (launch_editor(tmpfile, NULL, NULL) < 0)
                die("editing object file failed");
 -      import_object(new, type, tmpfile);
 +      import_object(new, type, raw, tmpfile);
  
        free(tmpfile);
  
        return replace_object_sha1(object_ref, old, "replacement", new, force);
  }
  
 +static void replace_parents(struct strbuf *buf, int argc, const char **argv)
 +{
 +      struct strbuf new_parents = STRBUF_INIT;
 +      const char *parent_start, *parent_end;
 +      int i;
 +
 +      /* find existing parents */
 +      parent_start = buf->buf;
 +      parent_start += 46; /* "tree " + "hex sha1" + "\n" */
 +      parent_end = parent_start;
 +
 +      while (starts_with(parent_end, "parent "))
 +              parent_end += 48; /* "parent " + "hex sha1" + "\n" */
 +
 +      /* prepare new parents */
 +      for (i = 0; i < argc; i++) {
 +              unsigned char sha1[20];
 +              if (get_sha1(argv[i], sha1) < 0)
 +                      die(_("Not a valid object name: '%s'"), argv[i]);
 +              lookup_commit_or_die(sha1, argv[i]);
 +              strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1));
 +      }
 +
 +      /* replace existing parents with new ones */
 +      strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start,
 +                    new_parents.buf, new_parents.len);
 +
 +      strbuf_release(&new_parents);
 +}
 +
 +struct check_mergetag_data {
 +      int argc;
 +      const char **argv;
 +};
 +
 +static void check_one_mergetag(struct commit *commit,
 +                             struct commit_extra_header *extra,
 +                             void *data)
 +{
 +      struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data;
 +      const char *ref = mergetag_data->argv[0];
 +      unsigned char tag_sha1[20];
 +      struct tag *tag;
 +      int i;
 +
 +      hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1);
 +      tag = lookup_tag(tag_sha1);
 +      if (!tag)
 +              die(_("bad mergetag in commit '%s'"), ref);
 +      if (parse_tag_buffer(tag, extra->value, extra->len))
 +              die(_("malformed mergetag in commit '%s'"), ref);
 +
 +      /* iterate over new parents */
 +      for (i = 1; i < mergetag_data->argc; i++) {
 +              unsigned char sha1[20];
 +              if (get_sha1(mergetag_data->argv[i], sha1) < 0)
 +                      die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]);
 +              if (!hashcmp(tag->tagged->sha1, sha1))
 +                      return; /* found */
 +      }
 +
 +      die(_("original commit '%s' contains mergetag '%s' that is discarded; "
 +            "use --edit instead of --graft"), ref, sha1_to_hex(tag_sha1));
 +}
 +
 +static void check_mergetags(struct commit *commit, int argc, const char **argv)
 +{
 +      struct check_mergetag_data mergetag_data;
 +
 +      mergetag_data.argc = argc;
 +      mergetag_data.argv = argv;
 +      for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
 +}
 +
 +static int create_graft(int argc, const char **argv, int force)
 +{
 +      unsigned char old[20], new[20];
 +      const char *old_ref = argv[0];
 +      struct commit *commit;
 +      struct strbuf buf = STRBUF_INIT;
 +      const char *buffer;
 +      unsigned long size;
 +
 +      if (get_sha1(old_ref, old) < 0)
 +              die(_("Not a valid object name: '%s'"), old_ref);
 +      commit = lookup_commit_or_die(old, old_ref);
 +
 +      buffer = get_commit_buffer(commit, &size);
 +      strbuf_add(&buf, buffer, size);
 +      unuse_commit_buffer(commit, buffer);
 +
 +      replace_parents(&buf, argc - 1, &argv[1]);
 +
 +      if (remove_signature(&buf)) {
 +              warning(_("the original commit '%s' has a gpg signature."), old_ref);
 +              warning(_("the signature will be removed in the replacement commit!"));
 +      }
 +
 +      check_mergetags(commit, argc, argv);
 +
 +      if (write_sha1_file(buf.buf, buf.len, commit_type, new))
 +              die(_("could not write replacement commit for: '%s'"), old_ref);
 +
 +      strbuf_release(&buf);
 +
 +      if (!hashcmp(old, new))
 +              return error("new commit is the same as the old one: '%s'", sha1_to_hex(old));
 +
 +      return replace_object_sha1(old_ref, old, "replacement", new, force);
 +}
 +
  int cmd_replace(int argc, const char **argv, const char *prefix)
  {
        int force = 0;
 +      int raw = 0;
        const char *format = NULL;
        enum {
                MODE_UNSPECIFIED = 0,
                MODE_LIST,
                MODE_DELETE,
                MODE_EDIT,
 +              MODE_GRAFT,
                MODE_REPLACE
        } cmdmode = MODE_UNSPECIFIED;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
                OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
 +              OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
                OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")),
 +              OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")),
                OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
                OPT_END()
        };
                usage_msg_opt("--format cannot be used when not listing",
                              git_replace_usage, options);
  
 -      if (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT)
 +      if (force &&
 +          cmdmode != MODE_REPLACE &&
 +          cmdmode != MODE_EDIT &&
 +          cmdmode != MODE_GRAFT)
                usage_msg_opt("-f only makes sense when writing a replacement",
                              git_replace_usage, options);
  
 +      if (raw && cmdmode != MODE_EDIT)
 +              usage_msg_opt("--raw only makes sense with --edit",
 +                            git_replace_usage, options);
 +
        switch (cmdmode) {
        case MODE_DELETE:
                if (argc < 1)
                if (argc != 1)
                        usage_msg_opt("-e needs exactly one argument",
                                      git_replace_usage, options);
 -              return edit_and_replace(argv[0], force);
 +              return edit_and_replace(argv[0], force, raw);
 +
 +      case MODE_GRAFT:
 +              if (argc < 1)
 +                      usage_msg_opt("-g needs at least one argument",
 +                                    git_replace_usage, options);
 +              return create_graft(argc, argv, force);
  
        case MODE_LIST:
                if (argc > 1)
diff --combined builtin/tag.c
index 19eb7478208d7e9c88ced92f2620572d2b234974,f3f172f9038d52824bde52d7baae4bee9c3158da..a81b9e4174c77fc10d9f0a37c547723505208c3c
@@@ -32,8 -32,6 +32,8 @@@ static const char * const git_tag_usage
  #define SORT_MASK       0x7fff
  #define REVERSE_SORT    0x8000
  
 +static int tag_sort;
 +
  struct tag_filter {
        const char **patterns;
        int lines;
@@@ -85,7 -83,7 +85,7 @@@ static int in_commit_list(const struct 
  enum contains_result {
        CONTAINS_UNKNOWN = -1,
        CONTAINS_NO = 0,
 -      CONTAINS_YES = 1,
 +      CONTAINS_YES = 1
  };
  
  /*
@@@ -348,51 -346,9 +348,51 @@@ static const char tag_template_nocleanu
        "Lines starting with '%c' will be kept; you may remove them"
        " yourself if you want to.\n");
  
 +/*
 + * Parse a sort string, and return 0 if parsed successfully. Will return
 + * non-zero when the sort string does not parse into a known type. If var is
 + * given, the error message becomes a warning and includes information about
 + * the configuration value.
 + */
 +static int parse_sort_string(const char *var, const char *arg, int *sort)
 +{
 +      int type = 0, flags = 0;
 +
 +      if (skip_prefix(arg, "-", &arg))
 +              flags |= REVERSE_SORT;
 +
 +      if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
 +              type = VERCMP_SORT;
 +      else
 +              type = STRCMP_SORT;
 +
 +      if (strcmp(arg, "refname")) {
 +              if (!var)
 +                      return error(_("unsupported sort specification '%s'"), arg);
 +              else {
 +                      warning(_("unsupported sort specification '%s' in variable '%s'"),
 +                              var, arg);
 +                      return -1;
 +              }
 +      }
 +
 +      *sort = (type | flags);
 +
 +      return 0;
 +}
 +
  static int git_tag_config(const char *var, const char *value, void *cb)
  {
 -      int status = git_gpg_config(var, value, cb);
 +      int status;
 +
 +      if (!strcmp(var, "tag.sort")) {
 +              if (!value)
 +                      return config_error_nonbool(var);
 +              parse_sort_string(var, value, &tag_sort);
 +              return 0;
 +      }
 +
 +      status = git_gpg_config(var, value, cb);
        if (status)
                return status;
        if (starts_with(var, "column."))
@@@ -566,8 -522,24 +566,8 @@@ static int parse_opt_points_at(const st
  static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
  {
        int *sort = opt->value;
 -      int flags = 0;
  
 -      if (*arg == '-') {
 -              flags |= REVERSE_SORT;
 -              arg++;
 -      }
 -      if (starts_with(arg, "version:")) {
 -              *sort = VERCMP_SORT;
 -              arg += 8;
 -      } else if (starts_with(arg, "v:")) {
 -              *sort = VERCMP_SORT;
 -              arg += 2;
 -      } else
 -              *sort = STRCMP_SORT;
 -      if (strcmp(arg, "refname"))
 -              die(_("unsupported sort specification %s"), arg);
 -      *sort |= flags;
 -      return 0;
 +      return parse_sort_string(NULL, arg, sort);
  }
  
  int cmd_tag(int argc, const char **argv, const char *prefix)
        struct strbuf ref = STRBUF_INIT;
        unsigned char object[20], prev[20];
        const char *object_ref, *tag;
-       struct ref_lock *lock;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
        int annotate = 0, force = 0, lines = -1;
 -      int cmdmode = 0, sort = 0;
 +      int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
                { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
                OPT__FORCE(&force, N_("replace the tag if exists")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
                {
 -                      OPTION_CALLBACK, 0, "sort", &sort, N_("type"), N_("sort tags"),
 +                      OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
                        PARSE_OPT_NONEG, parse_opt_sort
                },
  
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
 -              if (lines != -1 && sort)
 +              if (lines != -1 && tag_sort)
                        die(_("--sort and -n are incompatible"));
 -              ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);
 +              ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
                if (column_active(colopts))
                        stop_column_filter();
                return ret;
        if (annotate)
                create_tag(object, tag, &buf, &opt, prev, object);
  
-       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)
-               die(_("%s: cannot update the ref"), ref.buf);
+       transaction = ref_transaction_begin(&err);
+       if (!transaction ||
+           ref_transaction_update(transaction, ref.buf, object, prev,
+                                  0, 1, &err) ||
+           ref_transaction_commit(transaction, NULL, &err))
+               die("%s", err.buf);
+       ref_transaction_free(transaction);
        if (force && !is_null_sha1(prev) && hashcmp(prev, object))
                printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
  
+       strbuf_release(&err);
        strbuf_release(&buf);
        strbuf_release(&ref);
        return 0;
diff --combined fast-import.c
index 34e780dcd0ddaa98790f76eed7c515affe61c384,e7f6e376d2233b141595f092a77a48f7cc4e71a6..2b31053e0d916ce8b9379a1cde9345f3e265d188
@@@ -371,8 -371,8 +371,8 @@@ static volatile sig_atomic_t checkpoint
  static int cat_blob_fd = STDOUT_FILENO;
  
  static void parse_argv(void);
 -static void parse_cat_blob(void);
 -static void parse_ls(struct branch *b);
 +static void parse_cat_blob(const char *p);
 +static void parse_ls(const char *p, struct branch *b);
  
  static void write_branch_report(FILE *rpt, struct branch *b)
  {
@@@ -1679,8 -1679,9 +1679,9 @@@ found_entry
  static int update_branch(struct branch *b)
  {
        static const char *msg = "fast-import";
-       struct ref_lock *lock;
+       struct ref_transaction *transaction;
        unsigned char old_sha1[20];
+       struct strbuf err = STRBUF_INIT;
  
        if (read_ref(b->name, old_sha1))
                hashclr(old_sha1);
                        delete_ref(b->name, old_sha1, 0);
                return 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)) {
                struct commit *old_cmit, *new_cmit;
  
                old_cmit = lookup_commit_reference_gently(old_sha1, 0);
                new_cmit = lookup_commit_reference_gently(b->sha1, 0);
-               if (!old_cmit || !new_cmit) {
-                       unlock_ref(lock);
+               if (!old_cmit || !new_cmit)
                        return error("Branch %s is missing commits.", b->name);
-               }
  
                if (!in_merge_bases(old_cmit, new_cmit)) {
-                       unlock_ref(lock);
                        warning("Not updating %s"
                                " (new tip %s does not contain %s)",
                                b->name, sha1_to_hex(b->sha1), sha1_to_hex(old_sha1));
                        return -1;
                }
        }
-       if (write_ref_sha1(lock, b->sha1, msg) < 0)
-               return error("Unable to update %s", b->name);
+       transaction = ref_transaction_begin(&err);
+       if (!transaction ||
+           ref_transaction_update(transaction, b->name, b->sha1, old_sha1,
+                                  0, 1, &err) ||
+           ref_transaction_commit(transaction, msg, &err)) {
+               ref_transaction_free(transaction);
+               error("%s", err.buf);
+               strbuf_release(&err);
+               return -1;
+       }
+       ref_transaction_free(transaction);
+       strbuf_release(&err);
        return 0;
  }
  
@@@ -1730,15 -1735,32 +1735,32 @@@ static void dump_tags(void
  {
        static const char *msg = "fast-import";
        struct tag *t;
-       struct ref_lock *lock;
-       char ref_name[PATH_MAX];
+       struct strbuf ref_name = STRBUF_INIT;
+       struct strbuf err = STRBUF_INIT;
+       struct ref_transaction *transaction;
+       transaction = ref_transaction_begin(&err);
+       if (!transaction) {
+               failure |= error("%s", err.buf);
+               goto cleanup;
+       }
        for (t = first_tag; t; t = t->next_tag) {
-               sprintf(ref_name, "tags/%s", t->name);
-               lock = lock_ref_sha1(ref_name, NULL);
-               if (!lock || write_ref_sha1(lock, t->sha1, msg) < 0)
-                       failure |= error("Unable to update %s", ref_name);
+               strbuf_reset(&ref_name);
+               strbuf_addf(&ref_name, "refs/tags/%s", t->name);
+               if (ref_transaction_update(transaction, ref_name.buf, t->sha1,
+                                          NULL, 0, 0, &err)) {
+                       failure |= error("%s", err.buf);
+                       goto cleanup;
+               }
        }
+       if (ref_transaction_commit(transaction, msg, &err))
+               failure |= error("%s", err.buf);
+  cleanup:
+       ref_transaction_free(transaction);
+       strbuf_release(&ref_name);
+       strbuf_release(&err);
  }
  
  static void dump_marks_helper(FILE *f,
@@@ -1861,8 -1883,6 +1883,8 @@@ static int read_next_command(void
        }
  
        for (;;) {
 +              const char *p;
 +
                if (unread_command_buf) {
                        unread_command_buf = 0;
                } else {
                        rc->prev->next = rc;
                        cmd_tail = rc;
                }
 -              if (starts_with(command_buf.buf, "cat-blob ")) {
 -                      parse_cat_blob();
 +              if (skip_prefix(command_buf.buf, "cat-blob ", &p)) {
 +                      parse_cat_blob(p);
                        continue;
                }
                if (command_buf.buf[0] == '#')
@@@ -1914,9 -1934,8 +1936,9 @@@ static void skip_optional_lf(void
  
  static void parse_mark(void)
  {
 -      if (starts_with(command_buf.buf, "mark :")) {
 -              next_mark = strtoumax(command_buf.buf + 6, NULL, 10);
 +      const char *v;
 +      if (skip_prefix(command_buf.buf, "mark :", &v)) {
 +              next_mark = strtoumax(v, NULL, 10);
                read_next_command();
        }
        else
  
  static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
  {
 +      const char *data;
        strbuf_reset(sb);
  
 -      if (!starts_with(command_buf.buf, "data "))
 +      if (!skip_prefix(command_buf.buf, "data ", &data))
                die("Expected 'data n' command, found: %s", command_buf.buf);
  
 -      if (starts_with(command_buf.buf + 5, "<<")) {
 -              char *term = xstrdup(command_buf.buf + 5 + 2);
 -              size_t term_len = command_buf.len - 5 - 2;
 +      if (skip_prefix(data, "<<", &data)) {
 +              char *term = xstrdup(data);
 +              size_t term_len = command_buf.len - (data - command_buf.buf);
  
                strbuf_detach(&command_buf, NULL);
                for (;;) {
                free(term);
        }
        else {
 -              uintmax_t len = strtoumax(command_buf.buf + 5, NULL, 10);
 +              uintmax_t len = strtoumax(data, NULL, 10);
                size_t n = 0, length = (size_t)len;
  
                if (limit && limit < len) {
@@@ -2269,14 -2287,15 +2291,14 @@@ static uintmax_t parse_mark_ref_space(c
        char *end;
  
        mark = parse_mark_ref(*p, &end);
 -      if (*end != ' ')
 +      if (*end++ != ' ')
                die("Missing space after mark: %s", command_buf.buf);
        *p = end;
        return mark;
  }
  
 -static void file_change_m(struct branch *b)
 +static void file_change_m(const char *p, struct branch *b)
  {
 -      const char *p = command_buf.buf + 2;
        static struct strbuf uq = STRBUF_INIT;
        const char *endp;
        struct object_entry *oe;
        if (*p == ':') {
                oe = find_mark(parse_mark_ref_space(&p));
                hashcpy(sha1, oe->idx.sha1);
 -      } else if (starts_with(p, "inline ")) {
 +      } else if (skip_prefix(p, "inline ", &p)) {
                inline_data = 1;
                oe = NULL; /* not used with inline_data, but makes gcc happy */
 -              p += strlen("inline");  /* advance to space */
        } else {
                if (get_sha1_hex(p, sha1))
                        die("Invalid dataref: %s", command_buf.buf);
                oe = find_object(sha1);
                p += 40;
 -              if (*p != ' ')
 +              if (*p++ != ' ')
                        die("Missing space after SHA1: %s", command_buf.buf);
        }
 -      assert(*p == ' ');
 -      p++;  /* skip space */
  
        strbuf_reset(&uq);
        if (!unquote_c_style(&uq, p, &endp)) {
        }
  
        /* Git does not track empty, non-toplevel directories. */
 -      if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) {
 +      if (S_ISDIR(mode) && !hashcmp(sha1, EMPTY_TREE_SHA1_BIN) && *p) {
                tree_content_remove(&b->branch_tree, p, NULL, 0);
                return;
        }
        tree_content_set(&b->branch_tree, p, sha1, mode, NULL);
  }
  
 -static void file_change_d(struct branch *b)
 +static void file_change_d(const char *p, struct branch *b)
  {
 -      const char *p = command_buf.buf + 2;
        static struct strbuf uq = STRBUF_INIT;
        const char *endp;
  
        tree_content_remove(&b->branch_tree, p, NULL, 1);
  }
  
 -static void file_change_cr(struct branch *b, int rename)
 +static void file_change_cr(const char *s, struct branch *b, int rename)
  {
 -      const char *s, *d;
 +      const char *d;
        static struct strbuf s_uq = STRBUF_INIT;
        static struct strbuf d_uq = STRBUF_INIT;
        const char *endp;
        struct tree_entry leaf;
  
 -      s = command_buf.buf + 2;
        strbuf_reset(&s_uq);
        if (!unquote_c_style(&s_uq, s, &endp)) {
                if (*endp != ' ')
                leaf.tree);
  }
  
 -static void note_change_n(struct branch *b, unsigned char *old_fanout)
 +static void note_change_n(const char *p, struct branch *b, unsigned char *old_fanout)
  {
 -      const char *p = command_buf.buf + 2;
        static struct strbuf uq = STRBUF_INIT;
        struct object_entry *oe;
        struct branch *s;
        if (*p == ':') {
                oe = find_mark(parse_mark_ref_space(&p));
                hashcpy(sha1, oe->idx.sha1);
 -      } else if (starts_with(p, "inline ")) {
 +      } else if (skip_prefix(p, "inline ", &p)) {
                inline_data = 1;
                oe = NULL; /* not used with inline_data, but makes gcc happy */
 -              p += strlen("inline");  /* advance to space */
        } else {
                if (get_sha1_hex(p, sha1))
                        die("Invalid dataref: %s", command_buf.buf);
                oe = find_object(sha1);
                p += 40;
 -              if (*p != ' ')
 +              if (*p++ != ' ')
                        die("Missing space after SHA1: %s", command_buf.buf);
        }
 -      assert(*p == ' ');
 -      p++;  /* skip space */
  
        /* <commit-ish> */
        s = lookup_branch(p);
@@@ -2579,7 -2607,7 +2601,7 @@@ static int parse_from(struct branch *b
        const char *from;
        struct branch *s;
  
 -      if (!starts_with(command_buf.buf, "from "))
 +      if (!skip_prefix(command_buf.buf, "from ", &from))
                return 0;
  
        if (b->branch_tree.tree) {
                b->branch_tree.tree = NULL;
        }
  
 -      from = strchr(command_buf.buf, ' ') + 1;
        s = lookup_branch(from);
        if (b == s)
                die("Can't create a branch from itself: %s", b->name);
@@@ -2627,7 -2656,8 +2649,7 @@@ static struct hash_list *parse_merge(un
        struct branch *s;
  
        *count = 0;
 -      while (starts_with(command_buf.buf, "merge ")) {
 -              from = strchr(command_buf.buf, ' ') + 1;
 +      while (skip_prefix(command_buf.buf, "merge ", &from)) {
                n = xmalloc(sizeof(*n));
                s = lookup_branch(from);
                if (s)
        return list;
  }
  
 -static void parse_new_commit(void)
 +static void parse_new_commit(const char *arg)
  {
        static struct strbuf msg = STRBUF_INIT;
        struct branch *b;
 -      char *sp;
        char *author = NULL;
        char *committer = NULL;
        struct hash_list *merge_list = NULL;
        unsigned int merge_count;
        unsigned char prev_fanout, new_fanout;
 +      const char *v;
  
 -      /* Obtain the branch name from the rest of our command */
 -      sp = strchr(command_buf.buf, ' ') + 1;
 -      b = lookup_branch(sp);
 +      b = lookup_branch(arg);
        if (!b)
 -              b = new_branch(sp);
 +              b = new_branch(arg);
  
        read_next_command();
        parse_mark();
 -      if (starts_with(command_buf.buf, "author ")) {
 -              author = parse_ident(command_buf.buf + 7);
 +      if (skip_prefix(command_buf.buf, "author ", &v)) {
 +              author = parse_ident(v);
                read_next_command();
        }
 -      if (starts_with(command_buf.buf, "committer ")) {
 -              committer = parse_ident(command_buf.buf + 10);
 +      if (skip_prefix(command_buf.buf, "committer ", &v)) {
 +              committer = parse_ident(v);
                read_next_command();
        }
        if (!committer)
  
        /* file_change* */
        while (command_buf.len > 0) {
 -              if (starts_with(command_buf.buf, "M "))
 -                      file_change_m(b);
 -              else if (starts_with(command_buf.buf, "D "))
 -                      file_change_d(b);
 -              else if (starts_with(command_buf.buf, "R "))
 -                      file_change_cr(b, 1);
 -              else if (starts_with(command_buf.buf, "C "))
 -                      file_change_cr(b, 0);
 -              else if (starts_with(command_buf.buf, "N "))
 -                      note_change_n(b, &prev_fanout);
 +              if (skip_prefix(command_buf.buf, "M ", &v))
 +                      file_change_m(v, b);
 +              else if (skip_prefix(command_buf.buf, "D ", &v))
 +                      file_change_d(v, b);
 +              else if (skip_prefix(command_buf.buf, "R ", &v))
 +                      file_change_cr(v, b, 1);
 +              else if (skip_prefix(command_buf.buf, "C ", &v))
 +                      file_change_cr(v, b, 0);
 +              else if (skip_prefix(command_buf.buf, "N ", &v))
 +                      note_change_n(v, b, &prev_fanout);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
 -              else if (starts_with(command_buf.buf, "ls "))
 -                      parse_ls(b);
 +              else if (skip_prefix(command_buf.buf, "ls ", &v))
 +                      parse_ls(v, b);
                else {
                        unread_command_buf = 1;
                        break;
        b->last_commit = object_count_by_type[OBJ_COMMIT];
  }
  
 -static void parse_new_tag(void)
 +static void parse_new_tag(const char *arg)
  {
        static struct strbuf msg = STRBUF_INIT;
 -      char *sp;
        const char *from;
        char *tagger;
        struct branch *s;
        uintmax_t from_mark = 0;
        unsigned char sha1[20];
        enum object_type type;
 +      const char *v;
  
 -      /* Obtain the new tag name from the rest of our command */
 -      sp = strchr(command_buf.buf, ' ') + 1;
        t = pool_alloc(sizeof(struct tag));
        memset(t, 0, sizeof(struct tag));
 -      t->name = pool_strdup(sp);
 +      t->name = pool_strdup(arg);
        if (last_tag)
                last_tag->next_tag = t;
        else
        read_next_command();
  
        /* from ... */
 -      if (!starts_with(command_buf.buf, "from "))
 +      if (!skip_prefix(command_buf.buf, "from ", &from))
                die("Expected from command, got %s", command_buf.buf);
 -      from = strchr(command_buf.buf, ' ') + 1;
        s = lookup_branch(from);
        if (s) {
                if (is_null_sha1(s->sha1))
        read_next_command();
  
        /* tagger ... */
 -      if (starts_with(command_buf.buf, "tagger ")) {
 -              tagger = parse_ident(command_buf.buf + 7);
 +      if (skip_prefix(command_buf.buf, "tagger ", &v)) {
 +              tagger = parse_ident(v);
                read_next_command();
        } else
                tagger = NULL;
                t->pack_id = pack_id;
  }
  
 -static void parse_reset_branch(void)
 +static void parse_reset_branch(const char *arg)
  {
        struct branch *b;
 -      char *sp;
  
 -      /* Obtain the branch name from the rest of our command */
 -      sp = strchr(command_buf.buf, ' ') + 1;
 -      b = lookup_branch(sp);
 +      b = lookup_branch(arg);
        if (b) {
                hashclr(b->sha1);
                hashclr(b->branch_tree.versions[0].sha1);
                }
        }
        else
 -              b = new_branch(sp);
 +              b = new_branch(arg);
        read_next_command();
        parse_from(b);
        if (command_buf.len > 0)
@@@ -2909,12 -2947,14 +2931,12 @@@ static void cat_blob(struct object_entr
                free(buf);
  }
  
 -static void parse_cat_blob(void)
 +static void parse_cat_blob(const char *p)
  {
 -      const char *p;
        struct object_entry *oe = oe;
        unsigned char sha1[20];
  
        /* cat-blob SP <object> LF */
 -      p = command_buf.buf + strlen("cat-blob ");
        if (*p == ':') {
                oe = find_mark(parse_mark_ref_eol(p));
                if (!oe)
@@@ -2997,8 -3037,6 +3019,8 @@@ static struct object_entry *parse_treei
                        die("Invalid dataref: %s", command_buf.buf);
                e = find_object(sha1);
                *p += 40;
 +              if (*(*p)++ != ' ')
 +                      die("Missing space after tree-ish: %s", command_buf.buf);
        }
  
        while (!e || e->type != OBJ_TREE)
@@@ -3033,12 -3071,14 +3055,12 @@@ static void print_ls(int mode, const un
        cat_blob_write(line.buf, line.len);
  }
  
 -static void parse_ls(struct branch *b)
 +static void parse_ls(const char *p, struct branch *b)
  {
 -      const char *p;
        struct tree_entry *root = NULL;
        struct tree_entry leaf = {NULL};
  
        /* ls SP (<tree-ish> SP)? <path> */
 -      p = command_buf.buf + strlen("ls ");
        if (*p == '"') {
                if (!b)
                        die("Not in a commit: %s", command_buf.buf);
                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);
        }
        if (*p == '"') {
                static struct strbuf uq = STRBUF_INIT;
@@@ -3187,9 -3229,9 +3209,9 @@@ static void option_export_pack_edges(co
  
  static int parse_one_option(const char *option)
  {
 -      if (starts_with(option, "max-pack-size=")) {
 +      if (skip_prefix(option, "max-pack-size=", &option)) {
                unsigned long v;
 -              if (!git_parse_ulong(option + 14, &v))
 +              if (!git_parse_ulong(option, &v))
                        return 0;
                if (v < 8192) {
                        warning("max-pack-size is now in bytes, assuming --max-pack-size=%lum", v);
                        v = 1024 * 1024;
                }
                max_packsize = v;
 -      } else if (starts_with(option, "big-file-threshold=")) {
 +      } else if (skip_prefix(option, "big-file-threshold=", &option)) {
                unsigned long v;
 -              if (!git_parse_ulong(option + 19, &v))
 +              if (!git_parse_ulong(option, &v))
                        return 0;
                big_file_threshold = v;
 -      } else if (starts_with(option, "depth=")) {
 -              option_depth(option + 6);
 -      } else if (starts_with(option, "active-branches=")) {
 -              option_active_branches(option + 16);
 -      } else if (starts_with(option, "export-pack-edges=")) {
 -              option_export_pack_edges(option + 18);
 +      } else if (skip_prefix(option, "depth=", &option)) {
 +              option_depth(option);
 +      } else if (skip_prefix(option, "active-branches=", &option)) {
 +              option_active_branches(option);
 +      } else if (skip_prefix(option, "export-pack-edges=", &option)) {
 +              option_export_pack_edges(option);
        } else if (starts_with(option, "quiet")) {
                show_stats = 0;
        } else if (starts_with(option, "stats")) {
  
  static int parse_one_feature(const char *feature, int from_stream)
  {
 -      if (starts_with(feature, "date-format=")) {
 -              option_date_format(feature + 12);
 -      } else if (starts_with(feature, "import-marks=")) {
 -              option_import_marks(feature + 13, from_stream, 0);
 -      } else if (starts_with(feature, "import-marks-if-exists=")) {
 -              option_import_marks(feature + strlen("import-marks-if-exists="),
 -                                      from_stream, 1);
 -      } else if (starts_with(feature, "export-marks=")) {
 -              option_export_marks(feature + 13);
 +      const char *arg;
 +
 +      if (skip_prefix(feature, "date-format=", &arg)) {
 +              option_date_format(arg);
 +      } else if (skip_prefix(feature, "import-marks=", &arg)) {
 +              option_import_marks(arg, from_stream, 0);
 +      } else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) {
 +              option_import_marks(arg, from_stream, 1);
 +      } else if (skip_prefix(feature, "export-marks=", &arg)) {
 +              option_export_marks(arg);
        } else if (!strcmp(feature, "cat-blob")) {
                ; /* Don't die - this feature is supported */
        } else if (!strcmp(feature, "relative-marks")) {
        return 1;
  }
  
 -static void parse_feature(void)
 +static void parse_feature(const char *feature)
  {
 -      char *feature = command_buf.buf + 8;
 -
        if (seen_data_command)
                die("Got feature command '%s' after data command", feature);
  
        die("This version of fast-import does not support feature %s.", feature);
  }
  
 -static void parse_option(void)
 +static void parse_option(const char *option)
  {
 -      char *option = command_buf.buf + 11;
 -
        if (seen_data_command)
                die("Got option command '%s' after data command", option);
  
        die("This version of fast-import does not support option: %s", option);
  }
  
 -static int git_pack_config(const char *k, const char *v, void *cb)
 +static void git_pack_config(void)
  {
 -      if (!strcmp(k, "pack.depth")) {
 -              max_depth = git_config_int(k, v);
 +      int indexversion_value;
 +      unsigned long packsizelimit_value;
 +
 +      if (!git_config_get_ulong("pack.depth", &max_depth)) {
                if (max_depth > MAX_DEPTH)
                        max_depth = MAX_DEPTH;
 -              return 0;
        }
 -      if (!strcmp(k, "pack.compression")) {
 -              int level = git_config_int(k, v);
 -              if (level == -1)
 -                      level = Z_DEFAULT_COMPRESSION;
 -              else if (level < 0 || level > Z_BEST_COMPRESSION)
 -                      die("bad pack compression level %d", level);
 -              pack_compression_level = level;
 +      if (!git_config_get_int("pack.compression", &pack_compression_level)) {
 +              if (pack_compression_level == -1)
 +                      pack_compression_level = Z_DEFAULT_COMPRESSION;
 +              else if (pack_compression_level < 0 ||
 +                       pack_compression_level > Z_BEST_COMPRESSION)
 +                      git_die_config("pack.compression",
 +                                      "bad pack compression level %d", pack_compression_level);
                pack_compression_seen = 1;
 -              return 0;
        }
 -      if (!strcmp(k, "pack.indexversion")) {
 -              pack_idx_opts.version = git_config_int(k, v);
 +      if (!git_config_get_int("pack.indexversion", &indexversion_value)) {
 +              pack_idx_opts.version = indexversion_value;
                if (pack_idx_opts.version > 2)
 -                      die("bad pack.indexversion=%"PRIu32,
 -                          pack_idx_opts.version);
 -              return 0;
 +                      git_die_config("pack.indexversion",
 +                                      "bad pack.indexversion=%"PRIu32, pack_idx_opts.version);
        }
 -      if (!strcmp(k, "pack.packsizelimit")) {
 -              max_packsize = git_config_ulong(k, v);
 -              return 0;
 -      }
 -      return git_default_config(k, v, cb);
 +      if (!git_config_get_ulong("pack.packsizelimit", &packsizelimit_value))
 +              max_packsize = packsizelimit_value;
 +
 +      git_config(git_default_config, NULL);
  }
  
  static const char fast_import_usage[] =
@@@ -3317,21 -3364,18 +3339,21 @@@ static void parse_argv(void
                if (*a != '-' || !strcmp(a, "--"))
                        break;
  
 -              if (parse_one_option(a + 2))
 +              if (!skip_prefix(a, "--", &a))
 +                      die("unknown option %s", a);
 +
 +              if (parse_one_option(a))
                        continue;
  
 -              if (parse_one_feature(a + 2, 0))
 +              if (parse_one_feature(a, 0))
                        continue;
  
 -              if (starts_with(a + 2, "cat-blob-fd=")) {
 -                      option_cat_blob_fd(a + 2 + strlen("cat-blob-fd="));
 +              if (skip_prefix(a, "cat-blob-fd=", &a)) {
 +                      option_cat_blob_fd(a);
                        continue;
                }
  
 -              die("unknown option %s", a);
 +              die("unknown option --%s", a);
        }
        if (i != global_argc)
                usage(fast_import_usage);
@@@ -3354,7 -3398,7 +3376,7 @@@ int main(int argc, char **argv
  
        setup_git_directory();
        reset_pack_idx_option(&pack_idx_opts);
 -      git_config(git_pack_config, NULL);
 +      git_pack_config();
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
  
        set_die_routine(die_nicely);
        set_checkpoint_signal();
        while (read_next_command() != EOF) {
 +              const char *v;
                if (!strcmp("blob", command_buf.buf))
                        parse_new_blob();
 -              else if (starts_with(command_buf.buf, "ls "))
 -                      parse_ls(NULL);
 -              else if (starts_with(command_buf.buf, "commit "))
 -                      parse_new_commit();
 -              else if (starts_with(command_buf.buf, "tag "))
 -                      parse_new_tag();
 -              else if (starts_with(command_buf.buf, "reset "))
 -                      parse_reset_branch();
 +              else if (skip_prefix(command_buf.buf, "ls ", &v))
 +                      parse_ls(v, NULL);
 +              else if (skip_prefix(command_buf.buf, "commit ", &v))
 +                      parse_new_commit(v);
 +              else if (skip_prefix(command_buf.buf, "tag ", &v))
 +                      parse_new_tag(v);
 +              else if (skip_prefix(command_buf.buf, "reset ", &v))
 +                      parse_reset_branch(v);
                else if (!strcmp("checkpoint", command_buf.buf))
                        parse_checkpoint();
                else if (!strcmp("done", command_buf.buf))
                        break;
                else if (starts_with(command_buf.buf, "progress "))
                        parse_progress();
 -              else if (starts_with(command_buf.buf, "feature "))
 -                      parse_feature();
 -              else if (starts_with(command_buf.buf, "option git "))
 -                      parse_option();
 +              else if (skip_prefix(command_buf.buf, "feature ", &v))
 +                      parse_feature(v);
 +              else if (skip_prefix(command_buf.buf, "option git ", &v))
 +                      parse_option(v);
                else if (starts_with(command_buf.buf, "option "))
                        /* ignore non-git options*/;
                else
diff --combined refs.c
index 27927f2319130cc0575817542dfd47c37cc5149b,723557485d9f1ee76482fe3eae3e45b613de2788..808e261c235b9803a80faefd79a6329cebcf5fc1
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -24,6 -24,11 +24,11 @@@ static unsigned char refname_dispositio
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
  };
  
+ /*
+  * Used as a flag to ref_transaction_delete when a loose ref is being
+  * pruned.
+  */
+ #define REF_ISPRUNING 0x0100
  /*
   * Try to read one refname component from the front of refname.
   * Return the length of the component found, or -1 if the component is
@@@ -1162,7 -1167,7 +1167,7 @@@ static void read_loose_refs(const char 
  
                if (de->d_name[0] == '.')
                        continue;
 -              if (has_extension(de->d_name, ".lock"))
 +              if (ends_with(de->d_name, ".lock"))
                        continue;
                strbuf_addstr(&refname, de->d_name);
                refdir = *refs->name
@@@ -1542,8 -1547,9 +1547,8 @@@ static enum peel_status peel_object(con
  
        if (o->type == OBJ_NONE) {
                int type = sha1_object_info(name, NULL);
 -              if (type < 0)
 +              if (type < 0 || !object_as_type(o, type, 0))
                        return PEEL_INVALID;
 -              o->type = type;
        }
  
        if (o->type != OBJ_TAG)
@@@ -2068,7 -2074,10 +2073,10 @@@ int dwim_log(const char *str, int len, 
        return logs_found;
  }
  
- /* This function should make sure errno is meaningful on error */
+ /*
+  * Locks a "refs/" ref returning the lock on success and NULL on failure.
+  * On failure errno is set to something meaningful.
+  */
  static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                                            const unsigned char *old_sha1,
                                            int flags, int *type_p)
        return NULL;
  }
  
- struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
- {
-       char refpath[PATH_MAX];
-       if (check_refname_format(refname, 0))
-               return NULL;
-       strcpy(refpath, mkpath("refs/%s", refname));
-       return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
- }
  struct ref_lock *lock_any_ref_for_update(const char *refname,
                                         const unsigned char *old_sha1,
                                         int flags, int *type_p)
@@@ -2387,13 -2387,25 +2386,25 @@@ static void try_remove_empty_parents(ch
  /* make sure nobody touched the ref, and unlink */
  static void prune_ref(struct ref_to_prune *r)
  {
-       struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
  
-       if (lock) {
-               unlink_or_warn(git_path("%s", r->name));
-               unlock_ref(lock);
-               try_remove_empty_parents(r->name);
+       if (check_refname_format(r->name + 5, 0))
+               return;
+       transaction = ref_transaction_begin(&err);
+       if (!transaction ||
+           ref_transaction_delete(transaction, r->name, r->sha1,
+                                  REF_ISPRUNING, 1, &err) ||
+           ref_transaction_commit(transaction, NULL, &err)) {
+               ref_transaction_free(transaction);
+               error("%s", err.buf);
+               strbuf_release(&err);
+               return;
        }
+       ref_transaction_free(transaction);
+       strbuf_release(&err);
+       try_remove_empty_parents(r->name);
  }
  
  static void prune_refs(struct ref_to_prune *r)
@@@ -2536,11 -2548,6 +2547,6 @@@ int repack_without_refs(const char **re
        return ret;
  }
  
- static int repack_without_ref(const char *refname)
- {
-       return repack_without_refs(&refname, 1, NULL);
- }
  static int delete_ref_loose(struct ref_lock *lock, int flag)
  {
        if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
  
  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)
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
+       transaction = ref_transaction_begin(&err);
+       if (!transaction ||
+           ref_transaction_delete(transaction, refname, sha1, delopt,
+                                  sha1 && !is_null_sha1(sha1), &err) ||
+           ref_transaction_commit(transaction, NULL, &err)) {
+               error("%s", err.buf);
+               ref_transaction_free(transaction);
+               strbuf_release(&err);
                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.
-        */
-       ret |= repack_without_ref(lock->ref_name);
-       unlink_or_warn(git_path("logs/%s", lock->ref_name));
-       clear_loose_ref_cache(&ref_cache);
-       unlock_ref(lock);
-       return ret;
+       }
+       ref_transaction_free(transaction);
+       strbuf_release(&err);
+       return 0;
  }
  
  /*
@@@ -2874,7 -2879,7 +2878,7 @@@ static int log_ref_write(const char *re
        return 0;
  }
  
 -static int is_branch(const char *refname)
 +int is_branch(const char *refname)
  {
        return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
  }
@@@ -3297,7 -3302,7 +3301,7 @@@ static int do_for_each_reflog(struct st
  
                if (de->d_name[0] == '.')
                        continue;
 -              if (has_extension(de->d_name, ".lock"))
 +              if (ends_with(de->d_name, ".lock"))
                        continue;
                strbuf_addstr(name, de->d_name);
                if (stat(git_path("logs/%s", name->buf), &st) < 0) {
@@@ -3332,43 -3337,6 +3336,6 @@@ int for_each_reflog(each_ref_fn fn, voi
        return retval;
  }
  
- static struct ref_lock *update_ref_lock(const char *refname,
-                                       const unsigned char *oldval,
-                                       int flags, int *type_p,
-                                       enum action_on_err onerr)
- {
-       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 UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
-               case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
-               case UPDATE_REFS_QUIET_ON_ERR: break;
-               }
-       }
-       return lock;
- }
- static int update_ref_write(const char *action, const char *refname,
-                           const unsigned char *sha1, struct ref_lock *lock,
-                           struct strbuf *err, enum action_on_err onerr)
- {
-       if (write_ref_sha1(lock, sha1, action) < 0) {
-               const char *str = "Cannot update the ref '%s'.";
-               if (err)
-                       strbuf_addf(err, str, refname);
-               switch (onerr) {
-               case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
-               case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
-               case UPDATE_REFS_QUIET_ON_ERR: break;
-               }
-               return 1;
-       }
-       return 0;
- }
  /**
   * Information needed for a single ref update.  Set new_sha1 to the
   * new value or to zero to delete the ref.  To check the old value
@@@ -3385,6 -3353,21 +3352,21 @@@ struct ref_update 
        const char refname[FLEX_ARRAY];
  };
  
+ /*
+  * Transaction states.
+  * OPEN:   The transaction is in a valid state and can accept new updates.
+  *         An OPEN transaction can be committed.
+  * CLOSED: A closed transaction is no longer active and no other operations
+  *         than free can be used on it in this state.
+  *         A transaction can either become closed by successfully committing
+  *         an active transaction or if there is a failure while building
+  *         the transaction thus rendering it failed/inactive.
+  */
+ enum ref_transaction_state {
+       REF_TRANSACTION_OPEN   = 0,
+       REF_TRANSACTION_CLOSED = 1
+ };
  /*
   * Data structure for holding a reference transaction, which can
   * consist of checks and updates to multiple references, carried out
@@@ -3394,9 -3377,10 +3376,10 @@@ struct ref_transaction 
        struct ref_update **updates;
        size_t alloc;
        size_t nr;
+       enum ref_transaction_state state;
  };
  
- struct ref_transaction *ref_transaction_begin(void)
+ struct ref_transaction *ref_transaction_begin(struct strbuf *err)
  {
        return xcalloc(1, sizeof(struct ref_transaction));
  }
@@@ -3436,6 -3420,9 +3419,9 @@@ int ref_transaction_update(struct ref_t
  {
        struct ref_update *update;
  
+       if (transaction->state != REF_TRANSACTION_OPEN)
+               die("BUG: update called for transaction that is not open");
        if (have_old && !old_sha1)
                die("BUG: have_old is true but old_sha1 is NULL");
  
        return 0;
  }
  
- void ref_transaction_create(struct ref_transaction *transaction,
-                           const char *refname,
-                           const unsigned char *new_sha1,
-                           int flags)
+ int ref_transaction_create(struct ref_transaction *transaction,
+                          const char *refname,
+                          const unsigned char *new_sha1,
+                          int flags,
+                          struct strbuf *err)
  {
-       struct ref_update *update = add_update(transaction, refname);
+       struct ref_update *update;
+       if (transaction->state != REF_TRANSACTION_OPEN)
+               die("BUG: create called for transaction that is not open");
+       if (!new_sha1 || is_null_sha1(new_sha1))
+               die("BUG: create ref with null new_sha1");
+       update = add_update(transaction, refname);
  
-       assert(!is_null_sha1(new_sha1));
        hashcpy(update->new_sha1, new_sha1);
        hashclr(update->old_sha1);
        update->flags = flags;
        update->have_old = 1;
+       return 0;
  }
  
- void ref_transaction_delete(struct ref_transaction *transaction,
-                           const char *refname,
-                           const unsigned char *old_sha1,
-                           int flags, int have_old)
+ int ref_transaction_delete(struct ref_transaction *transaction,
+                          const char *refname,
+                          const unsigned char *old_sha1,
+                          int flags, int have_old,
+                          struct strbuf *err)
  {
-       struct ref_update *update = add_update(transaction, refname);
+       struct ref_update *update;
  
+       if (transaction->state != REF_TRANSACTION_OPEN)
+               die("BUG: delete called for transaction that is not open");
+       if (have_old && !old_sha1)
+               die("BUG: have_old is true but old_sha1 is NULL");
+       update = add_update(transaction, refname);
        update->flags = flags;
        update->have_old = have_old;
        if (have_old) {
                assert(!is_null_sha1(old_sha1));
                hashcpy(update->old_sha1, old_sha1);
        }
+       return 0;
  }
  
  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, NULL, onerr);
-       if (!lock)
+       struct ref_transaction *t;
+       struct strbuf err = STRBUF_INIT;
+       t = ref_transaction_begin(&err);
+       if (!t ||
+           ref_transaction_update(t, refname, sha1, oldval, flags,
+                                  !!oldval, &err) ||
+           ref_transaction_commit(t, action, &err)) {
+               const char *str = "update_ref failed for ref '%s': %s";
+               ref_transaction_free(t);
+               switch (onerr) {
+               case UPDATE_REFS_MSG_ON_ERR:
+                       error(str, refname, err.buf);
+                       break;
+               case UPDATE_REFS_DIE_ON_ERR:
+                       die(str, refname, err.buf);
+                       break;
+               case UPDATE_REFS_QUIET_ON_ERR:
+                       break;
+               }
+               strbuf_release(&err);
                return 1;
-       return update_ref_write(action, refname, sha1, lock, NULL, onerr);
+       }
+       strbuf_release(&err);
+       ref_transaction_free(t);
+       return 0;
  }
  
  static int ref_update_compare(const void *r1, const void *r2)
@@@ -3519,8 -3546,13 +3545,13 @@@ int ref_transaction_commit(struct ref_t
        int n = transaction->nr;
        struct ref_update **updates = transaction->updates;
  
-       if (!n)
+       if (transaction->state != REF_TRANSACTION_OPEN)
+               die("BUG: commit called for transaction that is not open");
+       if (!n) {
+               transaction->state = REF_TRANSACTION_CLOSED;
                return 0;
+       }
  
        /* Allocate work space */
        delnames = xmalloc(sizeof(*delnames) * n);
        for (i = 0; i < n; i++) {
                struct ref_update *update = updates[i];
  
-               update->lock = update_ref_lock(update->refname,
-                                              (update->have_old ?
-                                               update->old_sha1 : NULL),
-                                              update->flags,
-                                              &update->type,
-                                              UPDATE_REFS_QUIET_ON_ERR);
+               update->lock = lock_any_ref_for_update(update->refname,
+                                                      (update->have_old ?
+                                                       update->old_sha1 :
+                                                       NULL),
+                                                      update->flags,
+                                                      &update->type);
                if (!update->lock) {
                        if (err)
                                strbuf_addf(err, "Cannot lock the ref '%s'.",
                struct ref_update *update = updates[i];
  
                if (!is_null_sha1(update->new_sha1)) {
-                       ret = update_ref_write(msg,
-                                              update->refname,
-                                              update->new_sha1,
-                                              update->lock, err,
-                                              UPDATE_REFS_QUIET_ON_ERR);
-                       update->lock = NULL; /* freed by update_ref_write */
-                       if (ret)
+                       ret = write_ref_sha1(update->lock, update->new_sha1,
+                                            msg);
+                       update->lock = NULL; /* freed by write_ref_sha1 */
+                       if (ret) {
+                               if (err)
+                                       strbuf_addf(err, "Cannot update the ref '%s'.",
+                                                   update->refname);
                                goto cleanup;
+                       }
                }
        }
  
                struct ref_update *update = updates[i];
  
                if (update->lock) {
-                       delnames[delnum++] = update->lock->ref_name;
                        ret |= delete_ref_loose(update->lock, update->type);
+                       if (!(update->flags & REF_ISPRUNING))
+                               delnames[delnum++] = update->lock->ref_name;
                }
        }
  
        clear_loose_ref_cache(&ref_cache);
  
  cleanup:
+       transaction->state = REF_TRANSACTION_CLOSED;
        for (i = 0; i < n; i++)
                if (updates[i]->lock)
                        unlock_ref(updates[i]->lock);
diff --combined refs.h
index ec46acdde7b67d3531a4c0e18d33901d2a4f307a,69ef28c80f3732d28d03b6a717ab7d346151d238..68c57701646f05a44dd0da95d981787653ae032d
--- 1/refs.h
--- 2/refs.h
+++ b/refs.h
@@@ -10,6 -10,38 +10,38 @@@ struct ref_lock 
        int force_write;
  };
  
+ /*
+  * A ref_transaction represents a collection of ref updates
+  * that should succeed or fail together.
+  *
+  * Calling sequence
+  * ----------------
+  * - Allocate and initialize a `struct ref_transaction` by calling
+  *   `ref_transaction_begin()`.
+  *
+  * - List intended ref updates by calling functions like
+  *   `ref_transaction_update()` and `ref_transaction_create()`.
+  *
+  * - Call `ref_transaction_commit()` to execute the transaction.
+  *   If this succeeds, the ref updates will have taken place and
+  *   the transaction cannot be rolled back.
+  *
+  * - At any time call `ref_transaction_free()` to discard the
+  *   transaction and free associated resources.  In particular,
+  *   this rolls back the transaction if it has not been
+  *   successfully committed.
+  *
+  * Error handling
+  * --------------
+  *
+  * On error, transaction functions append a message about what
+  * went wrong to the 'err' argument.  The message mentions what
+  * ref was being updated (if any) when the error occurred so it
+  * can be passed to 'die' or 'error' as-is.
+  *
+  * The message is appended to err without first clearing err.
+  * err will not be '\n' terminated.
+  */
  struct ref_transaction;
  
  /*
@@@ -128,8 -160,6 +160,8 @@@ extern int repack_without_refs(const ch
  
  extern int ref_exists(const char *);
  
 +extern int is_branch(const char *refname);
 +
  /*
   * If refname is a non-symbolic reference that refers to a tag object,
   * and the tag can be (recursively) dereferenced to a non-tag object,
  extern int peel_ref(const char *refname, unsigned char *sha1);
  
  /*
-  * Locks a "refs/" ref returning the lock on success and NULL on failure.
-  * On failure errno is set to something meaningful.
+  * Flags controlling lock_any_ref_for_update(), ref_transaction_update(),
+  * ref_transaction_create(), etc.
+  * REF_NODEREF: act on the ref directly, instead of dereferencing
+  *              symbolic references.
+  *
+  * Flags >= 0x100 are reserved for internal use.
   */
- extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);
- /** Locks any ref (for 'HEAD' type refs). */
  #define REF_NODEREF   0x01
- /* errno is set to something meaningful on failure */
+ /*
+  * This function sets errno to something meaningful on failure.
+  */
  extern struct ref_lock *lock_any_ref_for_update(const char *refname,
                                                const unsigned char *old_sha1,
                                                int flags, int *type_p);
@@@ -232,7 -265,7 +267,7 @@@ enum action_on_err 
   * Begin a reference transaction.  The reference transaction must
   * be freed by calling ref_transaction_free().
   */
- struct ref_transaction *ref_transaction_begin(void);
+ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
  
  /*
   * The following functions add a reference check or update to a
   * it must not have existed beforehand.
   * Function returns 0 on success and non-zero on failure. A failure to update
   * means that the transaction as a whole has failed and will need to be
-  * rolled back. On failure the err buffer will be updated.
+  * rolled back.
   */
  int ref_transaction_update(struct ref_transaction *transaction,
                           const char *refname,
   * that the reference should have after the update; it must not be the
   * null SHA-1.  It is verified that the reference does not exist
   * already.
+  * Function returns 0 on success and non-zero on failure. A failure to create
+  * means that the transaction as a whole has failed and will need to be
+  * rolled back.
   */
- void ref_transaction_create(struct ref_transaction *transaction,
-                           const char *refname,
-                           const unsigned char *new_sha1,
-                           int flags);
+ int ref_transaction_create(struct ref_transaction *transaction,
+                          const char *refname,
+                          const unsigned char *new_sha1,
+                          int flags,
+                          struct strbuf *err);
  
  /*
   * Add a reference deletion to transaction.  If have_old is true, then
   * old_sha1 holds the value that the reference should have had before
   * the update (which must not be the null SHA-1).
+  * Function returns 0 on success and non-zero on failure. A failure to delete
+  * means that the transaction as a whole has failed and will need to be
+  * rolled back.
   */
- void ref_transaction_delete(struct ref_transaction *transaction,
-                           const char *refname,
-                           const unsigned char *old_sha1,
-                           int flags, int have_old);
+ int ref_transaction_delete(struct ref_transaction *transaction,
+                          const char *refname,
+                          const unsigned char *old_sha1,
+                          int flags, int have_old,
+                          struct strbuf *err);
  
  /*
   * Commit all of the changes that have been queued in transaction, as
   * atomically as possible.  Return a nonzero value if there is a
   * problem.
-  * If err is non-NULL we will add an error string to it to explain why
-  * the transaction failed. The string does not end in newline.
   */
  int ref_transaction_commit(struct ref_transaction *transaction,
                           const char *msg, struct strbuf *err);
diff --combined sequencer.c
index 3c060e054720b7b17f9a868cabb1a0ef62aa1b50,5e93b6ab5fdf2f5af4233986137d15351f7a741b..5e8a207474bc6971a0de4f9ff6160a5681ac7b6d
@@@ -116,23 -116,39 +116,23 @@@ static const char *action_name(const st
        return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
  }
  
 -static char *get_encoding(const char *message);
 -
  struct commit_message {
        char *parent_label;
        const char *label;
        const char *subject;
 -      char *reencoded_message;
        const char *message;
  };
  
  static int get_message(struct commit *commit, struct commit_message *out)
  {
 -      const char *encoding;
        const char *abbrev, *subject;
        int abbrev_len, subject_len;
        char *q;
  
 -      if (!commit->buffer)
 -              return -1;
 -      encoding = get_encoding(commit->buffer);
 -      if (!encoding)
 -              encoding = "UTF-8";
        if (!git_commit_encoding)
                git_commit_encoding = "UTF-8";
  
 -      out->reencoded_message = NULL;
 -      out->message = commit->buffer;
 -      if (same_encoding(encoding, git_commit_encoding))
 -              out->reencoded_message = reencode_string(commit->buffer,
 -                                      git_commit_encoding, encoding);
 -      if (out->reencoded_message)
 -              out->message = out->reencoded_message;
 -
 +      out->message = logmsg_reencode(commit, NULL, git_commit_encoding);
        abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
        abbrev_len = strlen(abbrev);
  
        return 0;
  }
  
 -static void free_message(struct commit_message *msg)
 +static void free_message(struct commit *commit, struct commit_message *msg)
  {
        free(msg->parent_label);
 -      free(msg->reencoded_message);
 -}
 -
 -static char *get_encoding(const char *message)
 -{
 -      const char *p = message, *eol;
 -
 -      while (*p && *p != '\n') {
 -              for (eol = p + 1; *eol && *eol != '\n'; eol++)
 -                      ; /* do nothing */
 -              if (starts_with(p, "encoding ")) {
 -                      char *result = xmalloc(eol - 8 - p);
 -                      strlcpy(result, p + 9, eol - 8 - p);
 -                      return result;
 -              }
 -              p = eol;
 -              if (*p == '\n')
 -                      p++;
 -      }
 -      return NULL;
 +      unuse_commit_buffer(commit, msg->message);
  }
  
  static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
@@@ -237,23 -272,33 +237,33 @@@ static int error_dirty_index(struct rep
  static int fast_forward_to(const unsigned char *to, const unsigned char *from,
                        int unborn, struct replay_opts *opts)
  {
-       struct ref_lock *ref_lock;
+       struct ref_transaction *transaction;
        struct strbuf sb = STRBUF_INIT;
-       int ret;
+       struct strbuf err = STRBUF_INIT;
  
        read_cache();
        if (checkout_fast_forward(from, to, 1))
 -              exit(1); /* the callee should have complained already */
 +              exit(128); /* the callee should have complained already */
-       ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
-                                          0, NULL);
-       if (!ref_lock)
-               return error(_("Failed to lock HEAD during fast_forward_to"));
  
        strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
-       ret = write_ref_sha1(ref_lock, to, sb.buf);
+       transaction = ref_transaction_begin(&err);
+       if (!transaction ||
+           ref_transaction_update(transaction, "HEAD",
+                                  to, unborn ? null_sha1 : from,
+                                  0, 1, &err) ||
+           ref_transaction_commit(transaction, sb.buf, &err)) {
+               ref_transaction_free(transaction);
+               error("%s", err.buf);
+               strbuf_release(&sb);
+               strbuf_release(&err);
+               return -1;
+       }
  
        strbuf_release(&sb);
-       return ret;
+       strbuf_release(&err);
+       ref_transaction_free(transaction);
+       return 0;
  }
  
  static int do_recursive_merge(struct commit *base, struct commit *next,
  {
        struct merge_options o;
        struct tree *result, *next_tree, *base_tree, *head_tree;
 -      int clean, index_fd;
 +      int clean;
        const char **xopt;
        static struct lock_file index_lock;
  
 -      index_fd = hold_locked_index(&index_lock, 1);
 +      hold_locked_index(&index_lock, 1);
  
        read_cache();
  
                            next_tree, base_tree, &result);
  
        if (active_cache_changed &&
 -          (write_cache(index_fd, active_cache, active_nr) ||
 -           commit_locked_index(&index_lock)))
 +          write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
                /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
                die(_("%s: Unable to write new index file"), action_name(opts));
        rollback_lock_file(&index_lock);
@@@ -340,7 -386,9 +350,7 @@@ static int is_index_unchanged(void
                active_cache_tree = cache_tree();
  
        if (!cache_tree_fully_valid(active_cache_tree))
 -              if (cache_tree_update(active_cache_tree,
 -                                    (const struct cache_entry * const *)active_cache,
 -                                    active_nr, 0))
 +              if (cache_tree_update(&the_index, 0))
                        return error(_("Unable to update cache tree\n"));
  
        return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
@@@ -358,13 -406,18 +368,13 @@@ static int run_git_commit(const char *d
  {
        struct argv_array array;
        int rc;
 -      char *gpg_sign;
  
        argv_array_init(&array);
        argv_array_push(&array, "commit");
        argv_array_push(&array, "-n");
  
 -      if (opts->gpg_sign) {
 -              gpg_sign = xmalloc(3 + strlen(opts->gpg_sign));
 -              sprintf(gpg_sign, "-S%s", opts->gpg_sign);
 -              argv_array_push(&array, gpg_sign);
 -              free(gpg_sign);
 -      }
 +      if (opts->gpg_sign)
 +              argv_array_pushf(&array, "-S%s", opts->gpg_sign);
        if (opts->signoff)
                argv_array_push(&array, "-s");
        if (!opts->edit) {
@@@ -446,7 -499,7 +456,7 @@@ static int do_pick_commit(struct commi
        unsigned char head[20];
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
 -      struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
 +      struct commit_message msg = { NULL, NULL, NULL, NULL };
        char *defmsg = NULL;
        struct strbuf msgbuf = STRBUF_INIT;
        int res, unborn = 0, allow;
                res = run_git_commit(defmsg, opts, allow);
  
  leave:
 -      free_message(&msg);
 +      free_message(commit, &msg);
        free(defmsg);
  
        return res;
@@@ -640,8 -693,9 +650,8 @@@ static void read_and_refresh_cache(stru
        if (read_index_preload(&the_index, NULL) < 0)
                die(_("git %s: failed to read the index"), action_name(opts));
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
 -      if (the_index.cache_changed) {
 -              if (write_index(&the_index, index_fd) ||
 -                  commit_locked_index(&index_lock))
 +      if (the_index.cache_changed && index_fd >= 0) {
 +              if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
                        die(_("git %s: failed to refresh the index"), action_name(opts));
        }
        rollback_lock_file(&index_lock);
@@@ -657,12 -711,10 +667,12 @@@ static int format_todo(struct strbuf *b
        int subject_len;
  
        for (cur = todo_list; cur; cur = cur->next) {
 +              const char *commit_buffer = get_commit_buffer(cur->item, NULL);
                sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
 -              subject_len = find_commit_subject(cur->item->buffer, &subject);
 +              subject_len = find_commit_subject(commit_buffer, &subject);
                strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
                        subject_len, subject);
 +              unuse_commit_buffer(cur->item, commit_buffer);
        }
        return 0;
  }
diff --combined walker.c
index 014826464e07c93374868c61673cfe308961dfb7,b8a5441d6a7cf1d3981ef17473f41a1984e73647..b929dcc6c043cc69b07846c16fb8aeb021aa3ae3
+++ b/walker.c
@@@ -251,64 -251,74 +251,73 @@@ void walker_targets_free(int targets, c
  int walker_fetch(struct walker *walker, int targets, char **target,
                 const char **write_ref, const char *write_ref_log_details)
  {
-       struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));
+       struct strbuf refname = STRBUF_INIT;
+       struct strbuf err = STRBUF_INIT;
+       struct ref_transaction *transaction = NULL;
        unsigned char *sha1 = xmalloc(targets * 20);
-       const char *msg;
-       char *to_free = NULL;
-       int ret;
-       int i;
+       char *msg = NULL;
+       int i, ret = -1;
  
        save_commit_buffer = 0;
  
-       for (i = 0; i < targets; i++) {
-               if (!write_ref || !write_ref[i])
-                       continue;
-               lock[i] = lock_ref_sha1(write_ref[i], NULL);
-               if (!lock[i]) {
-                       error("Can't lock ref %s", write_ref[i]);
-                       goto unlock_and_fail;
+       if (write_ref) {
+               transaction = ref_transaction_begin(&err);
+               if (!transaction) {
+                       error("%s", err.buf);
+                       goto done;
                }
        }
        if (!walker->get_recover)
                for_each_ref(mark_complete, NULL);
  
        for (i = 0; i < targets; i++) {
                if (interpret_target(walker, target[i], &sha1[20 * i])) {
                        error("Could not interpret response from server '%s' as something to pull", target[i]);
-                       goto unlock_and_fail;
+                       goto done;
                }
                if (process(walker, lookup_unknown_object(&sha1[20 * i])))
-                       goto unlock_and_fail;
+                       goto done;
        }
  
        if (loop(walker))
-               goto unlock_and_fail;
-       if (write_ref_log_details)
-               msg = to_free = xstrfmt("fetch from %s", write_ref_log_details);
-       else
-               msg = "fetch (unknown)";
+               goto done;
+       if (!write_ref) {
+               ret = 0;
+               goto done;
+       }
+       if (write_ref_log_details) {
 -              msg = xmalloc(strlen(write_ref_log_details) + 12);
 -              sprintf(msg, "fetch from %s", write_ref_log_details);
++              msg = xstrfmt("fetch from %s", write_ref_log_details);
+       } else {
+               msg = NULL;
+       }
        for (i = 0; i < targets; i++) {
-               if (!write_ref || !write_ref[i])
+               if (!write_ref[i])
                        continue;
-               ret = write_ref_sha1(lock[i], &sha1[20 * i], msg);
-               lock[i] = NULL;
-               if (ret)
-                       goto unlock_and_fail;
+               strbuf_reset(&refname);
+               strbuf_addf(&refname, "refs/%s", write_ref[i]);
+               if (ref_transaction_update(transaction, refname.buf,
+                                          &sha1[20 * i], NULL, 0, 0,
+                                          &err)) {
+                       error("%s", err.buf);
+                       goto done;
+               }
+       }
+       if (ref_transaction_commit(transaction,
+                                  msg ? msg : "fetch (unknown)",
+                                  &err)) {
+               error("%s", err.buf);
+               goto done;
        }
-       free(to_free);
-       return 0;
  
- unlock_and_fail:
-       for (i = 0; i < targets; i++)
-               if (lock[i])
-                       unlock_ref(lock[i]);
-       free(to_free);
+       ret = 0;
  
-       return -1;
+ done:
+       ref_transaction_free(transaction);
+       free(msg);
+       free(sha1);
+       strbuf_release(&err);
+       strbuf_release(&refname);
+       return ret;
  }
  
  void walker_free(struct walker *walker)