Code

Merge branch 'nd/war-on-nul-in-commit'
authorJunio C Hamano <gitster@pobox.com>
Thu, 22 Dec 2011 19:27:26 +0000 (11:27 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 22 Dec 2011 19:27:26 +0000 (11:27 -0800)
* nd/war-on-nul-in-commit:
  commit_tree(): refuse commit messages that contain NULs
  Convert commit_tree() to take strbuf as message
  merge: abort if fails to commit

Conflicts:
builtin/commit.c
commit.c
commit.h

1  2 
builtin/commit-tree.c
builtin/commit.c
builtin/merge.c
builtin/notes.c
commit.c
commit.h
notes-merge.c

diff --combined builtin/commit-tree.c
index b9c331225f0d5c960e9e03f2c92d498f8f01f74c,0895861457a2c90a0ddff9ab0864ef258e91cce5..05353c30f292684b44b42f7f744d8eb1b7062f69
@@@ -9,7 -9,7 +9,7 @@@
  #include "builtin.h"
  #include "utf8.h"
  
 -static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
 +static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-m <message>] [-F <file>] <sha1> <changelog";
  
  static void new_parent(struct commit *parent, struct commit_list **parents_p)
  {
@@@ -27,7 -27,7 +27,7 @@@
  
  int cmd_commit_tree(int argc, const char **argv, const char *prefix)
  {
 -      int i;
 +      int i, got_tree = 0;
        struct commit_list *parents = NULL;
        unsigned char tree_sha1[20];
        unsigned char commit_sha1[20];
  
        if (argc < 2 || !strcmp(argv[1], "-h"))
                usage(commit_tree_usage);
 -      if (get_sha1(argv[1], tree_sha1))
 -              die("Not a valid object name %s", argv[1]);
  
 -      for (i = 2; i < argc; i += 2) {
 -              unsigned char sha1[20];
 -              const char *a, *b;
 -              a = argv[i]; b = argv[i+1];
 -              if (!b || strcmp(a, "-p"))
 -                      usage(commit_tree_usage);
 +      for (i = 1; i < argc; i++) {
 +              const char *arg = argv[i];
 +              if (!strcmp(arg, "-p")) {
 +                      unsigned char sha1[20];
 +                      if (argc <= ++i)
 +                              usage(commit_tree_usage);
 +                      if (get_sha1(argv[i], sha1))
 +                              die("Not a valid object name %s", argv[i]);
 +                      assert_sha1_type(sha1, OBJ_COMMIT);
 +                      new_parent(lookup_commit(sha1), &parents);
 +                      continue;
 +              }
 +
 +              if (!strcmp(arg, "-m")) {
 +                      if (argc <= ++i)
 +                              usage(commit_tree_usage);
 +                      if (buffer.len)
 +                              strbuf_addch(&buffer, '\n');
 +                      strbuf_addstr(&buffer, argv[i]);
 +                      strbuf_complete_line(&buffer);
 +                      continue;
 +              }
 +
 +              if (!strcmp(arg, "-F")) {
 +                      int fd;
  
 -              if (get_sha1(b, sha1))
 -                      die("Not a valid object name %s", b);
 -              assert_sha1_type(sha1, OBJ_COMMIT);
 -              new_parent(lookup_commit(sha1), &parents);
 +                      if (argc <= ++i)
 +                              usage(commit_tree_usage);
 +                      if (buffer.len)
 +                              strbuf_addch(&buffer, '\n');
 +                      if (!strcmp(argv[i], "-"))
 +                              fd = 0;
 +                      else {
 +                              fd = open(argv[i], O_RDONLY);
 +                              if (fd < 0)
 +                                      die_errno("git commit-tree: failed to open '%s'",
 +                                                argv[i]);
 +                      }
 +                      if (strbuf_read(&buffer, fd, 0) < 0)
 +                              die_errno("git commit-tree: failed to read '%s'",
 +                                        argv[i]);
 +                      if (fd && close(fd))
 +                              die_errno("git commit-tree: failed to close '%s'",
 +                                        argv[i]);
 +                      strbuf_complete_line(&buffer);
 +                      continue;
 +              }
 +
 +              if (get_sha1(arg, tree_sha1))
 +                      die("Not a valid object name %s", arg);
 +              if (got_tree)
 +                      die("Cannot give more than one trees");
 +              got_tree = 1;
        }
  
 -      if (strbuf_read(&buffer, 0, 0) < 0)
 -              die_errno("git commit-tree: failed to read");
 +      if (!buffer.len) {
 +              if (strbuf_read(&buffer, 0, 0) < 0)
 +                      die_errno("git commit-tree: failed to read");
 +      }
  
-       if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+       if (commit_tree(&buffer, tree_sha1, parents, commit_sha1, NULL)) {
                strbuf_release(&buffer);
                return 1;
        }
diff --combined builtin/commit.c
index be1ab2e2570fe0b600f8b1aa55c32573578af766,849151e9517818203b9d7ba4368c87d26dd61ebf..3069041b8023224be0080f081d852dc001a81437
@@@ -81,8 -81,7 +81,8 @@@ static const char *template_file
  static const char *author_message, *author_message_buffer;
  static char *edit_message, *use_message;
  static char *fixup_message, *squash_message;
 -static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
 +static int all, also, interactive, patch_interactive, only, amend, signoff;
 +static int edit_flag = -1; /* unspecified */
  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
  static int no_post_rewrite, allow_empty_message;
  static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
@@@ -139,10 -138,10 +139,10 @@@ static struct option builtin_commit_opt
        OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
        OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
        OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
 -      OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
 +      OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
 -      OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
 +      OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"),
        OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
        OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
        /* end commit message options */
@@@ -395,7 -394,6 +395,7 @@@ static char *prepare_index(int argc, co
                fd = 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(1);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die(_("unable to write new_index file"));
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
 +                      update_main_cache_tree(1);
                        if (write_cache(fd, active_cache, active_nr) ||
                            commit_locked_index(&index_lock))
                                die(_("unable to write new_index file"));
@@@ -865,7 -862,10 +865,7 @@@ static int prepare_to_commit(const cha
         */
        discard_cache();
        read_cache_from(index_file);
 -      if (!active_cache_tree)
 -              active_cache_tree = cache_tree();
 -      if (cache_tree_update(active_cache_tree,
 -                            active_cache, active_nr, 0, 0) < 0) {
 +      if (update_main_cache_tree(0)) {
                error(_("Error building trees"));
                return 0;
        }
@@@ -1020,8 -1020,8 +1020,8 @@@ static int parse_and_validate_options(i
  
        if (logfile || message.len || use_message || fixup_message)
                use_editor = 0;
 -      if (edit_flag)
 -              use_editor = 1;
 +      if (0 <= edit_flag)
 +              use_editor = edit_flag;
        if (!use_editor)
                setenv("GIT_EDITOR", ":", 1);
  
@@@ -1259,7 -1259,7 +1259,7 @@@ static void print_summary(const char *p
        struct commit *commit;
        struct strbuf format = STRBUF_INIT;
        unsigned char junk_sha1[20];
 -      const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
 +      const char *head;
        struct pretty_print_context pctx = {0};
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
  
 +      head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
        printf("[%s%s ",
                !prefixcmp(head, "refs/heads/") ?
                        head + 11 :
@@@ -1383,7 -1382,6 +1383,7 @@@ int cmd_commit(int argc, const char **a
        int allow_fast_forward = 1;
        struct wt_status s;
        struct commit *current_head = NULL;
 +      struct commit_extra_header *extra = NULL;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
                        pptr = &commit_list_insert(c->item, pptr)->next;
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
 -              struct commit *commit;
                FILE *fp;
  
                if (!reflog_msg)
                        die_errno(_("could not open '%s' for reading"),
                                  git_path("MERGE_HEAD"));
                while (strbuf_getline(&m, fp, '\n') != EOF) {
 -                      unsigned char sha1[20];
 -                      if (get_sha1_hex(m.buf, sha1) < 0)
 +                      struct commit *parent;
 +
 +                      parent = get_merge_parent(m.buf);
 +                      if (!parent)
                                die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
 -                      commit = lookup_commit_or_die(sha1, "MERGE_HEAD");
 -                      pptr = &commit_list_insert(commit, pptr)->next;
 +                      pptr = &commit_list_insert(parent, pptr)->next;
                }
                fclose(fp);
                strbuf_release(&m);
                exit(1);
        }
  
 -      if (commit_tree(&sb, active_cache_tree->sha1, parents, sha1,
 -                      author_ident.buf)) {
 +      if (amend) {
 +              extra = read_commit_extra_headers(current_head);
 +      } else {
 +              struct commit_extra_header **tail = &extra;
 +              append_merge_tag_headers(parents, &tail);
 +      }
 +
-       if (commit_tree_extended(sb.buf, active_cache_tree->sha1, parents, sha1,
++      if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
 +                               author_ident.buf, 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
diff --combined builtin/merge.c
index 24579409c08f2e24ef3e5f2599a6af1aab8d28cd,e066bf1b0bbaca7820d32276e9c3c522ee23c991..a896165790d0c6cf05858f229cfaf05e62f67a9c
@@@ -26,7 -26,6 +26,7 @@@
  #include "merge-recursive.h"
  #include "resolve-undo.h"
  #include "remote.h"
 +#include "fmt-merge-msg.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -45,11 -44,10 +45,11 @@@ static const char * const builtin_merge
        NULL
  };
  
 -static int show_diffstat = 1, shortlog_len, squash;
 +static int show_diffstat = 1, shortlog_len = -1, squash;
  static int option_commit = 1, allow_fast_forward = 1;
  static int fast_forward_only, option_edit;
  static int allow_trivial = 1, have_message;
 +static int overwrite_ignore = 1;
  static struct strbuf merge_msg;
  static struct commit_list *remoteheads;
  static struct strategy **use_strategies;
@@@ -209,7 -207,6 +209,7 @@@ static struct option builtin_merge_opti
        OPT_BOOLEAN(0, "abort", &abort_current_merge,
                "abort the current in-progress merge"),
        OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
 +      OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"),
        OPT_END()
  };
  
@@@ -411,11 -408,21 +411,11 @@@ static void finish(struct commit *head_
        strbuf_release(&reflog_message);
  }
  
 -static struct object *want_commit(const char *name)
 -{
 -      struct object *obj;
 -      unsigned char sha1[20];
 -      if (get_sha1(name, sha1))
 -              return NULL;
 -      obj = parse_object(sha1);
 -      return peel_to_type(name, 0, obj, OBJ_COMMIT);
 -}
 -
  /* Get the name for the merge commit's message. */
  static void merge_name(const char *remote, struct strbuf *msg)
  {
 -      struct object *remote_head;
 -      unsigned char branch_head[20], buf_sha[20];
 +      struct commit *remote_head;
 +      unsigned char branch_head[20];
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        const char *ptr;
        remote = bname.buf;
  
        memset(branch_head, 0, sizeof(branch_head));
 -      remote_head = want_commit(remote);
 +      remote_head = get_merge_parent(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
  
                                    sha1_to_hex(branch_head), remote);
                        goto cleanup;
                }
 +              if (!prefixcmp(found_ref, "refs/tags/")) {
 +                      strbuf_addf(msg, "%s\t\ttag '%s' of .\n",
 +                                  sha1_to_hex(branch_head), remote);
 +                      goto cleanup;
 +              }
                if (!prefixcmp(found_ref, "refs/remotes/")) {
                        strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
                                    sha1_to_hex(branch_head), remote);
                strbuf_addstr(&truname, "refs/heads/");
                strbuf_addstr(&truname, remote);
                strbuf_setlen(&truname, truname.len - len);
 -              if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
 +              if (ref_exists(truname.buf)) {
                        strbuf_addf(msg,
                                    "%s\t\tbranch '%s'%s of .\n",
 -                                  sha1_to_hex(remote_head->sha1),
 +                                  sha1_to_hex(remote_head->object.sha1),
                                    truname.buf + 11,
                                    (early ? " (early part)" : ""));
                        strbuf_release(&truname);
                goto cleanup;
        }
        strbuf_addf(msg, "%s\t\tcommit '%s'\n",
 -              sha1_to_hex(remote_head->sha1), remote);
 +              sha1_to_hex(remote_head->object.sha1), remote);
  cleanup:
        strbuf_release(&buf);
        strbuf_release(&bname);
@@@ -540,8 -542,6 +540,8 @@@ static void parse_branch_merge_options(
  
  static int git_merge_config(const char *k, const char *v, void *cb)
  {
 +      int status;
 +
        if (branch && !prefixcmp(k, "branch.") &&
                !prefixcmp(k + 7, branch) &&
                !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
                return git_config_string(&pull_octopus, k, v);
        else if (!strcmp(k, "merge.renormalize"))
                option_renormalize = git_config_bool(k, v);
 -      else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
 -              int is_bool;
 -              shortlog_len = git_config_bool_or_int(k, v, &is_bool);
 -              if (!is_bool && shortlog_len < 0)
 -                      return error(_("%s: negative length %s"), k, v);
 -              if (is_bool && shortlog_len)
 -                      shortlog_len = DEFAULT_MERGE_LOG_LEN;
 -              return 0;
 -      } else if (!strcmp(k, "merge.ff")) {
 +      else if (!strcmp(k, "merge.ff")) {
                int boolval = git_config_maybe_bool(k, v);
                if (0 <= boolval) {
                        allow_fast_forward = boolval;
                default_to_upstream = git_config_bool(k, v);
                return 0;
        }
 +      status = fmt_merge_msg_config(k, v, cb);
 +      if (status)
 +              return status;
        return git_diff_ui_config(k, v, cb);
  }
  
@@@ -713,7 -718,7 +713,7 @@@ static int try_merge_strategy(const cha
                                die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
  
                o.branch1 = head_arg;
 -              o.branch2 = remoteheads->item->util;
 +              o.branch2 = merge_remote_util(remoteheads->item)->name;
  
                for (j = common; j; j = j->next)
                        commit_list_insert(j->item, &reversed);
@@@ -768,12 -773,10 +768,12 @@@ int checkout_fast_forward(const unsigne
        memset(&trees, 0, sizeof(trees));
        memset(&opts, 0, sizeof(opts));
        memset(&t, 0, sizeof(t));
 -      memset(&dir, 0, sizeof(dir));
 -      dir.flags |= DIR_SHOW_IGNORED;
 -      dir.exclude_per_dir = ".gitignore";
 -      opts.dir = &dir;
 +      if (overwrite_ignore) {
 +              memset(&dir, 0, sizeof(dir));
 +              dir.flags |= DIR_SHOW_IGNORED;
 +              setup_standard_excludes(&dir);
 +              opts.dir = &dir;
 +      }
  
        opts.head_idx = 1;
        opts.src_index = &the_index;
@@@ -910,7 -913,8 +910,8 @@@ static int merge_trivial(struct commit 
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
        prepare_to_commit();
-       commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+       if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL))
+               die(_("failed to write commit object"));
        finish(head, result_commit, "In-index merge");
        drop_save();
        return 0;
@@@ -941,7 -945,8 +942,8 @@@ static int finish_automerge(struct comm
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit();
        free_commit_list(remoteheads);
-       commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+       if (commit_tree(&merge_msg, result_tree, parents, result_commit, NULL))
+               die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(head, result_commit, buf.buf);
        strbuf_release(&buf);
@@@ -1055,16 -1060,9 +1057,16 @@@ static void write_merge_state(void
        struct commit_list *j;
        struct strbuf buf = STRBUF_INIT;
  
 -      for (j = remoteheads; j; j = j->next)
 -              strbuf_addf(&buf, "%s\n",
 -                      sha1_to_hex(j->item->object.sha1));
 +      for (j = remoteheads; j; j = j->next) {
 +              unsigned const char *sha1;
 +              struct commit *c = j->item;
 +              if (c->util && merge_remote_util(c)->obj) {
 +                      sha1 = merge_remote_util(c)->obj->sha1;
 +              } else {
 +                      sha1 = c->object.sha1;
 +              }
 +              strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
 +      }
        filename = git_path("MERGE_HEAD");
        fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
@@@ -1095,12 -1093,11 +1097,12 @@@ int cmd_merge(int argc, const char **ar
        struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
 -      int flag, i;
 +      int flag, i, ret = 0;
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list **remotes = &remoteheads;
 +      void *branch_to_free;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_merge_usage, builtin_merge_options);
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
 -      branch = resolve_ref("HEAD", head_sha1, 0, &flag);
 +      branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
        if (branch && !prefixcmp(branch, "refs/heads/"))
                branch += 11;
        if (!branch || is_null_sha1(head_sha1))
                parse_branch_merge_options(branch_mergeoptions);
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
                        builtin_merge_usage, 0);
 +      if (shortlog_len < 0)
 +              shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
  
        if (verbosity < 0 && show_progress == -1)
                show_progress = 0;
                        die(_("There is no merge to abort (MERGE_HEAD missing)."));
  
                /* Invoke 'git reset --merge' */
 -              return cmd_reset(nargc, nargv, prefix);
 +              ret = cmd_reset(nargc, nargv, prefix);
 +              goto done;
        }
  
        if (read_cache_unmerged())
                die(_("You cannot combine --no-ff with --ff-only."));
  
        if (!abort_current_merge) {
 -              if (!argc && default_to_upstream)
 -                      argc = setup_with_upstream(&argv);
 -              else if (argc == 1 && !strcmp(argv[0], "-"))
 +              if (!argc) {
 +                      if (default_to_upstream)
 +                              argc = setup_with_upstream(&argv);
 +                      else
 +                              die(_("No commit specified and merge.defaultToUpstream not set."));
 +              } else if (argc == 1 && !strcmp(argv[0], "-"))
                        argv[0] = "@{-1}";
        }
        if (!argc)
                argv += 2;
                argc -= 2;
        } else if (!head_commit) {
 -              struct object *remote_head;
 +              struct commit *remote_head;
                /*
                 * If the merged head is a valid one there is no reason
                 * to forbid "git merge" into a branch yet to be born.
                if (!allow_fast_forward)
                        die(_("Non-fast-forward commit does not make sense into "
                            "an empty head"));
 -              remote_head = want_commit(argv[0]);
 +              remote_head = get_merge_parent(argv[0]);
                if (!remote_head)
                        die(_("%s - not something we can merge"), argv[0]);
 -              read_empty(remote_head->sha1, 0);
 -              update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
 -                              DIE_ON_ERR);
 -              return 0;
 +              read_empty(remote_head->object.sha1, 0);
 +              update_ref("initial pull", "HEAD", remote_head->object.sha1,
 +                         NULL, 0, DIE_ON_ERR);
 +              goto done;
        } else {
                struct strbuf merge_names = STRBUF_INIT;
  
                head_arg = "HEAD";
  
                /*
 -               * All the rest are the commits being merged;
 -               * prepare the standard merge summary message to
 -               * be appended to the given message.  If remote
 -               * is invalid we will die later in the common
 -               * codepath so we discard the error in this
 -               * loop.
 +               * All the rest are the commits being merged; prepare
 +               * the standard merge summary message to be appended
 +               * to the given message.
                 */
                for (i = 0; i < argc; i++)
                        merge_name(argv[i], &merge_names);
  
                if (!have_message || shortlog_len) {
 -                      fmt_merge_msg(&merge_names, &merge_msg, !have_message,
 -                                    shortlog_len);
 +                      struct fmt_merge_msg_opts opts;
 +                      memset(&opts, 0, sizeof(opts));
 +                      opts.add_title = !have_message;
 +                      opts.shortlog_len = shortlog_len;
 +
 +                      fmt_merge_msg(&merge_names, &merge_msg, &opts);
                        if (merge_msg.len)
                                strbuf_setlen(&merge_msg, merge_msg.len - 1);
                }
        strbuf_reset(&buf);
  
        for (i = 0; i < argc; i++) {
 -              struct object *o;
 -              struct commit *commit;
 -
 -              o = want_commit(argv[i]);
 -              if (!o)
 +              struct commit *commit = get_merge_parent(argv[i]);
 +              if (!commit)
                        die(_("%s - not something we can merge"), argv[i]);
 -              commit = lookup_commit(o->sha1);
 -              commit->util = (void *)argv[i];
                remotes = &commit_list_insert(commit, remotes)->next;
 -
 -              strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
 +              strbuf_addf(&buf, "GITHEAD_%s",
 +                          sha1_to_hex(commit->object.sha1));
                setenv(buf.buf, argv[i], 1);
                strbuf_reset(&buf);
 +              if (merge_remote_util(commit) &&
 +                  merge_remote_util(commit)->obj &&
 +                  merge_remote_util(commit)->obj->type == OBJ_TAG) {
 +                      option_edit = 1;
 +                      allow_fast_forward = 0;
 +              }
        }
  
        if (!use_strategies) {
                 * but first the most common case of merging one remote.
                 */
                finish_up_to_date("Already up-to-date.");
 -              return 0;
 +              goto done;
        } else if (allow_fast_forward && !remoteheads->next &&
                        !common->next &&
                        !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
 -              struct object *o;
 +              struct commit *commit;
                char hex[41];
  
                strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
                if (have_message)
                        strbuf_addstr(&msg,
                                " (no commit created; -m option ignored)");
 -              o = want_commit(sha1_to_hex(remoteheads->item->object.sha1));
 -              if (!o)
 -                      return 1;
 +              commit = remoteheads->item;
 +              if (!commit) {
 +                      ret = 1;
 +                      goto done;
 +              }
  
 -              if (checkout_fast_forward(head_commit->object.sha1, remoteheads->item->object.sha1))
 -                      return 1;
 +              if (checkout_fast_forward(head_commit->object.sha1,
 +                                        commit->object.sha1)) {
 +                      ret = 1;
 +                      goto done;
 +              }
  
 -              finish(head_commit, o->sha1, msg.buf);
 +              finish(head_commit, commit->object.sha1, msg.buf);
                drop_save();
 -              return 0;
 +              goto done;
        } else if (!remoteheads->next && common->next)
                ;
                /*
                        git_committer_info(IDENT_ERROR_ON_NO_NAME);
                        printf(_("Trying really trivial in-index merge...\n"));
                        if (!read_tree_trivial(common->item->object.sha1,
 -                                      head_commit->object.sha1, remoteheads->item->object.sha1))
 -                              return merge_trivial(head_commit);
 +                                             head_commit->object.sha1,
 +                                             remoteheads->item->object.sha1)) {
 +                              ret = merge_trivial(head_commit);
 +                              goto done;
 +                      }
                        printf(_("Nope.\n"));
                }
        } else {
                }
                if (up_to_date) {
                        finish_up_to_date("Already up-to-date. Yeeah!");
 -                      return 0;
 +                      goto done;
                }
        }
  
         * If we have a resulting tree, that means the strategy module
         * auto resolved the merge cleanly.
         */
 -      if (automerge_was_ok)
 -              return finish_automerge(head_commit, common, result_tree,
 -                                      wt_strategy);
 +      if (automerge_was_ok) {
 +              ret = finish_automerge(head_commit, common, result_tree,
 +                                     wt_strategy);
 +              goto done;
 +      }
  
        /*
         * Pick the result from the best strategy and have the user fix
                else
                        fprintf(stderr, _("Merge with strategy %s failed.\n"),
                                use_strategies[0]->name);
 -              return 2;
 +              ret = 2;
 +              goto done;
        } else if (best_strategy == wt_strategy)
                ; /* We already have its result in the working tree. */
        else {
        else
                write_merge_state();
  
 -      if (merge_was_ok) {
 +      if (merge_was_ok)
                fprintf(stderr, _("Automatic merge went well; "
                        "stopped before committing as requested\n"));
 -              return 0;
 -      } else
 -              return suggest_conflicts(option_renormalize);
 +      else
 +              ret = suggest_conflicts(option_renormalize);
 +
 +done:
 +      free(branch_to_free);
 +      return ret;
  }
diff --combined builtin/notes.c
index 667e20a1e13770b845c0774e754043fae514a4b1,5e32548cdd89fa6a45f731dc9cae1bfca4955a0e..3644d140ece9f2cfc6381e1a60c5b4fc8f645e2a
@@@ -301,12 -301,12 +301,12 @@@ void commit_notes(struct notes_tree *t
                return; /* don't have to commit an unchanged tree */
  
        /* Prepare commit message and reflog message */
-       strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
        strbuf_addstr(&buf, msg);
        if (buf.buf[buf.len - 1] != '\n')
                strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
  
-       create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
+       create_notes_commit(t, NULL, &buf, commit_sha1);
+       strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
        update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
  
        strbuf_release(&buf);
@@@ -804,8 -804,6 +804,8 @@@ static int merge_commit(struct notes_me
        struct notes_tree *t;
        struct commit *partial;
        struct pretty_print_context pretty_ctx;
 +      void *local_ref_to_free;
 +      int ret;
  
        /*
         * Read partial merge result from .git/NOTES_MERGE_PARTIAL,
        t = xcalloc(1, sizeof(struct notes_tree));
        init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
  
 -      o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL);
 +      o->local_ref = local_ref_to_free =
 +              resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL);
        if (!o->local_ref)
                die("Failed to resolve NOTES_MERGE_REF");
  
  
        free_notes(t);
        strbuf_release(&msg);
 -      return merge_abort(o);
 +      ret = merge_abort(o);
 +      free(local_ref_to_free);
 +      return ret;
  }
  
  static int merge(int argc, const char **argv, const char *prefix)
diff --combined commit.c
index b78127403be65f5c26e35e53a59b91860396194a,80e61b4cf551a05b06f5944018265868767515bf..44bc96d44d391209d3a60e9e8c1b98ed209e6b4a
+++ b/commit.c
@@@ -840,160 -840,14 +840,160 @@@ struct commit_list *reduce_heads(struc
        return result;
  }
  
- int commit_tree(const char *msg, unsigned char *tree,
 +static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
 +{
 +      struct merge_remote_desc *desc;
 +      struct commit_extra_header *mergetag;
 +      char *buf;
 +      unsigned long size, len;
 +      enum object_type type;
 +
 +      desc = merge_remote_util(parent);
 +      if (!desc || !desc->obj)
 +              return;
 +      buf = read_sha1_file(desc->obj->sha1, &type, &size);
 +      if (!buf || type != OBJ_TAG)
 +              goto free_return;
 +      len = parse_signature(buf, size);
 +      if (size == len)
 +              goto free_return;
 +      /*
 +       * We could verify this signature and either omit the tag when
 +       * it does not validate, but the integrator may not have the
 +       * public key of the signer of the tag he is merging, while a
 +       * later auditor may have it while auditing, so let's not run
 +       * verify-signed-buffer here for now...
 +       *
 +       * if (verify_signed_buffer(buf, len, buf + len, size - len, ...))
 +       *      warn("warning: signed tag unverified.");
 +       */
 +      mergetag = xcalloc(1, sizeof(*mergetag));
 +      mergetag->key = xstrdup("mergetag");
 +      mergetag->value = buf;
 +      mergetag->len = size;
 +
 +      **tail = mergetag;
 +      *tail = &mergetag->next;
 +      return;
 +
 +free_return:
 +      free(buf);
 +}
 +
 +void append_merge_tag_headers(struct commit_list *parents,
 +                            struct commit_extra_header ***tail)
 +{
 +      while (parents) {
 +              struct commit *parent = parents->item;
 +              handle_signed_tag(parent, tail);
 +              parents = parents->next;
 +      }
 +}
 +
 +static void add_extra_header(struct strbuf *buffer,
 +                           struct commit_extra_header *extra)
 +{
 +      strbuf_addstr(buffer, extra->key);
 +      if (extra->len)
 +              strbuf_add_lines(buffer, " ", extra->value, extra->len);
 +      else
 +              strbuf_addch(buffer, '\n');
 +}
 +
 +struct commit_extra_header *read_commit_extra_headers(struct commit *commit)
 +{
 +      struct commit_extra_header *extra = NULL;
 +      unsigned long size;
 +      enum object_type type;
 +      char *buffer = read_sha1_file(commit->object.sha1, &type, &size);
 +      if (buffer && type == OBJ_COMMIT)
 +              extra = read_commit_extra_header_lines(buffer, size);
 +      free(buffer);
 +      return extra;
 +}
 +
 +static inline int standard_header_field(const char *field, size_t len)
 +{
 +      return ((len == 4 && !memcmp(field, "tree ", 5)) ||
 +              (len == 6 && !memcmp(field, "parent ", 7)) ||
 +              (len == 6 && !memcmp(field, "author ", 7)) ||
 +              (len == 9 && !memcmp(field, "committer ", 10)) ||
 +              (len == 8 && !memcmp(field, "encoding ", 9)));
 +}
 +
 +struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size)
 +{
 +      struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL;
 +      const char *line, *next, *eof, *eob;
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      for (line = buffer, eob = line + size;
 +           line < eob && *line != '\n';
 +           line = next) {
 +              next = memchr(line, '\n', eob - line);
 +              next = next ? next + 1 : eob;
 +              if (*line == ' ') {
 +                      /* continuation */
 +                      if (it)
 +                              strbuf_add(&buf, line + 1, next - (line + 1));
 +                      continue;
 +              }
 +              if (it)
 +                      it->value = strbuf_detach(&buf, &it->len);
 +              strbuf_reset(&buf);
 +              it = NULL;
 +
 +              eof = strchr(line, ' ');
 +              if (next <= eof)
 +                      eof = next;
 +
 +              if (standard_header_field(line, eof - line))
 +                      continue;
 +
 +              it = xcalloc(1, sizeof(*it));
 +              it->key = xmemdupz(line, eof-line);
 +              *tail = it;
 +              tail = &it->next;
 +              if (eof + 1 < next)
 +                      strbuf_add(&buf, eof + 1, next - (eof + 1));
 +      }
 +      if (it)
 +              it->value = strbuf_detach(&buf, &it->len);
 +      return extra;
 +}
 +
 +void free_commit_extra_headers(struct commit_extra_header *extra)
 +{
 +      while (extra) {
 +              struct commit_extra_header *next = extra->next;
 +              free(extra->key);
 +              free(extra->value);
 +              free(extra);
 +              extra = next;
 +      }
 +}
 +
++int commit_tree(const struct strbuf *msg, unsigned char *tree,
 +              struct commit_list *parents, unsigned char *ret,
 +              const char *author)
 +{
 +      struct commit_extra_header *extra = NULL, **tail = &extra;
 +      int result;
 +
 +      append_merge_tag_headers(parents, &tail);
 +      result = commit_tree_extended(msg, tree, parents, ret, author, extra);
 +      free_commit_extra_headers(extra);
 +      return result;
 +}
 +
  static const char commit_utf8_warn[] =
  "Warning: commit message does not conform to UTF-8.\n"
  "You may want to amend it after fixing the message, or set the config\n"
  "variable i18n.commitencoding to the encoding your project uses.\n";
  
- int commit_tree_extended(const char *msg, unsigned char *tree,
 -int commit_tree(const struct strbuf *msg, unsigned char *tree,
 -              struct commit_list *parents, unsigned char *ret,
 -              const char *author)
++int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
 +                       struct commit_list *parents, unsigned char *ret,
 +                       const char *author, struct commit_extra_header *extra)
  {
        int result;
        int encoding_is_utf8;
  
        assert_sha1_type(tree, OBJ_TREE);
  
+       if (memchr(msg->buf, '\0', msg->len))
+               return error("a NUL byte in commit log message not allowed.");
        /* Not having i18n.commitencoding is the same as having utf-8 */
        encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
  
         */
        while (parents) {
                struct commit_list *next = parents->next;
 +              struct commit *parent = parents->item;
 +
                strbuf_addf(&buffer, "parent %s\n",
 -                      sha1_to_hex(parents->item->object.sha1));
 +                          sha1_to_hex(parent->object.sha1));
                free(parents);
                parents = next;
        }
        strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
        if (!encoding_is_utf8)
                strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
 +
 +      while (extra) {
 +              add_extra_header(&buffer, extra);
 +              extra = extra->next;
 +      }
        strbuf_addch(&buffer, '\n');
  
        /* And add the comment */
-       strbuf_addstr(&buffer, msg);
+       strbuf_addbuf(&buffer, msg);
  
        /* And check the encoding */
        if (encoding_is_utf8 && !is_utf8(buffer.buf))
        strbuf_release(&buffer);
        return result;
  }
 +
 +struct commit *get_merge_parent(const char *name)
 +{
 +      struct object *obj;
 +      struct commit *commit;
 +      unsigned char sha1[20];
 +      if (get_sha1(name, sha1))
 +              return NULL;
 +      obj = parse_object(sha1);
 +      commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
 +      if (commit && !commit->util) {
 +              struct merge_remote_desc *desc;
 +              desc = xmalloc(sizeof(*desc));
 +              desc->obj = obj;
 +              desc->name = strdup(name);
 +              commit->util = desc;
 +      }
 +      return commit;
 +}
diff --combined commit.h
index 3745f120994cb6876e1ccc1bfc5e07c6cc3fc5c9,5cf46b2e0a5485757a23c86ed38b4d2de6c1342a..4df397865abe7161a9f16e3177e3b486bf8434ca
+++ b/commit.h
@@@ -181,41 -181,8 +181,41 @@@ static inline int single_parent(struct 
  
  struct commit_list *reduce_heads(struct commit_list *heads);
  
- extern int commit_tree(const char *msg, unsigned char *tree,
 +struct commit_extra_header {
 +      struct commit_extra_header *next;
 +      char *key;
 +      char *value;
 +      size_t len;
 +};
 +
 +extern void append_merge_tag_headers(struct commit_list *parents,
 +                                   struct commit_extra_header ***tail);
 +
 -              struct commit_list *parents, unsigned char *ret,
 -              const char *author);
+ extern int commit_tree(const struct strbuf *msg, unsigned char *tree,
- extern int commit_tree_extended(const char *msg, unsigned char *tree,
 +                     struct commit_list *parents, unsigned char *ret,
 +                     const char *author);
 +
++extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
 +                              struct commit_list *parents, unsigned char *ret,
 +                              const char *author,
 +                              struct commit_extra_header *);
 +
 +extern struct commit_extra_header *read_commit_extra_headers(struct commit *);
 +extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len);
 +
 +extern void free_commit_extra_headers(struct commit_extra_header *extra);
 +
 +struct merge_remote_desc {
 +      struct object *obj; /* the named object, could be a tag */
 +      const char *name;
 +};
 +#define merge_remote_util(commit) ((struct merge_remote_desc *)((commit)->util))
 +
 +/*
 + * Given "name" from the command line to merge, find the commit object
 + * and return it, while storing merge_remote_desc in its ->util field,
 + * to allow callers to tell if we are told to merge a tag.
 + */
 +struct commit *get_merge_parent(const char *name);
  
  #endif /* COMMIT_H */
diff --combined notes-merge.c
index 2de27af751476a62dc9b0afe48eb360f83a0702b,b5a36ac1587f00c21001840c5bc001b011aed554..0d38a1b17d8399c7acf6355d598f0a50d0ec1d3b
@@@ -530,7 -530,7 +530,7 @@@ static int merge_from_diffs(struct note
  }
  
  void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
-                        const char *msg, unsigned char *result_sha1)
+                        const struct strbuf *msg, unsigned char *result_sha1)
  {
        unsigned char tree_sha1[20];
  
@@@ -573,7 -573,7 +573,7 @@@ int notes_merge(struct notes_merge_opti
               o->local_ref, o->remote_ref);
  
        /* Dereference o->local_ref into local_sha1 */
 -      if (!resolve_ref(o->local_ref, local_sha1, 0, NULL))
 +      if (read_ref_full(o->local_ref, local_sha1, 0, NULL))
                die("Failed to resolve local notes ref '%s'", o->local_ref);
        else if (!check_refname_format(o->local_ref, 0) &&
                is_null_sha1(local_sha1))
                struct commit_list *parents = NULL;
                commit_list_insert(remote, &parents); /* LIFO order */
                commit_list_insert(local, &parents);
-               create_notes_commit(local_tree, parents, o->commit_msg.buf,
+               create_notes_commit(local_tree, parents, &o->commit_msg,
                                    result_sha1);
        }
  
@@@ -695,7 -695,8 +695,8 @@@ int notes_merge_commit(struct notes_mer
        struct dir_struct dir;
        char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/"));
        int path_len = strlen(path), i;
-       const char *msg = strstr(partial_commit->buffer, "\n\n");
+       char *msg = strstr(partial_commit->buffer, "\n\n");
+       struct strbuf sb_msg = STRBUF_INIT;
  
        if (o->verbosity >= 3)
                printf("Committing notes in notes merge worktree at %.*s\n",
                                sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
        }
  
-       create_notes_commit(partial_tree, partial_commit->parents, msg,
+       strbuf_attach(&sb_msg, msg, strlen(msg), strlen(msg) + 1);
+       create_notes_commit(partial_tree, partial_commit->parents, &sb_msg,
                            result_sha1);
        if (o->verbosity >= 4)
                printf("Finalized notes merge commit: %s\n",