]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jc/conflict-hint' into cc/interpret-trailers-more
authorJunio C Hamano <gitster@pobox.com>
Mon, 10 Nov 2014 17:56:39 +0000 (09:56 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 10 Nov 2014 17:56:39 +0000 (09:56 -0800)
* jc/conflict-hint:
  merge & sequencer: turn "Conflicts:" hint into a comment
  builtin/commit.c: extract ignore_non_trailer() helper function
  merge & sequencer: unify codepaths that write "Conflicts:" hint
  builtin/merge.c: drop a parameter that is never used
  git-tag.txt: Add a missing hyphen to `-s`

1  2 
Documentation/git-tag.txt
builtin/commit.c
builtin/merge.c
sequencer.c
sequencer.h

index 320908369f067f5d494c6bc0c3c9f28fa26859a6,a82d2e27bdd9c923603404d8bb9445e5de3f8edc..e953ba4439f5995c797b4d7ad869847a5479d982
@@@ -42,7 -42,7 +42,7 @@@ committer identity for the current use
  GnuPG key for signing.        The configuration variable `gpg.program`
  is used to specify custom GnuPG binary.
  
- Tag objects (created with `-a`, `s`, or `-u`) are called "annotated"
+ Tag objects (created with `-a`, `-s`, or `-u`) are called "annotated"
  tags; they contain a creation date, the tagger name and e-mail, a
  tagging message, and an optional GnuPG signature. Whereas a
  "lightweight" tag is simply a name for an object (usually a commit
@@@ -95,14 -95,6 +95,14 @@@ OPTION
        using fnmatch(3)).  Multiple patterns may be given; if any of
        them matches, the tag is shown.
  
 +--sort=<type>::
 +      Sort in a specific order. Supported type is "refname"
 +      (lexicographic order), "version:refname" or "v:refname" (tag
 +      names are treated as versions). Prepend "-" to reverse sort
 +      order. When this option is not given, the sort order defaults to the
 +      value configured for the 'tag.sort' variable if it exists, or
 +      lexicographic order otherwise. See linkgit:git-config[1].
 +
  --column[=<options>]::
  --no-column::
        Display tag listing in columns. See configuration variable
  +
  This option is only applicable when listing tags without annotation lines.
  
 ---contains <commit>::
 -      Only list tags which contain the specified commit.
 +--contains [<commit>]::
 +      Only list tags which contain the specified commit (HEAD if not
 +      specified).
  
  --points-at <object>::
        Only list tags of the given object.
@@@ -319,7 -310,6 +319,7 @@@ include::date-formats.txt[
  SEE ALSO
  --------
  linkgit:git-check-ref-format[1].
 +linkgit:git-config[1].
  
  GIT
  ---
diff --combined builtin/commit.c
index 41f481bd030ba96f8883e64517eda95931344545,0a78e7649b579b3392dcd2958717671d8bf49db4..80a618fe137811fff6e205104297e7158d0572a9
@@@ -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"
@@@ -126,7 -113,6 +126,7 @@@ static char *sign_commit
  static enum {
        CLEANUP_SPACE,
        CLEANUP_NONE,
 +      CLEANUP_SCISSORS,
        CLEANUP_ALL
  } cleanup_mode;
  static const char *cleanup_arg;
@@@ -248,7 -234,7 +248,7 @@@ static int list_paths(struct string_lis
  
                if (ce->ce_flags & CE_UPDATE)
                        continue;
 -              if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m))
 +              if (!ce_path_match(ce, pattern, m))
                        continue;
                item = string_list_insert(list, ce->name);
                if (ce_skip_worktree(ce))
@@@ -318,8 -304,10 +318,8 @@@ 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;
 -      char *old_index_env = NULL;
        int refresh_flags = REFRESH_QUIET;
  
        if (is_status)
                die(_("index file corrupt"));
  
        if (interactive) {
 -              fd = hold_locked_index(&index_lock, 1);
 +              char *old_index_env = NULL;
 +              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);
  
                discard_cache();
                read_cache_from(index_lock.filename);
 +              if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
 +                      if (reopen_lock_file(&index_lock) < 0)
 +                              die(_("unable to write index file"));
 +                      if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
 +                              die(_("unable to update temporary index"));
 +              } else
 +                      warning(_("Failed to update main cache tree"));
  
                commit_style = COMMIT_NORMAL;
                return index_lock.filename;
         * (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) {
 +              if (active_cache_changed
 +                  || !cache_tree_fully_valid(active_cache_tree)) {
                        update_main_cache_tree(WRITE_TREE_SILENT);
 -                      if (write_cache(fd, active_cache, active_nr) ||
 -                          commit_locked_index(&index_lock))
 +                      active_cache_changed = 1;
 +              }
 +              if (active_cache_changed) {
 +                      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))
 +      update_main_cache_tree(WRITE_TREE_SILENT);
 +      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();
