Merge branch 'cc/find-commit-subject'
authorJunio C Hamano <gitster@pobox.com>
Wed, 18 Aug 2010 19:46:55 +0000 (12:46 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 18 Aug 2010 19:46:55 +0000 (12:46 -0700)
* cc/find-commit-subject:
blame: use find_commit_subject() instead of custom code
merge-recursive: use find_commit_subject() instead of custom code
bisect: use find_commit_subject() instead of custom code
revert: rename variables related to subject in get_message()
revert: refactor code to find commit subject in find_commit_subject()
revert: fix off by one read when searching the end of a commit subject

1  2 
builtin/blame.c
builtin/revert.c
commit.c
commit.h
merge-recursive.c
diff --combined builtin/blame.c
index 01e62fdeb00b2d085efe96a07f2ee77776081a69,8dca385f1fd8f873c0b223b533be1b42dbffb5fd..437b1a433a78325cfbc61d2f76b5a114b049e679
@@@ -20,7 -20,6 +20,7 @@@
  #include "mailmap.h"
  #include "parse-options.h"
  #include "utf8.h"
 +#include "userdiff.h"
  
  static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
  
@@@ -86,51 -85,17 +86,51 @@@ struct origin 
        char path[FLEX_ARRAY];
  };
  
 +/*
 + * Prepare diff_filespec and convert it using diff textconv API
 + * if the textconv driver exists.
 + * Return 1 if the conversion succeeds, 0 otherwise.
 + */
 +int textconv_object(const char *path,
 +                  const unsigned char *sha1,
 +                  char **buf,
 +                  unsigned long *buf_size)
 +{
 +      struct diff_filespec *df;
 +      struct userdiff_driver *textconv;
 +
 +      df = alloc_filespec(path);
 +      fill_filespec(df, sha1, S_IFREG | 0664);
 +      textconv = get_textconv(df);
 +      if (!textconv) {
 +              free_filespec(df);
 +              return 0;
 +      }
 +
 +      *buf_size = fill_textconv(textconv, df, buf);
 +      free_filespec(df);
 +      return 1;
 +}
 +
  /*
   * Given an origin, prepare mmfile_t structure to be used by the
   * diff machinery
   */
 -static void fill_origin_blob(struct origin *o, mmfile_t *file)
 +static void fill_origin_blob(struct diff_options *opt,
 +                           struct origin *o, mmfile_t *file)
  {
        if (!o->file.ptr) {
                enum object_type type;
 +              unsigned long file_size;
 +
                num_read_blob++;
 -              file->ptr = read_sha1_file(o->blob_sha1, &type,
 -                                         (unsigned long *)(&(file->size)));
 +              if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
 +                  textconv_object(o->path, o->blob_sha1, &file->ptr, &file_size))
 +                      ;
 +              else
 +                      file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
 +              file->size = file_size;
 +
                if (!file->ptr)
                        die("Cannot read blob %s for path %s",
                            sha1_to_hex(o->blob_sha1),
@@@ -317,6 -282,7 +317,6 @@@ static struct origin *get_origin(struc
  static int fill_blob_sha1(struct origin *origin)
  {
        unsigned mode;
 -
        if (!is_null_sha1(origin->blob_sha1))
                return 0;
        if (get_tree_entry(origin->commit->object.sha1,
@@@ -767,17 -733,16 +767,17 @@@ static int pass_blame_to_parent(struct 
  {
        int last_in_target;
        mmfile_t file_p, file_o;
 -      struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
 +      struct blame_chunk_cb_data d;
        xpparam_t xpp;
        xdemitconf_t xecfg;
 -
 +      memset(&d, 0, sizeof(d));
 +      d.sb = sb; d.target = target; d.parent = parent;
        last_in_target = find_last_in_target(sb, target);
        if (last_in_target < 0)
                return 1; /* nothing remains for this target */
  
 -      fill_origin_blob(parent, &file_p);
 -      fill_origin_blob(target, &file_o);
 +      fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
 +      fill_origin_blob(&sb->revs->diffopt, target, &file_o);
        num_get_patch++;
  
        memset(&xpp, 0, sizeof(xpp));
@@@ -910,11 -875,10 +910,11 @@@ static void find_copy_in_blob(struct sc
        const char *cp;
        int cnt;
        mmfile_t file_o;
 -      struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 };
 +      struct handle_split_cb_data d;
        xpparam_t xpp;
        xdemitconf_t xecfg;
 -
 +      memset(&d, 0, sizeof(d));
 +      d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
        /*
         * Prepare mmfile that contains only the lines in ent.
         */
@@@ -958,7 -922,7 +958,7 @@@ static int find_move_in_parent(struct s
        if (last_in_target < 0)
                return 1; /* nothing remains for this target */
  
 -      fill_origin_blob(parent, &file_p);
 +      fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
        if (!file_p.ptr)
                return 0;
  
@@@ -1099,7 -1063,7 +1099,7 @@@ static int find_copy_in_parent(struct s
  
                        norigin = get_origin(sb, parent, p->one->path);
                        hashcpy(norigin->blob_sha1, p->one->sha1);
 -                      fill_origin_blob(norigin, &file_p);
 +                      fill_origin_blob(&sb->revs->diffopt, norigin, &file_p);
                        if (!file_p.ptr)
                                continue;
  
@@@ -1407,7 -1371,8 +1407,8 @@@ static void get_commit_info(struct comm
                            int detailed)
  {
        int len;
-       char *tmp, *endp, *reencoded, *message;
+       const char *subject;
+       char *reencoded, *message;
        static char author_name[1024];
        static char author_mail[1024];
        static char committer_name[1024];
                    &ret->committer_time, &ret->committer_tz);
  
        ret->summary = summary_buf;
-       tmp = strstr(message, "\n\n");
-       if (!tmp) {
-       error_out:
+       len = find_commit_subject(message, &subject);
+       if (len && len < sizeof(summary_buf)) {
+               memcpy(summary_buf, subject, len);
+               summary_buf[len] = 0;
+       } else {
                sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
-               free(reencoded);
-               return;
        }
-       tmp += 2;
-       endp = strchr(tmp, '\n');
-       if (!endp)
-               endp = tmp + strlen(tmp);
-       len = endp - tmp;
-       if (len >= sizeof(summary_buf) || len == 0)
-               goto error_out;
-       memcpy(summary_buf, tmp, len);
-       summary_buf[len] = 0;
        free(reencoded);
  }
  
@@@ -2019,16 -1975,6 +2011,16 @@@ static int git_blame_config(const char 
                blame_date_mode = parse_date_format(value);
                return 0;
        }
 +
 +      switch (userdiff_config(var, value)) {
 +      case 0:
 +              break;
 +      case -1:
 +              return -1;
 +      default:
 +              return 0;
 +      }
 +
        return git_default_config(var, value, cb);
  }
  
   * Prepare a dummy commit that represents the work tree (or staged) item.
   * Note that annotating work tree item never works in the reverse.
   */
 -static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
 +static struct commit *fake_working_tree_commit(struct diff_options *opt,
 +                                             const char *path,
 +                                             const char *contents_from)
  {
        struct commit *commit;
        struct origin *origin;
        if (!contents_from || strcmp("-", contents_from)) {
                struct stat st;
                const char *read_from;
 +              unsigned long buf_len;
  
                if (contents_from) {
                        if (stat(contents_from, &st) < 0)
                        read_from = path;
                }
                mode = canon_mode(st.st_mode);
 +
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
 -                      if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
 +                      if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
 +                          textconv_object(read_from, null_sha1, &buf.buf, &buf_len))
 +                              buf.len = buf_len;
 +                      else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
                                die_errno("cannot open or read '%s'", read_from);
                        break;
                case S_IFLNK:
@@@ -2301,7 -2240,6 +2293,7 @@@ int cmd_blame(int argc, const char **ar
        git_config(git_blame_config, NULL);
        init_revisions(&revs, NULL);
        revs.date_mode = blame_date_mode;
 +      DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
  
        save_commit_buffer = 0;
        dashdash_pos = 0;
@@@ -2438,8 -2376,7 +2430,8 @@@ parse_done
                 * or "--contents".
                 */
                setup_work_tree();
 -              sb.final = fake_working_tree_commit(path, contents_from);
 +              sb.final = fake_working_tree_commit(&sb.revs->diffopt,
 +                                                  path, contents_from);
                add_pending_object(&revs, &(sb.final->object), ":");
        }
        else if (contents_from)
                if (fill_blob_sha1(o))
                        die("no such path %s in %s", path, final_commit_name);
  
 -              sb.final_buf = read_sha1_file(o->blob_sha1, &type,
 -                                            &sb.final_buf_size);
 +              if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
 +                  textconv_object(path, o->blob_sha1, (char **) &sb.final_buf,
 +                                  &sb.final_buf_size))
 +                      ;
 +              else
 +                      sb.final_buf = read_sha1_file(o->blob_sha1, &type,
 +                                                    &sb.final_buf_size);
 +
                if (!sb.final_buf)
                        die("Cannot read blob %s for path %s",
                            sha1_to_hex(o->blob_sha1),
diff --combined builtin/revert.c
index 8b9d829a73a0862dfdf2c2499b18878489620f16,6d1b9ca1a5889525bdc7b1464f3bd8801093b1c1..9215e665046a8991b57f8d4cfda123dae084414c
@@@ -39,34 -39,29 +39,34 @@@ static const char * const cherry_pick_u
  static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
  static enum { REVERT, CHERRY_PICK } action;
  static struct commit *commit;
 -static const char *commit_name;
 +static int commit_argc;
 +static const char **commit_argv;
  static int allow_rerere_auto;
  
  static const char *me;
 +static const char *strategy;
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
  static char *get_encoding(const char *message);
  
 +static const char * const *revert_or_cherry_pick_usage(void)
 +{
 +      return action == REVERT ? revert_usage : cherry_pick_usage;
 +}
 +
  static void parse_args(int argc, const char **argv)
  {
 -      const char * const * usage_str =
 -              action == REVERT ?  revert_usage : cherry_pick_usage;
 -      unsigned char sha1[20];
 +      const char * const * usage_str = revert_or_cherry_pick_usage();
        int noop;
        struct option options[] = {
                OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
                OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
 -              OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"),
                OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
                OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
                OPT_INTEGER('m', "mainline", &mainline, "parent number"),
                OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
 +              OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
                OPT_END(),
                OPT_END(),
                OPT_END(),
@@@ -74,7 -69,6 +74,7 @@@
  
        if (action == CHERRY_PICK) {
                struct option cp_extra[] = {
 +                      OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
                        OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
                        OPT_END(),
                };
                        die("program error");
        }
  
 -      if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1)
 +      commit_argc = parse_options(argc, argv, NULL, options, usage_str,
 +                                  PARSE_OPT_KEEP_ARGV0 |
 +                                  PARSE_OPT_KEEP_UNKNOWN);
 +      if (commit_argc < 2)
                usage_with_options(usage_str, options);
  
 -      commit_name = argv[0];
 -      if (get_sha1(commit_name, sha1))
 -              die ("Cannot find '%s'", commit_name);
 -      commit = lookup_commit_reference(sha1);
 -      if (!commit)
 -              exit(1);
 +      commit_argv = argv;
  }
  
  struct commit_message {
  static int get_message(const char *raw_message, struct commit_message *out)
  {
        const char *encoding;
-       const char *p, *abbrev, *eol;
+       const char *abbrev, *subject;
+       int abbrev_len, subject_len;
        char *q;
-       int abbrev_len, oneline_len;
  
        if (!raw_message)
                return -1;
        abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
        abbrev_len = strlen(abbrev);
  
-       /* Find beginning and end of commit subject. */
-       p = out->message;
-       while (*p && (*p != '\n' || p[1] != '\n'))
-               p++;
-       if (*p) {
-               p += 2;
-               for (eol = p + 1; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
-       } else
-               eol = p;
-       oneline_len = eol - p;
+       subject_len = find_commit_subject(out->message, &subject);
  
        out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
-                             strlen("... ") + oneline_len + 1);
+                             strlen("... ") + subject_len + 1);
        q = out->parent_label;
        q = mempcpy(q, "parent of ", strlen("parent of "));
        out->label = q;
        q = mempcpy(q, abbrev, abbrev_len);
        q = mempcpy(q, "... ", strlen("... "));
        out->subject = q;
-       q = mempcpy(q, p, oneline_len);
+       q = mempcpy(q, subject, subject_len);
        *q = '\0';
        return 0;
  }
@@@ -178,17 -164,28 +168,17 @@@ static char *get_encoding(const char *m
        return NULL;
  }
  
 -static struct lock_file msg_file;
 -static int msg_fd;
 -
 -static void add_to_msg(const char *string)
 -{
 -      int len = strlen(string);
 -      if (write_in_full(msg_fd, string, len) < 0)
 -              die_errno ("Could not write to MERGE_MSG");
 -}
 -
 -static void add_message_to_msg(const char *message)
 +static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
  {
        const char *p = message;
        while (*p && (*p != '\n' || p[1] != '\n'))
                p++;
  
        if (!*p)
 -              add_to_msg(sha1_to_hex(commit->object.sha1));
 +              strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1));
  
        p += 2;
 -      add_to_msg(p);
 -      return;
 +      strbuf_addstr(msgbuf, p);
  }
  
  static void set_author_ident_env(const char *message)
                        sha1_to_hex(commit->object.sha1));
  }
  
 -static char *help_msg(const char *name)
 +static char *help_msg(void)
  {
        struct strbuf helpbuf = STRBUF_INIT;
        char *msg = getenv("GIT_CHERRY_PICK_HELP");
                strbuf_addf(&helpbuf, " with: \n"
                        "\n"
                        "        git commit -c %s\n",
 -                      name);
 +                          sha1_to_hex(commit->object.sha1));
        }
        else
                strbuf_addch(&helpbuf, '.');
        return strbuf_detach(&helpbuf, NULL);
  }
  
 +static void write_message(struct strbuf *msgbuf, const char *filename)
 +{
 +      static struct lock_file msg_file;
 +
 +      int msg_fd = hold_lock_file_for_update(&msg_file, filename,
 +                                             LOCK_DIE_ON_ERROR);
 +      if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
 +              die_errno("Could not write to %s.", filename);
 +      strbuf_release(msgbuf);
 +      if (commit_lock_file(&msg_file) < 0)
 +              die("Error wrapping up %s", filename);
 +}
 +
  static struct tree *empty_tree(void)
  {
        struct tree *tree = xcalloc(1, sizeof(struct tree));
@@@ -311,71 -295,40 +301,71 @@@ static int fast_forward_to(const unsign
        return write_ref_sha1(ref_lock, to, "cherry-pick");
  }
  
 -static int revert_or_cherry_pick(int argc, const char **argv)
 +static void do_recursive_merge(struct commit *base, struct commit *next,
 +                             const char *base_label, const char *next_label,
 +                             unsigned char *head, struct strbuf *msgbuf,
 +                             char *defmsg)
  {
 -      unsigned char head[20];
 -      struct commit *base, *next, *parent;
 -      const char *base_label, *next_label;
 -      int i, index_fd, clean;
 -      struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
 -      char *defmsg = NULL;
        struct merge_options o;
        struct tree *result, *next_tree, *base_tree, *head_tree;
 +      int clean, index_fd;
        static struct lock_file index_lock;
  
 -      git_config(git_default_config, NULL);
 -      me = action == REVERT ? "revert" : "cherry-pick";
 -      setenv(GIT_REFLOG_ACTION, me, 0);
 -      parse_args(argc, argv);
 +      index_fd = hold_locked_index(&index_lock, 1);
  
 -      /* this is copied from the shell script, but it's never triggered... */
 -      if (action == REVERT && !no_replay)
 -              die("revert is incompatible with replay");
 +      read_cache();
 +      init_merge_options(&o);
 +      o.ancestor = base ? base_label : "(empty tree)";
 +      o.branch1 = "HEAD";
 +      o.branch2 = next ? next_label : "(empty tree)";
  
 -      if (allow_ff) {
 -              if (signoff)
 -                      die("cherry-pick --ff cannot be used with --signoff");
 -              if (no_commit)
 -                      die("cherry-pick --ff cannot be used with --no-commit");
 -              if (no_replay)
 -                      die("cherry-pick --ff cannot be used with -x");
 -              if (edit)
 -                      die("cherry-pick --ff cannot be used with --edit");
 +      head_tree = parse_tree_indirect(head);
 +      next_tree = next ? next->tree : empty_tree();
 +      base_tree = base ? base->tree : empty_tree();
 +
 +      clean = merge_trees(&o,
 +                          head_tree,
 +                          next_tree, base_tree, &result);
 +
 +      if (active_cache_changed &&
 +          (write_cache(index_fd, active_cache, active_nr) ||
 +           commit_locked_index(&index_lock)))
 +              die("%s: Unable to write new index file", me);
 +      rollback_lock_file(&index_lock);
 +
 +      if (!clean) {
 +              int i;
 +              strbuf_addstr(msgbuf, "\nConflicts:\n\n");
 +              for (i = 0; i < active_nr;) {
 +                      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++;
 +                      }
 +              }
 +              write_message(msgbuf, defmsg);
 +              fprintf(stderr, "Automatic %s failed.%s\n",
 +                      me, help_msg());
 +              rerere(allow_rerere_auto);
 +              exit(1);
        }
 +      write_message(msgbuf, defmsg);
 +      fprintf(stderr, "Finished one %s.\n", me);
 +}
 +
 +static int do_pick_commit(void)
 +{
 +      unsigned char head[20];
 +      struct commit *base, *next, *parent;
 +      const char *base_label, *next_label;
 +      struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
 +      char *defmsg = NULL;
 +      struct strbuf msgbuf = STRBUF_INIT;
  
 -      if (read_cache() < 0)
 -              die("git %s: failed to read the index", me);
        if (no_commit) {
                /*
                 * We do not intend to commit immediately.  We just want to
         */
  
        defmsg = git_pathdup("MERGE_MSG");
 -      msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
 -                                         LOCK_DIE_ON_ERROR);
 -
 -      index_fd = hold_locked_index(&index_lock, 1);
  
        if (action == REVERT) {
                base = commit;
                base_label = msg.label;
                next = parent;
                next_label = msg.parent_label;
 -              add_to_msg("Revert \"");
 -              add_to_msg(msg.subject);
 -              add_to_msg("\"\n\nThis reverts commit ");
 -              add_to_msg(sha1_to_hex(commit->object.sha1));
 +              strbuf_addstr(&msgbuf, "Revert \"");
 +              strbuf_addstr(&msgbuf, msg.subject);
 +              strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
 +              strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
  
                if (commit->parents->next) {
 -                      add_to_msg(", reversing\nchanges made to ");
 -                      add_to_msg(sha1_to_hex(parent->object.sha1));
 +                      strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
 +                      strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
                }
 -              add_to_msg(".\n");
 +              strbuf_addstr(&msgbuf, ".\n");
        } else {
                base = parent;
                base_label = msg.parent_label;
                next = commit;
                next_label = msg.label;
                set_author_ident_env(msg.message);
 -              add_message_to_msg(msg.message);
 +              add_message_to_msg(&msgbuf, msg.message);
                if (no_replay) {
 -                      add_to_msg("(cherry picked from commit ");
 -                      add_to_msg(sha1_to_hex(commit->object.sha1));
 -                      add_to_msg(")\n");
 +                      strbuf_addstr(&msgbuf, "(cherry picked from commit ");
 +                      strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
 +                      strbuf_addstr(&msgbuf, ")\n");
                }
        }
  
 -      read_cache();
 -      init_merge_options(&o);
 -      o.ancestor = base ? base_label : "(empty tree)";
 -      o.branch1 = "HEAD";
 -      o.branch2 = next ? next_label : "(empty tree)";
 -
 -      head_tree = parse_tree_indirect(head);
 -      next_tree = next ? next->tree : empty_tree();
 -      base_tree = base ? base->tree : empty_tree();
 -
 -      clean = merge_trees(&o,
 -                          head_tree,
 -                          next_tree, base_tree, &result);
 -
 -      if (active_cache_changed &&
 -          (write_cache(index_fd, active_cache, active_nr) ||
 -           commit_locked_index(&index_lock)))
 -              die("%s: Unable to write new index file", me);
 -      rollback_lock_file(&index_lock);
 -
 -      if (!clean) {
 -              add_to_msg("\nConflicts:\n\n");
 -              for (i = 0; i < active_nr;) {
 -                      struct cache_entry *ce = active_cache[i++];
 -                      if (ce_stage(ce)) {
 -                              add_to_msg("\t");
 -                              add_to_msg(ce->name);
 -                              add_to_msg("\n");
 -                              while (i < active_nr && !strcmp(ce->name,
 -                                              active_cache[i]->name))
 -                                      i++;
 -                      }
 +      if (!strategy || !strcmp(strategy, "recursive") || action == REVERT)
 +              do_recursive_merge(base, next, base_label, next_label,
 +                                 head, &msgbuf, defmsg);
 +      else {
 +              int res;
 +              struct commit_list *common = NULL;
 +              struct commit_list *remotes = NULL;
 +              write_message(&msgbuf, defmsg);
 +              commit_list_insert(base, &common);
 +              commit_list_insert(next, &remotes);
 +              res = try_merge_command(strategy, common,
 +                                      sha1_to_hex(head), remotes);
 +              free_commit_list(common);
 +              free_commit_list(remotes);
 +              if (res) {
 +                      fprintf(stderr, "Automatic %s with strategy %s failed.%s\n",
 +                              me, strategy, help_msg());
 +                      rerere(allow_rerere_auto);
 +                      exit(1);
                }
 -              if (commit_lock_file(&msg_file) < 0)
 -                      die ("Error wrapping up %s", defmsg);
 -              fprintf(stderr, "Automatic %s failed.%s\n",
 -                      me, help_msg(commit_name));
 -              rerere(allow_rerere_auto);
 -              exit(1);
        }
 -      if (commit_lock_file(&msg_file) < 0)
 -              die ("Error wrapping up %s", defmsg);
 -      fprintf(stderr, "Finished one %s.\n", me);
 +
 +      free_message(&msg);
  
        /*
         *
        if (!no_commit) {
                /* 6 is max possible length of our args array including NULL */
                const char *args[6];
 +              int res;
                int i = 0;
 +
                args[i++] = "commit";
                args[i++] = "-n";
                if (signoff)
                        args[i++] = defmsg;
                }
                args[i] = NULL;
 -              return execv_git_cmd(args);
 +              res = run_command_v_opt(args, RUN_GIT_CMD);
 +              free(defmsg);
 +
 +              return res;
        }
 -      free_message(&msg);
 +
        free(defmsg);
  
        return 0;
  }
  
 +static void prepare_revs(struct rev_info *revs)
 +{
 +      int argc;
 +
 +      init_revisions(revs, NULL);
 +      revs->no_walk = 1;
 +      if (action != REVERT)
 +              revs->reverse = 1;
 +
 +      argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
 +      if (argc > 1)
 +              usage(*revert_or_cherry_pick_usage());
 +
 +      if (prepare_revision_walk(revs))
 +              die("revision walk setup failed");
 +
 +      if (!revs->commits)
 +              die("empty commit set passed");
 +}
 +
 +static int revert_or_cherry_pick(int argc, const char **argv)
 +{
 +      struct rev_info revs;
 +
 +      git_config(git_default_config, NULL);
 +      me = action == REVERT ? "revert" : "cherry-pick";
 +      setenv(GIT_REFLOG_ACTION, me, 0);
 +      parse_args(argc, argv);
 +
 +      if (allow_ff) {
 +              if (signoff)
 +                      die("cherry-pick --ff cannot be used with --signoff");
 +              if (no_commit)
 +                      die("cherry-pick --ff cannot be used with --no-commit");
 +              if (no_replay)
 +                      die("cherry-pick --ff cannot be used with -x");
 +              if (edit)
 +                      die("cherry-pick --ff cannot be used with --edit");
 +      }
 +
 +      if (read_cache() < 0)
 +              die("git %s: failed to read the index", me);
 +
 +      prepare_revs(&revs);
 +
 +      while ((commit = get_revision(&revs))) {
 +              int res = do_pick_commit();
 +              if (res)
 +                      return res;
 +      }
 +
 +      return 0;
 +}
 +
  int cmd_revert(int argc, const char **argv, const char *prefix)
  {
        if (isatty(0))
                edit = 1;
 -      no_replay = 1;
        action = REVERT;
        return revert_or_cherry_pick(argc, argv);
  }
  
  int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
  {
 -      no_replay = 0;
        action = CHERRY_PICK;
        return revert_or_cherry_pick(argc, argv);
  }
diff --combined commit.c
index e9b07509678f5a4f61e5bedadea14b726e290ed1,fa1a5bac30c9560fa304a77513c38b5bea49ea49..0094ec1c9278332f736d2814c847b272788a7dd3
+++ b/commit.c
@@@ -315,6 -315,25 +315,25 @@@ int parse_commit(struct commit *item
        return ret;
  }
  
+ int find_commit_subject(const char *commit_buffer, const char **subject)
+ {
+       const char *eol;
+       const char *p = commit_buffer;
+       while (*p && (*p != '\n' || p[1] != '\n'))
+               p++;
+       if (*p) {
+               p += 2;
+               for (eol = p; *eol && *eol != '\n'; eol++)
+                       ; /* do nothing */
+       } else
+               eol = p;
+       *subject = p;
+       return eol - p;
+ }
  struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
  {
        struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
@@@ -790,58 -809,3 +809,58 @@@ struct commit_list *reduce_heads(struc
        free(other);
        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(const char *msg, unsigned char *tree,
 +              struct commit_list *parents, unsigned char *ret,
 +              const char *author)
 +{
 +      int result;
 +      int encoding_is_utf8;
 +      struct strbuf buffer;
 +
 +      assert_sha1_type(tree, OBJ_TREE);
 +
 +      /* Not having i18n.commitencoding is the same as having utf-8 */
 +      encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
 +
 +      strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
 +      strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
 +
 +      /*
 +       * NOTE! This ordering means that the same exact tree merged with a
 +       * different order of parents will be a _different_ changeset even
 +       * if everything else stays the same.
 +       */
 +      while (parents) {
 +              struct commit_list *next = parents->next;
 +              strbuf_addf(&buffer, "parent %s\n",
 +                      sha1_to_hex(parents->item->object.sha1));
 +              free(parents);
 +              parents = next;
 +      }
 +
 +      /* Person/date information */
 +      if (!author)
 +              author = git_author_info(IDENT_ERROR_ON_NO_NAME);
 +      strbuf_addf(&buffer, "author %s\n", author);
 +      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);
 +      strbuf_addch(&buffer, '\n');
 +
 +      /* And add the comment */
 +      strbuf_addstr(&buffer, msg);
 +
 +      /* And check the encoding */
 +      if (encoding_is_utf8 && !is_utf8(buffer.buf))
 +              fprintf(stderr, commit_utf8_warn);
 +
 +      result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
 +      strbuf_release(&buffer);
 +      return result;
 +}
diff --combined commit.h
index eb2b8ac3cd5f375e70354e8c364abd036b0966ed,236cf1333abc087034028c3e1863cf75cda5026c..9113bbe4889d71e824348edcb920110598db18d2
+++ b/commit.h
@@@ -28,7 -28,6 +28,7 @@@ extern const char *commit_type
  extern struct decoration name_decoration;
  struct name_decoration {
        struct name_decoration *next;
 +      int type;
        char name[1];
  };
  
@@@ -41,6 -40,9 +41,9 @@@ int parse_commit_buffer(struct commit *
  
  int parse_commit(struct commit *item);
  
+ /* Find beginning and length of commit subject. */
+ int find_commit_subject(const char *commit_buffer, const char **subject);
  struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
  unsigned commit_list_count(const struct commit_list *l);
  struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
@@@ -61,7 -63,7 +64,7 @@@ enum cmit_fmt 
        CMIT_FMT_EMAIL,
        CMIT_FMT_USERFORMAT,
  
 -      CMIT_FMT_UNSPECIFIED,
 +      CMIT_FMT_UNSPECIFIED
  };
  
  struct pretty_print_context
@@@ -164,8 -166,4 +167,8 @@@ 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_list *parents, unsigned char *ret,
 +              const char *author);
 +
  #endif /* COMMIT_H */
diff --combined merge-recursive.c
index fb6aa4a551802de07be76bd838b6f22a236457ff,34837bd5d4111eb73715055f7d547a5a592835b4..7635659969f498b7a00b5019da32593bf7b9ba89
@@@ -136,16 -136,10 +136,10 @@@ static void output_commit_title(struct 
                if (parse_commit(commit) != 0)
                        printf("(bad commit)\n");
                else {
-                       const char *s;
-                       int len;
-                       for (s = commit->buffer; *s; s++)
-                               if (*s == '\n' && s[1] == '\n') {
-                                       s += 2;
-                                       break;
-                               }
-                       for (len = 0; s[len] && '\n' != s[len]; len++)
-                               ; /* do nothing */
-                       printf("%.*s\n", len, s);
+                       const char *title;
+                       int len = find_commit_subject(commit->buffer, &title);
+                       if (len)
+                               printf("%.*s\n", len, title);
                }
        }
  }
@@@ -238,9 -232,9 +232,9 @@@ static int save_files_dirs(const unsign
        newpath[baselen + len] = '\0';
  
        if (S_ISDIR(mode))
 -              string_list_insert(newpath, &o->current_directory_set);
 +              string_list_insert(&o->current_directory_set, newpath);
        else
 -              string_list_insert(newpath, &o->current_file_set);
 +              string_list_insert(&o->current_file_set, newpath);
        free(newpath);
  
        return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
@@@ -271,7 -265,7 +265,7 @@@ static struct stage_data *insert_stage_
                        e->stages[2].sha, &e->stages[2].mode);
        get_tree_entry(b->object.sha1, path,
                        e->stages[3].sha, &e->stages[3].mode);
 -      item = string_list_insert(path, entries);
 +      item = string_list_insert(entries, path);
        item->util = e;
        return e;
  }
@@@ -294,9 -288,9 +288,9 @@@ static struct string_list *get_unmerged
                if (!ce_stage(ce))
                        continue;
  
 -              item = string_list_lookup(ce->name, unmerged);
 +              item = string_list_lookup(unmerged, ce->name);
                if (!item) {
 -                      item = string_list_insert(ce->name, unmerged);
 +                      item = string_list_insert(unmerged, ce->name);
                        item->util = xcalloc(1, sizeof(struct stage_data));
                }
                e = item->util;
@@@ -356,20 -350,20 +350,20 @@@ static struct string_list *get_renames(
                re = xmalloc(sizeof(*re));
                re->processed = 0;
                re->pair = pair;
 -              item = string_list_lookup(re->pair->one->path, entries);
 +              item = string_list_lookup(entries, re->pair->one->path);
                if (!item)
                        re->src_entry = insert_stage_data(re->pair->one->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->src_entry = item->util;
  
 -              item = string_list_lookup(re->pair->two->path, entries);
 +              item = string_list_lookup(entries, re->pair->two->path);
                if (!item)
                        re->dst_entry = insert_stage_data(re->pair->two->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->dst_entry = item->util;
 -              item = string_list_insert(pair->one->path, renames);
 +              item = string_list_insert(renames, pair->one->path);
                item->util = re;
        }
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
@@@ -432,7 -426,7 +426,7 @@@ static char *unique_path(struct merge_o
               lstat(newpath, &st) == 0)
                sprintf(p, "_%d", suffix++);
  
 -      string_list_insert(newpath, &o->current_file_set);
 +      string_list_insert(&o->current_file_set, newpath);
        return newpath;
  }
  
@@@ -811,12 -805,12 +805,12 @@@ static int process_renames(struct merge
  
        for (i = 0; i < a_renames->nr; i++) {
                sre = a_renames->items[i].util;
 -              string_list_insert(sre->pair->two->path, &a_by_dst)->util
 +              string_list_insert(&a_by_dst, sre->pair->two->path)->util
                        = sre->dst_entry;
        }
        for (i = 0; i < b_renames->nr; i++) {
                sre = b_renames->items[i].util;
 -              string_list_insert(sre->pair->two->path, &b_by_dst)->util
 +              string_list_insert(&b_by_dst, sre->pair->two->path)->util
                        = sre->dst_entry;
        }
  
                                        output(o, 1, "Adding as %s instead", new_path);
                                        update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
                                }
 -                      } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {
 +                      } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
                                ren2->processed = 1;
@@@ -1214,7 -1208,7 +1208,7 @@@ int merge_trees(struct merge_options *o
        }
  
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
 -              output(o, 0, "Already uptodate!");
 +              output(o, 0, "Already up-to-date!");
                *result = head;
                return 1;
        }