{"thread":{"id":"28922","subject":"[PATCH 0/7] New sequencer workflow!","startedAt":"2011-11-13T10:46:14Z","lastAt":"2011-11-16T06:30:15Z","messageCount":17,"participants":["Ramkumar Ramachandra","Junio C Hamano","Phil Hord"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"179375","messageId":"1321181181-23923-1-git-send-email-artagnon@gmail.com","threadId":"28922","inReplyTo":null,"subject":"[PATCH 0/7] New sequencer workflow!","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-13T10:46:14Z","receivedAt":"2011-11-13T10:46:14Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi,\n\nAfter days of bending my mind around the backward-compatibility\nproblem, I'm happy to announce that we finally have an implementation!\nThis series preserves the old workflow:\n\n  $ git cherry-pick foo\n  ... conflict ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git commit\n\n  $ git cherry-pick foo..bar\n  ... conflict ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git commit\n  $ git revert --continue # No, there are no typos\n\nAnd introduces a brand new workflow:\n\n  $ git cherry-pick foo\n  ... conflict ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git sequencer --continue\n\n  $ git cherry-pick foo..bar\n  ... conflict ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git sequencer --continue\n\n  $ git revert moo..baz\n  ... conflict ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git sequencer --continue\n\nIn other words, it just doesn't matter how you started what sequencing\noperation: you can always continue a it with \"git sequencer\n--continue\" and reset it with \"git sequencer --reset\".\n\nThis series is reasonably well-polished and based on\nrr/revert-cherry-pick (since the topic hasn't made it to 'master'\nyet).  The only downside is that it's very complicated, and I've tried\nmy best to explain the motivation for everything in the commit\nmessages.  Needless to say, it requires a huge amount of testing to\ncatch unpleasant corner cases.\n\nPart 1 moves a lot of code from builtin/revert.c to sequencer.c.\n\nParts 2, 3, 4 carefully work around some complex issues related to\n'.git/CHERRY_PICK_HEAD'.\n\nPart 5 introduces a new git-sequencer builtin.  Although it's\nempty'ish now, I'd argue that adding it now helps think about the\nunified interface.  Any suggestions on what concrete function it\nshould implement?  I'm thinking of something along the lines of\n'rebase -i'.\n\nPart 6 is the patch that tells the sequencer \"how to continue\".  As\nmore commands grow sequencer functionalities, it will become clear\nthat committing isn't the only way to continue, but I think it's good\nfor the moment.\n\nPart 7 is the icing on the cake.  Enjoy.\n\nI'd sent out $gmane/184859 last week as a sanity check: thanks to\nJunio and Jonathan for looking at it.  It's always comforting to know\nthat I haven't lost my head yet :)\n\nIf you're looking to get involved before this series hits 'pu', use\nthe \"sequencer\" branch of my Git fork on Github:\nhttp://github.com/artagnon/git\n\nThanks.\n\n-- Ram\n\nRamkumar Ramachandra (7):\n  sequencer: factor code out of revert builtin\n  sequencer: invalidate sequencer state without todo\n  sequencer: handle single-commit pick as special case\n  sequencer: handle cherry-pick conflict in last commit\n  sequencer: introduce git-sequencer builtin\n  sequencer: teach '--continue' how to commit\n  sequencer: teach parser about CHERRY_PICK_HEAD\n\n .gitignore                                         |    1 +\n Documentation/git-sequencer.txt                    |   33 +\n Makefile                                           |    1 +\n builtin.h                                          |    1 +\n builtin/revert.c                                   |  821 +-------------------\n builtin/sequencer.c                                |   52 ++\n git.c                                              |    1 +\n sequencer.c                                        |  842 +++++++++++++++++++-\n sequencer.h                                        |   26 +\n ...-cherry-pick-sequence.sh => t3510-sequencer.sh} |  140 +---\n ...-sequence.sh => t3511-cherry-pick-sequencer.sh} |  124 +---\n 11 files changed, 1002 insertions(+), 1040 deletions(-)\n create mode 100644 Documentation/git-sequencer.txt\n create mode 100644 builtin/sequencer.c\n copy t/{t3510-cherry-pick-sequence.sh => t3510-sequencer.sh} (60%)\n rename t/{t3510-cherry-pick-sequence.sh => t3511-cherry-pick-sequencer.sh} (59%)\n\n-- \n1.7.6.351.gb35ac.dirty\n"},{"id":"179379","messageId":"1321181181-23923-2-git-send-email-artagnon@gmail.com","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 1/7] sequencer: factor code out of revert builtin","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-13T10:46:15Z","receivedAt":"2011-11-13T10:46:15Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"To build a generalized sequencer of which cherry-picking and reverting\nare special cases, we must first expose the cherry-picking machinery\nthrough a public API.  Move code from revert.c into sequencer.c so as\nto expose pick_revisions() as sequencer_pick_revisions() in\nsequencer.h.  Consequently, make the cherry-pick builtin a thin\nwrapper around sequencer_pick_revisions() that additionally does\ncommand-line argument parsing.  In the future, we can write a new\n\"foo\" builtin that calls into the sequencer like:\n\n  memset(&opts, 0, sizeof(opts));\n  opts.action = REPLAY_FOO;\n  opts.revisions = xmalloc(sizeof(*opts.revs));\n  parse_args_populate_opts(argc, argv, &opts);\n  init_revisions(opts.revs);\n  sequencer_pick_revisions(&opts);\n\nThis is intended to be almost a pure code movement patch with no\nfunctional changes.  Check with:\n\n  $ git blame -s -CCC HEAD^..HEAD -- sequencer.c | grep -C3 '^[^^]'\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n builtin/revert.c |  821 +-----------------------------------------------------\n sequencer.c      |  802 ++++++++++++++++++++++++++++++++++++++++++++++++++++-\n sequencer.h      |   26 ++\n 3 files changed, 828 insertions(+), 821 deletions(-)\n\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex df9459b..c272920 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -1,19 +1,9 @@\n #include \"cache.h\"\n #include \"builtin.h\"\n-#include \"object.h\"\n-#include \"commit.h\"\n-#include \"tag.h\"\n-#include \"run-command.h\"\n-#include \"exec_cmd.h\"\n-#include \"utf8.h\"\n #include \"parse-options.h\"\n-#include \"cache-tree.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n-#include \"merge-recursive.h\"\n-#include \"refs.h\"\n-#include \"dir.h\"\n #include \"sequencer.h\"\n \n /*\n@@ -39,40 +29,11 @@ static const char * const cherry_pick_usage[] = {\n \tNULL\n };\n \n-enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };\n-\n-struct replay_opts {\n-\tenum replay_action action;\n-\tenum replay_subcommand subcommand;\n-\n-\t/* Boolean options */\n-\tint edit;\n-\tint record_origin;\n-\tint no_commit;\n-\tint signoff;\n-\tint allow_ff;\n-\tint allow_rerere_auto;\n-\n-\tint mainline;\n-\n-\t/* Merge strategy */\n-\tconst char *strategy;\n-\tconst char **xopts;\n-\tsize_t xopts_nr, xopts_alloc;\n-\n-\t/* Only used by REPLAY_NONE */\n-\tstruct rev_info *revs;\n-};\n-\n-#define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n-\n static const char *action_name(const struct replay_opts *opts)\n {\n \treturn opts->action == REPLAY_REVERT ? \"revert\" : \"cherry-pick\";\n }\n \n-static char *get_encoding(const char *message);\n-\n static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)\n {\n \treturn opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;\n@@ -222,784 +183,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)\n \t\tusage_with_options(usage_str, options);\n }\n \n-struct commit_message {\n-\tchar *parent_label;\n-\tconst char *label;\n-\tconst char *subject;\n-\tchar *reencoded_message;\n-\tconst char *message;\n-};\n-\n-static int get_message(struct commit *commit, struct commit_message *out)\n-{\n-\tconst char *encoding;\n-\tconst char *abbrev, *subject;\n-\tint abbrev_len, subject_len;\n-\tchar *q;\n-\n-\tif (!commit->buffer)\n-\t\treturn -1;\n-\tencoding = get_encoding(commit->buffer);\n-\tif (!encoding)\n-\t\tencoding = \"UTF-8\";\n-\tif (!git_commit_encoding)\n-\t\tgit_commit_encoding = \"UTF-8\";\n-\n-\tout->reencoded_message = NULL;\n-\tout->message = commit->buffer;\n-\tif (strcmp(encoding, git_commit_encoding))\n-\t\tout->reencoded_message = reencode_string(commit->buffer,\n-\t\t\t\t\tgit_commit_encoding, encoding);\n-\tif (out->reencoded_message)\n-\t\tout->message = out->reencoded_message;\n-\n-\tabbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);\n-\tabbrev_len = strlen(abbrev);\n-\n-\tsubject_len = find_commit_subject(out->message, &subject);\n-\n-\tout->parent_label = xmalloc(strlen(\"parent of \") + abbrev_len +\n-\t\t\t      strlen(\"... \") + subject_len + 1);\n-\tq = out->parent_label;\n-\tq = mempcpy(q, \"parent of \", strlen(\"parent of \"));\n-\tout->label = q;\n-\tq = mempcpy(q, abbrev, abbrev_len);\n-\tq = mempcpy(q, \"... \", strlen(\"... \"));\n-\tout->subject = q;\n-\tq = mempcpy(q, subject, subject_len);\n-\t*q = '\\0';\n-\treturn 0;\n-}\n-\n-static void free_message(struct commit_message *msg)\n-{\n-\tfree(msg->parent_label);\n-\tfree(msg->reencoded_message);\n-}\n-\n-static char *get_encoding(const char *message)\n-{\n-\tconst char *p = message, *eol;\n-\n-\twhile (*p && *p != '\\n') {\n-\t\tfor (eol = p + 1; *eol && *eol != '\\n'; eol++)\n-\t\t\t; /* do nothing */\n-\t\tif (!prefixcmp(p, \"encoding \")) {\n-\t\t\tchar *result = xmalloc(eol - 8 - p);\n-\t\t\tstrlcpy(result, p + 9, eol - 8 - p);\n-\t\t\treturn result;\n-\t\t}\n-\t\tp = eol;\n-\t\tif (*p == '\\n')\n-\t\t\tp++;\n-\t}\n-\treturn NULL;\n-}\n-\n-static void write_cherry_pick_head(struct commit *commit)\n-{\n-\tint fd;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tstrbuf_addf(&buf, \"%s\\n\", sha1_to_hex(commit->object.sha1));\n-\n-\tfd = open(git_path(\"CHERRY_PICK_HEAD\"), O_WRONLY | O_CREAT, 0666);\n-\tif (fd < 0)\n-\t\tdie_errno(_(\"Could not open '%s' for writing\"),\n-\t\t\t  git_path(\"CHERRY_PICK_HEAD\"));\n-\tif (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))\n-\t\tdie_errno(_(\"Could not write to '%s'\"), git_path(\"CHERRY_PICK_HEAD\"));\n-\tstrbuf_release(&buf);\n-}\n-\n-static void print_advice(int show_hint)\n-{\n-\tchar *msg = getenv(\"GIT_CHERRY_PICK_HELP\");\n-\n-\tif (msg) {\n-\t\tfprintf(stderr, \"%s\\n\", msg);\n-\t\t/*\n-\t\t * A conflict has occured but the porcelain\n-\t\t * (typically rebase --interactive) wants to take care\n-\t\t * of the commit itself so remove CHERRY_PICK_HEAD\n-\t\t */\n-\t\tunlink(git_path(\"CHERRY_PICK_HEAD\"));\n-\t\treturn;\n-\t}\n-\n-\tif (show_hint) {\n-\t\tadvise(\"after resolving the conflicts, mark the corrected paths\");\n-\t\tadvise(\"with 'git add <paths>' or 'git rm <paths>'\");\n-\t\tadvise(\"and commit the result with 'git commit'\");\n-\t}\n-}\n-\n-static void write_message(struct strbuf *msgbuf, const char *filename)\n-{\n-\tstatic struct lock_file msg_file;\n-\n-\tint msg_fd = hold_lock_file_for_update(&msg_file, filename,\n-\t\t\t\t\t       LOCK_DIE_ON_ERROR);\n-\tif (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)\n-\t\tdie_errno(_(\"Could not write to %s.\"), filename);\n-\tstrbuf_release(msgbuf);\n-\tif (commit_lock_file(&msg_file) < 0)\n-\t\tdie(_(\"Error wrapping up %s\"), filename);\n-}\n-\n-static struct tree *empty_tree(void)\n-{\n-\treturn lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);\n-}\n-\n-static int error_dirty_index(struct replay_opts *opts)\n-{\n-\tif (read_cache_unmerged())\n-\t\treturn error_resolve_conflict(action_name(opts));\n-\n-\t/* Different translation strings for cherry-pick and revert */\n-\tif (opts->action == REPLAY_PICK)\n-\t\terror(_(\"Your local changes would be overwritten by cherry-pick.\"));\n-\telse\n-\t\terror(_(\"Your local changes would be overwritten by revert.\"));\n-\n-\tif (advice_commit_before_merge)\n-\t\tadvise(_(\"Commit your changes or stash them to proceed.\"));\n-\treturn -1;\n-}\n-\n-static int fast_forward_to(const unsigned char *to, const unsigned char *from)\n-{\n-\tstruct ref_lock *ref_lock;\n-\n-\tread_cache();\n-\tif (checkout_fast_forward(from, to))\n-\t\texit(1); /* the callee should have complained already */\n-\tref_lock = lock_any_ref_for_update(\"HEAD\", from, 0);\n-\treturn write_ref_sha1(ref_lock, to, \"cherry-pick\");\n-}\n-\n-static int do_recursive_merge(struct commit *base, struct commit *next,\n-\t\t\t      const char *base_label, const char *next_label,\n-\t\t\t      unsigned char *head, struct strbuf *msgbuf,\n-\t\t\t      struct replay_opts *opts)\n-{\n-\tstruct merge_options o;\n-\tstruct tree *result, *next_tree, *base_tree, *head_tree;\n-\tint clean, index_fd;\n-\tconst char **xopt;\n-\tstatic struct lock_file index_lock;\n-\n-\tindex_fd = hold_locked_index(&index_lock, 1);\n-\n-\tread_cache();\n-\n-\tinit_merge_options(&o);\n-\to.ancestor = base ? base_label : \"(empty tree)\";\n-\to.branch1 = \"HEAD\";\n-\to.branch2 = next ? next_label : \"(empty tree)\";\n-\n-\thead_tree = parse_tree_indirect(head);\n-\tnext_tree = next ? next->tree : empty_tree();\n-\tbase_tree = base ? base->tree : empty_tree();\n-\n-\tfor (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)\n-\t\tparse_merge_opt(&o, *xopt);\n-\n-\tclean = merge_trees(&o,\n-\t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree, &result);\n-\n-\tif (active_cache_changed &&\n-\t    (write_cache(index_fd, active_cache, active_nr) ||\n-\t     commit_locked_index(&index_lock)))\n-\t\t/* TRANSLATORS: %s will be \"revert\" or \"cherry-pick\" */\n-\t\tdie(_(\"%s: Unable to write new index file\"), action_name(opts));\n-\trollback_lock_file(&index_lock);\n-\n-\tif (!clean) {\n-\t\tint i;\n-\t\tstrbuf_addstr(msgbuf, \"\\nConflicts:\\n\\n\");\n-\t\tfor (i = 0; i < active_nr;) {\n-\t\t\tstruct cache_entry *ce = active_cache[i++];\n-\t\t\tif (ce_stage(ce)) {\n-\t\t\t\tstrbuf_addch(msgbuf, '\\t');\n-\t\t\t\tstrbuf_addstr(msgbuf, ce->name);\n-\t\t\t\tstrbuf_addch(msgbuf, '\\n');\n-\t\t\t\twhile (i < active_nr && !strcmp(ce->name,\n-\t\t\t\t\t\tactive_cache[i]->name))\n-\t\t\t\t\ti++;\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-\treturn !clean;\n-}\n-\n-/*\n- * If we are cherry-pick, and if the merge did not result in\n- * hand-editing, we will hit this commit and inherit the original\n- * author date and name.\n- * If we are revert, or if our cherry-pick results in a hand merge,\n- * we had better say that the current user is responsible for that.\n- */\n-static int run_git_commit(const char *defmsg, struct replay_opts *opts)\n-{\n-\t/* 6 is max possible length of our args array including NULL */\n-\tconst char *args[6];\n-\tint i = 0;\n-\n-\targs[i++] = \"commit\";\n-\targs[i++] = \"-n\";\n-\tif (opts->signoff)\n-\t\targs[i++] = \"-s\";\n-\tif (!opts->edit) {\n-\t\targs[i++] = \"-F\";\n-\t\targs[i++] = defmsg;\n-\t}\n-\targs[i] = NULL;\n-\n-\treturn run_command_v_opt(args, RUN_GIT_CMD);\n-}\n-\n-static int do_pick_commit(struct commit *commit, enum replay_action action,\n-\t\t\tstruct replay_opts *opts)\n-{\n-\tunsigned char head[20];\n-\tstruct commit *base, *next, *parent;\n-\tconst char *base_label, *next_label;\n-\tstruct commit_message msg = { NULL, NULL, NULL, NULL, NULL };\n-\tchar *defmsg = NULL;\n-\tstruct strbuf msgbuf = STRBUF_INIT;\n-\tint res;\n-\n-\tif (opts->no_commit) {\n-\t\t/*\n-\t\t * We do not intend to commit immediately.  We just want to\n-\t\t * merge the differences in, so let's compute the tree\n-\t\t * that represents the \"current\" state for merge-recursive\n-\t\t * to work on.\n-\t\t */\n-\t\tif (write_cache_as_tree(head, 0, NULL))\n-\t\t\tdie (_(\"Your index file is unmerged.\"));\n-\t} else {\n-\t\tif (get_sha1(\"HEAD\", head))\n-\t\t\treturn error(_(\"You do not have a valid HEAD\"));\n-\t\tif (index_differs_from(\"HEAD\", 0))\n-\t\t\treturn error_dirty_index(opts);\n-\t}\n-\tdiscard_cache();\n-\n-\tif (!commit->parents) {\n-\t\tparent = NULL;\n-\t}\n-\telse if (commit->parents->next) {\n-\t\t/* Reverting or cherry-picking a merge commit */\n-\t\tint cnt;\n-\t\tstruct commit_list *p;\n-\n-\t\tif (!opts->mainline)\n-\t\t\treturn error(_(\"Commit %s is a merge but no -m option was given.\"),\n-\t\t\t\tsha1_to_hex(commit->object.sha1));\n-\n-\t\tfor (cnt = 1, p = commit->parents;\n-\t\t     cnt != opts->mainline && p;\n-\t\t     cnt++)\n-\t\t\tp = p->next;\n-\t\tif (cnt != opts->mainline || !p)\n-\t\t\treturn error(_(\"Commit %s does not have parent %d\"),\n-\t\t\t\tsha1_to_hex(commit->object.sha1), opts->mainline);\n-\t\tparent = p->item;\n-\t} else if (0 < opts->mainline)\n-\t\treturn error(_(\"Mainline was specified but commit %s is not a merge.\"),\n-\t\t\tsha1_to_hex(commit->object.sha1));\n-\telse\n-\t\tparent = commit->parents->item;\n-\n-\tif (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))\n-\t\treturn fast_forward_to(commit->object.sha1, head);\n-\n-\tif (parent && parse_commit(parent) < 0)\n-\t\t/* TRANSLATORS: The first %s will be \"revert\" or\n-\t\t   \"cherry-pick\", the second %s a SHA1 */\n-\t\treturn error(_(\"%s: cannot parse parent commit %s\"),\n-\t\t\taction_name(opts), sha1_to_hex(parent->object.sha1));\n-\n-\tif (get_message(commit, &msg) != 0)\n-\t\treturn error(_(\"Cannot get commit message for %s\"),\n-\t\t\tsha1_to_hex(commit->object.sha1));\n-\n-\t/*\n-\t * \"commit\" is an existing commit.  We would want to apply\n-\t * the difference it introduces since its first parent \"prev\"\n-\t * on top of the current HEAD if we are cherry-pick.  Or the\n-\t * reverse of it if we are revert.\n-\t */\n-\n-\tdefmsg = git_pathdup(\"MERGE_MSG\");\n-\n-\tif (action == REPLAY_REVERT) {\n-\t\tbase = commit;\n-\t\tbase_label = msg.label;\n-\t\tnext = parent;\n-\t\tnext_label = msg.parent_label;\n-\t\tstrbuf_addstr(&msgbuf, \"Revert \\\"\");\n-\t\tstrbuf_addstr(&msgbuf, msg.subject);\n-\t\tstrbuf_addstr(&msgbuf, \"\\\"\\n\\nThis reverts commit \");\n-\t\tstrbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));\n-\n-\t\tif (commit->parents && commit->parents->next) {\n-\t\t\tstrbuf_addstr(&msgbuf, \", reversing\\nchanges made to \");\n-\t\t\tstrbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));\n-\t\t}\n-\t\tstrbuf_addstr(&msgbuf, \".\\n\");\n-\t} else {\n-\t\tconst char *p;\n-\n-\t\tbase = parent;\n-\t\tbase_label = msg.parent_label;\n-\t\tnext = commit;\n-\t\tnext_label = msg.label;\n-\n-\t\t/*\n-\t\t * Append the commit log message to msgbuf; it starts\n-\t\t * after the tree, parent, author, committer\n-\t\t * information followed by \"\\n\\n\".\n-\t\t */\n-\t\tp = strstr(msg.message, \"\\n\\n\");\n-\t\tif (p) {\n-\t\t\tp += 2;\n-\t\t\tstrbuf_addstr(&msgbuf, p);\n-\t\t}\n-\n-\t\tif (opts->record_origin) {\n-\t\t\tstrbuf_addstr(&msgbuf, \"(cherry picked from commit \");\n-\t\t\tstrbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));\n-\t\t\tstrbuf_addstr(&msgbuf, \")\\n\");\n-\t\t}\n-\t}\n-\n-\tif (!opts->strategy || !strcmp(opts->strategy, \"recursive\") || action == REPLAY_REVERT) {\n-\t\tres = do_recursive_merge(base, next, base_label, next_label,\n-\t\t\t\t\t head, &msgbuf, opts);\n-\t\twrite_message(&msgbuf, defmsg);\n-\t} else {\n-\t\tstruct commit_list *common = NULL;\n-\t\tstruct commit_list *remotes = NULL;\n-\n-\t\twrite_message(&msgbuf, defmsg);\n-\n-\t\tcommit_list_insert(base, &common);\n-\t\tcommit_list_insert(next, &remotes);\n-\t\tres = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,\n-\t\t\t\t\tcommon, sha1_to_hex(head), remotes);\n-\t\tfree_commit_list(common);\n-\t\tfree_commit_list(remotes);\n-\t}\n-\n-\t/*\n-\t * If the merge was clean or if it failed due to conflict, we write\n-\t * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.\n-\t * However, if the merge did not even start, then we don't want to\n-\t * write it at all.\n-\t */\n-\tif (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))\n-\t\twrite_cherry_pick_head(commit);\n-\n-\tif (res) {\n-\t\terror(action == REPLAY_REVERT\n-\t\t      ? _(\"could not revert %s... %s\")\n-\t\t      : _(\"could not apply %s... %s\"),\n-\t\t      find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),\n-\t\t      msg.subject);\n-\t\tprint_advice(res == 1);\n-\t\trerere(opts->allow_rerere_auto);\n-\t} else {\n-\t\tif (!opts->no_commit)\n-\t\t\tres = run_git_commit(defmsg, opts);\n-\t}\n-\n-\tfree_message(&msg);\n-\tfree(defmsg);\n-\n-\treturn res;\n-}\n-\n-static void prepare_revs(struct replay_opts *opts)\n-{\n-\tif (opts->action != REPLAY_REVERT)\n-\t\topts->revs->reverse ^= 1;\n-\n-\tif (prepare_revision_walk(opts->revs))\n-\t\tdie(_(\"revision walk setup failed\"));\n-\n-\tif (!opts->revs->commits)\n-\t\tdie(_(\"empty commit set passed\"));\n-}\n-\n-static void read_and_refresh_cache(struct replay_opts *opts)\n-{\n-\tstatic struct lock_file index_lock;\n-\tint index_fd = hold_locked_index(&index_lock, 0);\n-\tif (read_index_preload(&the_index, NULL) < 0)\n-\t\tdie(_(\"git %s: failed to read the index\"), action_name(opts));\n-\trefresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);\n-\tif (the_index.cache_changed) {\n-\t\tif (write_index(&the_index, index_fd) ||\n-\t\t    commit_locked_index(&index_lock))\n-\t\t\tdie(_(\"git %s: failed to refresh the index\"), action_name(opts));\n-\t}\n-\trollback_lock_file(&index_lock);\n-}\n-\n-/*\n- * Append a commit to the end of the commit_list.\n- *\n- * next starts by pointing to the variable that holds the head of an\n- * empty commit_list, and is updated to point to the \"next\" field of\n- * the last item on the list as new commits are appended.\n- *\n- * Usage example:\n- *\n- *     struct commit_list *list;\n- *     struct commit_list **next = &list;\n- *\n- *     next = commit_list_append(c1, next);\n- *     next = commit_list_append(c2, next);\n- *     assert(commit_list_count(list) == 2);\n- *     return list;\n- */\n-static struct replay_insn_list **replay_insn_list_append(enum replay_action action,\n-\t\t\t\t\t\tstruct commit *operand,\n-\t\t\t\t\t\tstruct replay_insn_list **next)\n-{\n-\tstruct replay_insn_list *new = xmalloc(sizeof(*new));\n-\tnew->action = action;\n-\tnew->operand = operand;\n-\t*next = new;\n-\tnew->next = NULL;\n-\treturn &new->next;\n-}\n-\n-static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list)\n-{\n-\tstruct replay_insn_list *cur;\n-\n-\tfor (cur = todo_list; cur; cur = cur->next) {\n-\t\tconst char *sha1_abbrev, *action_str, *subject;\n-\t\tint subject_len;\n-\n-\t\taction_str = cur->action == REPLAY_REVERT ? \"revert\" : \"pick\";\n-\t\tsha1_abbrev = find_unique_abbrev(cur->operand->object.sha1, DEFAULT_ABBREV);\n-\t\tsubject_len = find_commit_subject(cur->operand->buffer, &subject);\n-\t\tstrbuf_addf(buf, \"%s %s %.*s\\n\", action_str, sha1_abbrev,\n-\t\t\tsubject_len, subject);\n-\t}\n-\treturn 0;\n-}\n-\n-static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item)\n-{\n-\tunsigned char commit_sha1[20];\n-\tchar *end_of_object_name;\n-\tint saved, status;\n-\n-\tif (!prefixcmp(bol, \"pick \")) {\n-\t\titem->action = REPLAY_PICK;\n-\t\tbol += strlen(\"pick \");\n-\t} else if (!prefixcmp(bol, \"revert \")) {\n-\t\titem->action = REPLAY_REVERT;\n-\t\tbol += strlen(\"revert \");\n-\t} else {\n-\t\tsize_t len = strchrnul(bol, '\\n') - bol;\n-\t\tif (len > 255)\n-\t\t\tlen = 255;\n-\t\treturn error(_(\"Unrecognized action: %.*s\"), (int)len, bol);\n-\t}\n-\n-\tend_of_object_name = bol + strcspn(bol, \" \\n\");\n-\tsaved = *end_of_object_name;\n-\t*end_of_object_name = '\\0';\n-\tstatus = get_sha1(bol, commit_sha1);\n-\t*end_of_object_name = saved;\n-\n-\tif (status < 0)\n-\t\treturn error(_(\"Malformed object name: %s\"), bol);\n-\n-\titem->operand = lookup_commit_reference(commit_sha1);\n-\tif (!item->operand)\n-\t\treturn error(_(\"Not a valid commit: %s\"), bol);\n-\n-\titem->next = NULL;\n-\treturn 0;\n-}\n-\n-static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list)\n-{\n-\tstruct replay_insn_list **next = todo_list;\n-\tstruct replay_insn_list item = {0, NULL, NULL};\n-\tchar *p = buf;\n-\tint i;\n-\n-\tfor (i = 1; *p; i++) {\n-\t\tchar *eol = strchrnul(p, '\\n');\n-\t\tif (parse_insn_line(p, eol, &item) < 0)\n-\t\t\treturn error(_(\"on line %d.\"), i);\n-\t\tnext = replay_insn_list_append(item.action, item.operand, next);\n-\t\tp = *eol ? eol + 1 : eol;\n-\t}\n-\tif (!*todo_list)\n-\t\treturn error(_(\"No commits parsed.\"));\n-\treturn 0;\n-}\n-\n-static void read_populate_todo(struct replay_insn_list **todo_list)\n-{\n-\tconst char *todo_file = git_path(SEQ_TODO_FILE);\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tint fd, res;\n-\n-\tfd = open(todo_file, O_RDONLY);\n-\tif (fd < 0)\n-\t\tdie_errno(_(\"Could not open %s.\"), todo_file);\n-\tif (strbuf_read(&buf, fd, 0) < 0) {\n-\t\tclose(fd);\n-\t\tstrbuf_release(&buf);\n-\t\tdie(_(\"Could not read %s.\"), todo_file);\n-\t}\n-\tclose(fd);\n-\n-\tres = parse_insn_buffer(buf.buf, todo_list);\n-\tstrbuf_release(&buf);\n-\tif (res)\n-\t\tdie(_(\"Unusable instruction sheet: %s\"), todo_file);\n-}\n-\n-static int populate_opts_cb(const char *key, const char *value, void *data)\n-{\n-\tstruct replay_opts *opts = data;\n-\tint error_flag = 1;\n-\n-\tif (!value)\n-\t\terror_flag = 0;\n-\telse if (!strcmp(key, \"options.no-commit\"))\n-\t\topts->no_commit = git_config_bool_or_int(key, value, &error_flag);\n-\telse if (!strcmp(key, \"options.edit\"))\n-\t\topts->edit = git_config_bool_or_int(key, value, &error_flag);\n-\telse if (!strcmp(key, \"options.signoff\"))\n-\t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n-\telse if (!strcmp(key, \"options.record-origin\"))\n-\t\topts->record_origin = git_config_bool_or_int(key, value, &error_flag);\n-\telse if (!strcmp(key, \"options.allow-ff\"))\n-\t\topts->allow_ff = git_config_bool_or_int(key, value, &error_flag);\n-\telse if (!strcmp(key, \"options.mainline\"))\n-\t\topts->mainline = git_config_int(key, value);\n-\telse if (!strcmp(key, \"options.strategy\"))\n-\t\tgit_config_string(&opts->strategy, key, value);\n-\telse if (!strcmp(key, \"options.strategy-option\")) {\n-\t\tALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);\n-\t\topts->xopts[opts->xopts_nr++] = xstrdup(value);\n-\t} else\n-\t\treturn error(_(\"Invalid key: %s\"), key);\n-\n-\tif (!error_flag)\n-\t\treturn error(_(\"Invalid value for %s: %s\"), key, value);\n-\n-\treturn 0;\n-}\n-\n-static void read_populate_opts(struct replay_opts **opts_ptr)\n-{\n-\tconst char *opts_file = git_path(SEQ_OPTS_FILE);\n-\n-\tif (!file_exists(opts_file))\n-\t\treturn;\n-\tif (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)\n-\t\tdie(_(\"Malformed options sheet: %s\"), opts_file);\n-}\n-\n-static void walk_revs_populate_todo(struct replay_insn_list **todo_list,\n-\t\t\t\tstruct replay_opts *opts)\n-{\n-\tstruct commit *commit;\n-\tstruct replay_insn_list **next;\n-\n-\tprepare_revs(opts);\n-\n-\tnext = todo_list;\n-\twhile ((commit = get_revision(opts->revs)))\n-\t\tnext = replay_insn_list_append(opts->action, commit, next);\n-}\n-\n-static int create_seq_dir(void)\n-{\n-\tconst char *seq_dir = git_path(SEQ_DIR);\n-\n-\tif (file_exists(seq_dir))\n-\t\treturn error(_(\"%s already exists.\"), seq_dir);\n-\telse if (mkdir(seq_dir, 0777) < 0)\n-\t\tdie_errno(_(\"Could not create sequencer directory '%s'.\"), seq_dir);\n-\treturn 0;\n-}\n-\n-static void save_head(const char *head)\n-{\n-\tconst char *head_file = git_path(SEQ_HEAD_FILE);\n-\tstatic struct lock_file head_lock;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tint fd;\n-\n-\tfd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);\n-\tstrbuf_addf(&buf, \"%s\\n\", head);\n-\tif (write_in_full(fd, buf.buf, buf.len) < 0)\n-\t\tdie_errno(_(\"Could not write to %s.\"), head_file);\n-\tif (commit_lock_file(&head_lock) < 0)\n-\t\tdie(_(\"Error wrapping up %s.\"), head_file);\n-}\n-\n-static void save_todo(struct replay_insn_list *todo_list)\n-{\n-\tconst char *todo_file = git_path(SEQ_TODO_FILE);\n-\tstatic struct lock_file todo_lock;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tint fd;\n-\n-\tfd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);\n-\tif (format_todo(&buf, todo_list) < 0)\n-\t\tdie(_(\"Could not format %s.\"), todo_file);\n-\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n-\t\tstrbuf_release(&buf);\n-\t\tdie_errno(_(\"Could not write to %s.\"), todo_file);\n-\t}\n-\tif (commit_lock_file(&todo_lock) < 0) {\n-\t\tstrbuf_release(&buf);\n-\t\tdie(_(\"Error wrapping up %s.\"), todo_file);\n-\t}\n-\tstrbuf_release(&buf);\n-}\n-\n-static void save_opts(struct replay_opts *opts)\n-{\n-\tconst char *opts_file = git_path(SEQ_OPTS_FILE);\n-\n-\tif (opts->no_commit)\n-\t\tgit_config_set_in_file(opts_file, \"options.no-commit\", \"true\");\n-\tif (opts->edit)\n-\t\tgit_config_set_in_file(opts_file, \"options.edit\", \"true\");\n-\tif (opts->signoff)\n-\t\tgit_config_set_in_file(opts_file, \"options.signoff\", \"true\");\n-\tif (opts->record_origin)\n-\t\tgit_config_set_in_file(opts_file, \"options.record-origin\", \"true\");\n-\tif (opts->allow_ff)\n-\t\tgit_config_set_in_file(opts_file, \"options.allow-ff\", \"true\");\n-\tif (opts->mainline) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_addf(&buf, \"%d\", opts->mainline);\n-\t\tgit_config_set_in_file(opts_file, \"options.mainline\", buf.buf);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tif (opts->strategy)\n-\t\tgit_config_set_in_file(opts_file, \"options.strategy\", opts->strategy);\n-\tif (opts->xopts) {\n-\t\tint i;\n-\t\tfor (i = 0; i < opts->xopts_nr; i++)\n-\t\t\tgit_config_set_multivar_in_file(opts_file,\n-\t\t\t\t\t\t\t\"options.strategy-option\",\n-\t\t\t\t\t\t\topts->xopts[i], \"^$\", 0);\n-\t}\n-}\n-\n-static int pick_commits(struct replay_insn_list *todo_list,\n-\t\t\tstruct replay_opts *opts)\n-{\n-\tstruct replay_insn_list *cur;\n-\tint res;\n-\n-\tsetenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n-\tif (opts->allow_ff)\n-\t\tassert(!(opts->signoff || opts->no_commit ||\n-\t\t\t\topts->record_origin || opts->edit));\n-\tread_and_refresh_cache(opts);\n-\n-\tfor (cur = todo_list; cur; cur = cur->next) {\n-\t\tsave_todo(cur);\n-\t\tres = do_pick_commit(cur->operand, cur->action, opts);\n-\t\tif (res) {\n-\t\t\tif (!cur->next)\n-\t\t\t\t/*\n-\t\t\t\t * An error was encountered while\n-\t\t\t\t * picking the last commit; the\n-\t\t\t\t * sequencer state is useless now --\n-\t\t\t\t * the user simply needs to resolve\n-\t\t\t\t * the conflict and commit\n-\t\t\t\t */\n-\t\t\t\tremove_sequencer_state(0);\n-\t\t\treturn res;\n-\t\t}\n-\t}\n-\n-\t/*\n-\t * Sequence of picks finished successfully; cleanup by\n-\t * removing the .git/sequencer directory\n-\t */\n-\tremove_sequencer_state(1);\n-\treturn 0;\n-}\n-\n-static int pick_revisions(struct replay_opts *opts)\n-{\n-\tstruct replay_insn_list *todo_list = NULL;\n-\tunsigned char sha1[20];\n-\n-\tif (opts->subcommand == REPLAY_NONE)\n-\t\tassert(opts->revs);\n-\n-\tread_and_refresh_cache(opts);\n-\n-\t/*\n-\t * Decide what to do depending on the arguments; a fresh\n-\t * cherry-pick should be handled differently from an existing\n-\t * one that is being continued\n-\t */\n-\tif (opts->subcommand == REPLAY_RESET) {\n-\t\tremove_sequencer_state(1);\n-\t\treturn 0;\n-\t} else if (opts->subcommand == REPLAY_CONTINUE) {\n-\t\tif (!file_exists(git_path(SEQ_TODO_FILE)))\n-\t\t\tgoto error;\n-\t\tread_populate_opts(&opts);\n-\t\tread_populate_todo(&todo_list);\n-\n-\t\t/* Verify that the conflict has been resolved */\n-\t\tif (!index_differs_from(\"HEAD\", 0))\n-\t\t\ttodo_list = todo_list->next;\n-\t} else {\n-\t\t/*\n-\t\t * Start a new cherry-pick/ revert sequence; but\n-\t\t * first, make sure that an existing one isn't in\n-\t\t * progress\n-\t\t */\n-\n-\t\twalk_revs_populate_todo(&todo_list, opts);\n-\t\tif (create_seq_dir() < 0) {\n-\t\t\terror(_(\"A cherry-pick or revert is in progress.\"));\n-\t\t\tadvise(_(\"Use --continue to continue the operation\"));\n-\t\t\tadvise(_(\"or --reset to forget about it\"));\n-\t\t\treturn -1;\n-\t\t}\n-\t\tif (get_sha1(\"HEAD\", sha1)) {\n-\t\t\tif (opts->action == REPLAY_REVERT)\n-\t\t\t\treturn error(_(\"Can't revert as initial commit\"));\n-\t\t\treturn error(_(\"Can't cherry-pick into empty head\"));\n-\t\t}\n-\t\tsave_head(sha1_to_hex(sha1));\n-\t\tsave_opts(opts);\n-\t}\n-\treturn pick_commits(todo_list, opts);\n-error:\n-\treturn error(_(\"No %s in progress\"), action_name(opts));\n-}\n-\n int cmd_revert(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts;\n@@ -1011,7 +194,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix)\n \topts.action = REPLAY_REVERT;\n \tgit_config(git_default_config, NULL);\n \tparse_args(argc, argv, &opts);\n-\tres = pick_revisions(&opts);\n+\tres = sequencer_pick_revisions(&opts);\n \tif (res < 0)\n \t\tdie(_(\"revert failed\"));\n \treturn res;\n@@ -1026,7 +209,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)\n \topts.action = REPLAY_PICK;\n \tgit_config(git_default_config, NULL);\n \tparse_args(argc, argv, &opts);\n-\tres = pick_revisions(&opts);\n+\tres = sequencer_pick_revisions(&opts);\n \tif (res < 0)\n \t\tdie(_(\"cherry-pick failed\"));\n \treturn res;\ndiff --git a/sequencer.c b/sequencer.c\nindex bc2c046..87f146b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1,7 +1,27 @@\n #include \"cache.h\"\n-#include \"sequencer.h\"\n-#include \"strbuf.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"tag.h\"\n+#include \"run-command.h\"\n+#include \"exec_cmd.h\"\n+#include \"utf8.h\"\n+#include \"cache-tree.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rerere.h\"\n+#include \"merge-recursive.h\"\n+#include \"refs.h\"\n #include \"dir.h\"\n+#include \"sequencer.h\"\n+\n+#define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n+\n+static const char *action_name(const struct replay_opts *opts)\n+{\n+\treturn opts->action == REPLAY_REVERT ? \"revert\" : \"cherry-pick\";\n+}\n+\n+static char *get_encoding(const char *message);\n \n void remove_sequencer_state(int aggressive)\n {\n@@ -17,3 +37,781 @@ void remove_sequencer_state(int aggressive)\n \tstrbuf_release(&seq_dir);\n \tstrbuf_release(&seq_old_dir);\n }\n+\n+struct commit_message {\n+\tchar *parent_label;\n+\tconst char *label;\n+\tconst char *subject;\n+\tchar *reencoded_message;\n+\tconst char *message;\n+};\n+\n+static int get_message(struct commit *commit, struct commit_message *out)\n+{\n+\tconst char *encoding;\n+\tconst char *abbrev, *subject;\n+\tint abbrev_len, subject_len;\n+\tchar *q;\n+\n+\tif (!commit->buffer)\n+\t\treturn -1;\n+\tencoding = get_encoding(commit->buffer);\n+\tif (!encoding)\n+\t\tencoding = \"UTF-8\";\n+\tif (!git_commit_encoding)\n+\t\tgit_commit_encoding = \"UTF-8\";\n+\n+\tout->reencoded_message = NULL;\n+\tout->message = commit->buffer;\n+\tif (strcmp(encoding, git_commit_encoding))\n+\t\tout->reencoded_message = reencode_string(commit->buffer,\n+\t\t\t\t\tgit_commit_encoding, encoding);\n+\tif (out->reencoded_message)\n+\t\tout->message = out->reencoded_message;\n+\n+\tabbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);\n+\tabbrev_len = strlen(abbrev);\n+\n+\tsubject_len = find_commit_subject(out->message, &subject);\n+\n+\tout->parent_label = xmalloc(strlen(\"parent of \") + abbrev_len +\n+\t\t\t      strlen(\"... \") + subject_len + 1);\n+\tq = out->parent_label;\n+\tq = mempcpy(q, \"parent of \", strlen(\"parent of \"));\n+\tout->label = q;\n+\tq = mempcpy(q, abbrev, abbrev_len);\n+\tq = mempcpy(q, \"... \", strlen(\"... \"));\n+\tout->subject = q;\n+\tq = mempcpy(q, subject, subject_len);\n+\t*q = '\\0';\n+\treturn 0;\n+}\n+\n+static void free_message(struct commit_message *msg)\n+{\n+\tfree(msg->parent_label);\n+\tfree(msg->reencoded_message);\n+}\n+\n+static char *get_encoding(const char *message)\n+{\n+\tconst char *p = message, *eol;\n+\n+\twhile (*p && *p != '\\n') {\n+\t\tfor (eol = p + 1; *eol && *eol != '\\n'; eol++)\n+\t\t\t; /* do nothing */\n+\t\tif (!prefixcmp(p, \"encoding \")) {\n+\t\t\tchar *result = xmalloc(eol - 8 - p);\n+\t\t\tstrlcpy(result, p + 9, eol - 8 - p);\n+\t\t\treturn result;\n+\t\t}\n+\t\tp = eol;\n+\t\tif (*p == '\\n')\n+\t\t\tp++;\n+\t}\n+\treturn NULL;\n+}\n+\n+static void write_cherry_pick_head(struct commit *commit)\n+{\n+\tint fd;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\n+\tfd = open(git_path(\"CHERRY_PICK_HEAD\"), O_WRONLY | O_CREAT, 0666);\n+\tif (fd < 0)\n+\t\tdie_errno(_(\"Could not open '%s' for writing\"),\n+\t\t\t  git_path(\"CHERRY_PICK_HEAD\"));\n+\tif (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))\n+\t\tdie_errno(_(\"Could not write to '%s'\"), git_path(\"CHERRY_PICK_HEAD\"));\n+\tstrbuf_release(&buf);\n+}\n+\n+static void print_advice(int show_hint)\n+{\n+\tchar *msg = getenv(\"GIT_CHERRY_PICK_HELP\");\n+\n+\tif (msg) {\n+\t\tfprintf(stderr, \"%s\\n\", msg);\n+\t\t/*\n+\t\t * A conflict has occured but the porcelain\n+\t\t * (typically rebase --interactive) wants to take care\n+\t\t * of the commit itself so remove CHERRY_PICK_HEAD\n+\t\t */\n+\t\tunlink(git_path(\"CHERRY_PICK_HEAD\"));\n+\t\treturn;\n+\t}\n+\n+\tif (show_hint) {\n+\t\tadvise(\"after resolving the conflicts, mark the corrected paths\");\n+\t\tadvise(\"with 'git add <paths>' or 'git rm <paths>'\");\n+\t\tadvise(\"and commit the result with 'git commit'\");\n+\t}\n+}\n+\n+static void write_message(struct strbuf *msgbuf, const char *filename)\n+{\n+\tstatic struct lock_file msg_file;\n+\n+\tint msg_fd = hold_lock_file_for_update(&msg_file, filename,\n+\t\t\t\t\t       LOCK_DIE_ON_ERROR);\n+\tif (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)\n+\t\tdie_errno(_(\"Could not write to %s.\"), filename);\n+\tstrbuf_release(msgbuf);\n+\tif (commit_lock_file(&msg_file) < 0)\n+\t\tdie(_(\"Error wrapping up %s\"), filename);\n+}\n+\n+static struct tree *empty_tree(void)\n+{\n+\treturn lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);\n+}\n+\n+static int error_dirty_index(struct replay_opts *opts)\n+{\n+\tif (read_cache_unmerged())\n+\t\treturn error_resolve_conflict(action_name(opts));\n+\n+\t/* Different translation strings for cherry-pick and revert */\n+\tif (opts->action == REPLAY_PICK)\n+\t\terror(_(\"Your local changes would be overwritten by cherry-pick.\"));\n+\telse\n+\t\terror(_(\"Your local changes would be overwritten by revert.\"));\n+\n+\tif (advice_commit_before_merge)\n+\t\tadvise(_(\"Commit your changes or stash them to proceed.\"));\n+\treturn -1;\n+}\n+\n+static int fast_forward_to(const unsigned char *to, const unsigned char *from)\n+{\n+\tstruct ref_lock *ref_lock;\n+\n+\tread_cache();\n+\tif (checkout_fast_forward(from, to))\n+\t\texit(1); /* the callee should have complained already */\n+\tref_lock = lock_any_ref_for_update(\"HEAD\", from, 0);\n+\treturn write_ref_sha1(ref_lock, to, \"cherry-pick\");\n+}\n+\n+static int do_recursive_merge(struct commit *base, struct commit *next,\n+\t\t\t      const char *base_label, const char *next_label,\n+\t\t\t      unsigned char *head, struct strbuf *msgbuf,\n+\t\t\t      struct replay_opts *opts)\n+{\n+\tstruct merge_options o;\n+\tstruct tree *result, *next_tree, *base_tree, *head_tree;\n+\tint clean, index_fd;\n+\tconst char **xopt;\n+\tstatic struct lock_file index_lock;\n+\n+\tindex_fd = hold_locked_index(&index_lock, 1);\n+\n+\tread_cache();\n+\n+\tinit_merge_options(&o);\n+\to.ancestor = base ? base_label : \"(empty tree)\";\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = next ? next_label : \"(empty tree)\";\n+\n+\thead_tree = parse_tree_indirect(head);\n+\tnext_tree = next ? next->tree : empty_tree();\n+\tbase_tree = base ? base->tree : empty_tree();\n+\n+\tfor (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)\n+\t\tparse_merge_opt(&o, *xopt);\n+\n+\tclean = merge_trees(&o,\n+\t\t\t    head_tree,\n+\t\t\t    next_tree, base_tree, &result);\n+\n+\tif (active_cache_changed &&\n+\t    (write_cache(index_fd, active_cache, active_nr) ||\n+\t     commit_locked_index(&index_lock)))\n+\t\t/* TRANSLATORS: %s will be \"revert\" or \"cherry-pick\" */\n+\t\tdie(_(\"%s: Unable to write new index file\"), action_name(opts));\n+\trollback_lock_file(&index_lock);\n+\n+\tif (!clean) {\n+\t\tint i;\n+\t\tstrbuf_addstr(msgbuf, \"\\nConflicts:\\n\\n\");\n+\t\tfor (i = 0; i < active_nr;) {\n+\t\t\tstruct cache_entry *ce = active_cache[i++];\n+\t\t\tif (ce_stage(ce)) {\n+\t\t\t\tstrbuf_addch(msgbuf, '\\t');\n+\t\t\t\tstrbuf_addstr(msgbuf, ce->name);\n+\t\t\t\tstrbuf_addch(msgbuf, '\\n');\n+\t\t\t\twhile (i < active_nr && !strcmp(ce->name,\n+\t\t\t\t\t\tactive_cache[i]->name))\n+\t\t\t\t\ti++;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn !clean;\n+}\n+\n+/*\n+ * If we are cherry-pick, and if the merge did not result in\n+ * hand-editing, we will hit this commit and inherit the original\n+ * author date and name.\n+ * If we are revert, or if our cherry-pick results in a hand merge,\n+ * we had better say that the current user is responsible for that.\n+ */\n+static int run_git_commit(const char *defmsg, struct replay_opts *opts)\n+{\n+\t/* 6 is max possible length of our args array including NULL */\n+\tconst char *args[6];\n+\tint i = 0;\n+\n+\targs[i++] = \"commit\";\n+\targs[i++] = \"-n\";\n+\tif (opts->signoff)\n+\t\targs[i++] = \"-s\";\n+\tif (!opts->edit) {\n+\t\targs[i++] = \"-F\";\n+\t\targs[i++] = defmsg;\n+\t}\n+\targs[i] = NULL;\n+\n+\treturn run_command_v_opt(args, RUN_GIT_CMD);\n+}\n+\n+static int do_pick_commit(struct commit *commit, enum replay_action action,\n+\t\t\tstruct replay_opts *opts)\n+{\n+\tunsigned char head[20];\n+\tstruct commit *base, *next, *parent;\n+\tconst char *base_label, *next_label;\n+\tstruct commit_message msg = { NULL, NULL, NULL, NULL, NULL };\n+\tchar *defmsg = NULL;\n+\tstruct strbuf msgbuf = STRBUF_INIT;\n+\tint res;\n+\n+\tif (opts->no_commit) {\n+\t\t/*\n+\t\t * We do not intend to commit immediately.  We just want to\n+\t\t * merge the differences in, so let's compute the tree\n+\t\t * that represents the \"current\" state for merge-recursive\n+\t\t * to work on.\n+\t\t */\n+\t\tif (write_cache_as_tree(head, 0, NULL))\n+\t\t\tdie (_(\"Your index file is unmerged.\"));\n+\t} else {\n+\t\tif (get_sha1(\"HEAD\", head))\n+\t\t\treturn error(_(\"You do not have a valid HEAD\"));\n+\t\tif (index_differs_from(\"HEAD\", 0))\n+\t\t\treturn error_dirty_index(opts);\n+\t}\n+\tdiscard_cache();\n+\n+\tif (!commit->parents) {\n+\t\tparent = NULL;\n+\t}\n+\telse if (commit->parents->next) {\n+\t\t/* Reverting or cherry-picking a merge commit */\n+\t\tint cnt;\n+\t\tstruct commit_list *p;\n+\n+\t\tif (!opts->mainline)\n+\t\t\treturn error(_(\"Commit %s is a merge but no -m option was given.\"),\n+\t\t\t\tsha1_to_hex(commit->object.sha1));\n+\n+\t\tfor (cnt = 1, p = commit->parents;\n+\t\t     cnt != opts->mainline && p;\n+\t\t     cnt++)\n+\t\t\tp = p->next;\n+\t\tif (cnt != opts->mainline || !p)\n+\t\t\treturn error(_(\"Commit %s does not have parent %d\"),\n+\t\t\t\tsha1_to_hex(commit->object.sha1), opts->mainline);\n+\t\tparent = p->item;\n+\t} else if (0 < opts->mainline)\n+\t\treturn error(_(\"Mainline was specified but commit %s is not a merge.\"),\n+\t\t\tsha1_to_hex(commit->object.sha1));\n+\telse\n+\t\tparent = commit->parents->item;\n+\n+\tif (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))\n+\t\treturn fast_forward_to(commit->object.sha1, head);\n+\n+\tif (parent && parse_commit(parent) < 0)\n+\t\t/* TRANSLATORS: The first %s will be \"revert\" or\n+\t\t   \"cherry-pick\", the second %s a SHA1 */\n+\t\treturn error(_(\"%s: cannot parse parent commit %s\"),\n+\t\t\taction_name(opts), sha1_to_hex(parent->object.sha1));\n+\n+\tif (get_message(commit, &msg) != 0)\n+\t\treturn error(_(\"Cannot get commit message for %s\"),\n+\t\t\tsha1_to_hex(commit->object.sha1));\n+\n+\t/*\n+\t * \"commit\" is an existing commit.  We would want to apply\n+\t * the difference it introduces since its first parent \"prev\"\n+\t * on top of the current HEAD if we are cherry-pick.  Or the\n+\t * reverse of it if we are revert.\n+\t */\n+\n+\tdefmsg = git_pathdup(\"MERGE_MSG\");\n+\n+\tif (action == REPLAY_REVERT) {\n+\t\tbase = commit;\n+\t\tbase_label = msg.label;\n+\t\tnext = parent;\n+\t\tnext_label = msg.parent_label;\n+\t\tstrbuf_addstr(&msgbuf, \"Revert \\\"\");\n+\t\tstrbuf_addstr(&msgbuf, msg.subject);\n+\t\tstrbuf_addstr(&msgbuf, \"\\\"\\n\\nThis reverts commit \");\n+\t\tstrbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));\n+\n+\t\tif (commit->parents && commit->parents->next) {\n+\t\t\tstrbuf_addstr(&msgbuf, \", reversing\\nchanges made to \");\n+\t\t\tstrbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));\n+\t\t}\n+\t\tstrbuf_addstr(&msgbuf, \".\\n\");\n+\t} else {\n+\t\tconst char *p;\n+\n+\t\tbase = parent;\n+\t\tbase_label = msg.parent_label;\n+\t\tnext = commit;\n+\t\tnext_label = msg.label;\n+\n+\t\t/*\n+\t\t * Append the commit log message to msgbuf; it starts\n+\t\t * after the tree, parent, author, committer\n+\t\t * information followed by \"\\n\\n\".\n+\t\t */\n+\t\tp = strstr(msg.message, \"\\n\\n\");\n+\t\tif (p) {\n+\t\t\tp += 2;\n+\t\t\tstrbuf_addstr(&msgbuf, p);\n+\t\t}\n+\n+\t\tif (opts->record_origin) {\n+\t\t\tstrbuf_addstr(&msgbuf, \"(cherry picked from commit \");\n+\t\t\tstrbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));\n+\t\t\tstrbuf_addstr(&msgbuf, \")\\n\");\n+\t\t}\n+\t}\n+\n+\tif (!opts->strategy || !strcmp(opts->strategy, \"recursive\") || action == REPLAY_REVERT) {\n+\t\tres = do_recursive_merge(base, next, base_label, next_label,\n+\t\t\t\t\t head, &msgbuf, opts);\n+\t\twrite_message(&msgbuf, defmsg);\n+\t} else {\n+\t\tstruct commit_list *common = NULL;\n+\t\tstruct commit_list *remotes = NULL;\n+\n+\t\twrite_message(&msgbuf, defmsg);\n+\n+\t\tcommit_list_insert(base, &common);\n+\t\tcommit_list_insert(next, &remotes);\n+\t\tres = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,\n+\t\t\t\t\tcommon, sha1_to_hex(head), remotes);\n+\t\tfree_commit_list(common);\n+\t\tfree_commit_list(remotes);\n+\t}\n+\n+\t/*\n+\t * If the merge was clean or if it failed due to conflict, we write\n+\t * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.\n+\t * However, if the merge did not even start, then we don't want to\n+\t * write it at all.\n+\t */\n+\tif (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))\n+\t\twrite_cherry_pick_head(commit);\n+\n+\tif (res) {\n+\t\terror(action == REPLAY_REVERT\n+\t\t      ? _(\"could not revert %s... %s\")\n+\t\t      : _(\"could not apply %s... %s\"),\n+\t\t      find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),\n+\t\t      msg.subject);\n+\t\tprint_advice(res == 1);\n+\t\trerere(opts->allow_rerere_auto);\n+\t} else {\n+\t\tif (!opts->no_commit)\n+\t\t\tres = run_git_commit(defmsg, opts);\n+\t}\n+\n+\tfree_message(&msg);\n+\tfree(defmsg);\n+\n+\treturn res;\n+}\n+\n+static void prepare_revs(struct replay_opts *opts)\n+{\n+\tif (opts->action != REPLAY_REVERT)\n+\t\topts->revs->reverse ^= 1;\n+\n+\tif (prepare_revision_walk(opts->revs))\n+\t\tdie(_(\"revision walk setup failed\"));\n+\n+\tif (!opts->revs->commits)\n+\t\tdie(_(\"empty commit set passed\"));\n+}\n+\n+static void read_and_refresh_cache(struct replay_opts *opts)\n+{\n+\tstatic struct lock_file index_lock;\n+\tint index_fd = hold_locked_index(&index_lock, 0);\n+\tif (read_index_preload(&the_index, NULL) < 0)\n+\t\tdie(_(\"git %s: failed to read the index\"), action_name(opts));\n+\trefresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);\n+\tif (the_index.cache_changed) {\n+\t\tif (write_index(&the_index, index_fd) ||\n+\t\t    commit_locked_index(&index_lock))\n+\t\t\tdie(_(\"git %s: failed to refresh the index\"), action_name(opts));\n+\t}\n+\trollback_lock_file(&index_lock);\n+}\n+\n+/*\n+ * Append a commit to the end of the commit_list.\n+ *\n+ * next starts by pointing to the variable that holds the head of an\n+ * empty commit_list, and is updated to point to the \"next\" field of\n+ * the last item on the list as new commits are appended.\n+ *\n+ * Usage example:\n+ *\n+ *     struct commit_list *list;\n+ *     struct commit_list **next = &list;\n+ *\n+ *     next = commit_list_append(c1, next);\n+ *     next = commit_list_append(c2, next);\n+ *     assert(commit_list_count(list) == 2);\n+ *     return list;\n+ */\n+static struct replay_insn_list **replay_insn_list_append(enum replay_action action,\n+\t\t\t\t\t\tstruct commit *operand,\n+\t\t\t\t\t\tstruct replay_insn_list **next)\n+{\n+\tstruct replay_insn_list *new = xmalloc(sizeof(*new));\n+\tnew->action = action;\n+\tnew->operand = operand;\n+\t*next = new;\n+\tnew->next = NULL;\n+\treturn &new->next;\n+}\n+\n+static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list)\n+{\n+\tstruct replay_insn_list *cur;\n+\n+\tfor (cur = todo_list; cur; cur = cur->next) {\n+\t\tconst char *sha1_abbrev, *action_str, *subject;\n+\t\tint subject_len;\n+\n+\t\taction_str = cur->action == REPLAY_REVERT ? \"revert\" : \"pick\";\n+\t\tsha1_abbrev = find_unique_abbrev(cur->operand->object.sha1, DEFAULT_ABBREV);\n+\t\tsubject_len = find_commit_subject(cur->operand->buffer, &subject);\n+\t\tstrbuf_addf(buf, \"%s %s %.*s\\n\", action_str, sha1_abbrev,\n+\t\t\tsubject_len, subject);\n+\t}\n+\treturn 0;\n+}\n+\n+static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item)\n+{\n+\tunsigned char commit_sha1[20];\n+\tchar *end_of_object_name;\n+\tint saved, status;\n+\n+\tif (!prefixcmp(bol, \"pick \")) {\n+\t\titem->action = REPLAY_PICK;\n+\t\tbol += strlen(\"pick \");\n+\t} else if (!prefixcmp(bol, \"revert \")) {\n+\t\titem->action = REPLAY_REVERT;\n+\t\tbol += strlen(\"revert \");\n+\t} else {\n+\t\tsize_t len = strchrnul(bol, '\\n') - bol;\n+\t\tif (len > 255)\n+\t\t\tlen = 255;\n+\t\treturn error(_(\"Unrecognized action: %.*s\"), (int)len, bol);\n+\t}\n+\n+\tend_of_object_name = bol + strcspn(bol, \" \\n\");\n+\tsaved = *end_of_object_name;\n+\t*end_of_object_name = '\\0';\n+\tstatus = get_sha1(bol, commit_sha1);\n+\t*end_of_object_name = saved;\n+\n+\tif (status < 0)\n+\t\treturn error(_(\"Malformed object name: %s\"), bol);\n+\n+\titem->operand = lookup_commit_reference(commit_sha1);\n+\tif (!item->operand)\n+\t\treturn error(_(\"Not a valid commit: %s\"), bol);\n+\n+\titem->next = NULL;\n+\treturn 0;\n+}\n+\n+static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list)\n+{\n+\tstruct replay_insn_list **next = todo_list;\n+\tstruct replay_insn_list item = {0, NULL, NULL};\n+\tchar *p = buf;\n+\tint i;\n+\n+\tfor (i = 1; *p; i++) {\n+\t\tchar *eol = strchrnul(p, '\\n');\n+\t\tif (parse_insn_line(p, eol, &item) < 0)\n+\t\t\treturn error(_(\"on line %d.\"), i);\n+\t\tnext = replay_insn_list_append(item.action, item.operand, next);\n+\t\tp = *eol ? eol + 1 : eol;\n+\t}\n+\tif (!*todo_list)\n+\t\treturn error(_(\"No commits parsed.\"));\n+\treturn 0;\n+}\n+\n+static void read_populate_todo(struct replay_insn_list **todo_list)\n+{\n+\tconst char *todo_file = git_path(SEQ_TODO_FILE);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint fd, res;\n+\n+\tfd = open(todo_file, O_RDONLY);\n+\tif (fd < 0)\n+\t\tdie_errno(_(\"Could not open %s.\"), todo_file);\n+\tif (strbuf_read(&buf, fd, 0) < 0) {\n+\t\tclose(fd);\n+\t\tstrbuf_release(&buf);\n+\t\tdie(_(\"Could not read %s.\"), todo_file);\n+\t}\n+\tclose(fd);\n+\n+\tres = parse_insn_buffer(buf.buf, todo_list);\n+\tstrbuf_release(&buf);\n+\tif (res)\n+\t\tdie(_(\"Unusable instruction sheet: %s\"), todo_file);\n+}\n+\n+static int populate_opts_cb(const char *key, const char *value, void *data)\n+{\n+\tstruct replay_opts *opts = data;\n+\tint error_flag = 1;\n+\n+\tif (!value)\n+\t\terror_flag = 0;\n+\telse if (!strcmp(key, \"options.no-commit\"))\n+\t\topts->no_commit = git_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.edit\"))\n+\t\topts->edit = git_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.signoff\"))\n+\t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.record-origin\"))\n+\t\topts->record_origin = git_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.allow-ff\"))\n+\t\topts->allow_ff = git_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.mainline\"))\n+\t\topts->mainline = git_config_int(key, value);\n+\telse if (!strcmp(key, \"options.strategy\"))\n+\t\tgit_config_string(&opts->strategy, key, value);\n+\telse if (!strcmp(key, \"options.strategy-option\")) {\n+\t\tALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);\n+\t\topts->xopts[opts->xopts_nr++] = xstrdup(value);\n+\t} else\n+\t\treturn error(_(\"Invalid key: %s\"), key);\n+\n+\tif (!error_flag)\n+\t\treturn error(_(\"Invalid value for %s: %s\"), key, value);\n+\n+\treturn 0;\n+}\n+\n+static void read_populate_opts(struct replay_opts **opts_ptr)\n+{\n+\tconst char *opts_file = git_path(SEQ_OPTS_FILE);\n+\n+\tif (!file_exists(opts_file))\n+\t\treturn;\n+\tif (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)\n+\t\tdie(_(\"Malformed options sheet: %s\"), opts_file);\n+}\n+\n+static void walk_revs_populate_todo(struct replay_insn_list **todo_list,\n+\t\t\t\tstruct replay_opts *opts)\n+{\n+\tstruct commit *commit;\n+\tstruct replay_insn_list **next;\n+\n+\tprepare_revs(opts);\n+\n+\tnext = todo_list;\n+\twhile ((commit = get_revision(opts->revs)))\n+\t\tnext = replay_insn_list_append(opts->action, commit, next);\n+}\n+\n+static int create_seq_dir(void)\n+{\n+\tconst char *seq_dir = git_path(SEQ_DIR);\n+\n+\tif (file_exists(seq_dir))\n+\t\treturn error(_(\"%s already exists.\"), seq_dir);\n+\telse if (mkdir(seq_dir, 0777) < 0)\n+\t\tdie_errno(_(\"Could not create sequencer directory '%s'.\"), seq_dir);\n+\treturn 0;\n+}\n+\n+static void save_head(const char *head)\n+{\n+\tconst char *head_file = git_path(SEQ_HEAD_FILE);\n+\tstatic struct lock_file head_lock;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint fd;\n+\n+\tfd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);\n+\tstrbuf_addf(&buf, \"%s\\n\", head);\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0)\n+\t\tdie_errno(_(\"Could not write to %s.\"), head_file);\n+\tif (commit_lock_file(&head_lock) < 0)\n+\t\tdie(_(\"Error wrapping up %s.\"), head_file);\n+}\n+\n+static void save_todo(struct replay_insn_list *todo_list)\n+{\n+\tconst char *todo_file = git_path(SEQ_TODO_FILE);\n+\tstatic struct lock_file todo_lock;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint fd;\n+\n+\tfd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);\n+\tif (format_todo(&buf, todo_list) < 0)\n+\t\tdie(_(\"Could not format %s.\"), todo_file);\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\tdie_errno(_(\"Could not write to %s.\"), todo_file);\n+\t}\n+\tif (commit_lock_file(&todo_lock) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\tdie(_(\"Error wrapping up %s.\"), todo_file);\n+\t}\n+\tstrbuf_release(&buf);\n+}\n+\n+static void save_opts(struct replay_opts *opts)\n+{\n+\tconst char *opts_file = git_path(SEQ_OPTS_FILE);\n+\n+\tif (opts->no_commit)\n+\t\tgit_config_set_in_file(opts_file, \"options.no-commit\", \"true\");\n+\tif (opts->edit)\n+\t\tgit_config_set_in_file(opts_file, \"options.edit\", \"true\");\n+\tif (opts->signoff)\n+\t\tgit_config_set_in_file(opts_file, \"options.signoff\", \"true\");\n+\tif (opts->record_origin)\n+\t\tgit_config_set_in_file(opts_file, \"options.record-origin\", \"true\");\n+\tif (opts->allow_ff)\n+\t\tgit_config_set_in_file(opts_file, \"options.allow-ff\", \"true\");\n+\tif (opts->mainline) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tstrbuf_addf(&buf, \"%d\", opts->mainline);\n+\t\tgit_config_set_in_file(opts_file, \"options.mainline\", buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t}\n+\tif (opts->strategy)\n+\t\tgit_config_set_in_file(opts_file, \"options.strategy\", opts->strategy);\n+\tif (opts->xopts) {\n+\t\tint i;\n+\t\tfor (i = 0; i < opts->xopts_nr; i++)\n+\t\t\tgit_config_set_multivar_in_file(opts_file,\n+\t\t\t\t\t\t\t\"options.strategy-option\",\n+\t\t\t\t\t\t\topts->xopts[i], \"^$\", 0);\n+\t}\n+}\n+\n+static int pick_commits(struct replay_insn_list *todo_list,\n+\t\t\tstruct replay_opts *opts)\n+{\n+\tstruct replay_insn_list *cur;\n+\tint res;\n+\n+\tsetenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n+\tif (opts->allow_ff)\n+\t\tassert(!(opts->signoff || opts->no_commit ||\n+\t\t\t\topts->record_origin || opts->edit));\n+\tread_and_refresh_cache(opts);\n+\n+\tfor (cur = todo_list; cur; cur = cur->next) {\n+\t\tsave_todo(cur);\n+\t\tres = do_pick_commit(cur->operand, cur->action, opts);\n+\t\tif (res) {\n+\t\t\tif (!cur->next)\n+\t\t\t\t/*\n+\t\t\t\t * An error was encountered while\n+\t\t\t\t * picking the last commit; the\n+\t\t\t\t * sequencer state is useless now --\n+\t\t\t\t * the user simply needs to resolve\n+\t\t\t\t * the conflict and commit\n+\t\t\t\t */\n+\t\t\t\tremove_sequencer_state(0);\n+\t\t\treturn res;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Sequence of picks finished successfully; cleanup by\n+\t * removing the .git/sequencer directory\n+\t */\n+\tremove_sequencer_state(1);\n+\treturn 0;\n+}\n+\n+int sequencer_pick_revisions(struct replay_opts *opts)\n+{\n+\tstruct replay_insn_list *todo_list = NULL;\n+\tunsigned char sha1[20];\n+\n+\tif (opts->subcommand == REPLAY_NONE)\n+\t\tassert(opts->revs);\n+\n+\tread_and_refresh_cache(opts);\n+\n+\t/*\n+\t * Decide what to do depending on the arguments; a fresh\n+\t * cherry-pick should be handled differently from an existing\n+\t * one that is being continued\n+\t */\n+\tif (opts->subcommand == REPLAY_RESET) {\n+\t\tremove_sequencer_state(1);\n+\t\treturn 0;\n+\t} else if (opts->subcommand == REPLAY_CONTINUE) {\n+\t\tif (!file_exists(git_path(SEQ_TODO_FILE)))\n+\t\t\tgoto error;\n+\t\tread_populate_opts(&opts);\n+\t\tread_populate_todo(&todo_list);\n+\n+\t\t/* Verify that the conflict has been resolved */\n+\t\tif (!index_differs_from(\"HEAD\", 0))\n+\t\t\ttodo_list = todo_list->next;\n+\t} else {\n+\t\t/*\n+\t\t * Start a new cherry-pick/ revert sequence; but\n+\t\t * first, make sure that an existing one isn't in\n+\t\t * progress\n+\t\t */\n+\n+\t\twalk_revs_populate_todo(&todo_list, opts);\n+\t\tif (create_seq_dir() < 0) {\n+\t\t\terror(_(\"A cherry-pick or revert is in progress.\"));\n+\t\t\tadvise(_(\"Use --continue to continue the operation\"));\n+\t\t\tadvise(_(\"or --reset to forget about it\"));\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (get_sha1(\"HEAD\", sha1)) {\n+\t\t\tif (opts->action == REPLAY_REVERT)\n+\t\t\t\treturn error(_(\"Can't revert as initial commit\"));\n+\t\t\treturn error(_(\"Can't cherry-pick into empty head\"));\n+\t\t}\n+\t\tsave_head(sha1_to_hex(sha1));\n+\t\tsave_opts(opts);\n+\t}\n+\treturn pick_commits(todo_list, opts);\n+error:\n+\treturn error(_(\"No %s in progress\"), action_name(opts));\n+}\ndiff --git a/sequencer.h b/sequencer.h\nindex f4db257..92b2d63 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -8,6 +8,30 @@\n #define SEQ_OPTS_FILE\t\"sequencer/opts\"\n \n enum replay_action { REPLAY_REVERT, REPLAY_PICK };\n+enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };\n+\n+struct replay_opts {\n+\tenum replay_action action;\n+\tenum replay_subcommand subcommand;\n+\n+\t/* Boolean options */\n+\tint edit;\n+\tint record_origin;\n+\tint no_commit;\n+\tint signoff;\n+\tint allow_ff;\n+\tint allow_rerere_auto;\n+\n+\tint mainline;\n+\n+\t/* Merge strategy */\n+\tconst char *strategy;\n+\tconst char **xopts;\n+\tsize_t xopts_nr, xopts_alloc;\n+\n+\t/* Only used by REPLAY_NONE */\n+\tstruct rev_info *revs;\n+};\n \n struct replay_insn_list {\n \tenum replay_action action;\n@@ -25,4 +49,6 @@ struct replay_insn_list {\n  */\n void remove_sequencer_state(int aggressive);\n \n+int sequencer_pick_revisions(struct replay_opts *opts);\n+\n #endif\n-- \n1.7.6.351.gb35ac.dirty\n"},{"id":"179376","messageId":"1321181181-23923-3-git-send-email-artagnon@gmail.com","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 2/7] sequencer: invalidate sequencer state without todo","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-13T10:46:16Z","receivedAt":"2011-11-13T10:46:16Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"To check whether an existing sequencer operation is in progress and\nerror out, we currently check for the existence of the\n'.git/sequencer' directory.\n\n  $ git cherry-pick foo..bar\n  ... conflict in bar~1 ...\n  ... .git/sequencer is created ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git commit # Success!\n  $ git cherry-pick moo # .git/sequencer exists\n  error: A cherry-pick or revert is in progress\n\nAlthough the sequencer state is useless without '.git/sequencer/todo',\nthis case never occurs.  However, in the light of the next patch,\nwhere will handle single-commit picks as a special case by not\npersisting '.git/sequencer/todo' in the first place, we are forced to\nreconsider.  So, when starting a fresh sequencer invocation, remove\nthe sequencer state carried over from a previous invocation if it's\nmissing '.git/sequencer/todo'.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n sequencer.c |   10 +++++++---\n 1 files changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 87f146b..8b2518c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -649,11 +649,15 @@ static void walk_revs_populate_todo(struct replay_insn_list **todo_list,\n \n static int create_seq_dir(void)\n {\n+\tconst char *todo_file = git_path(SEQ_TODO_FILE);\n \tconst char *seq_dir = git_path(SEQ_DIR);\n \n-\tif (file_exists(seq_dir))\n-\t\treturn error(_(\"%s already exists.\"), seq_dir);\n-\telse if (mkdir(seq_dir, 0777) < 0)\n+\tif (file_exists(todo_file))\n+\t\treturn error(_(\"%s already exists.\"), todo_file);\n+\n+\t/* If todo_file doesn't exist, discard sequencer state */\n+\tremove_sequencer_state(1);\n+\tif (mkdir(seq_dir, 0777) < 0)\n \t\tdie_errno(_(\"Could not create sequencer directory '%s'.\"), seq_dir);\n \treturn 0;\n }\n-- \n1.7.6.351.gb35ac.dirty\n"},{"id":"179378","messageId":"1321181181-23923-4-git-send-email-artagnon@gmail.com","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 3/7] sequencer: handle single-commit pick as special case","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-13T10:46:17Z","receivedAt":"2011-11-13T10:46:17Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Prior to v1.7.2-rc1~4^2~7 (revert: allow cherry-picking more than one\ncommit, 2010-06-02), 'git cherry-pick' could only pick one commit at a\ntime, and it used '.git/CHERRY_PICK_HEAD' to pass on information to a\nsubsequent invocation in case of a conflict.  While\n'.git/CHERRY_PICK_HEAD' can only keep information about one commit,\nthe sequencer uses '.git/sequencer' to persist information in the\ngeneral case.\n\nA problem arises because a single-commit cherry-pick operation can be\ncompleted successfully using 'git commit'.  This removes\n'.git/CHERRY_PICK_HEAD' without informing the sequencer, leaving\nbehind a stale sequencer state as a result.  We have worked around\nthis problem already by prematurely removing the sequencer state in\nd3f4628e (revert: Remove sequencer state when no commits are pending,\n2011-06-06).  However, this gets in the way of our future plan to\neliminate a glaring workflow inconsistency:\n\n  $ git cherry-pick foo\n  ... .git/sequencer is created ....\n  ... .git/CHERRY_PICK_HEAD is created ...\n  ... conflict ...\n  .... .git/sequencer is prematurely removed ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git commit\n  ... .git/CHERRY_PICK_HEAD is removed ...\n  $ git cherry-pick --continue\n  error: No cherry-pick in progress\n\n  $ git cherry-pick foo..bar\n  ... .git/sequencer is created ....\n  ... CHERRY_PICK_HEAD is created ...\n  ... conflict in bar~1 ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git commit\n  ... CHERRY_PICK_HEAD is removed ...\n  $ git cherry-pick --continue # Success!\n\nTo eliminate this inconsistency, we have decided to make '--continue'\ncontinue any general sequencer operation bypassing 'git commit'\ncompletely (although preserving the existing workflow for backward\ncompatibility).  For '--continue' and '--reset' to work uniformly,\nthey must use the information in:\n\n1. '.git/sequencer/head', '.git/sequencer/opts', '.git/sequencer/todo'\n   in the general case.\n2. '.git/sequencer/head', '.git/sequencer/opts', '.git/CHERRY_PICK_HEAD'\n   in case of a single-commit cherry-pick.\n\nAs a start, handle cherry-picking a single commit as a special case by\nnot creating '.git/sequencer/todo' in the first place.  This will\neliminate the need for prematurely removing it in d3f4628e.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n sequencer.c |    8 ++++++++\n 1 files changed, 8 insertions(+), 0 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 8b2518c..b35fcc7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -741,6 +741,14 @@ static int pick_commits(struct replay_insn_list *todo_list,\n \t\t\t\topts->record_origin || opts->edit));\n \tread_and_refresh_cache(opts);\n \n+\t/*\n+\t * Backward compatibility hack: handle single-commit pick as a\n+\t * special case.\n+\t */\n+\tif (opts->subcommand == REPLAY_NONE &&\n+\t\ttodo_list->next == NULL && todo_list->action == REPLAY_PICK)\n+\t\treturn do_pick_commit(todo_list->operand, REPLAY_PICK, opts);\n+\n \tfor (cur = todo_list; cur; cur = cur->next) {\n \t\tsave_todo(cur);\n \t\tres = do_pick_commit(cur->operand, cur->action, opts);\n-- \n1.7.6.351.gb35ac.dirty\n"},{"id":"179377","messageId":"1321181181-23923-5-git-send-email-artagnon@gmail.com","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 4/7] sequencer: handle cherry-pick conflict in last commit","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-13T10:46:18Z","receivedAt":"2011-11-13T10:46:18Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"The previous two commits in the series implement special handling for\nthe single-commit cherry-pick case.  Although we can technically\nrevert the changes made by d3f4628e (revert: Remove sequencer state\nwhen no commits are pending, 2011-06-06) without breaking any existing\ntests now, there is one pending corner case: when a cherry-pick is\ninvoked on a commit range, and a conflict that occurs in the last\ncommit is concluded with a 'git commit'.  Without d3f4628e, we'd have\nthe following unpleasant case:\n\n  $ git cherry-pick foo..bar\n  ... .git/sequencer is created ...\n  ... .git/CHERRY_PICK_HEAD is created ...\n  ... conflict in bar ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git commit\n  ... .git/CHERRY_PICK_HEAD is removed ...\n  $ git cherry-pick moo\n  error: An existing cherry-pick or revert is in progress\n  $ git cherry-pick --continue\n  ... .git/sequencer is removed ...\n  # We're in pristine shape now\n\nWhile prematurely removing the entire sequencer state is an overkill,\nwe can revise our plan: prematurely remove only '.git/sequencer/todo'\nin the 'REPLAY_PICK' case, because this is the exact case where the\ninformation in '.git/sequencer/todo' can be inferred from\n'.git/CHERRY_PICK_HEAD'.  This will be compatible with our future plan\nto implement '--continue' and '--reset' consistently.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n sequencer.c                     |    8 +++-----\n t/t3510-cherry-pick-sequence.sh |    4 ++--\n 2 files changed, 5 insertions(+), 7 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b35fcc7..23fd3fe 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -753,15 +753,13 @@ static int pick_commits(struct replay_insn_list *todo_list,\n \t\tsave_todo(cur);\n \t\tres = do_pick_commit(cur->operand, cur->action, opts);\n \t\tif (res) {\n-\t\t\tif (!cur->next)\n+\t\t\tif (!cur->next && opts->action == REPLAY_PICK)\n \t\t\t\t/*\n \t\t\t\t * An error was encountered while\n \t\t\t\t * picking the last commit; the\n-\t\t\t\t * sequencer state is useless now --\n-\t\t\t\t * the user simply needs to resolve\n-\t\t\t\t * the conflict and commit\n+\t\t\t\t * sequencer todo is useless now.\n \t\t\t\t */\n-\t\t\t\tremove_sequencer_state(0);\n+\t\t\t\tunlink(git_path(SEQ_TODO_FILE));\n \t\t\treturn res;\n \t\t}\n \t}\ndiff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh\nindex 4b12244..09b9e65 100755\n--- a/t/t3510-cherry-pick-sequence.sh\n+++ b/t/t3510-cherry-pick-sequence.sh\n@@ -85,10 +85,10 @@ test_expect_success '--reset cleans up sequencer state' '\n \ttest_path_is_missing .git/sequencer\n '\n \n-test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '\n+test_expect_success 'cherry-pick cleans up sequencer todo when one commit is left' '\n \tpristine_detach initial &&\n \ttest_must_fail git cherry-pick base..picked &&\n-\ttest_path_is_missing .git/sequencer &&\n+\ttest_path_is_missing .git/sequencer/todo &&\n \techo \"resolved\" >foo &&\n \tgit add foo &&\n \tgit commit &&\n-- \n1.7.6.351.gb35ac.dirty\n"},{"id":"179381","messageId":"1321181181-23923-6-git-send-email-artagnon@gmail.com","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 5/7] sequencer: introduce git-sequencer builtin","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-13T10:46:19Z","receivedAt":"2011-11-13T10:46:19Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Introduce a new builtin 'git sequencer' that implements only\n'--continue' and '--reset' features of the sequencer.  As a result, it\ncan only be used to continue an existing sequencer operation, not to\nstart a new one.  Now you can do:\n\n  $ git cherry-pick foo..bar\n  ... conflict ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git sequencer --continue\n\nSo, irrespective of the concrete implementation of the sequencer\ninvoked earlier, any sequencer operation can be continued with 'git\nsequencer --continue' and reset with 'git sequencer --reset'.  Also,\nwe plan to make the 'git sequencer' builtin implement a concrete\noperation similar to 'git rebase -i' in the future.\n\nWhile at it, change the advice printed by the sequencer library and\nre-organize 't3510-cherry-pick-sequence.sh'.  There are now two\nseparate sets of tests:\n\n  t3510-sequencer.sh is meant to test the continuation features of the\n  sequencer, using 'git cherry-pick' to initiate an operation.\n\n  t3511-cherry-pick-sequencer.sh is meant to test 'git cherry-pick' as\n  a concrete implementation of the sequencer, stressing on many of the\n  backward compatibility features specific to it.\n\nIn the future, when the sequencer grows more functionality that\ndoesn't directly impact the concrete implementation 'git cherry-pick',\nthe corresponding tests should go into 't3510-sequencer.sh'.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n .gitignore                                         |    1 +\n Documentation/git-sequencer.txt                    |   33 +++++\n Makefile                                           |    1 +\n builtin.h                                          |    1 +\n builtin/sequencer.c                                |   52 +++++++\n git.c                                              |    1 +\n sequencer.c                                        |    6 +-\n t/t3510-sequencer.sh                               |  153 ++++++++++++++++++++\n ...-sequence.sh => t3511-cherry-pick-sequencer.sh} |  120 +---------------\n 9 files changed, 251 insertions(+), 117 deletions(-)\n create mode 100644 Documentation/git-sequencer.txt\n create mode 100644 builtin/sequencer.c\n create mode 100755 t/t3510-sequencer.sh\n rename t/{t3510-cherry-pick-sequence.sh => t3511-cherry-pick-sequencer.sh} (61%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 8572c8c..74ea408 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -128,6 +128,7 @@\n /git-rm\n /git-send-email\n /git-send-pack\n+/git-sequencer\n /git-sh-i18n\n /git-sh-i18n--envsubst\n /git-sh-setup\ndiff --git a/Documentation/git-sequencer.txt b/Documentation/git-sequencer.txt\nnew file mode 100644\nindex 0000000..aa8a80b\n--- /dev/null\n+++ b/Documentation/git-sequencer.txt\n@@ -0,0 +1,33 @@\n+git-sequencer(1)\n+================\n+\n+NAME\n+----\n+git-sequencer - Finish an existing sequencer operation\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git sequencer' --reset\n+'git sequencer' --continue\n+\n+DESCRIPTION\n+-----------\n+Given any arbitrary sequencer operation started using 'git\n+cherry-pick' or 'git revert', facilitate continuing or resetting it\n+via a uniform interface.\n+\n+\n+OPTIONS\n+-------\n+include::sequencer.txt[]\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-cherry-pick[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 17404c4..e367310 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -777,6 +777,7 @@ BUILTIN_OBJS += builtin/rev-parse.o\n BUILTIN_OBJS += builtin/revert.o\n BUILTIN_OBJS += builtin/rm.o\n BUILTIN_OBJS += builtin/send-pack.o\n+BUILTIN_OBJS += builtin/sequencer.o\n BUILTIN_OBJS += builtin/shortlog.o\n BUILTIN_OBJS += builtin/show-branch.o\n BUILTIN_OBJS += builtin/show-ref.o\ndiff --git a/builtin.h b/builtin.h\nindex 0e9da90..0335163 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -119,6 +119,7 @@ extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n extern int cmd_revert(int argc, const char **argv, const char *prefix);\n extern int cmd_rm(int argc, const char **argv, const char *prefix);\n extern int cmd_send_pack(int argc, const char **argv, const char *prefix);\n+extern int cmd_sequencer(int argc, const char **argv, const char *prefix);\n extern int cmd_shortlog(int argc, const char **argv, const char *prefix);\n extern int cmd_show(int argc, const char **argv, const char *prefix);\n extern int cmd_show_branch(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/sequencer.c b/builtin/sequencer.c\nnew file mode 100644\nindex 0000000..7364a52\n--- /dev/null\n+++ b/builtin/sequencer.c\n@@ -0,0 +1,52 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+#include \"sequencer.h\"\n+\n+static const char *const sequencer_usage[] = {\n+\t\"git sequencer --continue\",\n+\t\"git sequencer --reset\",\n+\tNULL\n+};\n+\n+static void parse_args(int argc, const char **argv, struct replay_opts *opts)\n+{\n+\tint reset = 0;\n+\tint contin = 0;\n+\tstruct option options[] = {\n+\t\tOPT_BOOLEAN(0, \"reset\", &reset, \"forget the current operation\"),\n+\t\tOPT_BOOLEAN(0, \"continue\", &contin, \"continue the current operation\"),\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options, sequencer_usage,\n+\t\t\tPARSE_OPT_KEEP_ARGV0 |\n+\t\t\tPARSE_OPT_KEEP_UNKNOWN);\n+\n+\t/* Set the subcommand */\n+\tif (reset)\n+\t\topts->subcommand = REPLAY_RESET;\n+\telse if (contin)\n+\t\topts->subcommand = REPLAY_CONTINUE;\n+\telse\n+\t\topts->subcommand = REPLAY_NONE;\n+\n+\t/* Forbid REPLAY_NONE and stray command-line arguments */\n+\tif (opts->subcommand == REPLAY_NONE || argc > 1)\n+\t\tusage_with_options(sequencer_usage, options);\n+}\n+\n+int cmd_sequencer(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct replay_opts opts;\n+\tint res;\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.action = REPLAY_REVERT;\n+\tgit_config(git_default_config, NULL);\n+\tparse_args(argc, argv, &opts);\n+\tres = sequencer_pick_revisions(&opts);\n+\tif (res < 0)\n+\t\tdie(_(\"sequencer failed\"));\n+\treturn res;\n+}\ndiff --git a/git.c b/git.c\nindex 8e34903..5262c9b 100644\n--- a/git.c\n+++ b/git.c\n@@ -418,6 +418,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"rm\", cmd_rm, RUN_SETUP },\n \t\t{ \"send-pack\", cmd_send_pack, RUN_SETUP },\n+\t\t{ \"sequencer\", cmd_sequencer, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"shortlog\", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },\n \t\t{ \"show\", cmd_show, RUN_SETUP },\n \t\t{ \"show-branch\", cmd_show_branch, RUN_SETUP },\ndiff --git a/sequencer.c b/sequencer.c\nindex 23fd3fe..012d531 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -808,9 +808,9 @@ int sequencer_pick_revisions(struct replay_opts *opts)\n \n \t\twalk_revs_populate_todo(&todo_list, opts);\n \t\tif (create_seq_dir() < 0) {\n-\t\t\terror(_(\"A cherry-pick or revert is in progress.\"));\n-\t\t\tadvise(_(\"Use --continue to continue the operation\"));\n-\t\t\tadvise(_(\"or --reset to forget about it\"));\n+\t\t\terror(_(\"A sequencer operation is in progress.\"));\n+\t\t\tadvise(_(\"Use git sequencer --continue to it\"));\n+\t\t\tadvise(_(\"or git sequencer --reset to forget about it\"));\n \t\t\treturn -1;\n \t\t}\n \t\tif (get_sha1(\"HEAD\", sha1)) {\ndiff --git a/t/t3510-sequencer.sh b/t/t3510-sequencer.sh\nnew file mode 100755\nindex 0000000..6b2e712\n--- /dev/null\n+++ b/t/t3510-sequencer.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+\n+test_description='Test sequencer continuation features\n+\n+  + anotherpick: rewrites foo to d\n+  + picked: rewrites foo to c\n+  + unrelatedpick: rewrites unrelated to reallyunrelated\n+  + base: rewrites foo to b\n+  + initial: writes foo as a, unrelated as unrelated\n+\n+'\n+\n+. ./test-lib.sh\n+\n+# Repeat first match 10 times\n+_r10='\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1'\n+\n+pristine_detach () {\n+\tgit cherry-pick --reset &&\n+\tgit checkout -f \"$1^0\" &&\n+\tgit read-tree -u --reset HEAD &&\n+\tgit clean -d -f -f -q -x\n+}\n+\n+test_expect_success setup '\n+\techo unrelated >unrelated &&\n+\tgit add unrelated &&\n+\ttest_commit initial foo a &&\n+\ttest_commit base foo b &&\n+\ttest_commit unrelatedpick unrelated reallyunrelated &&\n+\ttest_commit picked foo c &&\n+\ttest_commit anotherpick foo d &&\n+\tgit config advice.detachedhead false\n+\n+'\n+\n+test_expect_success '--continue complains when no sequencer operation is in progress' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git sequencer --continue\n+'\n+\n+test_expect_success '--continue complains when there are unresolved conflicts' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick base..anotherpick &&\n+\ttest_must_fail git sequencer --continue\n+'\n+\n+test_expect_success 'malformed instruction sheet 1' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick base..anotherpick &&\n+\techo \"resolved\" >foo &&\n+\tgit add foo &&\n+\tgit commit &&\n+\tsed \"s/pick /pick/\" .git/sequencer/todo >new_sheet &&\n+\tcp new_sheet .git/sequencer/todo &&\n+\ttest_must_fail git sequencer --continue\n+'\n+\n+test_expect_success 'malformed instruction sheet 2' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick base..anotherpick &&\n+\techo \"resolved\" >foo &&\n+\tgit add foo &&\n+\tgit commit &&\n+\tsed \"s/pick/revert/\" .git/sequencer/todo >new_sheet &&\n+\tcp new_sheet .git/sequencer/todo &&\n+\ttest_must_fail git sequencer --continue\n+'\n+\n+test_expect_success 'malformed instruction sheet 3' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick base..anotherpick &&\n+\techo \"resolved\" >foo &&\n+\tgit add foo &&\n+\tgit commit &&\n+\tsed \"s/pick \\([0-9a-f]*\\)/pick $_r10/\" .git/sequencer/todo >new_sheet &&\n+\tcp new_sheet .git/sequencer/todo &&\n+\ttest_must_fail git sequencer --continue\n+'\n+\n+test_expect_success 'commit descriptions in insn sheet are optional' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick base..anotherpick &&\n+\techo \"c\" >foo &&\n+\tgit add foo &&\n+\tgit commit &&\n+\tcut -d\" \" -f1,2 .git/sequencer/todo >new_sheet &&\n+\tcp new_sheet .git/sequencer/todo &&\n+\tgit sequencer --continue &&\n+\ttest_path_is_missing .git/sequencer &&\n+\tgit rev-list HEAD >commits\n+\ttest_line_count = 4 commits\n+'\n+\n+test_expect_success 'revert --continue continues after cherry-pick' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick base..anotherpick &&\n+\techo \"c\" >foo &&\n+\tgit add foo &&\n+\tgit commit &&\n+\tgit revert --continue &&\n+\ttest_path_is_missing .git/sequencer &&\n+\t{\n+\t\tgit rev-list HEAD |\n+\t\tgit diff-tree --root --stdin |\n+\t\tsed \"s/$_x40/OBJID/g\"\n+\t} >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tfoo\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tfoo\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tunrelated\n+\tOBJID\n+\t:000000 100644 OBJID OBJID A\tfoo\n+\t:000000 100644 OBJID OBJID A\tunrelated\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'mixed pick and revert instructions' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick base..anotherpick &&\n+\techo \"c\" >foo &&\n+\tgit add foo &&\n+\tgit commit &&\n+\toldsha=`git rev-parse --short HEAD~1` &&\n+\techo \"revert $oldsha unrelatedpick\" >>.git/sequencer/todo &&\n+\tgit sequencer --continue &&\n+\ttest_path_is_missing .git/sequencer &&\n+\t{\n+\t\tgit rev-list HEAD |\n+\t\tgit diff-tree --root --stdin |\n+\t\tsed \"s/$_x40/OBJID/g\"\n+\t} >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tunrelated\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tfoo\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tfoo\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tunrelated\n+\tOBJID\n+\t:000000 100644 OBJID OBJID A\tfoo\n+\t:000000 100644 OBJID OBJID A\tunrelated\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3511-cherry-pick-sequencer.sh\nsimilarity index 61%\nrename from t/t3510-cherry-pick-sequence.sh\nrename to t/t3511-cherry-pick-sequencer.sh\nindex 09b9e65..a9c6ac1 100755\n--- a/t/t3510-cherry-pick-sequence.sh\n+++ b/t/t3511-cherry-pick-sequencer.sh\n@@ -1,6 +1,6 @@\n #!/bin/sh\n \n-test_description='Test cherry-pick continuation features\n+test_description='Test cherry-pick as a sequencer implementation\n \n   + anotherpick: rewrites foo to d\n   + picked: rewrites foo to c\n@@ -12,9 +12,6 @@ test_description='Test cherry-pick continuation features\n \n . ./test-lib.sh\n \n-# Repeat first match 10 times\n-_r10='\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1'\n-\n pristine_detach () {\n \tgit cherry-pick --reset &&\n \tgit checkout -f \"$1^0\" &&\n@@ -34,7 +31,7 @@ test_expect_success setup '\n \n '\n \n-test_expect_success 'cherry-pick persists data on failure' '\n+test_expect_success 'sequencer persists data on failure' '\n \tpristine_detach initial &&\n \ttest_must_fail git cherry-pick -s base..anotherpick &&\n \ttest_path_is_dir .git/sequencer &&\n@@ -43,7 +40,7 @@ test_expect_success 'cherry-pick persists data on failure' '\n \ttest_path_is_file .git/sequencer/opts\n '\n \n-test_expect_success 'cherry-pick persists opts correctly' '\n+test_expect_success 'sequencer persists opts correctly' '\n \tpristine_detach initial &&\n \ttest_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick &&\n \ttest_path_is_dir .git/sequencer &&\n@@ -67,7 +64,7 @@ test_expect_success 'cherry-pick persists opts correctly' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'cherry-pick cleans up sequencer state upon success' '\n+test_expect_success 'sequencer cleans up state upon success' '\n \tpristine_detach initial &&\n \tgit cherry-pick initial..picked &&\n \ttest_path_is_missing .git/sequencer\n@@ -109,7 +106,7 @@ test_expect_success 'cherry-pick cleans up sequencer todo when one commit is lef\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '\n+test_expect_success 'sequencer does not implicitly stomp an existing operation' '\n \tpristine_detach initial &&\n \ttest_must_fail git cherry-pick base..anotherpick &&\n \ttest-chmtime -v +0 .git/sequencer >expect &&\n@@ -118,7 +115,7 @@ test_expect_success 'cherry-pick does not implicitly stomp an existing operation\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--continue complains when no cherry-pick is in progress' '\n+test_expect_success '--continue complains when no sequencer operation is in progress' '\n \tpristine_detach initial &&\n \ttest_must_fail git cherry-pick --continue\n '\n@@ -192,109 +189,4 @@ test_expect_success '--signoff is not automatically propagated to resolved confl\n \tgrep \"Signed-off-by:\" anotherpick_msg\n '\n \n-test_expect_success 'malformed instruction sheet 1' '\n-\tpristine_detach initial &&\n-\ttest_must_fail git cherry-pick base..anotherpick &&\n-\techo \"resolved\" >foo &&\n-\tgit add foo &&\n-\tgit commit &&\n-\tsed \"s/pick /pick/\" .git/sequencer/todo >new_sheet &&\n-\tcp new_sheet .git/sequencer/todo &&\n-\ttest_must_fail git cherry-pick --continue\n-'\n-\n-test_expect_success 'malformed instruction sheet 2' '\n-\tpristine_detach initial &&\n-\ttest_must_fail git cherry-pick base..anotherpick &&\n-\techo \"resolved\" >foo &&\n-\tgit add foo &&\n-\tgit commit &&\n-\tsed \"s/pick/revert/\" .git/sequencer/todo >new_sheet &&\n-\tcp new_sheet .git/sequencer/todo &&\n-\ttest_must_fail git cherry-pick --continue\n-'\n-\n-test_expect_success 'malformed instruction sheet 3' '\n-\tpristine_detach initial &&\n-\ttest_must_fail git cherry-pick base..anotherpick &&\n-\techo \"resolved\" >foo &&\n-\tgit add foo &&\n-\tgit commit &&\n-\tsed \"s/pick \\([0-9a-f]*\\)/pick $_r10/\" .git/sequencer/todo >new_sheet &&\n-\tcp new_sheet .git/sequencer/todo &&\n-\ttest_must_fail git cherry-pick --continue\n-'\n-\n-test_expect_success 'commit descriptions in insn sheet are optional' '\n-\tpristine_detach initial &&\n-\ttest_must_fail git cherry-pick base..anotherpick &&\n-\techo \"c\" >foo &&\n-\tgit add foo &&\n-\tgit commit &&\n-\tcut -d\" \" -f1,2 .git/sequencer/todo >new_sheet &&\n-\tcp new_sheet .git/sequencer/todo &&\n-\tgit cherry-pick --continue &&\n-\ttest_path_is_missing .git/sequencer &&\n-\tgit rev-list HEAD >commits\n-\ttest_line_count = 4 commits\n-'\n-\n-test_expect_success 'revert --continue continues after cherry-pick' '\n-\tpristine_detach initial &&\n-\ttest_must_fail git cherry-pick base..anotherpick &&\n-\techo \"c\" >foo &&\n-\tgit add foo &&\n-\tgit commit &&\n-\tgit revert --continue &&\n-\ttest_path_is_missing .git/sequencer &&\n-\t{\n-\t\tgit rev-list HEAD |\n-\t\tgit diff-tree --root --stdin |\n-\t\tsed \"s/$_x40/OBJID/g\"\n-\t} >actual &&\n-\tcat >expect <<-\\EOF &&\n-\tOBJID\n-\t:100644 100644 OBJID OBJID M\tfoo\n-\tOBJID\n-\t:100644 100644 OBJID OBJID M\tfoo\n-\tOBJID\n-\t:100644 100644 OBJID OBJID M\tunrelated\n-\tOBJID\n-\t:000000 100644 OBJID OBJID A\tfoo\n-\t:000000 100644 OBJID OBJID A\tunrelated\n-\tEOF\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success 'mixed pick and revert instructions' '\n-\tpristine_detach initial &&\n-\ttest_must_fail git cherry-pick base..anotherpick &&\n-\techo \"c\" >foo &&\n-\tgit add foo &&\n-\tgit commit &&\n-\toldsha=`git rev-parse --short HEAD~1` &&\n-\techo \"revert $oldsha unrelatedpick\" >>.git/sequencer/todo &&\n-\tgit cherry-pick --continue &&\n-\ttest_path_is_missing .git/sequencer &&\n-\t{\n-\t\tgit rev-list HEAD |\n-\t\tgit diff-tree --root --stdin |\n-\t\tsed \"s/$_x40/OBJID/g\"\n-\t} >actual &&\n-\tcat >expect <<-\\EOF &&\n-\tOBJID\n-\t:100644 100644 OBJID OBJID M\tunrelated\n-\tOBJID\n-\t:100644 100644 OBJID OBJID M\tfoo\n-\tOBJID\n-\t:100644 100644 OBJID OBJID M\tfoo\n-\tOBJID\n-\t:100644 100644 OBJID OBJID M\tunrelated\n-\tOBJID\n-\t:000000 100644 OBJID OBJID A\tfoo\n-\t:000000 100644 OBJID OBJID A\tunrelated\n-\tEOF\n-\ttest_cmp expect actual\n-'\n-\n test_done\n-- \n1.7.6.351.gb35ac.dirty\n"},{"id":"179380","messageId":"1321181181-23923-7-git-send-email-artagnon@gmail.com","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 6/7] sequencer: teach '--continue' how to commit","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-13T10:46:20Z","receivedAt":"2011-11-13T10:46:20Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"As discussed earlier in the series, the commit-before-continue\nconvention has caused a lot of workflow inconsistencies.  Teach\n'--continue' how to commit so that you can now do:\n\n  $ git cherry-pick foo..bar\n  ... conflict occured in bar~1 ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git sequencer --continue\n\nModify existing tests in 't3510-sequencer.sh' to exclude the 'git\ncommit' step, and port relevant tests from\n't3511-cherry-pick-sequence.sh' after removing the 'git commit' step.\nNote that the tests in 't3511-cherry-pick-sequence.sh' (with the 'git\ncommit' step) still need to respect backward compatibility.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n sequencer.c          |   10 +++++++-\n t/t3510-sequencer.sh |   51 +++++++++++++++++++++++++++++++++++++++++++------\n 2 files changed, 52 insertions(+), 9 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 012d531..7b10b7b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -269,7 +269,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts)\n \targs[i++] = \"-n\";\n \tif (opts->signoff)\n \t\targs[i++] = \"-s\";\n-\tif (!opts->edit) {\n+\tif (!opts->edit && defmsg) {\n \t\targs[i++] = \"-F\";\n \t\targs[i++] = defmsg;\n \t}\n@@ -776,6 +776,7 @@ int sequencer_pick_revisions(struct replay_opts *opts)\n {\n \tstruct replay_insn_list *todo_list = NULL;\n \tunsigned char sha1[20];\n+\tint res;\n \n \tif (opts->subcommand == REPLAY_NONE)\n \t\tassert(opts->revs);\n@@ -796,9 +797,14 @@ int sequencer_pick_revisions(struct replay_opts *opts)\n \t\tread_populate_opts(&opts);\n \t\tread_populate_todo(&todo_list);\n \n-\t\t/* Verify that the conflict has been resolved */\n \t\tif (!index_differs_from(\"HEAD\", 0))\n \t\t\ttodo_list = todo_list->next;\n+\t\telse if (!opts->no_commit && !read_cache_unmerged()) {\n+\t\t\tres = run_git_commit(NULL, opts);\n+\t\t\tif (res)\n+\t\t\t\treturn res;\n+\t\t\ttodo_list = todo_list->next;\n+\t\t}\n \t} else {\n \t\t/*\n \t\t * Start a new cherry-pick/ revert sequence; but\ndiff --git a/t/t3510-sequencer.sh b/t/t3510-sequencer.sh\nindex 6b2e712..65f2724 100755\n--- a/t/t3510-sequencer.sh\n+++ b/t/t3510-sequencer.sh\n@@ -45,12 +45,54 @@ test_expect_success '--continue complains when there are unresolved conflicts' '\n \ttest_must_fail git sequencer --continue\n '\n \n+test_expect_success '--continue continues after conflicts are resolved' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick base..anotherpick &&\n+\techo \"c\" >foo &&\n+\tgit add foo &&\n+\tgit sequencer --continue &&\n+\ttest_path_is_missing .git/sequencer &&\n+\t{\n+\t\tgit rev-list HEAD |\n+\t\tgit diff-tree --root --stdin |\n+\t\tsed \"s/$_x40/OBJID/g\"\n+\t} >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tfoo\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tfoo\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tunrelated\n+\tOBJID\n+\t:000000 100644 OBJID OBJID A\tfoo\n+\t:000000 100644 OBJID OBJID A\tunrelated\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--continue respects opts' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick -x base..anotherpick &&\n+\techo \"c\" >foo &&\n+\tgit add foo &&\n+\tgit sequencer --continue &&\n+\ttest_path_is_missing .git/sequencer &&\n+\tgit cat-file commit HEAD >anotherpick_msg &&\n+\tgit cat-file commit HEAD~1 >picked_msg &&\n+\tgit cat-file commit HEAD~2 >unrelatedpick_msg &&\n+\tgit cat-file commit HEAD~3 >initial_msg &&\n+\ttest_must_fail grep \"cherry picked from\" initial_msg &&\n+\tgrep \"cherry picked from\" unrelatedpick_msg &&\n+\tgrep \"cherry picked from\" picked_msg &&\n+\tgrep \"cherry picked from\" anotherpick_msg\n+'\n+\n test_expect_success 'malformed instruction sheet 1' '\n \tpristine_detach initial &&\n \ttest_must_fail git cherry-pick base..anotherpick &&\n \techo \"resolved\" >foo &&\n \tgit add foo &&\n-\tgit commit &&\n \tsed \"s/pick /pick/\" .git/sequencer/todo >new_sheet &&\n \tcp new_sheet .git/sequencer/todo &&\n \ttest_must_fail git sequencer --continue\n@@ -61,7 +103,6 @@ test_expect_success 'malformed instruction sheet 2' '\n \ttest_must_fail git cherry-pick base..anotherpick &&\n \techo \"resolved\" >foo &&\n \tgit add foo &&\n-\tgit commit &&\n \tsed \"s/pick/revert/\" .git/sequencer/todo >new_sheet &&\n \tcp new_sheet .git/sequencer/todo &&\n \ttest_must_fail git sequencer --continue\n@@ -72,7 +113,6 @@ test_expect_success 'malformed instruction sheet 3' '\n \ttest_must_fail git cherry-pick base..anotherpick &&\n \techo \"resolved\" >foo &&\n \tgit add foo &&\n-\tgit commit &&\n \tsed \"s/pick \\([0-9a-f]*\\)/pick $_r10/\" .git/sequencer/todo >new_sheet &&\n \tcp new_sheet .git/sequencer/todo &&\n \ttest_must_fail git sequencer --continue\n@@ -83,7 +123,6 @@ test_expect_success 'commit descriptions in insn sheet are optional' '\n \ttest_must_fail git cherry-pick base..anotherpick &&\n \techo \"c\" >foo &&\n \tgit add foo &&\n-\tgit commit &&\n \tcut -d\" \" -f1,2 .git/sequencer/todo >new_sheet &&\n \tcp new_sheet .git/sequencer/todo &&\n \tgit sequencer --continue &&\n@@ -97,7 +136,6 @@ test_expect_success 'revert --continue continues after cherry-pick' '\n \ttest_must_fail git cherry-pick base..anotherpick &&\n \techo \"c\" >foo &&\n \tgit add foo &&\n-\tgit commit &&\n \tgit revert --continue &&\n \ttest_path_is_missing .git/sequencer &&\n \t{\n@@ -120,12 +158,11 @@ test_expect_success 'revert --continue continues after cherry-pick' '\n '\n \n test_expect_success 'mixed pick and revert instructions' '\n+\toldsha=`git rev-parse --short HEAD~2` &&\n \tpristine_detach initial &&\n \ttest_must_fail git cherry-pick base..anotherpick &&\n \techo \"c\" >foo &&\n \tgit add foo &&\n-\tgit commit &&\n-\toldsha=`git rev-parse --short HEAD~1` &&\n \techo \"revert $oldsha unrelatedpick\" >>.git/sequencer/todo &&\n \tgit sequencer --continue &&\n \ttest_path_is_missing .git/sequencer &&\n-- \n1.7.6.351.gb35ac.dirty\n"},{"id":"179382","messageId":"1321181181-23923-8-git-send-email-artagnon@gmail.com","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 7/7] sequencer: teach parser about CHERRY_PICK_HEAD","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-13T10:46:21Z","receivedAt":"2011-11-13T10:46:21Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"The final step in unifying the sequencer interface involves making\nsure that a single-commit pick can be concluded with a '--continue'.\n\n  $ git cherry-pick foo\n  ... conflict ...\n  $ echo \"resolved\" >problematicfile\n  $ git add problematicfile\n  $ git sequencer --continue\n\nTo do this, we have to update our parser that normally reads\n'.git/sequencer/todo' to read '.git/CHERRY_PICK_HEAD' as a special\ncase before proceeding as usual.  Add a new test in\n't3510-sequencer.sh' to make sure that this works.  Although we have\nadded a similar test for revert in the same patch for symmetry, the\ntest doesn't depend on this patch to work.  A single-commit revert\noperation does not create a '.git/CHERRY_PICK_HEAD' at all: it uses\nthe information in '.git/sequencer' as usual.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n sequencer.c          |   32 ++++++++++++++++++++++++++++----\n t/t3510-sequencer.sh |   44 ++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 72 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 7b10b7b..84df926 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -569,12 +569,32 @@ static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list)\n \treturn 0;\n }\n \n-static void read_populate_todo(struct replay_insn_list **todo_list)\n+static void read_populate_todo(struct replay_insn_list **todo_list, int cph_flag)\n {\n \tconst char *todo_file = git_path(SEQ_TODO_FILE);\n \tstruct strbuf buf = STRBUF_INIT;\n \tint fd, res;\n \n+\tif (cph_flag) {\n+\t\tstruct replay_insn_list item = {0, NULL, NULL};\n+\t\tconst char *name = \"CHERRY_PICK_HEAD\";\n+\t\tconst char *CHERRY_PICK_HEAD = git_path(name);\n+\n+\t\tfd = open(CHERRY_PICK_HEAD, O_RDONLY);\n+\t\tif (fd < 0)\n+\t\t\tdie_errno(_(\"Could not open %s.\"), CHERRY_PICK_HEAD);\n+\n+\t\titem.action = REPLAY_PICK;\n+\t\titem.operand = lookup_commit_reference_by_name(name);\n+\n+\t\tif (!item.operand)\n+\t\t\tdie(_(\"could not lookup commit %s\"), name);\n+\t\treplay_insn_list_append(item.action, item.operand, todo_list);\n+\t\tclose(fd);\n+\t\tstrbuf_release(&buf);\n+\t\treturn;\n+\t}\n+\n \tfd = open(todo_file, O_RDONLY);\n \tif (fd < 0)\n \t\tdie_errno(_(\"Could not open %s.\"), todo_file);\n@@ -792,10 +812,14 @@ int sequencer_pick_revisions(struct replay_opts *opts)\n \t\tremove_sequencer_state(1);\n \t\treturn 0;\n \t} else if (opts->subcommand == REPLAY_CONTINUE) {\n-\t\tif (!file_exists(git_path(SEQ_TODO_FILE)))\n-\t\t\tgoto error;\n+\t\tif (!file_exists(git_path(SEQ_TODO_FILE))) {\n+\t\t\tif (!file_exists(git_path(\"CHERRY_PICK_HEAD\")))\n+\t\t\t\tgoto error;\n+\t\t\tread_populate_todo(&todo_list, 1);\n+\t\t}\n+\t\telse\n+\t\t\tread_populate_todo(&todo_list, 0);\n \t\tread_populate_opts(&opts);\n-\t\tread_populate_todo(&todo_list);\n \n \t\tif (!index_differs_from(\"HEAD\", 0))\n \t\t\ttodo_list = todo_list->next;\ndiff --git a/t/t3510-sequencer.sh b/t/t3510-sequencer.sh\nindex 65f2724..f7c3a37 100755\n--- a/t/t3510-sequencer.sh\n+++ b/t/t3510-sequencer.sh\n@@ -45,6 +45,50 @@ test_expect_success '--continue complains when there are unresolved conflicts' '\n \ttest_must_fail git sequencer --continue\n '\n \n+test_expect_success '--continue continues single-commit cherry-pick' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git cherry-pick anotherpick &&\n+\techo \"c\" >foo &&\n+\tgit add foo &&\n+\tgit sequencer --continue &&\n+\ttest_path_is_missing .git/sequencer &&\n+\t{\n+\t\tgit rev-list HEAD |\n+\t\tgit diff-tree --root --stdin |\n+\t\tsed \"s/$_x40/OBJID/g\"\n+\t} >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tfoo\n+\tOBJID\n+\t:000000 100644 OBJID OBJID A\tfoo\n+\t:000000 100644 OBJID OBJID A\tunrelated\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--continue continues single-commit revert' '\n+\tpristine_detach initial &&\n+\ttest_must_fail git revert anotherpick &&\n+\techo \"c\" >foo &&\n+\tgit add foo &&\n+\tgit sequencer --continue &&\n+\ttest_path_is_missing .git/sequencer &&\n+\t{\n+\t\tgit rev-list HEAD |\n+\t\tgit diff-tree --root --stdin |\n+\t\tsed \"s/$_x40/OBJID/g\"\n+\t} >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tOBJID\n+\t:100644 100644 OBJID OBJID M\tfoo\n+\tOBJID\n+\t:000000 100644 OBJID OBJID A\tfoo\n+\t:000000 100644 OBJID OBJID A\tunrelated\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success '--continue continues after conflicts are resolved' '\n \tpristine_detach initial &&\n \ttest_must_fail git cherry-pick base..anotherpick &&\n-- \n1.7.6.351.gb35ac.dirty\n"},{"id":"179394","messageId":"7v39droi63.fsf@alter.siamese.dyndns.org","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"Re: [PATCH 0/7] New sequencer workflow!","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-11-13T20:56:36Z","receivedAt":"2011-11-13T20:56:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ramkumar Ramachandra <artagnon@gmail.com> writes:\n\n> This series preserves the old workflow:\n>\n>   $ git cherry-pick foo\n>   ... conflict ...\n>   $ echo \"resolved\" >problematicfile\n>   $ git add problematicfile\n>   $ git commit\n>\n>   $ git cherry-pick foo..bar\n>   ... conflict ...\n>   $ echo \"resolved\" >problematicfile\n>   $ git add problematicfile\n>   $ git commit\n>   $ git revert --continue # No, there are no typos\n\nHmm, doesn't \"git add && git cherry-pick --continue\" (i.e. no \"git commit\"\nin between) trigger the \"commit the resolution first and then go on\"?\n\n> And introduces a brand new workflow:\n>\n>   $ git cherry-pick foo\n>   ... conflict ...\n>   $ echo \"resolved\" >problematicfile\n>   $ git add problematicfile\n>   $ git sequencer --continue\n>\n>   $ git cherry-pick foo..bar\n>   ... conflict ...\n>   $ echo \"resolved\" >problematicfile\n>   $ git add problematicfile\n>   $ git sequencer --continue\n>\n>   $ git revert moo..baz\n>   ... conflict ...\n>   $ echo \"resolved\" >problematicfile\n>   $ git add problematicfile\n>   $ git sequencer --continue\n\nAlmost. If these are replaced with \"git cherry-pick --continue\" and \"git\nrevert --continue\" that internally triggers \"git sequencer --continue\"\n*and* errors out if a wrong command was used to \"--continue\", it would be\nperfect.\n\nThe longer-term ultimate goal would be to make it \"git continue\" that\ncovers more than the sequencer based workflow, i.e. allow \"git merge\" that\nconflicted to be concluded with \"edit && git add && git continue\".\n"},{"id":"179399","messageId":"7vpqgvmwtl.fsf@alter.siamese.dyndns.org","threadId":"28922","inReplyTo":"1321181181-23923-4-git-send-email-artagnon@gmail.com","subject":"Re: [PATCH 3/7] sequencer: handle single-commit pick as special case","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-11-13T23:23:02Z","receivedAt":"2011-11-13T23:23:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ramkumar Ramachandra <artagnon@gmail.com> writes:\n\n> Prior to v1.7.2-rc1~4^2~7 (revert: allow cherry-picking more than one\n> commit, 2010-06-02), 'git cherry-pick' could only pick one commit at a\n> time, and it used '.git/CHERRY_PICK_HEAD' to pass on information to a\n> subsequent invocation in case of a conflict.  While\n> '.git/CHERRY_PICK_HEAD' can only keep information about one commit,\n> the sequencer uses '.git/sequencer' to persist information in the\n> general case.\n>\n> A problem arises because a single-commit cherry-pick operation can be\n> completed successfully using 'git commit'.  This removes\n> '.git/CHERRY_PICK_HEAD' without informing the sequencer, leaving\n> behind a stale sequencer state as a result.  We have worked around\n> this problem already by prematurely removing the sequencer state in\n> d3f4628e (revert: Remove sequencer state when no commits are pending,\n> 2011-06-06).  However, this gets in the way of our future plan to\n> eliminate a glaring workflow inconsistency:\n>\n>   $ git cherry-pick foo\n>   ... .git/sequencer is created ....\n>   ... .git/CHERRY_PICK_HEAD is created ...\n>   ... conflict ...\n>   .... .git/sequencer is prematurely removed ...\n\nIsn't the real problem that .git/sequencer is created in the first place,\nwhen this form of the command knows it will use CHERRY_PICK_HEAD?\n\n>   $ echo \"resolved\" >problematicfile\n>   $ git add problematicfile\n>   $ git commit\n>   ... .git/CHERRY_PICK_HEAD is removed ...\n>   $ git cherry-pick --continue\n>   error: No cherry-pick in progress\n\nThis is the right thing to happen, no? There is no in-progress cherry-pick\nanymore after that commit. The user said \"I want to replay this commit\",\nthe command couldn't finish it by itself and asked the user to help\nresolving the conflict, and the user resolved and recorded the result.\n\nIt is a different story if the sequence were like this:\n\n    $ git cherry-pick foo\n    ... conflict happens\n    $ edit problematicfile\n    $ git add problematicfile\n    $ git cherry-pick --continue\n    ... This should notice CHERRY_PICK_HEAD and record it.\n    ... After that, there is nothing remaining to be done.\n\nIn other words, the user said \"I want to replay this commit\", the command\ncouldn't finish it by itself and asked the user to help resolving the\nconflict, the user resolved and told the command to continue. The command\nshould continue recording the result.\n\nAnd if \"continue\" does not work in this sequence, that is a bug worth\nfixing.\n\n>   $ git cherry-pick foo..bar\n>   ... .git/sequencer is created ....\n>   ... CHERRY_PICK_HEAD is created ...\n>   ... conflict in bar~1 ...\n>   $ echo \"resolved\" >problematicfile\n>   $ git add problematicfile\n>   $ git commit\n>   ... CHERRY_PICK_HEAD is removed ...\n>   $ git cherry-pick --continue # Success!\n\nThis again is the right thing to happen, as the user had to help the\ncommand while replaying bar~1 and then told it to continue, which\nsuccessfully replayed bar.\n\nI do not see an inconsistency here, let alone any \"glaring\" one.\n"},{"id":"179400","messageId":"7vaa7zmuwt.fsf@alter.siamese.dyndns.org","threadId":"28922","inReplyTo":"7v39droi63.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 0/7] New sequencer workflow!","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-11-14T00:04:18Z","receivedAt":"2011-11-14T00:04:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Almost. If these are replaced with \"git cherry-pick --continue\" and \"git\n> revert --continue\" that internally triggers \"git sequencer --continue\"\n> *and* errors out if a wrong command was used to \"--continue\", it would be\n> perfect.\n>\n> The longer-term ultimate goal would be to make it \"git continue\" that\n> covers more than the sequencer based workflow, i.e. allow \"git merge\" that\n> conflicted to be concluded with \"edit && git add && git continue\".\n\nTo clarify a bit.\n\nWhile I do not mind \"sequencer --continue\" as a step to get us closer to a\nmore pleasant user experience at the implementation level, exposing the\nname \"sequencer\" or having the user to type \"sequencer --continue\" is going\nin a very wrong direction at the UI level.\n\nThere are many commands in the Git suite that take an order from the user,\nattempt to perform the necessary operation but stops in the middle to ask\nfor help from the user. \"cherry-pick\" and \"revert\" are two of them, and\nthere are many others: e.g. am, merge, pull, rebase, \"rebase -i\". They use\ndifferent mechanisms to keep track of their states before giving the\ncontrol back to the user when they ask for help.\n\nThe original workflow was \"pull; edit && add && commit\", and it is very\nunlikely this will change while we are still in Git 1.X series. The\noriginal single commit variants of \"cherry-pick\" and \"revert\" are in the\nsame category. We would need to keep supporting \"cherry-pick/revert; edit\n&& add && commit\" as a workflow for a while.\n\nOthers that deal with more than one stop-point follow a different pattern\nfrom necessity. The user tells the same command to continue after the\ncommand asks for help in \"am; edit && add && am --continue\" and \"rebase\n[-i]; edit && add && rebase --continue\" sequence. Multi-commit variants of\n\"cherry-pick\" and \"revert\" take the same approach for consistency.\n\nIn the shorter term, a person new to Git, after learning \"run command X, X\ngives back control asking for help, help X by editing and adding, and\ntelling X to continue\" pattern from these commands, will eventually find\nthat the commands in single stop-point category (i.e. \"pull\", \"merge\" and\nsingle-commit \"cherry-pick\" and \"revert\") inconsistent from others that\ntake \"--continue\" to resume. For this reason, \"git cherry-pick --continue\"\nthat would work even when picking a single commit like this would be\nbeneficial:\n\n    $ git cherry-pick X\n    ... conflicts\n    $ edit && add\n    $ git cherry-pick --continue\n\nThat is, no \"commit\" by the user. The \"helping\" part is literally that;\nthe user helps Git because Git is too stupid to read the user's mind to\ncome up with a perfect conflict resolution. Git knows, given a correct\nresolution, how to make a commit out of it perfectly fine and does not\nneed help from the user to commit the result.\n\nIn the medium term, extending the above \"shorter term\" goal, it would make\nsense to support (but not necessarily require) the following flow for\nconsistency:\n\n    $ git pull ;# or \"git merge branch\"\n    ... conflicts\n    $ edit && add ;# again, no \"commit\"\n    $ git pull --continue ;# or \"git merge --continue\"\n\nNow, if you rename \"cherry-pick --continue\" and \"revert --continue\" to\n\"sequencer --continue\", what message are you sending to the users? They\nnow need to learn that only these two commands are based on something\ncalled \"sequencer\", can be resumed with \"sequencer --continue\", but other\ncommands need to be continued with \"X --continue\".\n\nThat is totally backwards, no? You are _adding_ mental burden to your\nusers by introducing another thing or two they need to learn about.\n\nIn the longer term (now we are talking about Git 2.X version bump), it\nwould be ideal if all the \"git X --continue\" can be consolidated to a\nsingle \"git continue\" command (and \"git abort\" to give up the sequence\nof operations).\n\nGiven that bash-completion script can tell in what state the repository\nis, I think this is doable. \"git continue\" may invoke your implementation\nof \"git sequencer --continue\" internally when it detects that the state is\nsomething the \"sequencer\" machinery knows how to continue, and if it is in\nthe middle of conflicted \"am -3\" or rejected \"am\", the command may invoke\n\"git am --continue\" instead.\n\nThat way, the user does not have to learn which command can be resumed\nwith \"sequencer --continue\" and which other command needs to be called\nwith \"--continue\" to resume. The user does not even need to know there is\na mechanism called sequencer, some commands are already using it while\nothers are not yet using it, and these other commands are in the process\nof being rewritten to use the sequencer machinery.\n"},{"id":"179486","messageId":"CALkWK0kdCnU_BUaYPiNAKypRvEKgDtDKLO=Qed4CQtX23tq0wA@mail.gmail.com","threadId":"28922","inReplyTo":"7vaa7zmuwt.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 0/7] New sequencer workflow!","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-15T08:33:01Z","receivedAt":"2011-11-15T08:33:01Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi Junio,\n\nThanks for the fantastic review.  My lack of foresight is very disturbing.\n\nJunio C Hamano wrote:\n> Ramkumar Ramachandra <artagnon@gmail.com> writes:\n>> This series preserves the old workflow:\n>> [...]\n> Hmm, doesn't \"git add && git cherry-pick --continue\" (i.e. no \"git commit\"\n> in between) trigger the \"commit the resolution first and then go on\"?\n\nUgh, right.  I can't possibly preserve the old workflow fully- what I\nmeant is that users can still \"git commit\" before continuing (in the\ncase of a multi-commit operation).  It's not so much about preserving\na workflow, as it is about not breaking existing tests.\n\n> Almost. If these are replaced with \"git cherry-pick --continue\" and \"git\n> revert --continue\" that internally triggers \"git sequencer --continue\"\n> *and* errors out if a wrong command was used to \"--continue\", it would be\n> perfect.\n\nOkay, there are two ways to fix this:\n1. If the first instruction in the instruction sheet is a \"revert\" and\n\"git cherry-pick --continue\" is called or viceversa, error out.  The\ndownside is that there's really no sane way to handle mixed \"pick\" and\n\"revert\" instructions in the sheet using this approach: parsing the\nwhole sheet in advance is a total overkill.\n2. The key issue is that the sequencer doesn't keep track of which\ncommand was actually executed (argv[0]): fix this by creating a new\n'.git/sequencer/cmd' to keep this information.\n\nI think we should pick the latter option.  Also, why should \"git\n<cherry-pick|revert> --continue\" invoke \"git sequencer --continue\"\nwhen it can just call into the corresponding function in the sequencer\nlibrary (isn't lib'ification one of the issues we're trying to\naddress)?\n\n> While I do not mind \"sequencer --continue\" as a step to get us closer to a\n> more pleasant user experience at the implementation level, exposing the\n> name \"sequencer\" or having the user to type \"sequencer --continue\" is going\n> in a very wrong direction at the UI level.\n> [...]\n> Now, if you rename \"cherry-pick --continue\" and \"revert --continue\" to\n> \"sequencer --continue\", what message are you sending to the users? They\n> now need to learn that only these two commands are based on something\n> called \"sequencer\", can be resumed with \"sequencer --continue\", but other\n> commands need to be continued with \"X --continue\".\n\nThanks for the superb explanation- it's plain as day now.  In a\nnutshell: it makes little sense to have a grand plan about an\nall-encompassing \"git sequencer --continue\"; even if that is the plan\nin the super-long term (although \"git continue\" is a better idea), it\nmakes no sense to burden the user now with additional UI\ninconsistencies.\n\nI'm still a little confused about what to do with the \"git sequencer\"\nbuiltin though: how do we prevent users from being confused by it- by\nnot advertising it?\n\n> The original workflow was \"pull; edit && add && commit\", and it is very\n> unlikely this will change while we are still in Git 1.X series. The\n> original single commit variants of \"cherry-pick\" and \"revert\" are in the\n> same category. We would need to keep supporting \"cherry-pick/revert; edit\n> && add && commit\" as a workflow for a while.\n\nTrue.  I wrote this pretending that only \"git <cherry-pick|revert>\"\nwas stuck with this UI inconsistency between one-commit picks and\nmany-commit picks.\n\n> In the shorter term, a person new to Git, after learning \"run command X, X\n> gives back control asking for help, help X by editing and adding, and\n> telling X to continue\" pattern from these commands, will eventually find\n> that the commands in single stop-point category (i.e. \"pull\", \"merge\" and\n> single-commit \"cherry-pick\" and \"revert\") inconsistent from others that\n> take \"--continue\" to resume. For this reason, \"git cherry-pick --continue\"\n> that would work even when picking a single commit like this would be\n> beneficial:\n> [...]\n\nInteresting aside: this final detail is fixed only in the last patch\nin the series; that's how deep the problem is.\n\n> In the longer term (now we are talking about Git 2.X version bump), it\n> would be ideal if all the \"git X --continue\" can be consolidated to a\n> single \"git continue\" command (and \"git abort\" to give up the sequence\n> of operations).\n>\n> Given that bash-completion script can tell in what state the repository\n> is, I think this is doable. \"git continue\" may invoke your implementation\n> of \"git sequencer --continue\" internally when it detects that the state is\n> something the \"sequencer\" machinery knows how to continue, and if it is in\n> the middle of conflicted \"am -3\" or rejected \"am\", the command may invoke\n> \"git am --continue\" instead.\n\nMakes perfect sense.\n\nThanks.\n\n-- Ram\n"},{"id":"179487","messageId":"CALkWK0krTY+szqPrE9wxwfK76VBbFXh_Xaz8EyWZayLa_SFQTw@mail.gmail.com","threadId":"28922","inReplyTo":"7vpqgvmwtl.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 3/7] sequencer: handle single-commit pick as special case","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-15T08:47:08Z","receivedAt":"2011-11-15T08:47:08Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi Junio,\n\nJunio C Hamano writes:\n> Ramkumar Ramachandra <artagnon@gmail.com> writes:\n>> [...]\n> [...]\n> I do not see an inconsistency here, let alone any \"glaring\" one.\n\nYeah, my commit message is totally misleading and unclear.  Yes, all\nthe operations make sense to you and me; for a new user who doesn't\nunderstand how Git works, it's completely inconsistent at a very\nsuperficial level, no?  The sequence of operations he has to execute\ndepends on:\n1. If he literally specified a single commit or a commit range on the\ncommand-line.\n2. In the case of multi-commit picking, there's an additional layer of\ndecision making: did the conflict occur in the last commit in the\nrange?\n\nAnyway, I'll rewrite this commit message in the next iteration.\n\nThanks.\n\n-- Ram\n"},{"id":"179505","messageId":"CABURp0qt+r09Uy_nfLd60pXiMMXgTOUB__XL-N=S7HaJa-oWoA@mail.gmail.com","threadId":"28922","inReplyTo":"1321181181-23923-1-git-send-email-artagnon@gmail.com","subject":"Re: [PATCH 0/7] New sequencer workflow!","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2011-11-15T15:46:18Z","receivedAt":"2011-11-15T15:46:18Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"On Sun, Nov 13, 2011 at 5:46 AM, Ramkumar Ramachandra\n<artagnon@gmail.com> wrote:\n>  $ git revert moo..baz\n>  ... conflict ...\n>  $ echo \"resolved\" >problematicfile\n>  $ git add problematicfile\n>  $ git sequencer --continue\n>\n> In other words, it just doesn't matter how you started what sequencing\n> operation: you can always continue a it with \"git sequencer\n> --continue\" and reset it with \"git sequencer --reset\".\n\n\nI see that --reset was added to cherry-pick, revert and sequencer\naround the same time back in August.  Shouldn't it be spelled\n\"--abort\" instead?\n\nThis would align better with other commands that do the same thing\n(am, merge, notes-merge, rebase) since they all appear to spell it\n\"--abort\".  Also, it will make it easier in git 2.x when we begin\nusing \"git continue\" and \"git abort\" rather than \"git reset\".\n\nPhil\n"},{"id":"179507","messageId":"CALkWK0nPQm6Qnp2=0Bc+ntHU6sGSp2C+BFnEqTznws3mY9=E5g@mail.gmail.com","threadId":"28922","inReplyTo":"CABURp0qt+r09Uy_nfLd60pXiMMXgTOUB__XL-N=S7HaJa-oWoA@mail.gmail.com","subject":"Re: [PATCH 0/7] New sequencer workflow!","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-15T16:12:48Z","receivedAt":"2011-11-15T16:12:48Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi Phil,\n\nPhil Hord wrote:\n> I see that --reset was added to cherry-pick, revert and sequencer\n> around the same time back in August.  Shouldn't it be spelled\n> \"--abort\" instead?\n\n\"reset\" is actually different from \"abort\": it simply removes the\nsequencer state without touching the worktree, index or HEAD.  We\ndecided that this would be a nice low-level command to implement\n(since I find myself doing `rm -rf .git/rebase-todo` sometimes).\nSure, \"abort\" and a lot of other porcelain would be nice- we can\nalways implement them carefully later :)\n\nThanks.\n\n-- Ram\n"},{"id":"179535","messageId":"7v62ilujya.fsf@alter.siamese.dyndns.org","threadId":"28922","inReplyTo":"CALkWK0krTY+szqPrE9wxwfK76VBbFXh_Xaz8EyWZayLa_SFQTw@mail.gmail.com","subject":"Re: [PATCH 3/7] sequencer: handle single-commit pick as special case","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-11-15T21:58:21Z","receivedAt":"2011-11-15T21:58:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ramkumar Ramachandra <artagnon@gmail.com> writes:\n\n> Hi Junio,\n>\n> Junio C Hamano writes:\n>> Ramkumar Ramachandra <artagnon@gmail.com> writes:\n>>> [...]\n>> [...]\n>> I do not see an inconsistency here, let alone any \"glaring\" one.\n>\n> Yeah, my commit message is totally misleading and unclear.  Yes, all\n> the operations make sense to you and me; for a new user who doesn't\n> understand how Git works, it's completely inconsistent at a very\n> superficial level, no?  The sequence of operations he has to execute\n> depends on:\n> 1. If he literally specified a single commit or a commit range on the\n> command-line.\n\nBut we are already in agreement on this point, I think. Didn't I say:\n\n  It is a different story if the sequence were like this:\n\n      $ git cherry-pick foo\n      ... conflict happens\n      $ edit problematicfile\n      $ git add problematicfile\n      $ git cherry-pick --continue\n      ... This should notice CHERRY_PICK_HEAD and record it.\n      ... After that, there is nothing remaining to be done.\n\n  In other words, the user said \"I want to replay this commit\", the command\n  couldn't finish it by itself and asked the user to help resolving the\n  conflict, the user resolved and told the command to continue. The command\n  should continue recording the result.\n\n  And if \"continue\" does not work in this sequence, that is a bug worth\n  fixing.\n\nin the message you are responding to?\n\n> 2. In the case of multi-commit picking, there's an additional layer of\n> decision making: did the conflict occur in the last commit in the\n> range?\n\nAgain, it would be the same thing. If the implementation forces that\ndecision, that would be a bug, no?\n\nMy understanding is that multi-commit form of cherry-pick and revert\nintended to allow two forms of \"user done helping and telling the command\nto continue\" at any stage, be it the first, in the middle, or the last\noperation in a series:\n\n - User resolves, adds and commits, and then tells the command to\n   continue. The command notices that the user has committed, and goes on\n   to the next task; or\n\n - User resolves, adds, and then tells the command to continue. The\n   command notices that the user has not committed, makes a commit and\n   goes on to the next task.\n\nI think \"rebase -i\" works the same way and both are valid ways to tell the\ncommand to continue.\n\nNow, multi-commit form of cherry-pick/revert _may_ be buggy (I do not use\nthat form often myself to know if this is the case, though) and may only\nallow one or the other depending on which commit in the series the user is\nasked to help, but if that is the case it is a bug in the implementation\nof the multi-commit form done back in v1.7.2 timeframe.\n"},{"id":"179554","messageId":"CALkWK0mW4MOw_vvqRengbDrf4sd8NJZptr57b8LKDmfAhxevpA@mail.gmail.com","threadId":"28922","inReplyTo":"7v62ilujya.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 3/7] sequencer: handle single-commit pick as special case","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-11-16T06:30:15Z","receivedAt":"2011-11-16T06:30:15Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi Junio,\n\nJunio C Hamano wrote:\n> Ramkumar Ramachandra <artagnon@gmail.com> writes:\n>> [...]\n> But we are already in agreement on this point, I think. Didn't I say:\n\nYes, yes.  I was just thinking out loud so it'll help me write a new\ncommit message.  Sorry for the noise.\n\n>> 2. In the case of multi-commit picking, there's an additional layer of\n>> decision making: did the conflict occur in the last commit in the\n>> range?\n>\n> Again, it would be the same thing. If the implementation forces that\n> decision, that would be a bug, no?\n>\n> My understanding is that multi-commit form of cherry-pick and revert\n> intended to allow two forms of \"user done helping and telling the command\n> to continue\" at any stage, be it the first, in the middle, or the last\n> operation in a series:\n>\n>  - User resolves, adds and commits, and then tells the command to\n>   continue. The command notices that the user has committed, and goes on\n>   to the next task; or\n>\n>  - User resolves, adds, and then tells the command to continue. The\n>   command notices that the user has not committed, makes a commit and\n>   goes on to the next task.\n\nYep, this is exactly how it'll work after the series :D\n\nThanks.\n\n-- Ram\n"}]}