@@@ -545,29 -525,10 +545,29 @@@ static int sane_ident_split(struct iden
        return 1;
  }
  
 +static int parse_force_date(const char *in, char *out, int len)
 +{
 +      if (len < 1)
 +              return -1;
 +      *out++ = '@';
 +      len--;
 +
 +      if (parse_date(in, out, len) < 0) {
 +              int errors = 0;
 +              unsigned long t = approxidate_careful(in, &errors);
 +              if (errors)
 +                      return -1;
 +              snprintf(out, len, "%lu", t);
 +      }
 +
 +      return 0;
 +}
 +
  static void determine_author_info(struct strbuf *author_ident)
  {
        char *name, *email, *date;
        struct ident_split author;
 +      char date_buf[64];
  
        name = getenv("GIT_AUTHOR_NAME");
        email = getenv("GIT_AUTHOR_EMAIL");
                email = xstrndup(lb + 2, rb - (lb + 2));
        }
  
 -      if (force_date)
 -              date = force_date;
 +      if (force_date) {
 +              if (parse_force_date(force_date, date_buf, sizeof(date_buf)))
 +                      die(_("invalid date format: %s"), force_date);
 +              date = date_buf;
 +      }
 +
        strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
        if (!split_ident_line(&author, author_ident->buf, author_ident->len) &&
            sane_ident_split(&author)) {
        }
  }
  
 -static char *cut_ident_timestamp_part(char *string)
 +static void split_ident_or_die(struct ident_split *id, const struct strbuf *buf)
  {
 -      char *ket = strrchr(string, '>');
 -      if (!ket || ket[1] != ' ')
 -              die(_("Malformed ident string: '%s'"), string);
 -      *++ket = '\0';
 -      return ket;
 +      if (split_ident_line(id, buf->buf, buf->len) ||
 +          !sane_ident_split(id))
 +              die(_("Malformed ident string: '%s'"), buf->buf);
 +}
 +
 +static int author_date_is_interesting(void)
 +{
 +      return author_message || force_date;
 +}
 +
 +static void adjust_comment_line_char(const struct strbuf *sb)
 +{
 +      char candidates[] = "#;@!$%^&|:";
 +      char *candidate;
 +      const char *p;
 +
 +      comment_line_char = candidates[0];
 +      if (!memchr(sb->buf, comment_line_char, sb->len))
 +              return;
 +
 +      p = sb->buf;
 +      candidate = strchr(candidates, *p);
 +      if (candidate)
 +              *candidate = ' ';
 +      for (p = sb->buf; *p; p++) {
 +              if ((p[0] == '\n' || p[0] == '\r') && p[1]) {
 +                      candidate = strchr(candidates, p[1]);
 +                      if (candidate)
 +                              *candidate = ' ';
 +              }
 +      }
 +
 +      for (p = candidates; *p == ' '; p++)
 +              ;
 +      if (!*p)
 +              die(_("unable to select a comment character that is not used\n"
 +                    "in the current commit message"));
 +      comment_line_char = *p;
  }
  
 -              } else if (!prefixcmp(sb->buf + bol, "Conflicts:\n")) {
+ /*
+  * Inspect sb and determine the true "end" of the log message, in
+  * order to find where to put a new Signed-off-by: line.  Ignored are
+  * trailing comment lines and blank lines, and also the traditional
+  * "Conflicts:" block that is not commented out, so that we can use
+  * "git commit -s --amend" on an existing commit that forgot to remove
+  * it.
+  *
+  * Returns the number of bytes from the tail to ignore, to be fed as
+  * the second parameter to append_signoff().
+  */
+ static int ignore_non_trailer(struct strbuf *sb)
+ {
+       int boc = 0;
+       int bol = 0;
+       int in_old_conflicts_block = 0;
+       while (bol < sb->len) {
+               char *next_line;
+               if (!(next_line = memchr(sb->buf + bol, '\n', sb->len - bol)))
+                       next_line = sb->buf + sb->len;
+               else
+                       next_line++;
+               if (sb->buf[bol] == comment_line_char || sb->buf[bol] == '\n') {
+                       /* is this the first of the run of comments? */
+                       if (!boc)
+                               boc = bol;
+                       /* otherwise, it is just continuing */
++              } else if (starts_with(sb->buf + bol, "Conflicts:\n")) {
+                       in_old_conflicts_block = 1;
+                       if (!boc)
+                               boc = bol;
+               } else if (in_old_conflicts_block && sb->buf[bol] == '\t') {
+                       ; /* a pathname in the conflicts block */
+               } else if (boc) {
+                       /* the previous was not trailing comment */
+                       boc = 0;
+                       in_old_conflicts_block = 0;
+               }
+               bol = next_line - sb->buf;
+       }
+       return boc ? sb->len - boc : 0;
+ }
  static int prepare_to_commit(const char *index_file, const char *prefix,
                             struct commit *current_head,
                             struct wt_status *s,
  {
        struct stat statbuf;
        struct strbuf committer_ident = STRBUF_INIT;
 -      int commitable, saved_color_setting;
 +      int commitable;
        struct strbuf sb = STRBUF_INIT;
 -      char *buffer;
        const char *hook_arg1 = NULL;
        const char *hook_arg2 = NULL;
 -      int ident_shown = 0;
        int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
        int old_display_comment_prefix;
  
        /* This checks and barfs if author is badly specified */
        determine_author_info(author_ident);
  
 -      if (!no_verify && run_hook(index_file, "pre-commit", NULL))
 +      if (!no_verify && run_commit_hook(use_editor, index_file, "pre-commit", NULL))
                return 0;
  
        if (squash_message) {
                                  logfile);
                hook_arg1 = "message";
        } else if (use_message) {
 +              char *buffer;
                buffer = strstr(use_message_buffer, "\n\n");
 -              if (!use_editor && (!buffer || buffer[2] == '\0'))
 -                      die(_("commit has empty message"));
 -              strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
 +              if (buffer)
 +                      strbuf_addstr(&sb, buffer + 2);
                hook_arg1 = "commit";
                hook_arg2 = use_message;
        } else if (fixup_message) {
        if (clean_message_contents)
                stripspace(&sb, 0);
  
-       if (signoff) {
-               /*
-                * See if we have a Conflicts: block at the end. If yes, count
-                * its size, so we can ignore it.
-                */
-               int ignore_footer = 0;
-               int i, eol, previous = 0;
-               const char *nl;
-               for (i = 0; i < sb.len; i++) {
-                       nl = memchr(sb.buf + i, '\n', sb.len - i);
-                       if (nl)
-                               eol = nl - sb.buf;
-                       else
-                               eol = sb.len;
-                       if (starts_with(sb.buf + previous, "\nConflicts:\n")) {
-                               ignore_footer = sb.len - previous;
-                               break;
-                       }
-                       while (i < eol)
-                               i++;
-                       previous = eol;
-               }
-               append_signoff(&sb, ignore_footer, 0);
-       }
+       if (signoff)
+               append_signoff(&sb, ignore_non_trailer(&sb), 0);
  
        if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
                die_errno(_("could not write commit template"));
  
 +      if (auto_comment_line_char)
 +              adjust_comment_line_char(&sb);
        strbuf_release(&sb);
  
        /* This checks if committer ident is explicitly given */
        strbuf_addstr(&committer_ident, git_committer_info(IDENT_STRICT));
        if (use_editor && include_status) {
 -              char *ai_tmp, *ci_tmp;
 -              if (whence != FROM_COMMIT)
 +              int ident_shown = 0;
 +              int saved_color_setting;
 +              struct ident_split ci, ai;
 +
 +              if (whence != FROM_COMMIT) {
 +                      if (cleanup_mode == CLEANUP_SCISSORS)
 +                              wt_status_add_cut_line(s->fp);
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                            whence == FROM_MERGE
                                ? _("\n"
                                git_path(whence == FROM_MERGE
                                         ? "MERGE_HEAD"
                                         : "CHERRY_PICK_HEAD"));
 +              }
  
                fprintf(s->fp, "\n");
                if (cleanup_mode == CLEANUP_ALL)
                                _("Please enter the commit message for your changes."
                                  " Lines starting\nwith '%c' will be ignored, and an empty"
                                  " message aborts the commit.\n"), comment_line_char);
 +              else if (cleanup_mode == CLEANUP_SCISSORS && whence == FROM_COMMIT)
 +                      wt_status_add_cut_line(s->fp);
                else /* CLEANUP_SPACE, that is. */
                        status_printf(s, GIT_COLOR_NORMAL,
                                _("Please enter the commit message for your changes."
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                        "%s", only_include_assumed);
  
 -              ai_tmp = cut_ident_timestamp_part(author_ident->buf);
 -              ci_tmp = cut_ident_timestamp_part(committer_ident.buf);
 -              if (strcmp(author_ident->buf, committer_ident.buf))
 +              split_ident_or_die(&ai, author_ident);
 +              split_ident_or_die(&ci, &committer_ident);
 +
 +              if (ident_cmp(&ai, &ci))
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                _("%s"
 -                              "Author:    %s"),
 +                              "Author:    %.*s <%.*s>"),
                                ident_shown++ ? "" : "\n",
 -                              author_ident->buf);
 +                              (int)(ai.name_end - ai.name_begin), ai.name_begin,
 +                              (int)(ai.mail_end - ai.mail_begin), ai.mail_begin);
 +
 +              if (author_date_is_interesting())
 +                      status_printf_ln(s, GIT_COLOR_NORMAL,
 +                              _("%s"
 +                              "Date:      %s"),
 +                              ident_shown++ ? "" : "\n",
 +                              show_ident_date(&ai, DATE_NORMAL));
  
                if (!committer_ident_sufficiently_given())
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                _("%s"
 -                              "Committer: %s"),
 +                              "Committer: %.*s <%.*s>"),
                                ident_shown++ ? "" : "\n",
 -                              committer_ident.buf);
 +                              (int)(ci.name_end - ci.name_begin), ci.name_begin,
 +                              (int)(ci.mail_end - ci.mail_begin), ci.mail_begin);
  
                if (ident_shown)
 -                      status_printf_ln(s, GIT_COLOR_NORMAL, "");
 +                      status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
  
                saved_color_setting = s->use_color;
                s->use_color = 0;
                commitable = run_status(s->fp, index_file, prefix, 1, s);
                s->use_color = saved_color_setting;
 -
 -              *ai_tmp = ' ';
 -              *ci_tmp = ' ';
        } else {
                unsigned char sha1[20];
                const char *parent = "HEAD";
  
                if (get_sha1(parent, sha1))
                        commitable = !!active_nr;
 -              else
 -                      commitable = index_differs_from(parent, 0);
 +              else {
 +                      /*
 +                       * Unless the user did explicitly request a submodule
 +                       * ignore mode by passing a command line option we do
 +                       * not ignore any changed submodule SHA-1s when
 +                       * comparing index and parent, no matter what is
 +                       * configured. Otherwise we won't commit any
 +                       * submodules which were manually staged, which would
 +                       * be really confusing.
 +                       */
 +                      int diff_flags = DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
 +                      if (ignore_submodule_arg &&
 +                          !strcmp(ignore_submodule_arg, "all"))
 +                              diff_flags |= DIFF_OPT_IGNORE_SUBMODULES;
 +                      commitable = index_differs_from(parent, diff_flags);
 +              }
        }
        strbuf_release(&committer_ident);
  
                return 0;
        }
  
 -      if (run_hook(index_file, "prepare-commit-msg",
 -                   git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
 +      if (run_commit_hook(use_editor, index_file, "prepare-commit-msg",
 +                          git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
                return 0;
  
        if (use_editor) {
        }
  
        if (!no_verify &&
 -          run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
 +          run_commit_hook(use_editor, index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
                return 0;
        }
  
@@@ -1009,7 -926,7 +1031,7 @@@ static int rest_is_empty(struct strbuf 
                        eol = sb->len;
  
                if (strlen(sign_off_header) <= eol - i &&
 -                  !prefixcmp(sb->buf + i, sign_off_header)) {
 +                  starts_with(sb->buf + i, sign_off_header)) {
                        i = eol;
                        continue;
                }
@@@ -1039,7 -956,7 +1061,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);
@@@ -1073,8 -991,7 +1095,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};
@@@ -1172,6 -1089,8 +1194,6 @@@ static int parse_and_validate_options(i
                use_editor = 0;
        if (0 <= edit_flag)
                use_editor = edit_flag;
 -      if (!use_editor)
 -              setenv("GIT_EDITOR", ":", 1);
  
        /* Sanity check options */
        if (amend && !current_head)
        if (argc == 0 && only && amend)
                only_include_assumed = _("Clever... amending the last one with dirty index.");
        if (argc > 0 && !also && !only)
 -              only_include_assumed = _("Explicit paths specified without -i nor -o; assuming --only paths...");
 +              only_include_assumed = _("Explicit paths specified without -i or -o; assuming --only paths...");
        if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
                cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
        else if (!strcmp(cleanup_arg, "verbatim"))
                cleanup_mode = CLEANUP_SPACE;
        else if (!strcmp(cleanup_arg, "strip"))
                cleanup_mode = CLEANUP_ALL;
 +      else if (!strcmp(cleanup_arg, "scissors"))
 +              cleanup_mode = use_editor ? CLEANUP_SCISSORS : CLEANUP_SPACE;
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
  
@@@ -1288,7 -1205,7 +1310,7 @@@ static int git_status_config(const cha
  {
        struct wt_status *s = cb;
  
 -      if (!prefixcmp(k, "column."))
 +      if (starts_with(k, "column."))
                return git_column_config(k, v, "status", &s->colopts);
        if (!strcmp(k, "status.submodulesummary")) {
                int is_bool;
                s->display_comment_prefix = git_config_bool(k, v);
                return 0;
        }
 -      if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
 +      if (starts_with(k, "status.color.") || starts_with(k, "color.status.")) {
                int slot = parse_status_slot(k, 13);
                if (slot < 0)
                        return 0;
@@@ -1428,24 -1345,6 +1450,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)
  {
        commit = lookup_commit(sha1);
        if (!commit)
                die(_("couldn't look up newly created commit"));
 -      if (!commit || parse_commit(commit))
 +      if (parse_commit(commit))
                die(_("could not parse newly created commit"));
  
        strbuf_addstr(&format, "format:%h] %s");
                strbuf_addstr(&format, "\n Author: ");
                strbuf_addbuf_percentquote(&format, &author_ident);
        }
 +      if (author_date_is_interesting()) {
 +              struct strbuf date = STRBUF_INIT;
 +              format_commit_message(commit, "%ad", &date, &pctx);
 +              strbuf_addstr(&format, "\n Date: ");
 +              strbuf_addbuf_percentquote(&format, &date);
 +              strbuf_release(&date);
 +      }
        if (!committer_ident_sufficiently_given()) {
                strbuf_addstr(&format, "\n Committer: ");
                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);
  
        head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
        printf("[%s%s ",
 -              !prefixcmp(head, "refs/heads/") ?
 +              starts_with(head, "refs/heads/") ?
                        head + 11 :
                        !strcmp(head, "HEAD") ?
                                _("detached HEAD") :
@@@ -1536,10 -1428,6 +1558,10 @@@ static int git_commit_config(const cha
        }
        if (!strcmp(k, "commit.cleanup"))
                return git_config_string(&cleanup_arg, k, v);
 +      if (!strcmp(k, "commit.gpgsign")) {
 +              sign_commit = git_config_bool(k, v) ? "" : NULL;
 +              return 0;
 +      }
  
        status = git_gpg_config(k, v, NULL);
        if (status)
@@@ -1552,7 -1440,7 +1574,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;
        return finish_command(&proc);
  }
  
 +int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
 +{
 +      const char *hook_env[3] =  { NULL };
 +      char index[PATH_MAX];
 +      va_list args;
 +      int ret;
 +
 +      snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
 +      hook_env[0] = index;
 +
 +      /*
 +       * Let the hook know that no editor will be launched.
 +       */
 +      if (!editor_is_used)
 +              hook_env[1] = "GIT_EDITOR=:";
 +
 +      va_start(args, name);
 +      ret = run_hook_ve(hook_env, name, args);
 +      va_end(args);
 +
 +      return ret;
 +}
 +
  int cmd_commit(int argc, const char **argv, const char *prefix)
  {
        static struct wt_status s;
                OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
                OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")),
                OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")),
 -              { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"),
 +              { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
                  N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
                /* end commit message options */
  
        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;
 -      int allow_fast_forward = 1;
        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);
                current_head = NULL;
        else {
                current_head = lookup_commit_or_die(sha1, "HEAD");
 -              if (!current_head || parse_commit(current_head))
 +              if (parse_commit(current_head))
                        die(_("could not parse HEAD commit"));
        }
        argc = parse_and_validate_options(argc, argv, builtin_commit_options,
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
                FILE *fp;
 +              int allow_fast_forward = 1;
  
                if (!reflog_msg)
                        reflog_msg = "commit (merge)";
                die(_("could not read commit message: %s"), strerror(saved_errno));
        }
  
 -      /* Truncate the message just before the diff, if any. */
 -      if (verbose)
 +      if (verbose || /* Truncate the message just before the diff, if any. */
 +          cleanup_mode == CLEANUP_SCISSORS)
                wt_status_truncate_message_at_cut_line(&sb);
  
        if (cleanup_mode != CLEANUP_NONE)
                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);
 -
        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 (!ref_lock) {
 -              rollback_index_files();
 -              die(_("cannot lock HEAD ref"));
 -      }
 -      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"));
                     "not exceeded, and then \"git reset HEAD\" to recover."));
  
        rerere(0);
 -      run_hook(get_index_file(), "post-commit", NULL);
 +      run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
        if (amend && !no_post_rewrite) {
                struct notes_rewrite_cfg *cfg;
                cfg = init_copy_notes_for_rewrite("amend");
        if (!quiet)
                print_summary(prefix, sha1, !current_head);
  
 +      strbuf_release(&err);
        return 0;
  }
diff --combined builtin/merge.c
index 9da9e30d9be46a5ed2de06e8545fe61537ba3676,d30cb60966855eaa5a419fd8ffc4f248c12fb094..5370412e00620d85a84d94795e9049fda573fb29
@@@ -28,6 -28,7 +28,7 @@@
  #include "remote.h"
  #include "fmt-merge-msg.h"
  #include "gpg-interface.h"
+ #include "sequencer.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -63,7 -64,7 +64,7 @@@ static int verbosity
  static int allow_rerere_auto;
  static int abort_current_merge;
  static int show_progress = -1;
 -static int default_to_upstream;
 +static int default_to_upstream = 1;
  static const char *sign_commit;
  
  static struct strategy all_strategy[] = {
@@@ -220,7 -221,7 +221,7 @@@ static struct option builtin_merge_opti
        OPT_BOOL(0, "abort", &abort_current_merge,
                N_("abort the current in-progress merge")),
        OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
 -      { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"),
 +      { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
          N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
        OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
        OPT_END()
@@@ -237,10 -238,11 +238,10 @@@ static void drop_save(void
  static int save_state(unsigned char *stash)
  {
        int len;
 -      struct child_process cp;
 +      struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf buffer = STRBUF_INIT;
        const char *argv[] = {"stash", "create", NULL};
  
 -      memset(&cp, 0, sizeof(cp));
        cp.argv = argv;
        cp.out = -1;
        cp.git_cmd = 1;
@@@ -366,7 -368,7 +367,7 @@@ static void squash_message(struct commi
                        sha1_to_hex(commit->object.sha1));
                pretty_print_commit(&ctx, commit, &out);
        }
 -      if (write(fd, out.buf, out.len) < 0)
 +      if (write_in_full(fd, out.buf, out.len) != out.len)
                die_errno(_("Writing SQUASH_MSG"));
        if (close(fd))
                die_errno(_("Finishing SQUASH_MSG"));
@@@ -397,7 -399,7 +398,7 @@@ static void finish(struct commit *head_
                        const char *argv_gc_auto[] = { "gc", "--auto", NULL };
                        update_ref(reflog_message.buf, "HEAD",
                                new_head, head, 0,
 -                              DIE_ON_ERR);
 +                              UPDATE_REFS_DIE_ON_ERR);
                        /*
                         * We ignore errors in 'gc --auto', since the
                         * user should see them.
        }
  
        /* Run a post-merge hook */
 -      run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
 +      run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
  
        strbuf_release(&reflog_message);
  }
@@@ -445,17 -447,17 +446,17 @@@ static void merge_name(const char *remo
                die(_("'%s' does not point to a commit"), remote);
  
        if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
 -              if (!prefixcmp(found_ref, "refs/heads/")) {
 +              if (starts_with(found_ref, "refs/heads/")) {
                        strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
                                    sha1_to_hex(branch_head), remote);
                        goto cleanup;
                }
 -              if (!prefixcmp(found_ref, "refs/tags/")) {
 +              if (starts_with(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/")) {
 +              if (starts_with(found_ref, "refs/remotes/")) {
                        strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
                                    sha1_to_hex(branch_head), remote);
                        goto cleanup;
@@@ -569,8 -571,8 +570,8 @@@ static int git_merge_config(const char 
  {
        int status;
  
 -      if (branch && !prefixcmp(k, "branch.") &&
 -              !prefixcmp(k + 7, branch) &&
 +      if (branch && starts_with(k, "branch.") &&
 +              starts_with(k + 7, branch) &&
                !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
                free(branch_mergeoptions);
                branch_mergeoptions = xstrdup(v);
        } else if (!strcmp(k, "merge.defaulttoupstream")) {
                default_to_upstream = git_config_bool(k, v);
                return 0;
 +      } else if (!strcmp(k, "commit.gpgsign")) {
 +              sign_commit = git_config_bool(k, v) ? "" : NULL;
 +              return 0;
        }
  
        status = fmt_merge_msg_config(k, v, cb);
@@@ -656,12 -655,14 +657,12 @@@ static int try_merge_strategy(const cha
                              struct commit_list *remoteheads,
                              struct commit *head, const char *head_arg)
  {
 -      int index_fd;
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
  
 -      index_fd = hold_locked_index(lock, 1);
 +      hold_locked_index(lock, 1);
        refresh_cache(REFRESH_QUIET);
        if (active_cache_changed &&
 -                      (write_cache(index_fd, active_cache, active_nr) ||
 -                       commit_locked_index(lock)))
 +          write_locked_index(&the_index, lock, COMMIT_LOCK))
                return error(_("Unable to write index."));
        rollback_lock_file(lock);
  
                int clean, x;
                struct commit *result;
                struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 -              int index_fd;
                struct commit_list *reversed = NULL;
                struct merge_options o;
                struct commit_list *j;
                for (j = common; j; j = j->next)
                        commit_list_insert(j->item, &reversed);
  
 -              index_fd = hold_locked_index(lock, 1);
 +              hold_locked_index(lock, 1);
                clean = merge_recursive(&o, head,
                                remoteheads->item, reversed, &result);
                if (active_cache_changed &&
 -                              (write_cache(index_fd, active_cache, active_nr) ||
 -                               commit_locked_index(lock)))
 +                  write_locked_index(&the_index, lock, COMMIT_LOCK))
                        die (_("unable to write %s"), get_index_file());
                rollback_lock_file(lock);
                return clean ? 0 : 1;
@@@ -819,8 -822,8 +820,8 @@@ static void prepare_to_commit(struct co
        if (0 < option_edit)
                strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char);
        write_merge_msg(&msg);
 -      if (run_hook(get_index_file(), "prepare-commit-msg",
 -                   git_path("MERGE_MSG"), "merge", NULL, NULL))
 +      if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg",
 +                          git_path("MERGE_MSG"), "merge", NULL))
                abort_commit(remoteheads, NULL);
        if (0 < option_edit) {
                if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
  static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
  {
        unsigned char result_tree[20], result_commit[20];
 -      struct commit_list *parent = xmalloc(sizeof(*parent));
 +      struct commit_list *parents, **pptr = &parents;
  
        write_tree_trivial(result_tree);
        printf(_("Wonderful.\n"));
 -      parent->item = head;
 -      parent->next = xmalloc(sizeof(*parent->next));
 -      parent->next->item = remoteheads->item;
 -      parent->next->next = NULL;
 +      pptr = commit_list_append(head, pptr);
 +      pptr = commit_list_append(remoteheads->item, pptr);
        prepare_to_commit(remoteheads);
 -      if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
 -                      sign_commit))
 +      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
 +                      result_commit, NULL, sign_commit))
                die(_("failed to write commit object"));
        finish(head, remoteheads, result_commit, "In-index merge");
        drop_save();
@@@ -870,8 -875,8 +871,8 @@@ static int finish_automerge(struct comm
                commit_list_insert(head, &parents);
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit(remoteheads);
 -      if (commit_tree(&merge_msg, result_tree, parents, result_commit,
 -                      NULL, sign_commit))
 +      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
 +                      result_commit, NULL, sign_commit))
                die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(head, remoteheads, result_commit, buf.buf);
        return 0;
  }
  
- static int suggest_conflicts(int renormalizing)
+ static int suggest_conflicts(void)
  {
        const char *filename;
        FILE *fp;
-       int pos;
+       struct strbuf msgbuf = STRBUF_INIT;
  
        filename = git_path("MERGE_MSG");
        fp = fopen(filename, "a");
        if (!fp)
                die_errno(_("Could not open '%s' for writing"), filename);
-       fprintf(fp, "\nConflicts:\n");
-       for (pos = 0; pos < active_nr; pos++) {
-               const struct cache_entry *ce = active_cache[pos];
-               if (ce_stage(ce)) {
-                       fprintf(fp, "\t%s\n", ce->name);
-                       while (pos + 1 < active_nr &&
-                                       !strcmp(ce->name,
-                                               active_cache[pos + 1]->name))
-                               pos++;
-               }
-       }
+       append_conflicts_hint(&msgbuf);
+       fputs(msgbuf.buf, fp);
        fclose(fp);
        rerere(allow_rerere_auto);
        printf(_("Automatic merge failed; "
@@@ -1102,7 -1098,7 +1094,7 @@@ int cmd_merge(int argc, const char **ar
         * current branch.
         */
        branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
 -      if (branch && !prefixcmp(branch, "refs/heads/"))
 +      if (branch && starts_with(branch, "refs/heads/"))
                branch += 11;
        if (!branch || is_null_sha1(head_sha1))
                head_commit = NULL;
                        die(_("%s - not something we can merge"), argv[0]);
                read_empty(remote_head->object.sha1, 0);
                update_ref("initial pull", "HEAD", remote_head->object.sha1,
 -                         NULL, 0, DIE_ON_ERR);
 +                         NULL, 0, UPDATE_REFS_DIE_ON_ERR);
                goto done;
        } else {
                struct strbuf merge_names = STRBUF_INIT;
                                printf(_("Commit %s has a good GPG signature by %s\n"),
                                       hex, signature_check.signer);
  
 -                      free(signature_check.gpg_output);
 -                      free(signature_check.gpg_status);
 -                      free(signature_check.signer);
 -                      free(signature_check.key);
 +                      signature_check_clear(&signature_check);
                }
        }
  
        }
  
        update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
 -                 NULL, 0, DIE_ON_ERR);
 +                 NULL, 0, UPDATE_REFS_DIE_ON_ERR);
  
        if (remoteheads && !common)
                ; /* No common ancestors found. We need a real merge. */
                fprintf(stderr, _("Automatic merge went well; "
                        "stopped before committing as requested\n"));
        else
-               ret = suggest_conflicts(option_renormalize);
+               ret = suggest_conflicts();
  
  done:
        free(branch_to_free);
diff --combined sequencer.c
index 5e8a207474bc6971a0de4f9ff6160a5681ac7b6d,1d97da3ca478f70cee1ccd5a23a0773ca47bec49..f00fd7ce0358a8a8ace379cd874174a0b6b7d2db
@@@ -41,7 -41,7 +41,7 @@@ static int is_cherry_picked_from_line(c
         * We only care that it looks roughly like (cherry picked from ...)
         */
        return len > strlen(cherry_picked_prefix) + 1 &&
 -              !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')';
 +              starts_with(buf, cherry_picked_prefix) && buf[len - 1] == ')';
  }
  
  /*
@@@ -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 (!prefixcmp(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,35 -272,38 +237,52 @@@ 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 */
 -      ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
 -                                         0, NULL);
 +              exit(128); /* the callee should have complained already */
 +
        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;
  }
  
+ void append_conflicts_hint(struct strbuf *msgbuf)
+ {
+       int i;
+       strbuf_addch(msgbuf, '\n');
+       strbuf_commented_addf(msgbuf, "Conflicts:\n");
+       for (i = 0; i < active_nr;) {
+               const struct cache_entry *ce = active_cache[i++];
+               if (ce_stage(ce)) {
+                       strbuf_commented_addf(msgbuf, "\t%s\n", ce->name);
+                       while (i < active_nr && !strcmp(ce->name,
+                                                       active_cache[i]->name))
+                               i++;
+               }
+       }
+ }
  static int do_recursive_merge(struct commit *base, struct commit *next,
                              const char *base_label, const char *next_label,
                              unsigned char *head, struct strbuf *msgbuf,
  {
        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);
        if (opts->signoff)
                append_signoff(msgbuf, 0, 0);
  
-       if (!clean) {
-               int i;
-               strbuf_addstr(msgbuf, "\nConflicts:\n");
-               for (i = 0; i < active_nr;) {
-                       const struct cache_entry *ce = active_cache[i++];
-                       if (ce_stage(ce)) {
-                               strbuf_addch(msgbuf, '\t');
-                               strbuf_addstr(msgbuf, ce->name);
-                               strbuf_addch(msgbuf, '\n');
-                               while (i < active_nr && !strcmp(ce->name,
-                                               active_cache[i]->name))
-                                       i++;
-                       }
-               }
-       }
+       if (!clean)
+               append_conflicts_hint(msgbuf);
  
        return !clean;
  }
@@@ -350,7 -376,9 +354,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);
@@@ -373,8 -401,6 +377,8 @@@ static int run_git_commit(const char *d
        argv_array_push(&array, "commit");
        argv_array_push(&array, "-n");
  
 +      if (opts->gpg_sign)
 +              argv_array_pushf(&array, "-S%s", opts->gpg_sign);
        if (opts->signoff)
                argv_array_push(&array, "-s");
        if (!opts->edit) {
@@@ -456,7 -482,7 +460,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;
@@@ -650,8 -676,9 +654,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);
@@@ -667,12 -694,10 +671,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;
  }
@@@ -684,10 -709,10 +688,10 @@@ static struct commit *parse_insn_line(c
        char *end_of_object_name;
        int saved, status, padding;
  
 -      if (!prefixcmp(bol, "pick")) {
 +      if (starts_with(bol, "pick")) {
                action = REPLAY_PICK;
                bol += strlen("pick");
 -      } else if (!prefixcmp(bol, "revert")) {
 +      } else if (starts_with(bol, "revert")) {
                action = REPLAY_REVERT;
                bol += strlen("revert");
        } else
@@@ -787,8 -812,6 +791,8 @@@ static int populate_opts_cb(const char 
                opts->mainline = git_config_int(key, value);
        else if (!strcmp(key, "options.strategy"))
                git_config_string(&opts->strategy, key, value);
 +      else if (!strcmp(key, "options.gpg-sign"))
 +              git_config_string(&opts->gpg_sign, key, value);
        else if (!strcmp(key, "options.strategy-option")) {
                ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
                opts->xopts[opts->xopts_nr++] = xstrdup(value);
@@@ -962,8 -985,6 +966,8 @@@ static void save_opts(struct replay_opt
        }
        if (opts->strategy)
                git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
 +      if (opts->gpg_sign)
 +              git_config_set_in_file(opts_file, "options.gpg-sign", opts->gpg_sign);
        if (opts->xopts) {
                int i;
                for (i = 0; i < opts->xopts_nr; i++)
diff --combined sequencer.h
index db43e9cf86dcc0914a674adca63303c44e2ccc24,c53519d9c0c0c5881c1f62e757ae48a24699fcae..5ed5cb1d97ced70a9ebffd1dad524a12e7f7810a
@@@ -37,8 -37,6 +37,8 @@@ struct replay_opts 
  
        int mainline;
  
 +      const char *gpg_sign;
 +
        /* Merge strategy */
        const char *strategy;
        const char **xopts;
@@@ -53,5 -51,6 +53,6 @@@ int sequencer_pick_revisions(struct rep
  extern const char sign_off_header[];
  
  void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
+ void append_conflicts_hint(struct strbuf *msgbuf);
  
  #endif