{"thread":{"id":"47635","subject":"[PATCH 0/8] rebase -i: offer to recreate merge commits","startedAt":"2018-01-18T15:35:36Z","lastAt":"2018-10-08T13:38:04Z","messageCount":412,"participants":["Johannes Schindelin","Jacob Keller","Stefan Beller","Philip Oakley","Eric Sunshine","Phillip Wood","Junio C Hamano","Sergey Organov","Johannes Sixt","Øyvind Rønningstad","Martin Ågren","Igor Djordjevic","Elijah Newren","Ben Peart","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":8},"messages":[{"id":"336792","messageId":"cover.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":null,"subject":"[PATCH 0/8] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:35:23Z","receivedAt":"2018-01-18T15:35:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, I dreamt of an interactive rebase that would not\nflatten branch structure, but instead recreate the commit topology\nfaithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --recreate-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge 3456 D C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --recreate-merges.\n\n\nJohannes Schindelin (8):\n  sequencer: introduce new commands to reset the revision\n  sequencer: introduce the `merge` command\n  sequencer: fast-forward merge commits, if possible\n  rebase-helper --make-script: introduce a flag to recreate merges\n  rebase: introduce the --recreate-merges option\n  sequencer: handle autosquash and post-rewrite for merge commands\n  pull: accept --rebase=recreate to recreate the branch topology\n  rebase -i: introduce --recreate-merges=no-rebase-cousins\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   5 +-\n Documentation/git-rebase.txt           |  13 +-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |   2 +\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |   6 +\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 697 ++++++++++++++++++++++++++++++++-\n sequencer.h                            |   9 +\n t/t3430-rebase-recreate-merges.sh      | 208 ++++++++++\n 13 files changed, 977 insertions(+), 21 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\n\nbase-commit: 2512f15446149235156528dafbe75930c712b29e\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v1\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v1\n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n\n"},{"id":"336793","messageId":"8a91bf2184a3da4c0d5a13ba184813068e51f5c8.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:35:27Z","receivedAt":"2018-01-18T15:35:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming commits, we will teach the sequencer to recreate merges.\nThis will be done in a very different way from the unfortunate design of\n`git rebase --preserve-merges` (which does not allow for reordering\ncommits, or changing the branch topology).\n\nThe main idea is to introduce new todo list commands, to support\nlabeling the current revision with a given name, resetting the current\nrevision to a previous state, merging labeled revisions.\n\nThis idea was developed in Git for Windows' Git garden shears (that are\nused to maintain the \"thicket of branches\" on top of upstream Git), and\nthis patch is part of the effort to make it available to a wider\naudience, as well as to make the entire process more robust (by\nimplementing it in a safe and portable language rather than a Unix shell\nscript).\n\nThis commit implements the commands to label, and to reset to, given\nrevisions. The syntax is:\n\n\tlabel <name>\n\treset <name>\n\nAs a convenience shortcut, also to improve readability of the generated\ntodo list, a third command is introduced: bud. It simply resets to the\n\"onto\" revision, i.e. the commit onto which we currently rebase.\n\nInternally, the `label <name>` command creates the ref\n`refs/rewritten/<name>`. This makes it possible to work with the labeled\nrevisions interactively, or in a scripted fashion (e.g. via the todo\nlist command `exec`).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   3 +\n sequencer.c                | 181 ++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 180 insertions(+), 4 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex d47bd29593a..3d2cd19d65a 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,9 @@ s, squash = use commit, but meld into previous commit\n f, fixup = like \\\"squash\\\", but discard this commit's log message\n x, exec = run command (the rest of the line) using shell\n d, drop = remove commit\n+l, label = label current HEAD with a name\n+t, reset = reset HEAD to a label\n+b, bud = reset HEAD to the revision labeled 'onto'\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 4d3f60594cb..91cc55a002f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -21,6 +21,8 @@\n #include \"log-tree.h\"\n #include \"wt-status.h\"\n #include \"hashmap.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -116,6 +118,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `merge` command.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -767,6 +776,9 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n+\tTODO_BUD,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -785,6 +797,9 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n+\t{ 'b', \"bud\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1253,7 +1268,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\tif (skip_prefix(bol, todo_command_info[i].str, &bol)) {\n \t\t\titem->command = i;\n \t\t\tbreak;\n-\t\t} else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {\n+\t\t} else if ((bol + 1 == eol || bol[1] == ' ') &&\n+\t\t\t   *bol == todo_command_info[i].c) {\n \t\t\tbol++;\n \t\t\titem->command = i;\n \t\t\tbreak;\n@@ -1265,7 +1281,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \tpadding = strspn(bol, \" \\t\");\n \tbol += padding;\n \n-\tif (item->command == TODO_NOOP) {\n+\tif (item->command == TODO_NOOP || item->command == TODO_BUD) {\n \t\tif (bol != eol)\n \t\t\treturn error(_(\"%s does not accept arguments: '%s'\"),\n \t\t\t\t     command_to_string(item->command), bol);\n@@ -1279,7 +1295,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -1919,6 +1936,139 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename, 0);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n+\t\treturn error_errno(_(\"could not read '%s'\"), filename);\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'.\"), filename);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"label '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction ||\n+\t    get_oid(\"HEAD\", &head_oid) ||\n+\t    ref_transaction_update(transaction, ref_name.buf, &head_oid, NULL,\n+\t\t\t\t   0, msg.buf, &err) < 0 ||\n+\t    ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static int do_reset(const char *name, int len)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = &the_index;\n+\topts.dst_index = &the_index;\n+\topts.fn = oneway_merge;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.reset = 1;\n+\n+\tread_cache_unmerged();\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"Failed to find tree of %s.\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret) {\n+\t\tstruct strbuf msg = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&msg, \"(rebase -i) reset '%.*s'\", len, name);\n+\t\tret = update_ref(msg.buf, \"HEAD\", &oid, NULL, 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n+\t\tstrbuf_release(&msg);\n+\t}\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2102,7 +2252,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n-\t\t} else if (!is_noop(item->command))\n+\t\t} else if (item->command == TODO_LABEL)\n+\t\t\tres = do_label(item->arg, item->arg_len);\n+\t\telse if (item->command == TODO_RESET)\n+\t\t\tres = do_reset(item->arg, item->arg_len);\n+\t\telse if (item->command == TODO_BUD)\n+\t\t\tres = do_reset(\"onto\", 4);\n+\t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\n@@ -2207,6 +2363,23 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t}\n \t\tapply_autostash(opts);\n \n+\t\tstrbuf_reset(&buf);\n+\t\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0)\n+\t\t    > 0) {\n+\t\t\tchar *p = buf.buf;\n+\t\t\twhile (*p) {\n+\t\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\t\tif (eol)\n+\t\t\t\t\t*eol = '\\0';\n+\t\t\t\tif (delete_ref(\"(rebase -i) cleanup\",\n+\t\t\t\t\t       p, NULL, 0) < 0)\n+\t\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\t\tif (!eol)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp = eol + 1;\n+\t\t\t}\n+\t\t}\n+\n \t\tfprintf(stderr, \"Successfully rebased and updated %s.\\n\",\n \t\t\thead_ref.buf);\n \n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n\n\n"},{"id":"336794","messageId":"647382ac70bfb7035345304a32d08f4e7b51cd40.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:35:30Z","receivedAt":"2018-01-18T15:35:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label`, `bud` and `reset` commands\nto label commits and to reset to a labeled commits. This patch adds the\n`merge` command, with the following syntax:\n\n\tmerge <commit> <rev> <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the to-be-created merge\ncommit.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list:\n\n\tlabel onto\n\n\t# Branch abc\n\tbud\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\tbud\n\tpick cafecafe And now for something completely different\n\tmerge baaabaaa abc Merge the branch 'abc' into master\n\nTo support creating *new* merges, i.e. without copying the commit\nmessage from an existing commit, use the special value `-` as <commit>\nparameter (in which case the text after the <rev> parameter is used as\ncommit message):\n\n\tmerge - abc This will be the actual commit message of the merge\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in this patch\nseries, support for merges using strategies other than the recursive\nmerge is left for future contributions.\n\nThe design of the `merge` command as introduced by this patch only\nsupports creating new merge commits with exactly two parents, i.e. it\nadds no support for octopus merges.\n\nWe will introduce support for octopus merges in a later commit.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   1 +\n sequencer.c                | 146 +++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 143 insertions(+), 4 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 3d2cd19d65a..5bf1ea3781f 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -165,6 +165,7 @@ d, drop = remove commit\n l, label = label current HEAD with a name\n t, reset = reset HEAD to a label\n b, bud = reset HEAD to the revision labeled 'onto'\n+m, merge = create a merge commit using a given commit's message\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 91cc55a002f..567cfcbbe8b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -779,6 +779,7 @@ enum todo_command {\n \tTODO_LABEL,\n \tTODO_RESET,\n \tTODO_BUD,\n+\tTODO_MERGE,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -800,6 +801,7 @@ static struct {\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n \t{ 'b', \"bud\" },\n+\t{ 'm', \"merge\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1304,14 +1306,20 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t}\n \n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n+\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n+\titem->arg_len = (int)(eol - item->arg);\n+\n \tsaved = *end_of_object_name;\n+\tif (item->command == TODO_MERGE && *bol == '-' &&\n+\t    bol + 1 == end_of_object_name) {\n+\t\titem->commit = NULL;\n+\t\treturn 0;\n+\t}\n+\n \t*end_of_object_name = '\\0';\n \tstatus = get_oid(bol, &commit_oid);\n \t*end_of_object_name = saved;\n \n-\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n-\titem->arg_len = (int)(eol - item->arg);\n-\n \tif (status < 0)\n \t\treturn -1;\n \n@@ -2069,6 +2077,132 @@ static int do_reset(const char *name, int len)\n \treturn ret;\n }\n \n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    struct replay_opts *opts)\n+{\n+\tint merge_arg_len;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *common, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint ret;\n+\tstatic struct lock_file lock;\n+\n+\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n+\t\tif (isspace(arg[merge_arg_len]))\n+\t\t\tbreak;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t     oid_to_hex(&commit->object.oid));\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"Could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tunuse_commit_buffer(commit, message);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tunuse_commit_buffer(commit, message);\n+\t} else {\n+\t\tconst char *p = arg + merge_arg_len;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tp += strspn(p, \" \\t\");\n+\t\tif (*p)\n+\t\t\tlen = strlen(p);\n+\t\telse {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"Could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tstrbuf_release(&buf);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"Cannot merge without a current revision\"));\n+\t}\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\tif (!merge_commit) {\n+\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tcommon = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(common);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n+\t\t\t     merge_arg_len, arg);\n+\t}\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error(_(\"merge: Unable to write new index file\"));\n+\t}\n+\trollback_lock_file(&lock);\n+\n+\tret = run_git_commit(git_path_merge_msg(), opts, 0);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2258,6 +2392,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_reset(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_BUD)\n \t\t\tres = do_reset(\"onto\", 4);\n+\t\telse if (item->command == TODO_MERGE)\n+\t\t\tres = do_merge(item->commit,\n+\t\t\t\t       item->arg, item->arg_len, opts);\n \t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n@@ -2757,7 +2894,8 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n-\t\t}\n+\t\t} else if (item->command == TODO_MERGE)\n+\t\t\tstrbuf_addstr(&buf, \" -\");\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n\n\n"},{"id":"336795","messageId":"9878bd57cc82daf08309943305460c1e8a050518.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 3/8] sequencer: fast-forward merge commits, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:35:34Z","receivedAt":"2018-01-18T15:35:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to recreate a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 21 ++++++++++++++++++++-\n 1 file changed, 20 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 567cfcbbe8b..a96255426e7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2085,7 +2085,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *common, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint ret;\n+\tint can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \n \tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n@@ -2151,6 +2151,14 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\treturn error(_(\"Cannot merge without a current revision\"));\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the parent of the original merge commit,\n+\t * we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n \tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n \tif (!merge_commit) {\n@@ -2164,6 +2172,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\trollback_lock_file(&lock);\n \t\treturn -1;\n \t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn fast_forward_to(&commit->object.oid,\n+\t\t\t\t       &head_commit->object.oid, 0, opts);\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n\n\n"},{"id":"336796","messageId":"b9cb2d915aa4ff564b1960ce0352664b1d26eecb.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 4/8] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:35:40Z","receivedAt":"2018-01-18T15:35:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned a new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --recreate-merges option. For a\ncommit topology like this:\n\n\tA - B - C\n\t  \\   /\n\t    D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge 3456 D C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch in this patch series.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 343 ++++++++++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 345 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex 7daee544b7b..a34ab5c0655 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -22,6 +22,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n+\t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -55,6 +56,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex a96255426e7..1bef16647b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2785,6 +2787,335 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, we append a dash and a number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *p = abbr ? \"p\" : \"pick\", *l = abbr ? \"l\" : \"label\",\n+\t\t *t = abbr ? \"t\" : \"reset\", *b = abbr ? \"b\" : \"bud\",\n+\t\t *m = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tif ((commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_original_commit_empty(commit))\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", p,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s %s\", m, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", l);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s\\n\", b);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(\"onto\", to))\n+\t\t\t\tfprintf(out, \"%s\\n\", b);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s %s\\n\",\n+\t\t\t\t\tt, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\", l, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -2795,11 +3126,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint recreate_merges = flags & TODO_LIST_RECREATE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_pick = 1;\n+\tif (recreate_merges)\n+\t\trevs.cherry_mark = 1;\n+\telse {\n+\t\trevs.max_parents = 1;\n+\t\trevs.cherry_pick = 1;\n+\t}\n \trevs.limited = 1;\n \trevs.reverse = 1;\n \trevs.right_only = 1;\n@@ -2823,6 +3159,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (recreate_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tstrbuf_reset(&buf);\n \t\tif (!keep_empty && is_original_commit_empty(commit))\ndiff --git a/sequencer.h b/sequencer.h\nindex 81f6d7d393f..11d1ac925ef 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -48,6 +48,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_RECREATE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n\n\n"},{"id":"336797","messageId":"71c42d6d3bb240d90071d5afdde81d1293fdf0ab.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:35:48Z","receivedAt":"2018-01-18T15:36:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt at an answer was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto flatten the branch topology or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--recreate-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge - <label> <oneline>`. And once this\nmode has become stable and universally accepted, we can deprecate the\ndesign mistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |   8 +-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 ++\n t/t3430-rebase-recreate-merges.sh      | 146 +++++++++++++++++++++++++++++++++\n 5 files changed, 161 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 8a861c1e0d6..1d061373288 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -368,6 +368,11 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+--recreate-merges::\n+\tRecreate merge commits instead of flattening the history by replaying\n+\tmerges. Merge conflict resolutions or manual amendments to merge\n+\tcommits are not preserved.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -770,7 +775,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+--recreate-merges for a more faithful representation.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3683c772c55..6893c3adabc 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2008,7 +2008,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--recreate-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 5bf1ea3781f..3459ec5a018 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -900,6 +900,7 @@ fi\n if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${recreate_merges:+--recreate-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex fd72a35c65b..d69bc7d0e0d 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+recreate-merges!   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -86,6 +87,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+recreate_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -262,6 +264,10 @@ do\n \t--keep-empty)\n \t\tkeep_empty=yes\n \t\t;;\n+\t--recreate-merges)\n+\t\trecreate_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nnew file mode 100755\nindex 00000000000..46ae52f88b3\n--- /dev/null\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -0,0 +1,146 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2017 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --recreate-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+cat >script-from-scratch <<\\EOF\n+label onto\n+\n+# onebranch\n+pick G\n+pick D\n+label onebranch\n+\n+# second\n+bud\n+pick B\n+label second\n+\n+bud\n+merge H second\n+merge - onebranch Merge the topic branch 'onebranch'\n+EOF\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'create completely different structure' '\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\tbud\n+\tpick d9df450 B\n+\tlabel E\n+\n+\tbud\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point C\n+\tpick 12bd07b D\n+\tmerge 2051b56 E E\n+\tmerge 233d48a H H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n\n\n"},{"id":"336798","messageId":"219dbf95a8069190abe0986ca66b65a8ae3e670d.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 6/8] sequencer: handle autosquash and post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:35:57Z","receivedAt":"2018-01-18T15:36:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --recreate-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits. And the\ninteractive rebase also supports the autosquash mode, where commits\nwhose oneline is of the form `fixup! <oneline>` or `squash! <oneline>`\nare rearranged to amend commits whose oneline they match.\n\nThis patch implements the post-rewrite and autosquash handling for the\n`merge` command we just introduced. The other commands that were added\nrecently (`label`, `reset` and `bud`) do not create new commits,\ntherefore post-rewrite & autosquash do not need to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                            |  3 ++-\n sequencer.c                       | 10 +++++++---\n t/t3430-rebase-recreate-merges.sh | 39 +++++++++++++++++++++++++++++++++++++++\n 3 files changed, 48 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 20ba82b4343..e8b84c189ff 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/sequencer.c b/sequencer.c\nindex 1bef16647b4..b63bfb9a141 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2413,10 +2413,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_reset(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_BUD)\n \t\t\tres = do_reset(\"onto\", 4);\n-\t\telse if (item->command == TODO_MERGE)\n+\t\telse if (item->command == TODO_MERGE) {\n \t\t\tres = do_merge(item->commit,\n \t\t\t\t       item->arg, item->arg_len, opts);\n-\t\telse if (!is_noop(item->command))\n+\t\t\tif (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n+\t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\n@@ -3556,7 +3559,8 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (item->command >= TODO_EXEC &&\n+\t\t    (item->command != TODO_MERGE || !item->commit)) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 46ae52f88b3..76e615bd7c1 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,4 +143,43 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash --recreate-merges HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n\n\n"},{"id":"336799","messageId":"59228b6446bd86863850665d0e1b29954b88f305.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 7/8] pull: accept --rebase=recreate to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:36:03Z","receivedAt":"2018-01-18T15:36:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `recreate` mode simply passes the\n`--recreate-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  5 ++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       |  2 ++\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 25 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0e25b2c92b3..da41ab246dc 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2607,6 +2611,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..b4f9f057ea9 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,16 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|recreate|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to recreate, rebase with the `--recreate-merges` option passed\n+to `git rebase` so that locally created merge commits will not be flattened.\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 511dbbe0f6e..e33c84e0345 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_RECREATE,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"recreate\", returns REBASE_RECREATE. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"recreate\"))\n+\t\treturn REBASE_RECREATE;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|recreate|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -798,7 +802,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_RECREATE)\n+\t\targv_array_push(&args, \"--recreate-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d95bf904c3b..b7d0f7ce596 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"recreate\"))\n+\t\t\t\tinfo->rebase = NORMAL_REBASE;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 6893c3adabc..6f98c96fee9 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2182,7 +2182,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true recreate preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n\n\n"},{"id":"336800","messageId":"e3c3c703f8eae723c4fbbd08d2c329d81179dbee.1516225925.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 8/8] rebase -i: introduce --recreate-merges=no-rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T15:36:19Z","receivedAt":"2018-01-18T15:36:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This one is a bit tricky to explain, so let's try with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nTo illustrate what this new mode is all about, let's consider what\nhappens upon `git rebase -i --recreate-merges B`, in particular to\nthe commit `D`. In the default mode, the new branch structure is:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nHowever, when recreating branch structure, there are legitimate use\ncases where one might want to preserve the branch points of commits that\ndo not descend from the <upstream> commit that was passed to the rebase\ncommand, e.g. when a branch from core Git's `next` was merged into Git\nfor Windows' master we will not want to rebase those commits on top of a\nWindows-specific commit. In the example above, the desired outcome would\nlook like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and the \"no-rebase-cousins\" mode of the merge-recreating\nrebase, to help those use cases.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt      |  7 ++++++-\n builtin/rebase--helper.c          |  9 ++++++++-\n git-rebase--interactive.sh        |  1 +\n git-rebase.sh                     | 12 +++++++++++-\n sequencer.c                       |  4 ++++\n sequencer.h                       |  8 ++++++++\n t/t3430-rebase-recreate-merges.sh | 23 +++++++++++++++++++++++\n 7 files changed, 61 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 1d061373288..ac07a5c3fc9 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -368,10 +368,15 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n---recreate-merges::\n+--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tRecreate merge commits instead of flattening the history by replaying\n \tmerges. Merge conflict resolutions or manual amendments to merge\n \tcommits are not preserved.\n++\n+By default, or when `rebase-cousins` was specified, commits which do not have\n+`<upstream>` as direct ancestor are rebased onto `<upstream>` (or `<onto>`,\n+if specified). If the `rebase-cousins` mode is turned off, such commits will\n+retain their original branch point.\n \n -p::\n --preserve-merges::\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex a34ab5c0655..ef08fef4d14 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, no_rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -23,6 +23,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n+\t\tOPT_BOOL(0, \"no-rebase-cousins\", &no_rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,8 +59,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n+\tflags |= no_rebase_cousins > 0 ? TODO_LIST_NO_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (no_rebase_cousins >= 0&& !recreate_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--recreate-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 3459ec5a018..23184c77e88 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -901,6 +901,7 @@ if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${recreate_merges:+--recreate-merges} \\\n+\t\t${no_rebase_cousins:+--no-rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex d69bc7d0e0d..3403b1416a8 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-recreate-merges!   try to recreate merges instead of skipping them\n+recreate-merges?   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -88,6 +88,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n recreate_merges=\n+no_rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -268,6 +269,15 @@ do\n \t\trecreate_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--recreate-merges=*)\n+\t\trecreate_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) no_rebase_cousins=;;\n+\t\tno-rebase-cousins) no_rebase_cousins=t;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex b63bfb9a141..2b4e6b12321 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2905,6 +2905,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint no_rebase_cousins = flags & TODO_LIST_NO_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3078,6 +3079,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (no_rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(\"onto\", to))\n \t\t\t\tfprintf(out, \"%s\\n\", b);\ndiff --git a/sequencer.h b/sequencer.h\nindex 11d1ac925ef..9530dba3cba 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -49,6 +49,14 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_RECREATE_MERGES (1U << 3)\n+/*\n+ * When recreating merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are rebased onto the new base by default. If those commits\n+ * should keep their original branch point, this flag needs to be passed.\n+ *\n+ * This flag only makes sense when <base> and <onto> are different.\n+ */\n+#define TODO_LIST_NO_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 76e615bd7c1..22930e470a4 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,6 +143,29 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'rebase cousins unless told not to' '\n+\twrite_script copy-editor.sh <<-\\EOF &&\n+\tcp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tEOF\n+\n+\ttest_config sequence.editor \\\"\"$PWD\"/copy-editor.sh\\\" &&\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges=no-rebase-cousins HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.15.1.windows.2.1430.ga56c4f9e2a9\n"},{"id":"336802","messageId":"CA+P7+xozcQD2xuys6mh8MsfcYZ_nb2c9yxhDxkc7FTN2SfoofQ@mail.gmail.com","threadId":"47635","inReplyTo":"8a91bf2184a3da4c0d5a13ba184813068e51f5c8.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T16:25:30Z","receivedAt":"2018-01-18T16:26:03Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This commit implements the commands to label, and to reset to, given\n> revisions. The syntax is:\n>\n>         label <name>\n>         reset <name>\n>\n> As a convenience shortcut, also to improve readability of the generated\n> todo list, a third command is introduced: bud. It simply resets to the\n> \"onto\" revision, i.e. the commit onto which we currently rebase.\n>\n\nThe code looks good, but I'm a little wary of adding bud which\nhard-codes a specific label. I suppose it does grant a bit of\nreadability to the resulting script... ? It doesn't seem that\nimportant compared to use using \"reset onto\"? At least when\ndocumenting this it should be made clear that the \"onto\" label is\nspecial.\n\nThanks,\nJake.\n"},{"id":"336804","messageId":"CA+P7+xrLrU=s4EgPYs7HP4JRBj1fW2YJNUEpswJENBxHAvz5LQ@mail.gmail.com","threadId":"47635","inReplyTo":"647382ac70bfb7035345304a32d08f4e7b51cd40.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T16:31:38Z","receivedAt":"2018-01-18T16:32:08Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This patch is part of the effort to reimplement `--preserve-merges` with\n> a substantially improved design, a design that has been developed in the\n> Git for Windows project to maintain the dozens of Windows-specific patch\n> series on top of upstream Git.\n>\n> The previous patch implemented the `label`, `bud` and `reset` commands\n> to label commits and to reset to a labeled commits. This patch adds the\n> `merge` command, with the following syntax:\n>\n>         merge <commit> <rev> <oneline>\n>\n> The <commit> parameter in this instance is the *original* merge commit,\n> whose author and message will be used for the to-be-created merge\n> commit.\n>\n> The <rev> parameter refers to the (possibly rewritten) revision to\n> merge. Let's see an example of a todo list:\n>\n>         label onto\n>\n>         # Branch abc\n>         bud\n>         pick deadbeef Hello, world!\n>         label abc\n>\n>         bud\n>         pick cafecafe And now for something completely different\n>         merge baaabaaa abc Merge the branch 'abc' into master\n>\n> To support creating *new* merges, i.e. without copying the commit\n> message from an existing commit, use the special value `-` as <commit>\n> parameter (in which case the text after the <rev> parameter is used as\n> commit message):\n>\n>         merge - abc This will be the actual commit message of the merge\n>\n> This comes in handy when splitting a branch into two or more branches.\n>\n\nWould it be possible to open the editor with the supplied text when\nthere's no commit? The text after <rev> must be oneline only..\n\nIt's difficult to reword merges because of the nature of rebase\ninteractive, you can't just re-run the rebase command and use\n\"reword\".\n\nI suppose you could cheat by putting in an \"edit\" command that let you\ncreate an empty commit with a message...\n\nThanks,\nJake\n"},{"id":"336805","messageId":"CA+P7+xr0FpgTUa9SgkXM1U86i8f0cParNqGkP=KNPOR_EqNHYg@mail.gmail.com","threadId":"47635","inReplyTo":"219dbf95a8069190abe0986ca66b65a8ae3e670d.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 6/8] sequencer: handle autosquash and post-rewrite for merge commands","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T16:43:49Z","receivedAt":"2018-01-18T16:44:17Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> In the previous patches, we implemented the basic functionality of the\n> `git rebase -i --recreate-merges` command, in particular the `merge`\n> command to create merge commits in the sequencer.\n>\n> The interactive rebase is a lot more these days, though, than a simple\n> cherry-pick in a loop. For example, it calls the post-rewrite hook (if\n> any) after rebasing with a mapping of the old->new commits. And the\n> interactive rebase also supports the autosquash mode, where commits\n> whose oneline is of the form `fixup! <oneline>` or `squash! <oneline>`\n> are rearranged to amend commits whose oneline they match.\n>\n> This patch implements the post-rewrite and autosquash handling for the\n> `merge` command we just introduced. The other commands that were added\n> recently (`label`, `reset` and `bud`) do not create new commits,\n> therefore post-rewrite & autosquash do not need to handle them.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  refs.c                            |  3 ++-\n>  sequencer.c                       | 10 +++++++---\n>  t/t3430-rebase-recreate-merges.sh | 39 +++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 48 insertions(+), 4 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index 20ba82b4343..e8b84c189ff 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n>  static int is_per_worktree_ref(const char *refname)\n>  {\n>         return !strcmp(refname, \"HEAD\") ||\n> -               starts_with(refname, \"refs/bisect/\");\n> +               starts_with(refname, \"refs/bisect/\") ||\n> +               starts_with(refname, \"refs/rewritten/\");\n>  }\n\nWould this part make more sense to move into the commit that\nintroduces writing these refs, or does it only matter once you start\nthis step here?\n\n>\n>  static int is_pseudoref_syntax(const char *refname)\n> diff --git a/sequencer.c b/sequencer.c\n> index 1bef16647b4..b63bfb9a141 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -2413,10 +2413,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>                         res = do_reset(item->arg, item->arg_len);\n>                 else if (item->command == TODO_BUD)\n>                         res = do_reset(\"onto\", 4);\n> -               else if (item->command == TODO_MERGE)\n> +               else if (item->command == TODO_MERGE) {\n>                         res = do_merge(item->commit,\n>                                        item->arg, item->arg_len, opts);\n> -               else if (!is_noop(item->command))\n> +                       if (item->commit)\n> +                               record_in_rewritten(&item->commit->object.oid,\n> +                                                   peek_command(todo_list, 1));\n> +               } else if (!is_noop(item->command))\n>                         return error(_(\"unknown command %d\"), item->command);\n>\n>                 todo_list->current++;\n> @@ -3556,7 +3559,8 @@ int rearrange_squash(void)\n>                 struct subject2item_entry *entry;\n>\n>                 next[i] = tail[i] = -1;\n> -               if (item->command >= TODO_EXEC) {\n> +               if (item->command >= TODO_EXEC &&\n> +                   (item->command != TODO_MERGE || !item->commit)) {\n>                         subjects[i] = NULL;\n>                         continue;\n>                 }\n> diff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\n> index 46ae52f88b3..76e615bd7c1 100755\n> --- a/t/t3430-rebase-recreate-merges.sh\n> +++ b/t/t3430-rebase-recreate-merges.sh\n> @@ -143,4 +143,43 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n>         EOF\n>  '\n>\n> +test_expect_success 'refs/rewritten/* is worktree-local' '\n> +       git worktree add wt &&\n> +       cat >wt/script-from-scratch <<-\\EOF &&\n> +       label xyz\n> +       exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n> +       exec git rev-parse --verify refs/rewritten/xyz >b\n> +       EOF\n> +\n> +       test_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n> +       git -C wt rebase -i HEAD &&\n> +       test_must_be_empty wt/a &&\n> +       test_cmp_rev HEAD \"$(cat wt/b)\"\n> +'\n> +\n\nSame for the test here, I can't figure out why this is necessary in\nthis patch as opposed to the patch which first introduced the\nrefs/rewritten/<label> refs.\n\n> +test_expect_success 'post-rewrite hook and fixups work for merges' '\n> +       git checkout -b post-rewrite &&\n> +       test_commit same1 &&\n> +       git reset --hard HEAD^ &&\n> +       test_commit same2 &&\n> +       git merge -m \"to fix up\" same1 &&\n> +       echo same old same old >same2.t &&\n> +       test_tick &&\n> +       git commit --fixup HEAD same2.t &&\n> +       fixup=\"$(git rev-parse HEAD)\" &&\n> +\n> +       mkdir -p .git/hooks &&\n> +       test_when_finished \"rm .git/hooks/post-rewrite\" &&\n> +       echo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n> +\n> +       test_tick &&\n> +       git rebase -i --autosquash --recreate-merges HEAD^^^ &&\n> +       printf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n> +               $fixup^^2 HEAD^2 \\\n> +               $fixup^^ HEAD^ \\\n> +               $fixup^ HEAD \\\n> +               $fixup HEAD) &&\n> +       test_cmp expect actual\n> +'\n> +\n>  test_done\n> --\n> 2.15.1.windows.2.1430.ga56c4f9e2a9\n>\n>\n"},{"id":"336806","messageId":"CA+P7+xqJNVPVd22nCZqXc0r66NOZsOnvpKoL-WF8WZZUgdCOyw@mail.gmail.com","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 0/8] rebase -i: offer to recreate merge commits","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T16:49:50Z","receivedAt":"2018-01-18T16:50:18Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> Once upon a time, I dreamt of an interactive rebase that would not\n> flatten branch structure, but instead recreate the commit topology\n> faithfully.\n>\n> My original attempt was --preserve-merges, but that design was so\n> limited that I did not even enable it in interactive mode.\n>\n> Subsequently, it *was* enabled in interactive mode, with the predictable\n> consequences: as the --preserve-merges design does not allow for\n> specifying the parents of merge commits explicitly, all the new commits'\n> parents are defined *implicitly* by the previous commit history, and\n> hence it is *not possible to even reorder commits*.\n>\n> This design flaw cannot be fixed. Not without a complete re-design, at\n> least. This patch series offers such a re-design.\n>\n> Think of --recreate-merges as \"--preserve-merges done right\". It\n> introduces new verbs for the todo list, `label`, `reset` and `merge`.\n> For a commit topology like this:\n>\n>             A - B - C\n>               \\   /\n>                 D\n>\n> the generated todo list would look like this:\n>\n>             # branch D\n>             pick 0123 A\n>             label branch-point\n>             pick 1234 D\n>             label D\n>\n>             reset branch-point\n>             pick 2345 B\n>             merge 3456 D C\n>\n> There are more patches in the pipeline, based on this patch series, but\n> left for later in the interest of reviewable patch series: one mini\n> series to use the sequencer even for `git rebase -i --root`, and another\n> one to add support for octopus merges to --recreate-merges.\n>\n>\n\nI've been looking forward to seeing this hit the list! Overall I don't\nthink I have any major complaints, except to make sure the special\nlabel \"onto\" is documented.\n\nI think it's possible to \"reword\" or \"edit\" the merge commit by\ninserting \"x false\" after the merge commnd to halt the rebase and let\nthe user perform commands to edit, so that should work well.\n\nThanks for taking the time to make this a reality!\n\nThanks,\nJake\n\n> Johannes Schindelin (8):\n>   sequencer: introduce new commands to reset the revision\n>   sequencer: introduce the `merge` command\n>   sequencer: fast-forward merge commits, if possible\n>   rebase-helper --make-script: introduce a flag to recreate merges\n>   rebase: introduce the --recreate-merges option\n>   sequencer: handle autosquash and post-rewrite for merge commands\n>   pull: accept --rebase=recreate to recreate the branch topology\n>   rebase -i: introduce --recreate-merges=no-rebase-cousins\n>\n>  Documentation/config.txt               |   8 +\n>  Documentation/git-pull.txt             |   5 +-\n>  Documentation/git-rebase.txt           |  13 +-\n>  builtin/pull.c                         |  14 +-\n>  builtin/rebase--helper.c               |  13 +-\n>  builtin/remote.c                       |   2 +\n>  contrib/completion/git-completion.bash |   4 +-\n>  git-rebase--interactive.sh             |   6 +\n>  git-rebase.sh                          |  16 +\n>  refs.c                                 |   3 +-\n>  sequencer.c                            | 697 ++++++++++++++++++++++++++++++++-\n>  sequencer.h                            |   9 +\n>  t/t3430-rebase-recreate-merges.sh      | 208 ++++++++++\n>  13 files changed, 977 insertions(+), 21 deletions(-)\n>  create mode 100755 t/t3430-rebase-recreate-merges.sh\n>\n>\n> base-commit: 2512f15446149235156528dafbe75930c712b29e\n> Published-As: https://github.com/dscho/git/releases/tag/recreate-merges-v1\n> Fetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v1\n> --\n> 2.15.1.windows.2.1430.ga56c4f9e2a9\n>\n"},{"id":"336810","messageId":"20180118183618.39853-1-sbeller@google.com","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH 9, 10/8] interactive rebase feedback","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-18T18:36:16Z","receivedAt":"2018-01-18T18:36:36Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"I thought of writing a reply, but instead I wrote it in patch form.\n\nStefan Beller (2):\n  [DO NOT APPLY, but squash?] git-rebase--interactive: clarify arguments\n  [DO NOT APPLY, but improve?] rebase--interactive: introduce \"stop\"\n    command\n\n git-rebase--interactive.sh | 23 ++++++++++++-----------\n sequencer.c                | 10 ++++++++++\n 2 files changed, 22 insertions(+), 11 deletions(-)\n\n-- \n2.16.0.rc1.238.g530d649a79-goog\n\n"},{"id":"336811","messageId":"20180118183618.39853-2-sbeller@google.com","threadId":"47635","inReplyTo":"20180118183618.39853-1-sbeller@google.com","subject":"[PATCH 9/8] [DO NOT APPLY, but squash?] git-rebase--interactive: clarify arguments","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-18T18:36:17Z","receivedAt":"2018-01-18T18:36:37Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Up to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we have commands that take different arguments, clarify each\ncommand by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n git-rebase--interactive.sh | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 23184c77e8..3cd7446d0b 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,17 +155,17 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n-l, label = label current HEAD with a name\n-t, reset = reset HEAD to a label\n-b, bud = reset HEAD to the revision labeled 'onto'\n-m, merge = create a merge commit using a given commit's message\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n+l, label <label>= label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n+b, bud = reset HEAD to the revision labeled 'onto', no arguments\n+m, merge [<label-or-commit>]* = create a merge commit using a given commit's message\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.16.0.rc1.238.g530d649a79-goog\n\n"},{"id":"336812","messageId":"20180118183618.39853-3-sbeller@google.com","threadId":"47635","inReplyTo":"20180118183618.39853-1-sbeller@google.com","subject":"[PATCH 10/8] [DO NOT APPLY, but improve?] rebase--interactive: introduce \"stop\" command","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-18T18:36:18Z","receivedAt":"2018-01-18T18:36:39Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Jake suggested using \"x false\" instead of \"edit\" for some corner cases.\n\nI do prefer using \"x false\" for all kinds of things such as stopping\nbefore a commit (edit only let's you stop after a commit), and the\nknowledge that \"x false\" does the least amount of actions behind my back.\n\nWe should have that command as well, maybe?\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n git-rebase--interactive.sh |  1 +\n sequencer.c                | 10 ++++++++++\n 2 files changed, 11 insertions(+)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 3cd7446d0b..9eac53f0c5 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -166,6 +166,7 @@ l, label <label>= label current HEAD with a name\n t, reset <label> = reset HEAD to a label\n b, bud = reset HEAD to the revision labeled 'onto', no arguments\n m, merge [<label-or-commit>]* = create a merge commit using a given commit's message\n+y, stay = stop for  shortcut for\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 2b4e6b1232..4b3b9fe59d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -782,6 +782,7 @@ enum todo_command {\n \tTODO_RESET,\n \tTODO_BUD,\n \tTODO_MERGE,\n+\tTODO_STOP,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -803,6 +804,7 @@ static struct {\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n \t{ 'b', \"bud\" },\n+\t{ 'y', \"stay\" },\n \t{ 'm', \"merge\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n@@ -1307,6 +1309,12 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn 0;\n \t}\n \n+\tif (item->command == TODO_STOP) {\n+\t\titem->commit = NULL;\n+\t\titem->arg = \"\";\n+\t\titem->arg_len = 0;\n+\t}\n+\n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n \titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n \titem->arg_len = (int)(eol - item->arg);\n@@ -2407,6 +2415,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n+\t\t} else if (item->command == TODO_STOP) {\n+\t\t\ttodo_list->current = -1;\n \t\t} else if (item->command == TODO_LABEL)\n \t\t\tres = do_label(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_RESET)\n-- \n2.16.0.rc1.238.g530d649a79-goog\n\n"},{"id":"336818","messageId":"nycvar.QRO.7.76.6.1801182212210.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xozcQD2xuys6mh8MsfcYZ_nb2c9yxhDxkc7FTN2SfoofQ@mail.gmail.com","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T21:13:54Z","receivedAt":"2018-01-18T21:14:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Thu, 18 Jan 2018, Jacob Keller wrote:\n\n> On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > This commit implements the commands to label, and to reset to, given\n> > revisions. The syntax is:\n> >\n> >         label <name>\n> >         reset <name>\n> >\n> > As a convenience shortcut, also to improve readability of the generated\n> > todo list, a third command is introduced: bud. It simply resets to the\n> > \"onto\" revision, i.e. the commit onto which we currently rebase.\n> >\n> \n> The code looks good, but I'm a little wary of adding bud which\n> hard-codes a specific label. I suppose it does grant a bit of\n> readability to the resulting script... ? It doesn't seem that\n> important compared to use using \"reset onto\"? At least when\n> documenting this it should be made clear that the \"onto\" label is\n> special.\n\nIndeed, `bud` helped me more in the earlier times of the Git garden shears\nwhen I had to *introduce* branch structure into the long list of Git for\nWindows' patches.\n\nIf there are no voices in favor of `bud` other than mine, I will remove\nsupport for it in the next iteration.\n\nCiao,\nDscho\n"},{"id":"336819","messageId":"CA+P7+xp3BVSPapd=P1+fPqimQXYhc2z_A_7djwvrv0HE_WB=LA@mail.gmail.com","threadId":"47635","inReplyTo":"20180118183618.39853-2-sbeller@google.com","subject":"Re: [PATCH 9/8] [DO NOT APPLY, but squash?] git-rebase--interactive: clarify arguments","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T21:18:41Z","receivedAt":"2018-01-18T21:19:09Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 10:36 AM, Stefan Beller <sbeller@google.com> wrote:\n> Up to now each command took a commit as its first argument and ignored\n> the rest of the line (usually the subject of the commit)\n>\n> Now that we have commands that take different arguments, clarify each\n> command by giving the argument list.\n>\n> Signed-off-by: Stefan Beller <sbeller@google.com>\n> ---\n>  git-rebase--interactive.sh | 22 +++++++++++-----------\n>  1 file changed, 11 insertions(+), 11 deletions(-)\n>\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index 23184c77e8..3cd7446d0b 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -155,17 +155,17 @@ reschedule_last_action () {\n>  append_todo_help () {\n>         gettext \"\n>  Commands:\n> -p, pick = use commit\n> -r, reword = use commit, but edit the commit message\n> -e, edit = use commit, but stop for amending\n> -s, squash = use commit, but meld into previous commit\n> -f, fixup = like \\\"squash\\\", but discard this commit's log message\n> -x, exec = run command (the rest of the line) using shell\n> -d, drop = remove commit\n> -l, label = label current HEAD with a name\n> -t, reset = reset HEAD to a label\n> -b, bud = reset HEAD to the revision labeled 'onto'\n> -m, merge = create a merge commit using a given commit's message\n> +p, pick <commit> = use commit\n> +r, reword <commit> = use commit, but edit the commit message\n> +e, edit <commit> = use commit, but stop for amending\n> +s, squash <commit> = use commit, but meld into previous commit\n> +f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n> +x, exec <commit> = run command (the rest of the line) using shell\n> +d, drop <commit> = remove commit\n> +l, label <label>= label current HEAD with a name\n> +t, reset <label> = reset HEAD to a label\n> +b, bud = reset HEAD to the revision labeled 'onto', no arguments\n> +m, merge [<label-or-commit>]* = create a merge commit using a given commit's message\n\nThe merge arguments are complicated, i'm not sure how best to add\nclarification here.. I think it might need its own paragraph, since it\ntakes something like: <commit> <parents to merge>* <possibly message>?\n\nThanks,\nJake\n\n>\n>  These lines can be re-ordered; they are executed from top to bottom.\n>  \" | git stripspace --comment-lines >>\"$todo\"\n> --\n> 2.16.0.rc1.238.g530d649a79-goog\n>\n"},{"id":"336820","messageId":"CA+P7+xpwYDeNHfzMtHJkKEB8-A+kZ95wBEn0RC5R8pWbgKOmiQ@mail.gmail.com","threadId":"47635","inReplyTo":"20180118183618.39853-3-sbeller@google.com","subject":"Re: [PATCH 10/8] [DO NOT APPLY, but improve?] rebase--interactive: introduce \"stop\" command","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T21:20:04Z","receivedAt":"2018-01-18T21:20:34Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 10:36 AM, Stefan Beller <sbeller@google.com> wrote:\n> Jake suggested using \"x false\" instead of \"edit\" for some corner cases.\n>\n> I do prefer using \"x false\" for all kinds of things such as stopping\n> before a commit (edit only let's you stop after a commit), and the\n> knowledge that \"x false\" does the least amount of actions behind my back.\n>\n> We should have that command as well, maybe?\n>\n\n\nI agree. I use \"x false\" very often, and I think stop is probably a\nbetter solution since it avoids spawning an extra shell that will just\nfail. Not sure if stop implies too much about \"stop the whole thing\"\nas opposed to \"stop here and let me do something manual\", but I think\nit's clear enough.\n\nThanks,\nJake\n\n> Signed-off-by: Stefan Beller <sbeller@google.com>\n> ---\n>  git-rebase--interactive.sh |  1 +\n>  sequencer.c                | 10 ++++++++++\n>  2 files changed, 11 insertions(+)\n>\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index 3cd7446d0b..9eac53f0c5 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -166,6 +166,7 @@ l, label <label>= label current HEAD with a name\n>  t, reset <label> = reset HEAD to a label\n>  b, bud = reset HEAD to the revision labeled 'onto', no arguments\n>  m, merge [<label-or-commit>]* = create a merge commit using a given commit's message\n> +y, stay = stop for  shortcut for\n>\n>  These lines can be re-ordered; they are executed from top to bottom.\n>  \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 2b4e6b1232..4b3b9fe59d 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -782,6 +782,7 @@ enum todo_command {\n>         TODO_RESET,\n>         TODO_BUD,\n>         TODO_MERGE,\n> +       TODO_STOP,\n>         /* commands that do nothing but are counted for reporting progress */\n>         TODO_NOOP,\n>         TODO_DROP,\n> @@ -803,6 +804,7 @@ static struct {\n>         { 'l', \"label\" },\n>         { 't', \"reset\" },\n>         { 'b', \"bud\" },\n> +       { 'y', \"stay\" },\n>         { 'm', \"merge\" },\n>         { 0,   \"noop\" },\n>         { 'd', \"drop\" },\n> @@ -1307,6 +1309,12 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>                 return 0;\n>         }\n>\n> +       if (item->command == TODO_STOP) {\n> +               item->commit = NULL;\n> +               item->arg = \"\";\n> +               item->arg_len = 0;\n> +       }\n> +\n>         end_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n>         item->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n>         item->arg_len = (int)(eol - item->arg);\n> @@ -2407,6 +2415,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>                                 /* `current` will be incremented below */\n>                                 todo_list->current = -1;\n>                         }\n> +               } else if (item->command == TODO_STOP) {\n> +                       todo_list->current = -1;\n>                 } else if (item->command == TODO_LABEL)\n>                         res = do_label(item->arg, item->arg_len);\n>                 else if (item->command == TODO_RESET)\n> --\n> 2.16.0.rc1.238.g530d649a79-goog\n>\n"},{"id":"336821","messageId":"CA+P7+xo096LyacgFM3kLMHovXT3sWsf_m9AB7oENREZ7TfWa9Q@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801182212210.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T21:21:49Z","receivedAt":"2018-01-18T21:22:17Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 1:13 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Hi Jake,\n>\n> On Thu, 18 Jan 2018, Jacob Keller wrote:\n>\n>> On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n>> <johannes.schindelin@gmx.de> wrote:\n>> > This commit implements the commands to label, and to reset to, given\n>> > revisions. The syntax is:\n>> >\n>> >         label <name>\n>> >         reset <name>\n>> >\n>> > As a convenience shortcut, also to improve readability of the generated\n>> > todo list, a third command is introduced: bud. It simply resets to the\n>> > \"onto\" revision, i.e. the commit onto which we currently rebase.\n>> >\n>>\n>> The code looks good, but I'm a little wary of adding bud which\n>> hard-codes a specific label. I suppose it does grant a bit of\n>> readability to the resulting script... ? It doesn't seem that\n>> important compared to use using \"reset onto\"? At least when\n>> documenting this it should be made clear that the \"onto\" label is\n>> special.\n>\n> Indeed, `bud` helped me more in the earlier times of the Git garden shears\n> when I had to *introduce* branch structure into the long list of Git for\n> Windows' patches.\n>\n> If there are no voices in favor of `bud` other than mine, I will remove\n> support for it in the next iteration.\n>\n> Ciao,\n> Dscho\n\nAfter I saw more examples, I don't mind it. I do think it's not\nstrictly necessary, but it seemed to help me with the readability. My\nonly main concern is that it's limited to a special label.\n\nThanks,\nJake\n"},{"id":"336822","messageId":"nycvar.QRO.7.76.6.1801182214350.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xrLrU=s4EgPYs7HP4JRBj1fW2YJNUEpswJENBxHAvz5LQ@mail.gmail.com","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T21:22:32Z","receivedAt":"2018-01-18T21:22:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Thu, 18 Jan 2018, Jacob Keller wrote:\n\n> On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > This patch is part of the effort to reimplement `--preserve-merges` with\n> > a substantially improved design, a design that has been developed in the\n> > Git for Windows project to maintain the dozens of Windows-specific patch\n> > series on top of upstream Git.\n> >\n> > The previous patch implemented the `label`, `bud` and `reset` commands\n> > to label commits and to reset to a labeled commits. This patch adds the\n> > `merge` command, with the following syntax:\n> >\n> >         merge <commit> <rev> <oneline>\n> >\n> > The <commit> parameter in this instance is the *original* merge commit,\n> > whose author and message will be used for the to-be-created merge\n> > commit.\n> >\n> > The <rev> parameter refers to the (possibly rewritten) revision to\n> > merge. Let's see an example of a todo list:\n> >\n> >         label onto\n> >\n> >         # Branch abc\n> >         bud\n> >         pick deadbeef Hello, world!\n> >         label abc\n> >\n> >         bud\n> >         pick cafecafe And now for something completely different\n> >         merge baaabaaa abc Merge the branch 'abc' into master\n> >\n> > To support creating *new* merges, i.e. without copying the commit\n> > message from an existing commit, use the special value `-` as <commit>\n> > parameter (in which case the text after the <rev> parameter is used as\n> > commit message):\n> >\n> >         merge - abc This will be the actual commit message of the merge\n> >\n> > This comes in handy when splitting a branch into two or more branches.\n> >\n> \n> Would it be possible to open the editor with the supplied text when\n> there's no commit?  The text after <rev> must be oneline only..\n\nI actually want to avoid that because my main use case is fire-and-forget,\ni.e. I want to edit only the todo list and then (barring any merge\nconflicts) I do not want to edit anything anymore.\n\nBut I guess we could special-case the thing where `-` is specified as\n\"merge commit message provider\" and an empty oneline is provided?\n\n> It's difficult to reword merges because of the nature of rebase\n> interactive, you can't just re-run the rebase command and use\n> \"reword\".\n> \n> I suppose you could cheat by putting in an \"edit\" command that let you\n> create an empty commit with a message...\n\nOr you could \"cheat\" by adding `exec git commit --amend`...\n\nSeriously again, I have no good idea how to provide an equivalent to the\n`reword` verb that would work on merge commits...\n\nAnyone?\n\nCiao,\nDscho\n"},{"id":"336824","messageId":"F2A95961E128479484699EC5DDC9243D@PhilipOakley","threadId":"47635","inReplyTo":"CA+P7+xozcQD2xuys6mh8MsfcYZ_nb2c9yxhDxkc7FTN2SfoofQ@mail.gmail.com","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":null,"receivedAt":"2018-01-18T21:24:35Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Jacob Keller\" <jacob.keller@gmail.com>\n> On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n>> This commit implements the commands to label, and to reset to, given\n>> revisions. The syntax is:\n>>\n>>         label <name>\n>>         reset <name>\n>>\n>> As a convenience shortcut, also to improve readability of the generated\n>> todo list, a third command is introduced: bud. It simply resets to the\n>> \"onto\" revision, i.e. the commit onto which we currently rebase.\n>>\n>\n> The code looks good, but I'm a little wary of adding bud which\n> hard-codes a specific label. I suppose it does grant a bit of\n> readability to the resulting script... ? It doesn't seem that\n> important compared to use using \"reset onto\"? At least when\n> documenting this it should be made clear that the \"onto\" label is\n> special.\n>\n> Thanks,\n> Jake.\n\nI'd agree.\n\nThe special 'onto' label should be fully documented, and the commit message \nshould indicate which patch actually defines it (and all its corner cases \nand fall backs if --onto isn't explicitly given..)\n\nLikewise the choice of 'bud' should be explained with some nice phraseology \nindicating that we are growing the new flowering from the bud, otherwise the \nword is a bit too short and sudden for easy explanation.\n\nPhilip \n\n"},{"id":"336825","messageId":"CA+P7+xrWc9mO8AuVomUbFr-JBfzmggCs9qE4yois27t4drHSMQ@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801182214350.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T21:26:37Z","receivedAt":"2018-01-18T21:27:03Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 1:22 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>> Would it be possible to open the editor with the supplied text when\n>> there's no commit?  The text after <rev> must be oneline only..\n>\n> I actually want to avoid that because my main use case is fire-and-forget,\n> i.e. I want to edit only the todo list and then (barring any merge\n> conflicts) I do not want to edit anything anymore.\n>\n\nAgreed, for the case where we copy a commit message, I do not want the\neditor either.\n\n> But I guess we could special-case the thing where `-` is specified as\n> \"merge commit message provider\" and an empty oneline is provided?\n>\n\nIt's for when there is a new merge, for when we are creating a new one\nusing \"-\", yes.\n\n>> It's difficult to reword merges because of the nature of rebase\n>> interactive, you can't just re-run the rebase command and use\n>> \"reword\".\n>>\n>> I suppose you could cheat by putting in an \"edit\" command that let you\n>> create an empty commit with a message...\n>\n> Or you could \"cheat\" by adding `exec git commit --amend`...\n>\n> Seriously again, I have no good idea how to provide an equivalent to the\n> `reword` verb that would work on merge commits...\n>\n\nGiven that there is a work around, and I doubt it's that common, I'm\nnot sure we need one, plus i have no idea what verb to use....\n\nWe could allow reword on its own to simply reword the top commit?\n\nThat being said, since there's a simple-ish workaruond using \"stop\",\nor \"exec git commit --amend\" I don't see this as being important\nenough to worry about now.\n\nThanks,\nJake\n"},{"id":"336826","messageId":"CA+P7+xqHZ58MEaj7KrCpk_-MprqipGwjXMvhPaeUP0MsBLPgsQ@mail.gmail.com","threadId":"47635","inReplyTo":"F2A95961E128479484699EC5DDC9243D@PhilipOakley","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T21:28:02Z","receivedAt":"2018-01-18T21:28:41Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 1:24 PM, Philip Oakley <philipoakley@iee.org> wrote:\n> From: \"Jacob Keller\" <jacob.keller@gmail.com>\n>\n>> On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n>> <johannes.schindelin@gmx.de> wrote:\n>>>\n>>> This commit implements the commands to label, and to reset to, given\n>>> revisions. The syntax is:\n>>>\n>>>         label <name>\n>>>         reset <name>\n>>>\n>>> As a convenience shortcut, also to improve readability of the generated\n>>> todo list, a third command is introduced: bud. It simply resets to the\n>>> \"onto\" revision, i.e. the commit onto which we currently rebase.\n>>>\n>>\n>> The code looks good, but I'm a little wary of adding bud which\n>> hard-codes a specific label. I suppose it does grant a bit of\n>> readability to the resulting script... ? It doesn't seem that\n>> important compared to use using \"reset onto\"? At least when\n>> documenting this it should be made clear that the \"onto\" label is\n>> special.\n>>\n>> Thanks,\n>> Jake.\n>\n>\n> I'd agree.\n>\n> The special 'onto' label should be fully documented, and the commit message\n> should indicate which patch actually defines it (and all its corner cases\n> and fall backs if --onto isn't explicitly given..)\n\nI don't think it actually relates to \"--onto\" but rather to simply\nusing \"label onto\" in your sequencer script allows bud to work, and\nsimply shortens the overall work necessary. It's equivalent to \"reset\nonto\" if I understand.\n\n>\n> Likewise the choice of 'bud' should be explained with some nice phraseology\n> indicating that we are growing the new flowering from the bud, otherwise the\n> word is a bit too short and sudden for easy explanation.\n>\n> Philip\n"},{"id":"336827","messageId":"nycvar.QRO.7.76.6.1801182226160.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xr0FpgTUa9SgkXM1U86i8f0cParNqGkP=KNPOR_EqNHYg@mail.gmail.com","subject":"Re: [PATCH 6/8] sequencer: handle autosquash and post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T21:27:40Z","receivedAt":"2018-01-18T21:29:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Thu, 18 Jan 2018, Jacob Keller wrote:\n\n> On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > In the previous patches, we implemented the basic functionality of the\n> > `git rebase -i --recreate-merges` command, in particular the `merge`\n> > command to create merge commits in the sequencer.\n> >\n> > The interactive rebase is a lot more these days, though, than a simple\n> > cherry-pick in a loop. For example, it calls the post-rewrite hook (if\n> > any) after rebasing with a mapping of the old->new commits. And the\n> > interactive rebase also supports the autosquash mode, where commits\n> > whose oneline is of the form `fixup! <oneline>` or `squash! <oneline>`\n> > are rearranged to amend commits whose oneline they match.\n> >\n> > This patch implements the post-rewrite and autosquash handling for the\n> > `merge` command we just introduced. The other commands that were added\n> > recently (`label`, `reset` and `bud`) do not create new commits,\n> > therefore post-rewrite & autosquash do not need to handle them.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  refs.c                            |  3 ++-\n> >  sequencer.c                       | 10 +++++++---\n> >  t/t3430-rebase-recreate-merges.sh | 39 +++++++++++++++++++++++++++++++++++++++\n> >  3 files changed, 48 insertions(+), 4 deletions(-)\n> >\n> > diff --git a/refs.c b/refs.c\n> > index 20ba82b4343..e8b84c189ff 100644\n> > --- a/refs.c\n> > +++ b/refs.c\n> > @@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n> >  static int is_per_worktree_ref(const char *refname)\n> >  {\n> >         return !strcmp(refname, \"HEAD\") ||\n> > -               starts_with(refname, \"refs/bisect/\");\n> > +               starts_with(refname, \"refs/bisect/\") ||\n> > +               starts_with(refname, \"refs/rewritten/\");\n> >  }\n> \n> Would this part make more sense to move into the commit that\n> introduces writing these refs, or does it only matter once you start\n> this step here?\n> \n> >\n> >  static int is_pseudoref_syntax(const char *refname)\n> > diff --git a/sequencer.c b/sequencer.c\n> > index 1bef16647b4..b63bfb9a141 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -2413,10 +2413,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n> >                         res = do_reset(item->arg, item->arg_len);\n> >                 else if (item->command == TODO_BUD)\n> >                         res = do_reset(\"onto\", 4);\n> > -               else if (item->command == TODO_MERGE)\n> > +               else if (item->command == TODO_MERGE) {\n> >                         res = do_merge(item->commit,\n> >                                        item->arg, item->arg_len, opts);\n> > -               else if (!is_noop(item->command))\n> > +                       if (item->commit)\n> > +                               record_in_rewritten(&item->commit->object.oid,\n> > +                                                   peek_command(todo_list, 1));\n> > +               } else if (!is_noop(item->command))\n> >                         return error(_(\"unknown command %d\"), item->command);\n> >\n> >                 todo_list->current++;\n> > @@ -3556,7 +3559,8 @@ int rearrange_squash(void)\n> >                 struct subject2item_entry *entry;\n> >\n> >                 next[i] = tail[i] = -1;\n> > -               if (item->command >= TODO_EXEC) {\n> > +               if (item->command >= TODO_EXEC &&\n> > +                   (item->command != TODO_MERGE || !item->commit)) {\n> >                         subjects[i] = NULL;\n> >                         continue;\n> >                 }\n> > diff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\n> > index 46ae52f88b3..76e615bd7c1 100755\n> > --- a/t/t3430-rebase-recreate-merges.sh\n> > +++ b/t/t3430-rebase-recreate-merges.sh\n> > @@ -143,4 +143,43 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n> >         EOF\n> >  '\n> >\n> > +test_expect_success 'refs/rewritten/* is worktree-local' '\n> > +       git worktree add wt &&\n> > +       cat >wt/script-from-scratch <<-\\EOF &&\n> > +       label xyz\n> > +       exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n> > +       exec git rev-parse --verify refs/rewritten/xyz >b\n> > +       EOF\n> > +\n> > +       test_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n> > +       git -C wt rebase -i HEAD &&\n> > +       test_must_be_empty wt/a &&\n> > +       test_cmp_rev HEAD \"$(cat wt/b)\"\n> > +'\n> > +\n> \n> Same for the test here, I can't figure out why this is necessary in\n> this patch as opposed to the patch which first introduced the\n> refs/rewritten/<label> refs.\n\nWoops. This was its own commit, and must have been accidentally squashed\nduring one of my rebases. Will re-introduce it; this is roughly what it\nwill look like:\n\n-- snipsnap --\nAuthor: Johannes Schindelin <johannes.schindelin@gmx.de>\nSubject: sequencer: make refs generated by the `label` command worktree-local\n\nThis allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n\ndiff --git a/refs.c b/refs.c\nindex 20ba82b4343..e8b84c189ff 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex f5e476d948b..50a8eefc81e 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -115,4 +115,18 @@ test_expect_success 'generate correct todo list' '\n \ttest_cmp expect output\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n"},{"id":"336828","messageId":"CA+P7+xpAX-pgGAZS84DtOVSk9RfnLtD2JqsSYDKYZ30dcLKYJw@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801182226160.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 6/8] sequencer: handle autosquash and post-rewrite for merge commands","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T21:29:02Z","receivedAt":"2018-01-18T21:30:16Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":">> Same for the test here, I can't figure out why this is necessary in\n>> this patch as opposed to the patch which first introduced the\n>> refs/rewritten/<label> refs.\n>\n> Woops. This was its own commit, and must have been accidentally squashed\n> during one of my rebases. Will re-introduce it;\n\nYep that makes sense!\n\nThanks,\nJake\n"},{"id":"336829","messageId":"nycvar.QRO.7.76.6.1801182233480.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"20180118183618.39853-2-sbeller@google.com","subject":"Re: [PATCH 9/8] [DO NOT APPLY, but squash?] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T21:36:51Z","receivedAt":"2018-01-18T21:37:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Thu, 18 Jan 2018, Stefan Beller wrote:\n\n> Up to now each command took a commit as its first argument and ignored\n> the rest of the line (usually the subject of the commit)\n> \n> Now that we have commands that take different arguments, clarify each\n> command by giving the argument list.\n> \n> Signed-off-by: Stefan Beller <sbeller@google.com>\n> ---\n>  git-rebase--interactive.sh | 22 +++++++++++-----------\n>  1 file changed, 11 insertions(+), 11 deletions(-)\n> \n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index 23184c77e8..3cd7446d0b 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -155,17 +155,17 @@ reschedule_last_action () {\n>  append_todo_help () {\n>  \tgettext \"\n>  Commands:\n> -p, pick = use commit\n> -r, reword = use commit, but edit the commit message\n> -e, edit = use commit, but stop for amending\n> -s, squash = use commit, but meld into previous commit\n> -f, fixup = like \\\"squash\\\", but discard this commit's log message\n> -x, exec = run command (the rest of the line) using shell\n> -d, drop = remove commit\n> -l, label = label current HEAD with a name\n> -t, reset = reset HEAD to a label\n> -b, bud = reset HEAD to the revision labeled 'onto'\n> -m, merge = create a merge commit using a given commit's message\n> +p, pick <commit> = use commit\n> +r, reword <commit> = use commit, but edit the commit message\n> +e, edit <commit> = use commit, but stop for amending\n> +s, squash <commit> = use commit, but meld into previous commit\n> +f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n> +x, exec <commit> = run command (the rest of the line) using shell\n> +d, drop <commit> = remove commit\n> +l, label <label>= label current HEAD with a name\n> +t, reset <label> = reset HEAD to a label\n> +b, bud = reset HEAD to the revision labeled 'onto', no arguments\n> +m, merge [<label-or-commit>]* = create a merge commit using a given commit's message\n\nGood idea! I would rather do it as an introductory patch (that only\nconverts the existing list).\n\nAs to `merge`: it is a bit more complicated ;-)\n\n\tm, merge <original-merge-commit> ( <label> | \"<label>...\" ) [<oneline>]\n\t\tcreate a merge commit using the original merge commit's\n\t\tmessage (or the oneline, if \"-\" is given). Use a quoted\n\t\tlist of commits to be merged for octopus merges.\n\nThanks,\nDscho\n"},{"id":"336831","messageId":"D8F2BF74F8CF46A3B61EE75301953516@PhilipOakley","threadId":"47635","inReplyTo":"b9cb2d915aa4ff564b1960ce0352664b1d26eecb.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 4/8] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":null,"receivedAt":"2018-01-18T21:40:00Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> The sequencer just learned a new commands intended to recreate branch\n> structure (similar in spirit to --preserve-merges, but with a\n> substantially less-broken design).\n>\n> Let's allow the rebase--helper to generate todo lists making use of\n> these commands, triggered by the new --recreate-merges option. For a\n> commit topology like this:\n>\n> A - B - C\n>   \\   /\n>     D\n\nCould the topology include the predecessor for context. Alo it is easy for \nreaders to become confused between the arcs of the graphs and the nodes of \nthe graphs, such that we confuse 'commits as patches' with 'commits as \nsnapshots'. It might need an 'Aa' distinction between the two types, \nespecially around merges and potential evilness.\n\n>\n> the generated todo list would look like this:\n>\n> # branch D\n> pick 0123 A\n> label branch-point\n> pick 1234 D\n> label D\n>\n> reset branch-point\n> pick 2345 B\n> merge 3456 D C\n>\n> To keep things simple, we first only implement support for merge commits\n> with exactly two parents, leaving support for octopus merges to a later\n> patch in this patch series.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> builtin/rebase--helper.c |   4 +-\n> sequencer.c              | 343 \n> ++++++++++++++++++++++++++++++++++++++++++++++-\n> sequencer.h              |   1 +\n> 3 files changed, 345 insertions(+), 3 deletions(-)\n>\n> diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\n> index 7daee544b7b..a34ab5c0655 100644\n> --- a/builtin/rebase--helper.c\n> +++ b/builtin/rebase--helper.c\n> @@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] \n> = {\n> int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n> {\n>  struct replay_opts opts = REPLAY_OPTS_INIT;\n> - unsigned flags = 0, keep_empty = 0;\n> + unsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n>  int abbreviate_commands = 0;\n>  enum {\n>  CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n> @@ -22,6 +22,7 @@ int cmd_rebase__helper(int argc, const char **argv, \n> const char *prefix)\n>  struct option options[] = {\n>  OPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n>  OPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n> + OPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge \n> commits\")),\n>  OPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n>  CONTINUE),\n>  OPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n> @@ -55,6 +56,7 @@ int cmd_rebase__helper(int argc, const char **argv, \n> const char *prefix)\n>\n>  flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n>  flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n> + flags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n>  flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n>\n>  if (command == CONTINUE && argc == 1)\n> diff --git a/sequencer.c b/sequencer.c\n> index a96255426e7..1bef16647b4 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -23,6 +23,8 @@\n> #include \"hashmap.h\"\n> #include \"unpack-trees.h\"\n> #include \"worktree.h\"\n> +#include \"oidmap.h\"\n> +#include \"oidset.h\"\n>\n> #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>\n> @@ -2785,6 +2787,335 @@ void append_signoff(struct strbuf *msgbuf, int \n> ignore_footer, unsigned flag)\n>  strbuf_release(&sob);\n> }\n>\n> +struct labels_entry {\n> + struct hashmap_entry entry;\n> + char label[FLEX_ARRAY];\n> +};\n> +\n> +static int labels_cmp(const void *fndata, const struct labels_entry *a,\n> +       const struct labels_entry *b, const void *key)\n> +{\n> + return key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n> +}\n> +\n> +struct string_entry {\n> + struct oidmap_entry entry;\n> + char string[FLEX_ARRAY];\n> +};\n> +\n> +struct label_state {\n> + struct oidmap commit2label;\n> + struct hashmap labels;\n> + struct strbuf buf;\n> +};\n> +\n> +static const char *label_oid(struct object_id *oid, const char *label,\n> +      struct label_state *state)\n> +{\n> + struct labels_entry *labels_entry;\n> + struct string_entry *string_entry;\n> + struct object_id dummy;\n> + size_t len;\n> + int i;\n> +\n> + string_entry = oidmap_get(&state->commit2label, oid);\n> + if (string_entry)\n> + return string_entry->string;\n> +\n> + /*\n> + * For \"uninteresting\" commits, i.e. commits that are not to be\n> + * rebased, and which can therefore not be labeled, we use a unique\n> + * abbreviation of the commit name. This is slightly more complicated\n> + * than calling find_unique_abbrev() because we also need to make\n> + * sure that the abbreviation does not conflict with any other\n> + * label.\n> + *\n> + * We disallow \"interesting\" commits to be labeled by a string that\n> + * is a valid full-length hash, to ensure that we always can find an\n> + * abbreviation for any uninteresting commit's names that does not\n> + * clash with any other label.\n> + */\n> + if (!label) {\n> + char *p;\n> +\n> + strbuf_reset(&state->buf);\n> + strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n> + label = p = state->buf.buf;\n> +\n> + find_unique_abbrev_r(p, oid->hash, default_abbrev);\n> +\n> + /*\n> + * We may need to extend the abbreviated hash so that there is\n> + * no conflicting label.\n> + */\n> + if (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n> + size_t i = strlen(p) + 1;\n> +\n> + oid_to_hex_r(p, oid);\n> + for (; i < GIT_SHA1_HEXSZ; i++) {\n> + char save = p[i];\n> + p[i] = '\\0';\n> + if (!hashmap_get_from_hash(&state->labels,\n> +    strihash(p), p))\n> + break;\n> + p[i] = save;\n> + }\n> + }\n> + } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n> +     !get_oid_hex(label, &dummy)) ||\n> +    hashmap_get_from_hash(&state->labels,\n> + strihash(label), label)) {\n> + /*\n> + * If the label already exists, or if the label is a valid full\n> + * OID, we append a dash and a number to make it unique.\n> + */\n> + struct strbuf *buf = &state->buf;\n> +\n> + strbuf_reset(buf);\n> + strbuf_add(buf, label, len);\n> +\n> + for (i = 2; ; i++) {\n> + strbuf_setlen(buf, len);\n> + strbuf_addf(buf, \"-%d\", i);\n> + if (!hashmap_get_from_hash(&state->labels,\n> +    strihash(buf->buf),\n> +    buf->buf))\n> + break;\n> + }\n> +\n> + label = buf->buf;\n> + }\n> +\n> + FLEX_ALLOC_STR(labels_entry, label, label);\n> + hashmap_entry_init(labels_entry, strihash(label));\n> + hashmap_add(&state->labels, labels_entry);\n> +\n> + FLEX_ALLOC_STR(string_entry, string, label);\n> + oidcpy(&string_entry->entry.oid, oid);\n> + oidmap_put(&state->commit2label, string_entry);\n> +\n> + return string_entry->string;\n> +}\n> +\n> +static int make_script_with_merges(struct pretty_print_context *pp,\n> +    struct rev_info *revs, FILE *out,\n> +    unsigned flags)\n> +{\n> + int keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n> + struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n> + struct strbuf label = STRBUF_INIT;\n> + struct commit_list *commits = NULL, **tail = &commits, *iter;\n> + struct commit_list *tips = NULL, **tips_tail = &tips;\n> + struct commit *commit;\n> + struct oidmap commit2todo = OIDMAP_INIT;\n> + struct string_entry *entry;\n> + struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n> + shown = OIDSET_INIT;\n> + struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n> +\n> + int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n> + const char *p = abbr ? \"p\" : \"pick\", *l = abbr ? \"l\" : \"label\",\n> + *t = abbr ? \"t\" : \"reset\", *b = abbr ? \"b\" : \"bud\",\n> + *m = abbr ? \"m\" : \"merge\";\n> +\n> + oidmap_init(&commit2todo, 0);\n> + oidmap_init(&state.commit2label, 0);\n> + hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n> + strbuf_init(&state.buf, 32);\n> +\n> + if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n> + struct object_id *oid = &revs->cmdline.rev[0].item->oid;\n> + FLEX_ALLOC_STR(entry, string, \"onto\");\n\n\"onto\" Needs documentation / commit message?\n\n\n> + oidcpy(&entry->entry.oid, oid);\n> + oidmap_put(&state.commit2label, entry);\n> + }\n> +\n> + /*\n> + * First phase:\n> + * - get onelines for all commits\n> + * - gather all branch tips (i.e. 2nd or later parents of merges)\n> + * - label all branch tips\n> + */\n> + while ((commit = get_revision(revs))) {\n> + struct commit_list *to_merge;\n> + int is_octopus;\n> + const char *p1, *p2;\n> + struct object_id *oid;\n> +\n> + tail = &commit_list_insert(commit, tail)->next;\n> + oidset_insert(&interesting, &commit->object.oid);\n> +\n> + if ((commit->object.flags & PATCHSAME))\n> + continue;\n> +\n> + strbuf_reset(&oneline);\n> + pretty_print_commit(pp, commit, &oneline);\n> +\n> + to_merge = commit->parents ? commit->parents->next : NULL;\n> + if (!to_merge) {\n> + /* non-merge commit: easy case */\n> + strbuf_reset(&buf);\n> + if (!keep_empty && is_original_commit_empty(commit))\n> + strbuf_addf(&buf, \"%c \", comment_line_char);\n> + strbuf_addf(&buf, \"%s %s %s\", p,\n> +     oid_to_hex(&commit->object.oid),\n> +     oneline.buf);\n> +\n> + FLEX_ALLOC_STR(entry, string, buf.buf);\n> + oidcpy(&entry->entry.oid, &commit->object.oid);\n> + oidmap_put(&commit2todo, entry);\n> +\n> + continue;\n> + }\n> +\n> + is_octopus = to_merge && to_merge->next;\n> +\n> + if (is_octopus)\n> + BUG(\"Octopus merges not yet supported\");\n> +\n> + /* Create a label */\n> + strbuf_reset(&label);\n> + if (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n> +     (p1 = strchr(p1, '\\'')) &&\n> +     (p2 = strchr(++p1, '\\'')))\n> + strbuf_add(&label, p1, p2 - p1);\n> + else if (skip_prefix(oneline.buf, \"Merge pull request \",\n> +      &p1) &&\n> + (p1 = strstr(p1, \" from \")))\n> + strbuf_addstr(&label, p1 + strlen(\" from \"));\n> + else\n> + strbuf_addbuf(&label, &oneline);\n> +\n> + for (p1 = label.buf; *p1; p1++)\n> + if (isspace(*p1))\n> + *(char *)p1 = '-';\n> +\n> + strbuf_reset(&buf);\n> + strbuf_addf(&buf, \"%s %s\", m, oid_to_hex(&commit->object.oid));\n> +\n> + /* label the tip of merged branch */\n> + oid = &to_merge->item->object.oid;\n> + strbuf_addch(&buf, ' ');\n> +\n> + if (!oidset_contains(&interesting, oid))\n> + strbuf_addstr(&buf, label_oid(oid, NULL, &state));\n> + else {\n> + tips_tail = &commit_list_insert(to_merge->item,\n> + tips_tail)->next;\n> +\n> + strbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n> + }\n> + strbuf_addf(&buf, \" %s\", oneline.buf);\n> +\n> + FLEX_ALLOC_STR(entry, string, buf.buf);\n> + oidcpy(&entry->entry.oid, &commit->object.oid);\n> + oidmap_put(&commit2todo, entry);\n> + }\n> +\n> + /*\n> + * Second phase:\n> + * - label branch points\n> + * - add HEAD to the branch tips\n> + */\n> + for (iter = commits; iter; iter = iter->next) {\n> + struct commit_list *parent = iter->item->parents;\n> + for (; parent; parent = parent->next) {\n> + struct object_id *oid = &parent->item->object.oid;\n> + if (!oidset_contains(&interesting, oid))\n> + continue;\n> + if (!oidset_contains(&child_seen, oid))\n> + oidset_insert(&child_seen, oid);\n> + else\n> + label_oid(oid, \"branch-point\", &state);\n> + }\n> +\n> + /* Add HEAD as implict \"tip of branch\" */\n> + if (!iter->next)\n> + tips_tail = &commit_list_insert(iter->item,\n> + tips_tail)->next;\n> + }\n> +\n> + /*\n> + * Third phase: output the todo list. This is a bit tricky, as we\n> + * want to avoid jumping back and forth between revisions. To\n> + * accomplish that goal, we walk backwards from the branch tips,\n> + * gathering commits not yet shown, reversing the list on the fly,\n> + * then outputting that list (labeling revisions as needed).\n> + */\n> + fprintf(out, \"%s onto\\n\", l);\n> + for (iter = tips; iter; iter = iter->next) {\n> + struct commit_list *list = NULL, *iter2;\n> +\n> + commit = iter->item;\n> + if (oidset_contains(&shown, &commit->object.oid))\n> + continue;\n> + entry = oidmap_get(&state.commit2label, &commit->object.oid);\n> +\n> + if (entry)\n> + fprintf(out, \"\\n# Branch %s\\n\", entry->string);\n> + else\n> + fprintf(out, \"\\n\");\n> +\n> + while (oidset_contains(&interesting, &commit->object.oid) &&\n> +        !oidset_contains(&shown, &commit->object.oid)) {\n> + commit_list_insert(commit, &list);\n> + if (!commit->parents) {\n> + commit = NULL;\n> + break;\n> + }\n> + commit = commit->parents->item;\n> + }\n> +\n> + if (!commit)\n> + fprintf(out, \"%s\\n\", b);\n> + else {\n> + const char *to = NULL;\n> +\n> + entry = oidmap_get(&state.commit2label,\n> +    &commit->object.oid);\n> + if (entry)\n> + to = entry->string;\n> +\n> + if (!to || !strcmp(\"onto\", to))\n> + fprintf(out, \"%s\\n\", b);\n> + else {\n> + strbuf_reset(&oneline);\n> + pretty_print_commit(pp, commit, &oneline);\n> + fprintf(out, \"%s %s %s\\n\",\n> + t, to, oneline.buf);\n> + }\n> + }\n> +\n> + for (iter2 = list; iter2; iter2 = iter2->next) {\n> + struct object_id *oid = &iter2->item->object.oid;\n> + entry = oidmap_get(&commit2todo, oid);\n> + /* only show if not already upstream */\n> + if (entry)\n> + fprintf(out, \"%s\\n\", entry->string);\n> + entry = oidmap_get(&state.commit2label, oid);\n> + if (entry)\n> + fprintf(out, \"%s %s\\n\", l, entry->string);\n> + oidset_insert(&shown, oid);\n> + }\n> +\n> + free_commit_list(list);\n> + }\n> +\n> + free_commit_list(commits);\n> + free_commit_list(tips);\n> +\n> + strbuf_release(&label);\n> + strbuf_release(&oneline);\n> + strbuf_release(&buf);\n> +\n> + oidmap_free(&commit2todo, 1);\n> + oidmap_free(&state.commit2label, 1);\n> + hashmap_free(&state.labels, 1);\n> + strbuf_release(&state.buf);\n> +\n> + return 0;\n> +}\n> +\n> int sequencer_make_script(FILE *out, int argc, const char **argv,\n>    unsigned flags)\n> {\n> @@ -2795,11 +3126,16 @@ int sequencer_make_script(FILE *out, int argc, \n> const char **argv,\n>  struct commit *commit;\n>  int keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n>  const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n> + int recreate_merges = flags & TODO_LIST_RECREATE_MERGES;\n>\n>  init_revisions(&revs, NULL);\n>  revs.verbose_header = 1;\n> - revs.max_parents = 1;\n> - revs.cherry_pick = 1;\n> + if (recreate_merges)\n> + revs.cherry_mark = 1;\n> + else {\n> + revs.max_parents = 1;\n> + revs.cherry_pick = 1;\n> + }\n>  revs.limited = 1;\n>  revs.reverse = 1;\n>  revs.right_only = 1;\n> @@ -2823,6 +3159,9 @@ int sequencer_make_script(FILE *out, int argc, const \n> char **argv,\n>  if (prepare_revision_walk(&revs) < 0)\n>  return error(_(\"make_script: error preparing revisions\"));\n>\n> + if (recreate_merges)\n> + return make_script_with_merges(&pp, &revs, out, flags);\n> +\n>  while ((commit = get_revision(&revs))) {\n>  strbuf_reset(&buf);\n>  if (!keep_empty && is_original_commit_empty(commit))\n> diff --git a/sequencer.h b/sequencer.h\n> index 81f6d7d393f..11d1ac925ef 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -48,6 +48,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n> #define TODO_LIST_KEEP_EMPTY (1U << 0)\n> #define TODO_LIST_SHORTEN_IDS (1U << 1)\n> #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n> +#define TODO_LIST_RECREATE_MERGES (1U << 3)\n> int sequencer_make_script(FILE *out, int argc, const char **argv,\n>    unsigned flags);\n>\n> -- \n> 2.15.1.windows.2.1430.ga56c4f9e2a9\n>\n> \n\n"},{"id":"336834","messageId":"CAGZ79kak8u0fuKW5Jg5+hjERCL+RtSCVhMjJz38qQbAG0KKG=w@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801182233480.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 9/8] [DO NOT APPLY, but squash?] git-rebase--interactive: clarify arguments","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-18T21:58:39Z","receivedAt":"2018-01-18T21:58:46Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Thu, Jan 18, 2018 at 1:36 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n\n> Good idea! I would rather do it as an introductory patch (that only\n> converts the existing list).\n\nThat makes sense.\n\n>\n> As to `merge`: it is a bit more complicated ;-)\n>\n>         m, merge <original-merge-commit> ( <label> | \"<label>...\" ) [<oneline>]\n>                 create a merge commit using the original merge commit's\n>                 message (or the oneline, if \"-\" is given). Use a quoted\n>                 list of commits to be merged for octopus merges.\n\nThat makes sense. Thanks for being precise at the the user facing UX here.\n\nThanks,\nStefan\n"},{"id":"336835","messageId":"nycvar.QRO.7.76.6.1801182254470.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"20180118183618.39853-3-sbeller@google.com","subject":"Re: [PATCH 10/8] [DO NOT APPLY, but improve?] rebase--interactive: introduce \"stop\" command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-18T22:00:39Z","receivedAt":"2018-01-18T22:00:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Thu, 18 Jan 2018, Stefan Beller wrote:\n\n> Jake suggested using \"x false\" instead of \"edit\" for some corner cases.\n> \n> I do prefer using \"x false\" for all kinds of things such as stopping\n> before a commit (edit only let's you stop after a commit), and the\n> knowledge that \"x false\" does the least amount of actions behind my back.\n> \n> We should have that command as well, maybe?\n> \n> Signed-off-by: Stefan Beller <sbeller@google.com>\n> ---\n>  git-rebase--interactive.sh |  1 +\n>  sequencer.c                | 10 ++++++++++\n>  2 files changed, 11 insertions(+)\n> \n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index 3cd7446d0b..9eac53f0c5 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -166,6 +166,7 @@ l, label <label>= label current HEAD with a name\n>  t, reset <label> = reset HEAD to a label\n>  b, bud = reset HEAD to the revision labeled 'onto', no arguments\n>  m, merge [<label-or-commit>]* = create a merge commit using a given commit's message\n> +y, stay = stop for  shortcut for\n>  \n>  These lines can be re-ordered; they are executed from top to bottom.\n>  \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 2b4e6b1232..4b3b9fe59d 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -782,6 +782,7 @@ enum todo_command {\n>  \tTODO_RESET,\n>  \tTODO_BUD,\n>  \tTODO_MERGE,\n> +\tTODO_STOP,\n\nI see that your original idea was \"stop\", but then you probably realized\nthat there would be no good abbreviation for that, and changed your mind.\n\nPersonally, I would have called it `break`...\n\n> @@ -2407,6 +2415,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t\t\t\t/* `current` will be incremented below */\n>  \t\t\t\ttodo_list->current = -1;\n>  \t\t\t}\n> +\t\t} else if (item->command == TODO_STOP) {\n> +\t\t\ttodo_list->current = -1;\n\nThat is incorrect, it will most likely write an unexpected `done` file.\n\nDid you mean `return 0` instead?\n\nCiao,\nDscho\n"},{"id":"336836","messageId":"5FC5C1765B7648F7A166363BED3AFFA3@PhilipOakley","threadId":"47635","inReplyTo":"e3c3c703f8eae723c4fbbd08d2c329d81179dbee.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 8/8] rebase -i: introduce --recreate-merges=no-rebase-cousins","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":null,"receivedAt":"2018-01-18T22:00:54Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> This one is a bit tricky to explain, so let's try with a diagram:\n>\n>        C\n>      /   \\\n> A - B - E - F\n>  \\   /\n>    D\n>\n> To illustrate what this new mode is all about, let's consider what\n> happens upon `git rebase -i --recreate-merges B`, in particular to\n> the commit `D`. In the default mode, the new branch structure is:\n>\n>       --- C' --\n>      /         \\\n> A - B ------ E' - F'\n>      \\    /\n>        D'\n>\n> This is not really preserving the branch topology from before! The\n> reason is that the commit `D` does not have `B` as ancestor, and\n> therefore it gets rebased onto `B`.\n>\n> However, when recreating branch structure, there are legitimate use\n> cases where one might want to preserve the branch points of commits that\n> do not descend from the <upstream> commit that was passed to the rebase\n> command, e.g. when a branch from core Git's `next` was merged into Git\n> for Windows' master we will not want to rebase those commits on top of a\n> Windows-specific commit. In the example above, the desired outcome would\n> look like this:\n>\n>       --- C' --\n>      /         \\\n> A - B ------ E' - F'\n>  \\        /\n>   -- D' --\n\nI'm not understanding this. I see that D properly starts from A, but don't \nsee why it is now D'. Surely it's unchanged.\nMaybe it's the arc/node confusion. Maybe even spell out that the rebased \ncommits from the command are B..HEAD, but that includes D, which may not be \nwhat folk had expected. (not even sure if the reflog comes into determining \nmerge-bases here..)\n\nI do think an exact definition is needed (e.g. via --ancestry-path or its \nequivalent?).\n\n>\n> Let's introduce the term \"cousins\" for such commits (\"D\" in the\n> example), and the \"no-rebase-cousins\" mode of the merge-recreating\n> rebase, to help those use cases.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> Documentation/git-rebase.txt      |  7 ++++++-\n> builtin/rebase--helper.c          |  9 ++++++++-\n> git-rebase--interactive.sh        |  1 +\n> git-rebase.sh                     | 12 +++++++++++-\n> sequencer.c                       |  4 ++++\n> sequencer.h                       |  8 ++++++++\n> t/t3430-rebase-recreate-merges.sh | 23 +++++++++++++++++++++++\n> 7 files changed, 61 insertions(+), 3 deletions(-)\n>\n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 1d061373288..ac07a5c3fc9 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -368,10 +368,15 @@ The commit list format can be changed by setting the \n> configuration option\n> rebase.instructionFormat.  A customized instruction format will \n> automatically\n> have the long commit hash prepended to the format.\n>\n> ---recreate-merges::\n> +--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n>  Recreate merge commits instead of flattening the history by replaying\n>  merges. Merge conflict resolutions or manual amendments to merge\n>  commits are not preserved.\n> ++\n> +By default, or when `rebase-cousins` was specified, commits which do not \n> have\n> +`<upstream>` as direct ancestor are rebased onto `<upstream>` (or \n> `<onto>`,\n> +if specified). If the `rebase-cousins` mode is turned off, such commits \n> will\n> +retain their original branch point.\n>\n> -p::\n> --preserve-merges::\n> diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\n> index a34ab5c0655..ef08fef4d14 100644\n> --- a/builtin/rebase--helper.c\n> +++ b/builtin/rebase--helper.c\n> @@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, \n> const char *prefix)\n> {\n>  struct replay_opts opts = REPLAY_OPTS_INIT;\n>  unsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n> - int abbreviate_commands = 0;\n> + int abbreviate_commands = 0, no_rebase_cousins = -1;\n>  enum {\n>  CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n>  CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n> @@ -23,6 +23,8 @@ int cmd_rebase__helper(int argc, const char **argv, \n> const char *prefix)\n>  OPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n>  OPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n>  OPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge \n> commits\")),\n> + OPT_BOOL(0, \"no-rebase-cousins\", &no_rebase_cousins,\n> + N_(\"keep original branch points of cousins\")),\n>  OPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n>  CONTINUE),\n>  OPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n> @@ -57,8 +59,13 @@ int cmd_rebase__helper(int argc, const char **argv, \n> const char *prefix)\n>  flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n>  flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n>  flags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n> + flags |= no_rebase_cousins > 0 ? TODO_LIST_NO_REBASE_COUSINS : 0;\n>  flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n>\n> + if (no_rebase_cousins >= 0&& !recreate_merges)\n> + warning(_(\"--[no-]rebase-cousins has no effect without \"\n> +   \"--recreate-merges\"));\n> +\n>  if (command == CONTINUE && argc == 1)\n>  return !!sequencer_continue(&opts);\n>  if (command == ABORT && argc == 1)\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index 3459ec5a018..23184c77e88 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -901,6 +901,7 @@ if test t != \"$preserve_merges\"\n> then\n>  git rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n>  ${recreate_merges:+--recreate-merges} \\\n> + ${no_rebase_cousins:+--no-rebase-cousins} \\\n>  $revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n>  die \"$(gettext \"Could not generate todo list\")\"\n> else\n> diff --git a/git-rebase.sh b/git-rebase.sh\n> index d69bc7d0e0d..3403b1416a8 100755\n> --- a/git-rebase.sh\n> +++ b/git-rebase.sh\n> @@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n> autostash          automatically stash/stash pop before and after\n> fork-point         use 'merge-base --fork-point' to refine upstream\n> onto=!             rebase onto given branch instead of upstream\n> -recreate-merges!   try to recreate merges instead of skipping them\n> +recreate-merges?   try to recreate merges instead of skipping them\n> p,preserve-merges! try to recreate merges instead of ignoring them\n> s,strategy=!       use the given merge strategy\n> no-ff!             cherry-pick all commits, even if unchanged\n> @@ -88,6 +88,7 @@ state_dir=\n> # One of {'', continue, skip, abort}, as parsed from command line\n> action=\n> recreate_merges=\n> +no_rebase_cousins=\n> preserve_merges=\n> autosquash=\n> keep_empty=\n> @@ -268,6 +269,15 @@ do\n>  recreate_merges=t\n>  test -z \"$interactive_rebase\" && interactive_rebase=implied\n>  ;;\n> + --recreate-merges=*)\n> + recreate_merges=t\n> + case \"${1#*=}\" in\n> + rebase-cousins) no_rebase_cousins=;;\n> + no-rebase-cousins) no_rebase_cousins=t;;\n> + *) die \"Unknown mode: $1\";;\n> + esac\n> + test -z \"$interactive_rebase\" && interactive_rebase=implied\n> + ;;\n>  --preserve-merges)\n>  preserve_merges=t\n>  test -z \"$interactive_rebase\" && interactive_rebase=implied\n> diff --git a/sequencer.c b/sequencer.c\n> index b63bfb9a141..2b4e6b12321 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -2905,6 +2905,7 @@ static int make_script_with_merges(struct \n> pretty_print_context *pp,\n>     unsigned flags)\n> {\n>  int keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n> + int no_rebase_cousins = flags & TODO_LIST_NO_REBASE_COUSINS;\n>  struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n>  struct strbuf label = STRBUF_INIT;\n>  struct commit_list *commits = NULL, **tail = &commits, *iter;\n> @@ -3078,6 +3079,9 @@ static int make_script_with_merges(struct \n> pretty_print_context *pp,\n>     &commit->object.oid);\n>  if (entry)\n>  to = entry->string;\n> + else if (no_rebase_cousins)\n> + to = label_oid(&commit->object.oid, NULL,\n> +        &state);\n>\n>  if (!to || !strcmp(\"onto\", to))\n>  fprintf(out, \"%s\\n\", b);\n> diff --git a/sequencer.h b/sequencer.h\n> index 11d1ac925ef..9530dba3cba 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -49,6 +49,14 @@ int sequencer_remove_state(struct replay_opts *opts);\n> #define TODO_LIST_SHORTEN_IDS (1U << 1)\n> #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n> #define TODO_LIST_RECREATE_MERGES (1U << 3)\n> +/*\n> + * When recreating merges, commits that do have the base commit as \n> ancestor\n> + * (\"cousins\") are rebased onto the new base by default. If those commits\n> + * should keep their original branch point, this flag needs to be passed.\n> + *\n> + * This flag only makes sense when <base> and <onto> are different.\n> + */\n> +#define TODO_LIST_NO_REBASE_COUSINS (1U << 4)\n> int sequencer_make_script(FILE *out, int argc, const char **argv,\n>    unsigned flags);\n>\n> diff --git a/t/t3430-rebase-recreate-merges.sh \n> b/t/t3430-rebase-recreate-merges.sh\n> index 76e615bd7c1..22930e470a4 100755\n> --- a/t/t3430-rebase-recreate-merges.sh\n> +++ b/t/t3430-rebase-recreate-merges.sh\n> @@ -143,6 +143,29 @@ test_expect_success 'with a branch tip that was \n> cherry-picked already' '\n>  EOF\n> '\n>\n> +test_expect_success 'rebase cousins unless told not to' '\n> + write_script copy-editor.sh <<-\\EOF &&\n> + cp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n> + EOF\n> +\n> + test_config sequence.editor \\\"\"$PWD\"/copy-editor.sh\\\" &&\n> + git checkout -b cousins master &&\n> + before=\"$(git rev-parse --verify HEAD)\" &&\n> + test_tick &&\n> + git rebase -i --recreate-merges=no-rebase-cousins HEAD^ &&\n> + test_cmp_rev HEAD $before &&\n> + test_tick &&\n> + git rebase -i --recreate-merges HEAD^ &&\n> + test_cmp_graph HEAD^.. <<-\\EOF\n> + *   Merge the topic branch '\\''onebranch'\\''\n> + |\\\n> + | * D\n> + | * G\n> + |/\n> + o H\n> + EOF\n> +'\n> +\n> test_expect_success 'refs/rewritten/* is worktree-local' '\n>  git worktree add wt &&\n>  cat >wt/script-from-scratch <<-\\EOF &&\n> -- \n> 2.15.1.windows.2.1430.ga56c4f9e2a9 \n\n"},{"id":"336837","messageId":"32EA49C74ECD470F8EE898D965833E02@PhilipOakley","threadId":"47635","inReplyTo":"CA+P7+xpwYDeNHfzMtHJkKEB8-A+kZ95wBEn0RC5R8pWbgKOmiQ@mail.gmail.com","subject":"Re: [PATCH 10/8] [DO NOT APPLY, but improve?] rebase--interactive: introduce \"stop\" command","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":null,"receivedAt":"2018-01-18T22:08:14Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Jacob Keller\" <jacob.keller@gmail.com>\n> On Thu, Jan 18, 2018 at 10:36 AM, Stefan Beller <sbeller@google.com> \n> wrote:\n>> Jake suggested using \"x false\" instead of \"edit\" for some corner cases.\n>>\n>> I do prefer using \"x false\" for all kinds of things such as stopping\n>> before a commit (edit only let's you stop after a commit), and the\n>> knowledge that \"x false\" does the least amount of actions behind my back.\n>>\n>> We should have that command as well, maybe?\n>>\n>\n>\n> I agree. I use \"x false\" very often, and I think stop is probably a\n> better solution since it avoids spawning an extra shell that will just\n> fail. Not sure if stop implies too much about \"stop the whole thing\"\n> as opposed to \"stop here and let me do something manual\", but I think\n> it's clear enough.\n>\n'hold' or 'pause' maybe options (leads to \nhttp://www.thesaurus.com/browse/put+on+hold offering procastinate etc.)\n'adjourn'.\n\n>\n>> Signed-off-by: Stefan Beller <sbeller@google.com>\n>> ---\n>>  git-rebase--interactive.sh |  1 +\n>>  sequencer.c                | 10 ++++++++++\n>>  2 files changed, 11 insertions(+)\n>>\n>> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n>> index 3cd7446d0b..9eac53f0c5 100644\n>> --- a/git-rebase--interactive.sh\n>> +++ b/git-rebase--interactive.sh\n>> @@ -166,6 +166,7 @@ l, label <label>= label current HEAD with a name\n>>  t, reset <label> = reset HEAD to a label\n>>  b, bud = reset HEAD to the revision labeled 'onto', no arguments\n>>  m, merge [<label-or-commit>]* = create a merge commit using a given \n>> commit's message\n>> +y, stay = stop for  shortcut for\n>>\n>>  These lines can be re-ordered; they are executed from top to bottom.\n>>  \" | git stripspace --comment-lines >>\"$todo\"\n>> diff --git a/sequencer.c b/sequencer.c\n>> index 2b4e6b1232..4b3b9fe59d 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -782,6 +782,7 @@ enum todo_command {\n>>         TODO_RESET,\n>>         TODO_BUD,\n>>         TODO_MERGE,\n>> +       TODO_STOP,\n>>         /* commands that do nothing but are counted for reporting \n>> progress */\n>>         TODO_NOOP,\n>>         TODO_DROP,\n>> @@ -803,6 +804,7 @@ static struct {\n>>         { 'l', \"label\" },\n>>         { 't', \"reset\" },\n>>         { 'b', \"bud\" },\n>> +       { 'y', \"stay\" },\n>>         { 'm', \"merge\" },\n>>         { 0,   \"noop\" },\n>>         { 'd', \"drop\" },\n>> @@ -1307,6 +1309,12 @@ static int parse_insn_line(struct todo_item *item, \n>> const char *bol, char *eol)\n>>                 return 0;\n>>         }\n>>\n>> +       if (item->command == TODO_STOP) {\n>> +               item->commit = NULL;\n>> +               item->arg = \"\";\n>> +               item->arg_len = 0;\n>> +       }\n>> +\n>>         end_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n>>         item->arg = end_of_object_name + strspn(end_of_object_name, \" \n>> \\t\");\n>>         item->arg_len = (int)(eol - item->arg);\n>> @@ -2407,6 +2415,8 @@ static int pick_commits(struct todo_list \n>> *todo_list, struct replay_opts *opts)\n>>                                 /* `current` will be incremented below */\n>>                                 todo_list->current = -1;\n>>                         }\n>> +               } else if (item->command == TODO_STOP) {\n>> +                       todo_list->current = -1;\n>>                 } else if (item->command == TODO_LABEL)\n>>                         res = do_label(item->arg, item->arg_len);\n>>                 else if (item->command == TODO_RESET)\n>> --\n>> 2.16.0.rc1.238.g530d649a79-goog\n>> \n\n"},{"id":"336838","messageId":"CAGZ79kYn5cuuJ6YJSUcEBjpRHJU4fy+PQPB3haF-92cRO87dFQ@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801182254470.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 10/8] [DO NOT APPLY, but improve?] rebase--interactive: introduce \"stop\" command","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-18T22:09:07Z","receivedAt":"2018-01-18T22:09:13Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Thu, Jan 18, 2018 at 2:00 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n\n>> +     TODO_STOP,\n>\n> I see that your original idea was \"stop\", but then you probably realized\n> that there would be no good abbreviation for that, and changed your mind.\n>\n> Personally, I would have called it `break`...\n\nI was looking at a synonym list of stop to find a word that contained a letter\nwhich was not already taken. 'break' would allow for 'a', or 'k', assuming 'bud'\ntakes 'b' (or can that go to 'u'? Are there people out there with muscle memory\non these letters already?)\n\nAny word (of stop, break, stay, control) sounds good to me, though 'break' might\nbe the clearest.\n\n>\n>> @@ -2407,6 +2415,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>>                               /* `current` will be incremented below */\n>>                               todo_list->current = -1;\n>>                       }\n>> +             } else if (item->command == TODO_STOP) {\n>> +                     todo_list->current = -1;\n>\n> That is incorrect, it will most likely write an unexpected `done` file.\n>\n> Did you mean `return 0` instead?\n\nI guess. I did not compile or test the patch, I was merely writing down enough\nto convey the idea, hopefully.\n\nWhile talking about this idea of exploding the number of keywords,\nmaybe we can also have 'abort', which does the same as deleting all lines\n(every time I want to abort I still get shivers if I just drop all\npatches instead\nof aborting, so maybe typing 'abort-and-restore' as the first thing in the file\nwould convey a safer feeling to users?)\n\nThanks for taking these additional considerations into mind while I don't\nreview the actual patches,\n\nStefan\n"},{"id":"336839","messageId":"CA+P7+xpMGkBhBQEmB5akqi2hhCr=VzMbJopNPNbkAbF6sdzPUg@mail.gmail.com","threadId":"47635","inReplyTo":"32EA49C74ECD470F8EE898D965833E02@PhilipOakley","subject":"Re: [PATCH 10/8] [DO NOT APPLY, but improve?] rebase--interactive: introduce \"stop\" command","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-18T22:09:52Z","receivedAt":"2018-01-18T22:10:19Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 18, 2018 at 2:08 PM, Philip Oakley <philipoakley@iee.org> wrote:\n> From: \"Jacob Keller\" <jacob.keller@gmail.com>\n>>\n>> On Thu, Jan 18, 2018 at 10:36 AM, Stefan Beller <sbeller@google.com>\n>> wrote:\n>>>\n>>> Jake suggested using \"x false\" instead of \"edit\" for some corner cases.\n>>>\n>>> I do prefer using \"x false\" for all kinds of things such as stopping\n>>> before a commit (edit only let's you stop after a commit), and the\n>>> knowledge that \"x false\" does the least amount of actions behind my back.\n>>>\n>>> We should have that command as well, maybe?\n>>>\n>>\n>>\n>> I agree. I use \"x false\" very often, and I think stop is probably a\n>> better solution since it avoids spawning an extra shell that will just\n>> fail. Not sure if stop implies too much about \"stop the whole thing\"\n>> as opposed to \"stop here and let me do something manual\", but I think\n>> it's clear enough.\n>>\n> 'hold' or 'pause' maybe options (leads to\n> http://www.thesaurus.com/browse/put+on+hold offering procastinate etc.)\n> 'adjourn'.\n>\n>\n\nI like break, as suggested by Dscho. That also works well for\nabbreviation if we drop the \"bud\" command.\n\nThanks,\nJake\n"},{"id":"336883","messageId":"CAPig+cQbG2s-LrAo9+7C7=dXifbWFJ3SzuNa-QePHDk7egK=jg@mail.gmail.com","threadId":"47635","inReplyTo":"8a91bf2184a3da4c0d5a13ba184813068e51f5c8.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-01-19T08:59:47Z","receivedAt":"2018-01-19T09:00:03Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jan 18, 2018 at 10:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> [...]\n> This commit implements the commands to label, and to reset to, given\n> revisions. The syntax is:\n>\n>         label <name>\n>         reset <name>\n> [...]\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/sequencer.c b/sequencer.c\n> @@ -1919,6 +1936,139 @@ static int do_exec(const char *command_line)\n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> +       [...]\n> +       if (commit_lock_file(&lock) < 0) {\n> +               rollback_lock_file(&lock);\n> +               return error(_(\"failed to finalize '%s'.\"), filename);\n\ns/\\.//\n\n> +       }\n> +\n> +       return 0;\n> +}\n> +\n> +static int do_reset(const char *name, int len)\n> +{\n> +       [...]\n> +       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> +               return -1;\n> +\n> +       for (i = 0; i < len; i++)\n> +               if (isspace(name[i]))\n> +                       len = i;\n\nWhat is the purpose of this loop? I could imagine that it's trying to\nstrip all whitespace from the end of 'name', however, to do that it\nwould iterate backward, not forward. (Or perhaps it's trying to\ntruncate at the first space, but then it would need to invert the\ncondition or use 'break'.) Am I missing something obvious?\n\n> +       read_cache_unmerged();\n> +       if (!fill_tree_descriptor(&desc, &oid)) {\n> +               error(_(\"Failed to find tree of %s.\"), oid_to_hex(&oid));\n\ns/Failed/failed/\ns/\\.//\n\n> +               rollback_lock_file(&lock);\n> +               free((void *)desc.buffer);\n> +               strbuf_release(&ref_name);\n> +               return -1;\n> +       }\n"},{"id":"336884","messageId":"CAPig+cQgNSijA-sW+AGNAHOgnJVdUGaDWjro4KVWO=vefnEPuQ@mail.gmail.com","threadId":"47635","inReplyTo":"647382ac70bfb7035345304a32d08f4e7b51cd40.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-01-19T09:54:38Z","receivedAt":"2018-01-19T09:54:57Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jan 18, 2018 at 10:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> [...]\n> Note: this patch only adds support for recursive merges, to keep things\n> simple. Support for octopus merges will be added later in this patch\n> series, support for merges using strategies other than the recursive\n> merge is left for future contributions.\n\nThe above paragraph...\n\n> The design of the `merge` command as introduced by this patch only\n> supports creating new merge commits with exactly two parents, i.e. it\n> adds no support for octopus merges.\n>\n> We will introduce support for octopus merges in a later commit.\n\nand these two sentences say the same thing. I suppose one or the other\nwas meant to be dropped(?).\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/sequencer.c b/sequencer.c\n> @@ -2069,6 +2077,132 @@ static int do_reset(const char *name, int len)\n> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n> +                   struct replay_opts *opts)\n> +{\n> +       [...]\n> +               if (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n> +                       error_errno(_(\"Could not write '%s'\"),\n\ns/Could/could/\n\n> +               if (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n> +                       error_errno(_(\"Could not write '%s'\"),\n\nDitto.\n\n> +       if (!head_commit) {\n> +               rollback_lock_file(&lock);\n> +               return error(_(\"Cannot merge without a current revision\"));\n\ns/Cannot/cannot/\n"},{"id":"336885","messageId":"CAPig+cSn_X-0iir-cKSd=P8p5QCxrR2qU_GBj7zDbv8zTc+AQw@mail.gmail.com","threadId":"47635","inReplyTo":"b9cb2d915aa4ff564b1960ce0352664b1d26eecb.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 4/8] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-01-19T10:34:20Z","receivedAt":"2018-01-19T10:34:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jan 18, 2018 at 10:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> The sequencer just learned a new commands intended to recreate branch\n\ns/a //\n\n> structure (similar in spirit to --preserve-merges, but with a\n> substantially less-broken design).\n> [...]\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/sequencer.c b/sequencer.c\n> @@ -2785,6 +2787,335 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n> +static const char *label_oid(struct object_id *oid, const char *label,\n> +                            struct label_state *state)\n> +{\n> +       [...]\n> +       } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n> +                   !get_oid_hex(label, &dummy)) ||\n> +                  hashmap_get_from_hash(&state->labels,\n> +                                        strihash(label), label)) {\n> +               /*\n> +                * If the label already exists, or if the label is a valid full\n> +                * OID, we append a dash and a number to make it unique.\n> +                */\n> +               [...]\n> +               for (i = 2; ; i++) {\n\nWhy '2'? Is there some non-obvious significance to this value?\n\n> +                       strbuf_setlen(buf, len);\n> +                       strbuf_addf(buf, \"-%d\", i);\n> +                       if (!hashmap_get_from_hash(&state->labels,\n> +                                                  strihash(buf->buf),\n> +                                                  buf->buf))\n> +                               break;\n> +               }\n> +\n> +static int make_script_with_merges(struct pretty_print_context *pp,\n> +                                  struct rev_info *revs, FILE *out,\n> +                                  unsigned flags)\n> +{\n> +       [...]\n> +               is_octopus = to_merge && to_merge->next;\n> +\n> +               if (is_octopus)\n> +                       BUG(\"Octopus merges not yet supported\");\n\nIs this a situation which the end-user can trigger by specifying a\nmerge with more than two parents? If so, shouldn't this be just a\nnormal error message rather than a (developer) bug message? Or, am I\nmisunderstanding?\n"},{"id":"336886","messageId":"CAPig+cQZTd77oqod8EZbXqroaaYb7oYbXUOW+jWFfRMrOMonPw@mail.gmail.com","threadId":"47635","inReplyTo":"71c42d6d3bb240d90071d5afdde81d1293fdf0ab.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-01-19T10:55:39Z","receivedAt":"2018-01-19T10:55:51Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jan 18, 2018 at 10:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> [...]\n> With this patch, the goodness of the Git garden shears comes to `git\n> rebase -i` itself. Passing the `--recreate-merges` option will generate\n> a todo list that can be understood readily, and where it is obvious\n> how to reorder commits. New branches can be introduced by inserting\n> `label` commands and calling `merge - <label> <oneline>`. And once this\n> mode has become stable and universally accepted, we can deprecate the\n> design mistake that was `--preserve-merges`.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> @@ -900,6 +900,7 @@ fi\n>  if test t != \"$preserve_merges\"\n>  then\n>         git rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n> +               ${recreate_merges:+--recreate-merges} \\\n\nIf the user specifies both --preserve-merges and --recreate-merges, it\nlooks like --preserve-merges will take precedence.\n\nShould git-rebase.sh have a mutual-exclusion check and error out if\nboth are specified?\n\n>                 $revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n>         die \"$(gettext \"Could not generate todo list\")\"\n> diff --git a/git-rebase.sh b/git-rebase.sh\n> @@ -262,6 +264,10 @@ do\n> +       --recreate-merges)\n> +               recreate_merges=t\n> +               test -z \"$interactive_rebase\" && interactive_rebase=implied\n> +               ;;\n>         --preserve-merges)\n>                 preserve_merges=t\n>                 test -z \"$interactive_rebase\" && interactive_rebase=implied\n"},{"id":"336888","messageId":"d069a3c7-198e-81d2-0f35-f934e110c991@philandanna.no-ip.org","threadId":"47635","inReplyTo":"8a91bf2184a3da4c0d5a13ba184813068e51f5c8.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to resettherevision","fromName":"Phillip Wood","fromEmail":"phil@philandanna.no-ip.org","sentAt":null,"receivedAt":"2018-01-19T12:32:58Z","isPatch":true,"sender":{"key":"phil@philandanna.no-ip.org","avatar":null},"body":"\nOn 18/01/18 15:35, Johannes Schindelin wrote:\n> \n> In the upcoming commits, we will teach the sequencer to recreate merges.\n> This will be done in a very different way from the unfortunate design of\n> `git rebase --preserve-merges` (which does not allow for reordering\n> commits, or changing the branch topology).\n> \n> The main idea is to introduce new todo list commands, to support\n> labeling the current revision with a given name, resetting the current\n> revision to a previous state, merging labeled revisions.\n\nI think this would be a great improvement to rebase -i, thanks for\nworking on it.\n\n> This idea was developed in Git for Windows' Git garden shears (that are\n> used to maintain the \"thicket of branches\" on top of upstream Git), and\n> this patch is part of the effort to make it available to a wider\n> audience, as well as to make the entire process more robust (by\n> implementing it in a safe and portable language rather than a Unix shell\n> script).\n> \n> This commit implements the commands to label, and to reset to, given\n> revisions. The syntax is:\n> \n> \tlabel <name>\n> \treset <name>\n\nIf I've understood the code below correctly then reset will clobber\nuntracked files, this is the opposite behaviour to what happens when\ntries to checkout <onto> at the start of a rebase - then it will fail if\nuntracked files would be overwritten.\n\n> As a convenience shortcut, also to improve readability of the generated\n> todo list, a third command is introduced: bud. It simply resets to the\n> \"onto\" revision, i.e. the commit onto which we currently rebase.\n\nI found the whole bud business bewildering at first, reading the other\nreplies it seems I wasn't the only one to be befuddled by it. Having\nseen an example I can see what it's trying to do but I still think it\nadds more confusion than value.\n\n> Internally, the `label <name>` command creates the ref\n> `refs/rewritten/<name>`. This makes it possible to work with the labeled\n> revisions interactively, or in a scripted fashion (e.g. via the todo\n> list command `exec`).\n\nIf a user has two work trees and runs a rebase in each with the same\nlabel name, they'll clobber each other. I'd suggest storing them under\nrefs/rewritten/<branch-name or detached HEAD SHA> instead. If the user\ntries to rebase a second worktree with the same detached HEAD as an\nexisting rebase then refuse to start.\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  git-rebase--interactive.sh |   3 +\n>  sequencer.c                | 181 ++++++++++++++++++++++++++++++++++++++++++++-\n>  2 files changed, 180 insertions(+), 4 deletions(-)\n> \n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index d47bd29593a..3d2cd19d65a 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -162,6 +162,9 @@ s, squash = use commit, but meld into previous commit\n>  f, fixup = like \\\"squash\\\", but discard this commit's log message\n>  x, exec = run command (the rest of the line) using shell\n>  d, drop = remove commit\n> +l, label = label current HEAD with a name\n> +t, reset = reset HEAD to a label\n> +b, bud = reset HEAD to the revision labeled 'onto'\n>  \n>  These lines can be re-ordered; they are executed from top to bottom.\n>  \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 4d3f60594cb..91cc55a002f 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -21,6 +21,8 @@\n>  #include \"log-tree.h\"\n>  #include \"wt-status.h\"\n>  #include \"hashmap.h\"\n> +#include \"unpack-trees.h\"\n> +#include \"worktree.h\"\n>  \n>  #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>  \n> @@ -116,6 +118,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n>  static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n>  static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n>  \t\"rebase-merge/rewritten-pending\")\n> +\n> +/*\n> + * The path of the file listing refs that need to be deleted after the rebase\n> + * finishes. This is used by the `merge` command.\n> + */\n> +static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n> +\n>  /*\n>   * The following files are written by git-rebase just after parsing the\n>   * command-line (and are only consumed, not modified, by the sequencer).\n> @@ -767,6 +776,9 @@ enum todo_command {\n>  \tTODO_SQUASH,\n>  \t/* commands that do something else than handling a single commit */\n>  \tTODO_EXEC,\n> +\tTODO_LABEL,\n> +\tTODO_RESET,\n> +\tTODO_BUD,\n>  \t/* commands that do nothing but are counted for reporting progress */\n>  \tTODO_NOOP,\n>  \tTODO_DROP,\n> @@ -785,6 +797,9 @@ static struct {\n>  \t{ 'f', \"fixup\" },\n>  \t{ 's', \"squash\" },\n>  \t{ 'x', \"exec\" },\n> +\t{ 'l', \"label\" },\n> +\t{ 't', \"reset\" },\n> +\t{ 'b', \"bud\" },\n>  \t{ 0,   \"noop\" },\n>  \t{ 'd', \"drop\" },\n>  \t{ 0,   NULL }\n> @@ -1253,7 +1268,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>  \t\tif (skip_prefix(bol, todo_command_info[i].str, &bol)) {\n>  \t\t\titem->command = i;\n>  \t\t\tbreak;\n> -\t\t} else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {\n> +\t\t} else if ((bol + 1 == eol || bol[1] == ' ') &&\n> +\t\t\t   *bol == todo_command_info[i].c) {\n>  \t\t\tbol++;\n>  \t\t\titem->command = i;\n>  \t\t\tbreak;\n> @@ -1265,7 +1281,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>  \tpadding = strspn(bol, \" \\t\");\n>  \tbol += padding;\n>  \n> -\tif (item->command == TODO_NOOP) {\n> +\tif (item->command == TODO_NOOP || item->command == TODO_BUD) {\n>  \t\tif (bol != eol)\n>  \t\t\treturn error(_(\"%s does not accept arguments: '%s'\"),\n>  \t\t\t\t     command_to_string(item->command), bol);\n> @@ -1279,7 +1295,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>  \t\treturn error(_(\"missing arguments for %s\"),\n>  \t\t\t     command_to_string(item->command));\n>  \n> -\tif (item->command == TODO_EXEC) {\n> +\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n> +\t    item->command == TODO_RESET) {\n>  \t\titem->commit = NULL;\n>  \t\titem->arg = bol;\n>  \t\titem->arg_len = (int)(eol - bol);\n> @@ -1919,6 +1936,139 @@ static int do_exec(const char *command_line)\n>  \treturn status;\n>  }\n>  \n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> +\tva_list ap;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tint fd = hold_lock_file_for_update(&lock, filename, 0);\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\n> +\tif (fd < 0)\n> +\t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n> +\n> +\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n> +\t\treturn error_errno(_(\"could not read '%s'\"), filename);\n> +\tstrbuf_complete(&buf, '\\n');\n> +\tva_start(ap, fmt);\n> +\tstrbuf_vaddf(&buf, fmt, ap);\n> +\tva_end(ap);\n> +\n> +\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n> +\t}\n> +\tif (commit_lock_file(&lock) < 0) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error(_(\"failed to finalize '%s'.\"), filename);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int do_label(const char *name, int len)\n> +{\n> +\tstruct ref_store *refs = get_main_ref_store();\n> +\tstruct ref_transaction *transaction;\n> +\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n> +\tstruct strbuf msg = STRBUF_INIT;\n> +\tint ret = 0;\n> +\tstruct object_id head_oid;\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +\tstrbuf_addf(&msg, \"label '%.*s'\", len, name);\n\nThe other reflog messages below have a (rebase -i) prefix\n\n> +\n> +\ttransaction = ref_store_transaction_begin(refs, &err);\n> +\tif (!transaction ||\n> +\t    get_oid(\"HEAD\", &head_oid) ||\n> +\t    ref_transaction_update(transaction, ref_name.buf, &head_oid, NULL,\n> +\t\t\t\t   0, msg.buf, &err) < 0 ||\n> +\t    ref_transaction_commit(transaction, &err)) {\n> +\t\terror(\"%s\", err.buf);\n\nif get_oid() fails then err is empty so there wont be an message after\nthe 'error: '\n\n> +\t\tret = -1;\n> +\t}\n> +\tref_transaction_free(transaction);\n> +\tstrbuf_release(&err);\n> +\tstrbuf_release(&msg);\n> +\n> +\tif (!ret)\n> +\t\tret = safe_append(rebase_path_refs_to_delete(),\n> +\t\t\t\t  \"%s\\n\", ref_name.buf);\n> +\tstrbuf_release(&ref_name);\n> +\n> +\treturn ret;\n> +}\n> +\n> +static int do_reset(const char *name, int len)\n> +{\n> +\tstruct strbuf ref_name = STRBUF_INIT;\n> +\tstruct object_id oid;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tstruct tree_desc desc;\n> +\tstruct tree *tree;\n> +\tstruct unpack_trees_options opts;\n> +\tint ret = 0, i;\n> +\n> +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> +\t\treturn -1;\n> +\n> +\tfor (i = 0; i < len; i++)\n> +\t\tif (isspace(name[i]))\n> +\t\t\tlen = i;\n\nIf name starts with any white space then I think this effectively\ntruncates name to a bunch of white space which doesn't sound right. I'm\nnot sure how this is being called, but it might be better to clean up\nname when the to-do list is parsed instead.\n\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +\tif (get_oid(ref_name.buf, &oid) &&\n> +\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> +\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n> +\t\trollback_lock_file(&lock);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tmemset(&opts, 0, sizeof(opts));\n> +\topts.head_idx = 1;\n> +\topts.src_index = &the_index;\n> +\topts.dst_index = &the_index;\n> +\topts.fn = oneway_merge;\n> +\topts.merge = 1;\n> +\topts.update = 1;\n> +\topts.reset = 1;\n> +\n> +\tread_cache_unmerged();\n> +\tif (!fill_tree_descriptor(&desc, &oid)) {\n> +\t\terror(_(\"Failed to find tree of %s.\"), oid_to_hex(&oid));\n> +\t\trollback_lock_file(&lock);\n> +\t\tfree((void *)desc.buffer);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (unpack_trees(1, &desc, &opts)) {\n> +\t\trollback_lock_file(&lock);\n> +\t\tfree((void *)desc.buffer);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttree = parse_tree_indirect(&oid);\n> +\tprime_cache_tree(&the_index, tree);\n> +\n> +\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n> +\t\tret = error(_(\"could not write index\"));\n> +\tfree((void *)desc.buffer);\n> +\n> +\tif (!ret) {\n> +\t\tstruct strbuf msg = STRBUF_INIT;\n> +\n> +\t\tstrbuf_addf(&msg, \"(rebase -i) reset '%.*s'\", len, name);\n> +\t\tret = update_ref(msg.buf, \"HEAD\", &oid, NULL, 0,\n> +\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n> +\t\tstrbuf_release(&msg);\n> +\t}\n> +\n> +\tstrbuf_release(&ref_name);\n> +\treturn ret;\n> +}\n> +\n>  static int is_final_fixup(struct todo_list *todo_list)\n>  {\n>  \tint i = todo_list->current;\n> @@ -2102,7 +2252,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t\t\t\t/* `current` will be incremented below */\n>  \t\t\t\ttodo_list->current = -1;\n>  \t\t\t}\n> -\t\t} else if (!is_noop(item->command))\n> +\t\t} else if (item->command == TODO_LABEL)\n> +\t\t\tres = do_label(item->arg, item->arg_len);\n> +\t\telse if (item->command == TODO_RESET)\n> +\t\t\tres = do_reset(item->arg, item->arg_len);\n> +\t\telse if (item->command == TODO_BUD)\n> +\t\t\tres = do_reset(\"onto\", 4);\n> +\t\telse if (!is_noop(item->command))\n>  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n>  \n>  \t\ttodo_list->current++;\n> @@ -2207,6 +2363,23 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t\t}\n>  \t\tapply_autostash(opts);\n>  \n> +\t\tstrbuf_reset(&buf);\n> +\t\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0)\n> +\t\t    > 0) {\n> +\t\t\tchar *p = buf.buf;\n> +\t\t\twhile (*p) {\n> +\t\t\t\tchar *eol = strchr(p, '\\n');\n> +\t\t\t\tif (eol)\n> +\t\t\t\t\t*eol = '\\0';\n> +\t\t\t\tif (delete_ref(\"(rebase -i) cleanup\",\n> +\t\t\t\t\t       p, NULL, 0) < 0)\n> +\t\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n> +\t\t\t\tif (!eol)\n> +\t\t\t\t\tbreak;\n> +\t\t\t\tp = eol + 1;\n> +\t\t\t}\n> +\t\t}\n> +\n>  \t\tfprintf(stderr, \"Successfully rebased and updated %s.\\n\",\n>  \t\t\thead_ref.buf);\n>  \n> \n\n"},{"id":"336893","messageId":"b3b37af6-4b65-5a44-a395-6f75a4adc98e@talktalk.net","threadId":"47635","inReplyTo":"647382ac70bfb7035345304a32d08f4e7b51cd40.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-01-19T14:45:22Z","receivedAt":"2018-01-19T14:45:44Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 18/01/18 15:35, Johannes Schindelin wrote:\n> \n> This patch is part of the effort to reimplement `--preserve-merges` with\n> a substantially improved design, a design that has been developed in the\n> Git for Windows project to maintain the dozens of Windows-specific patch\n> series on top of upstream Git.\n> \n> The previous patch implemented the `label`, `bud` and `reset` commands\n> to label commits and to reset to a labeled commits. This patch adds the\n> `merge` command, with the following syntax:\n> \n> \tmerge <commit> <rev> <oneline>\n\nI'm concerned that this will be confusing for users. All of the other\nrebase commands replay the changes in the commit hash immediately\nfollowing the command name. This command instead uses the first commit\nto specify the message which is different to both 'git merge' and the\nexisting rebase commands. I wonder if it would be clearer to have 'merge\n-C <commit> <rev> ...' instead so it's clear which argument specifies\nthe message and which the remote head to merge. It would also allow for\n'merge -c <commit> <rev> ...' in the future for rewording an existing\nmerge message and also avoid the slightly odd 'merge - <rev> ...'. Where\nit's creating new merges I'm not sure it's a good idea to encourage\npeople to only have oneline commit messages by making it harder to edit\nthem, perhaps it could take another argument to mean open the editor or\nnot, though as Jake said I guess it's not that common.\n\nOne thought that just struck me - if a merge or reset command specifies\nan invalid label is it rescheduled so that it's still in the to-do list\nwhen the user edits it after rebase stops?\n\nIn the future it might be nice if the label, reset and merge commands\nwere validated when the to-do list is parsed so that the user gets\nimmediate feedback if they try to create a label that is not a valid ref\nname or that they have a typo in a name given to reset or merge rather\nthan the rebase stopping later.\n\n> The <commit> parameter in this instance is the *original* merge commit,\n> whose author and message will be used for the to-be-created merge\n> commit.\n> \n> The <rev> parameter refers to the (possibly rewritten) revision to\n> merge. Let's see an example of a todo list:\n> \n> \tlabel onto\n> \n> \t# Branch abc\n> \tbud\n> \tpick deadbeef Hello, world!\n> \tlabel abc\n> \n> \tbud\n> \tpick cafecafe And now for something completely different\n> \tmerge baaabaaa abc Merge the branch 'abc' into master\n> \n> To support creating *new* merges, i.e. without copying the commit\n> message from an existing commit, use the special value `-` as <commit>\n> parameter (in which case the text after the <rev> parameter is used as\n> commit message):\n> \n> \tmerge - abc This will be the actual commit message of the merge\n> \n> This comes in handy when splitting a branch into two or more branches.\n> \n> Note: this patch only adds support for recursive merges, to keep things\n> simple. Support for octopus merges will be added later in this patch\n> series, support for merges using strategies other than the recursive\n> merge is left for future contributions.\n> \n> The design of the `merge` command as introduced by this patch only\n> supports creating new merge commits with exactly two parents, i.e. it\n> adds no support for octopus merges.\n> \n> We will introduce support for octopus merges in a later commit.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  git-rebase--interactive.sh |   1 +\n>  sequencer.c                | 146 +++++++++++++++++++++++++++++++++++++++++++--\n>  2 files changed, 143 insertions(+), 4 deletions(-)\n> \n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index 3d2cd19d65a..5bf1ea3781f 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -165,6 +165,7 @@ d, drop = remove commit\n>  l, label = label current HEAD with a name\n>  t, reset = reset HEAD to a label\n>  b, bud = reset HEAD to the revision labeled 'onto'\n> +m, merge = create a merge commit using a given commit's message\n>  \n>  These lines can be re-ordered; they are executed from top to bottom.\n>  \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 91cc55a002f..567cfcbbe8b 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -779,6 +779,7 @@ enum todo_command {\n>  \tTODO_LABEL,\n>  \tTODO_RESET,\n>  \tTODO_BUD,\n> +\tTODO_MERGE,\n>  \t/* commands that do nothing but are counted for reporting progress */\n>  \tTODO_NOOP,\n>  \tTODO_DROP,\n> @@ -800,6 +801,7 @@ static struct {\n>  \t{ 'l', \"label\" },\n>  \t{ 't', \"reset\" },\n>  \t{ 'b', \"bud\" },\n> +\t{ 'm', \"merge\" },\n>  \t{ 0,   \"noop\" },\n>  \t{ 'd', \"drop\" },\n>  \t{ 0,   NULL }\n> @@ -1304,14 +1306,20 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>  \t}\n>  \n>  \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n> +\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n> +\titem->arg_len = (int)(eol - item->arg);\n> +\n>  \tsaved = *end_of_object_name;\n> +\tif (item->command == TODO_MERGE && *bol == '-' &&\n> +\t    bol + 1 == end_of_object_name) {\n> +\t\titem->commit = NULL;\n> +\t\treturn 0;\n> +\t}\n> +\n>  \t*end_of_object_name = '\\0';\n>  \tstatus = get_oid(bol, &commit_oid);\n>  \t*end_of_object_name = saved;\n>  \n> -\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n> -\titem->arg_len = (int)(eol - item->arg);\n> -\n>  \tif (status < 0)\n>  \t\treturn -1;\n>  \n> @@ -2069,6 +2077,132 @@ static int do_reset(const char *name, int len)\n>  \treturn ret;\n>  }\n>  \n> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n> +\t\t    struct replay_opts *opts)\n> +{\n> +\tint merge_arg_len;\n> +\tstruct strbuf ref_name = STRBUF_INIT;\n> +\tstruct commit *head_commit, *merge_commit, *i;\n> +\tstruct commit_list *common, *j, *reversed = NULL;\n> +\tstruct merge_options o;\n> +\tint ret;\n> +\tstatic struct lock_file lock;\n> +\n> +\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n> +\t\tif (isspace(arg[merge_arg_len]))\n> +\t\t\tbreak;\n> +\n> +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> +\t\treturn -1;\n> +\n> +\tif (commit) {\n> +\t\tconst char *message = get_commit_buffer(commit, NULL);\n> +\t\tconst char *body;\n> +\t\tint len;\n> +\n> +\t\tif (!message) {\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n> +\t\t\t\t     oid_to_hex(&commit->object.oid));\n> +\t\t}\n> +\t\twrite_author_script(message);\n> +\t\tfind_commit_subject(message, &body);\n> +\t\tlen = strlen(body);\n> +\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n> +\t\t\terror_errno(_(\"Could not write '%s'\"),\n> +\t\t\t\t    git_path_merge_msg());\n> +\t\t\tunuse_commit_buffer(commit, message);\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tunuse_commit_buffer(commit, message);\n> +\t} else {\n> +\t\tconst char *p = arg + merge_arg_len;\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tint len;\n> +\n> +\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n> +\t\twrite_author_script(buf.buf);\n> +\t\tstrbuf_reset(&buf);\n> +\n> +\t\tp += strspn(p, \" \\t\");\n> +\t\tif (*p)\n> +\t\t\tlen = strlen(p);\n> +\t\telse {\n> +\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n> +\t\t\t\t    merge_arg_len, arg);\n> +\t\t\tp = buf.buf;\n> +\t\t\tlen = buf.len;\n> +\t\t}\n> +\n> +\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n> +\t\t\terror_errno(_(\"Could not write '%s'\"),\n> +\t\t\t\t    git_path_merge_msg());\n> +\t\t\tstrbuf_release(&buf);\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tstrbuf_release(&buf);\n> +\t}\n> +\n> +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n> +\tif (!head_commit) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error(_(\"Cannot merge without a current revision\"));\n> +\t}\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n> +\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> +\tif (!merge_commit) {\n> +\t\t/* fall back to non-rewritten ref or commit */\n> +\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n> +\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> +\t}\n> +\tif (!merge_commit) {\n> +\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn -1;\n> +\t}\n> +\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n> +\t\t      git_path_merge_head(), 0);\n> +\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n> +\n> +\tcommon = get_merge_bases(head_commit, merge_commit);\n> +\tfor (j = common; j; j = j->next)\n> +\t\tcommit_list_insert(j->item, &reversed);\n> +\tfree_commit_list(common);\n> +\n> +\tread_cache();\n> +\tinit_merge_options(&o);\n> +\to.branch1 = \"HEAD\";\n> +\to.branch2 = ref_name.buf;\n> +\to.buffer_output = 2;\n> +\n> +\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n> +\tif (ret <= 0)\n> +\t\tfputs(o.obuf.buf, stdout);\n> +\tstrbuf_release(&o.obuf);\n> +\tif (ret < 0) {\n> +\t\tstrbuf_release(&ref_name);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n> +\t\t\t     merge_arg_len, arg);\n> +\t}\n> +\n> +\tif (active_cache_changed &&\n> +\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn error(_(\"merge: Unable to write new index file\"));\n> +\t}\n> +\trollback_lock_file(&lock);\n> +\n> +\tret = run_git_commit(git_path_merge_msg(), opts, 0);\n> +\tstrbuf_release(&ref_name);\n> +\n> +\treturn ret;\n> +}\n> +\n>  static int is_final_fixup(struct todo_list *todo_list)\n>  {\n>  \tint i = todo_list->current;\n> @@ -2258,6 +2392,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t\t\tres = do_reset(item->arg, item->arg_len);\n>  \t\telse if (item->command == TODO_BUD)\n>  \t\t\tres = do_reset(\"onto\", 4);\n> +\t\telse if (item->command == TODO_MERGE)\n> +\t\t\tres = do_merge(item->commit,\n> +\t\t\t\t       item->arg, item->arg_len, opts);\n>  \t\telse if (!is_noop(item->command))\n>  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n>  \n> @@ -2757,7 +2894,8 @@ int transform_todos(unsigned flags)\n>  \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n>  \n>  \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n> -\t\t}\n> +\t\t} else if (item->command == TODO_MERGE)\n> +\t\t\tstrbuf_addstr(&buf, \" -\");\n>  \t\t/* add all the rest */\n>  \t\tif (!item->arg_len)\n>  \t\t\tstrbuf_addch(&buf, '\\n');\n> \n\n"},{"id":"336894","messageId":"7e6906c9-d642-ee8d-82fd-29ee6c60e308@talktalk.net","threadId":"47635","inReplyTo":"9878bd57cc82daf08309943305460c1e8a050518.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 3/8] sequencer: fast-forward merge commits, if possible","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-01-19T14:53:09Z","receivedAt":"2018-01-19T14:53:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 18/01/18 15:35, Johannes Schindelin wrote:\n> \n> Just like with regular `pick` commands, if we are trying to recreate a\n> merge commit, we now test whether the parents of said commit match HEAD\n> and the commits to be merged, and fast-forward if possible.\n> \n> This is not only faster, but also avoids unnecessary proliferation of\n> new objects.\n\nI might have missed something but shouldn't this be checking opts->allow_ff?\n\nAnother possible optimization is that if the parent branches have only\nreworded commits or some commits that have been squashed but no other\nchanges then their trees will be the same as in the original merge\ncommit and so could be reused without calling merge_recursive().\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  sequencer.c | 21 ++++++++++++++++++++-\n>  1 file changed, 20 insertions(+), 1 deletion(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index 567cfcbbe8b..a96255426e7 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -2085,7 +2085,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n>  \tstruct commit *head_commit, *merge_commit, *i;\n>  \tstruct commit_list *common, *j, *reversed = NULL;\n>  \tstruct merge_options o;\n> -\tint ret;\n> +\tint can_fast_forward, ret;\n>  \tstatic struct lock_file lock;\n>  \n>  \tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n> @@ -2151,6 +2151,14 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n>  \t\treturn error(_(\"Cannot merge without a current revision\"));\n>  \t}\n>  \n> +\t/*\n> +\t * If HEAD is not identical to the parent of the original merge commit,\n> +\t * we cannot fast-forward.\n> +\t */\n> +\tcan_fast_forward = commit && commit->parents &&\n> +\t\t!oidcmp(&commit->parents->item->object.oid,\n> +\t\t\t&head_commit->object.oid);\n> +\n>  \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n>  \tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n>  \tif (!merge_commit) {\n> @@ -2164,6 +2172,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n>  \t\trollback_lock_file(&lock);\n>  \t\treturn -1;\n>  \t}\n> +\n> +\tif (can_fast_forward && commit->parents->next &&\n> +\t    !commit->parents->next->next &&\n> +\t    !oidcmp(&commit->parents->next->item->object.oid,\n> +\t\t    &merge_commit->object.oid)) {\n> +\t\tstrbuf_release(&ref_name);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn fast_forward_to(&commit->object.oid,\n> +\t\t\t\t       &head_commit->object.oid, 0, opts);\n> +\t}\n> +\n>  \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n>  \t\t      git_path_merge_head(), 0);\n>  \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n> \n\n"},{"id":"336922","messageId":"229b332d-56b8-0183-f57b-b0db0c62f781@talktalk.net","threadId":"47635","inReplyTo":"d069a3c7-198e-81d2-0f35-f934e110c991@philandanna.no-ip.org","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to resettherevision","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-01-19T18:55:01Z","receivedAt":"2018-01-19T18:55:21Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 19/01/18 12:24, Phillip Wood wrote:\n> \n> On 18/01/18 15:35, Johannes Schindelin wrote:\n>>\n>> Internally, the `label <name>` command creates the ref\n>> `refs/rewritten/<name>`. This makes it possible to work with the labeled\n>> revisions interactively, or in a scripted fashion (e.g. via the todo\n>> list command `exec`).\n> \n> If a user has two work trees and runs a rebase in each with the same\n> label name, they'll clobber each other. I'd suggest storing them under\n> refs/rewritten/<branch-name or detached HEAD SHA> instead. If the user\n> tries to rebase a second worktree with the same detached HEAD as an\n> existing rebase then refuse to start.\n> \n\nAh this isn't a concern after all as patch 5 makes refs/rewritten local\nto the worktree. Perhaps you could move that part of patch 5 here or add\na note to the commit message that it will become worktree local later in\nthe series\n\nBest Wishes\n\nPhillip\n"},{"id":"336923","messageId":"CA+P7+xpj503VKEf4jzYdbk33aAvaMQ8+T1bQ3EB0T+AWEU3pKA@mail.gmail.com","threadId":"47635","inReplyTo":"229b332d-56b8-0183-f57b-b0db0c62f781@talktalk.net","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to resettherevision","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-19T18:59:07Z","receivedAt":"2018-01-19T18:59:33Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Fri, Jan 19, 2018 at 10:55 AM, Phillip Wood\n<phillip.wood@talktalk.net> wrote:\n> On 19/01/18 12:24, Phillip Wood wrote:\n>>\n>> On 18/01/18 15:35, Johannes Schindelin wrote:\n>>>\n>>> Internally, the `label <name>` command creates the ref\n>>> `refs/rewritten/<name>`. This makes it possible to work with the labeled\n>>> revisions interactively, or in a scripted fashion (e.g. via the todo\n>>> list command `exec`).\n>>\n>> If a user has two work trees and runs a rebase in each with the same\n>> label name, they'll clobber each other. I'd suggest storing them under\n>> refs/rewritten/<branch-name or detached HEAD SHA> instead. If the user\n>> tries to rebase a second worktree with the same detached HEAD as an\n>> existing rebase then refuse to start.\n>>\n>\n> Ah this isn't a concern after all as patch 5 makes refs/rewritten local\n> to the worktree. Perhaps you could move that part of patch 5 here or add\n> a note to the commit message that it will become worktree local later in\n> the series\n>\n> Best Wishes\n>\n> Phillip\n\nI'd rather it be included here as well.\n\nThanks,\nJake\n"},{"id":"336931","messageId":"xmqqmv19ppen.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 0/8] rebase -i: offer to recreate merge commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-19T20:25:04Z","receivedAt":"2018-01-19T20:25:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Think of --recreate-merges as \"--preserve-merges done right\". It\n> introduces new verbs for the todo list, `label`, `reset` and `merge`.\n> For a commit topology like this:\n>\n>             A - B - C\n>               \\   /\n>                 D\n>\n> the generated todo list would look like this:\n>\n>             # branch D\n>             pick 0123 A\n>             label branch-point\n>             pick 1234 D\n>             label D\n>\n>             reset branch-point\n>             pick 2345 B\n>             merge 3456 D C\n\nYup.  I've seen this design talked about on list in the past, and\nI've always felt that this is \"sequencer done right\".\n\nAt the first glance, it may feel somewhat unsatisfying that \"merge\"\nhas to say effects of which commits should be reflected in the\nresult and which commot to take the log message from, i.e.\n(recreated)D is merged to form the resulting tree, and 3456=C is\nused for the log, to recreate C in the above example, while \"pick\"\nalways uses the same commit for both, i.e. recreated B inherits both\nthe changes and log message from the original B=2345 (or depending\non the readers' point of view, \"merge\" is allowed to use two\ndifferent commits, while \"pick\" is always limited to the same one).\n\nBut I think this distinction is probably fundamental and I am not\nopposed to it at all.  The result of \"pick\" has only one parent, and\nthe parent is determined only by the previous actions and not by\nanything on the \"pick\" line in the todo list.  But the result of\n\"merge\" has to record all the other parents, and only the first\nparent is determined implicitly by the previous actions.  We need to\ntell the \"merge\" command about \"3456=C\" in order to recreate the\neffect of original merge commit (i.e. changes between B and C) as\nwell as its log message, and we also need to tell it about label \"D\"\nthat it is the \"other parent\" that need to be recorded.\n\nObviously \"merge\" command syntax should allow recreating an octopus,\nso whenever I said \"two\" in the above, I meant \"N\".  The original\nmerge commit is needed so that the effect to replay (roughly: a\npatch going to the original merge result from its first parent) can\nbe learned from the existing history, and all the other \"N-1\"\nparents needs to be given (and they must have been already created\nin the todo list) so that the resulting recreated merge can be\nrecorded with them as parents (in addition to the first parent that\nis implicitly given as the result of all the previous steps).\n\nOne interesting (and probably useful) thing to notice is that if A\nwere not rebased in the above sample picture, and only B were the\none that was tweaked, then a recreated C may use the same original D\nas its side parent, and the mechanism outlined above naturally can\nsupport it by allowing an un-rewritten commit to be given as a side\nparent when \"merge\" is redoing C.\n\nI probably won't have time to actually look at the code for a few\ndays, but I am reasonably excited about the topic ;-)\n\nThanks.\n"},{"id":"336933","messageId":"xmqqinbxpp5m.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801182233480.31@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 9/8] [DO NOT APPLY, but squash?] git-rebase--interactive: clarify arguments","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-19T20:30:29Z","receivedAt":"2018-01-19T20:30:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Good idea! I would rather do it as an introductory patch (that only\n> converts the existing list).\n>\n> As to `merge`: it is a bit more complicated ;-)\n>\n> \tm, merge <original-merge-commit> ( <label> | \"<label>...\" ) [<oneline>]\n> \t\tcreate a merge commit using the original merge commit's\n> \t\tmessage (or the oneline, if \"-\" is given). Use a quoted\n> \t\tlist of commits to be merged for octopus merges.\n\nIs it just the message that is being reused?  \n\nAren't the trees of the original commit and its parents participate\nin creating the tree of the recreated merge?  One way to preserve an\noriginally evil merge is to notice how it was made by taking the\ndifference between the result of mechanical merge of original merge\nparents and the original merge result, and carry it forward when\nrecreating the merge across new parents.  Just being curious.\n\n"},{"id":"336967","messageId":"CAPig+cRcj7nDv1+nmzw5WiOBLPB00EBv-tz=6sLAzM3iXLKvFw@mail.gmail.com","threadId":"47635","inReplyTo":"e3c3c703f8eae723c4fbbd08d2c329d81179dbee.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 8/8] rebase -i: introduce --recreate-merges=no-rebase-cousins","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-01-20T01:09:18Z","receivedAt":"2018-01-20T01:11:31Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jan 18, 2018 at 10:36 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This one is a bit tricky to explain, so let's try with a diagram:\n> [...]\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\n> @@ -57,8 +59,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n> +       if (no_rebase_cousins >= 0&& !recreate_merges)\n\nStyle: space before &&\n\n> +               warning(_(\"--[no-]rebase-cousins has no effect without \"\n> +                         \"--recreate-merges\"));\n"},{"id":"336974","messageId":"CA+P7+xoO4twSgYVNJ4WaTj80WHS_ViEOBOEVST1nX9UzRA4JxQ@mail.gmail.com","threadId":"47635","inReplyTo":"xmqqinbxpp5m.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 9/8] [DO NOT APPLY, but squash?] git-rebase--interactive: clarify arguments","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-20T09:14:35Z","receivedAt":"2018-01-20T09:15:05Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Fri, Jan 19, 2018 at 12:30 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n>> Good idea! I would rather do it as an introductory patch (that only\n>> converts the existing list).\n>>\n>> As to `merge`: it is a bit more complicated ;-)\n>>\n>>       m, merge <original-merge-commit> ( <label> | \"<label>...\" ) [<oneline>]\n>>               create a merge commit using the original merge commit's\n>>               message (or the oneline, if \"-\" is given). Use a quoted\n>>               list of commits to be merged for octopus merges.\n>\n> Is it just the message that is being reused?\n>\n> Aren't the trees of the original commit and its parents participate\n> in creating the tree of the recreated merge?  One way to preserve an\n> originally evil merge is to notice how it was made by taking the\n> difference between the result of mechanical merge of original merge\n> parents and the original merge result, and carry it forward when\n> recreating the merge across new parents.  Just being curious.\n>\n\nIt looks like currently that only the commit is kept, with no attempt\nto recreate evil merges.\n\nThanks,\nJake\n"},{"id":"336975","messageId":"CA+P7+xr4KtR4q8Y=-+pv2TzvP009zRVR6a_zh2GOZXt_LXrFOg@mail.gmail.com","threadId":"47635","inReplyTo":"b3b37af6-4b65-5a44-a395-6f75a4adc98e@talktalk.net","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-01-20T09:18:59Z","receivedAt":"2018-01-20T09:19:28Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Fri, Jan 19, 2018 at 6:45 AM, Phillip Wood <phillip.wood@talktalk.net> wrote:\n> On 18/01/18 15:35, Johannes Schindelin wrote:\n>>\n>> This patch is part of the effort to reimplement `--preserve-merges` with\n>> a substantially improved design, a design that has been developed in the\n>> Git for Windows project to maintain the dozens of Windows-specific patch\n>> series on top of upstream Git.\n>>\n>> The previous patch implemented the `label`, `bud` and `reset` commands\n>> to label commits and to reset to a labeled commits. This patch adds the\n>> `merge` command, with the following syntax:\n>>\n>>       merge <commit> <rev> <oneline>\n>\n> I'm concerned that this will be confusing for users. All of the other\n> rebase commands replay the changes in the commit hash immediately\n> following the command name. This command instead uses the first commit\n> to specify the message which is different to both 'git merge' and the\n> existing rebase commands. I wonder if it would be clearer to have 'merge\n> -C <commit> <rev> ...' instead so it's clear which argument specifies\n> the message and which the remote head to merge. It would also allow for\n> 'merge -c <commit> <rev> ...' in the future for rewording an existing\n> merge message and also avoid the slightly odd 'merge - <rev> ...'. Where\n> it's creating new merges I'm not sure it's a good idea to encourage\n> people to only have oneline commit messages by making it harder to edit\n> them, perhaps it could take another argument to mean open the editor or\n> not, though as Jake said I guess it's not that common.\n\nI actually like the idea of re-using commit message options like -C,\n-c,  and -m, so we could do:\n\nmerge -C <commit> ... to take message from commit\nmerge -c <commit> ...  to take the message from commit and open editor to edit\nmerge -m \"<message>\" ... to take the message from the quoted test\nmerge ... to merge and open commit editor with default message\n\nThis also, I think, allows us to not need to put the oneline on the\nend, meaning we wouldn't have to quote the parent commit arguments\nsince we could use option semantics?\n\n>\n> One thought that just struck me - if a merge or reset command specifies\n> an invalid label is it rescheduled so that it's still in the to-do list\n> when the user edits it after rebase stops?\n>\n> In the future it might be nice if the label, reset and merge commands\n> were validated when the to-do list is parsed so that the user gets\n> immediate feedback if they try to create a label that is not a valid ref\n> name or that they have a typo in a name given to reset or merge rather\n> than the rebase stopping later.\n>\n"},{"id":"337106","messageId":"xmqqwp09lh6b.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"CA+P7+xozcQD2xuys6mh8MsfcYZ_nb2c9yxhDxkc7FTN2SfoofQ@mail.gmail.com","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-22T21:25:32Z","receivedAt":"2018-01-22T21:25:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.keller@gmail.com> writes:\n\n> The code looks good, but I'm a little wary of adding bud which\n> hard-codes a specific label. I suppose it does grant a bit of\n> readability to the resulting script... ? It doesn't seem that\n> important compared to use using \"reset onto\"? At least when\n> documenting this it should be made clear that the \"onto\" label is\n> special.\n\nI do not think we would mind \"bud\" too much in the end result, but\nthe change in 1/8 is made harder to read than necessary with it.  It\nis the only thing that needs \"a single-letter command name may now\nnot have any argument after it\" change to the parser among the three\nthings being added here, and it also needs to be added to the list\nof special commands without arguments.\n\nIt would have been easier to reason about if addition of \"bud\" was\nin its own patch done after label and reset are added.  And if done\nas a separate step, perhaps it would have been easier to realize\nthat it would be a more future-proof solution for handling the\n\"special\" ness of BUD to add a new \"unsigned flags\" word to\ntodo_command_info[] structure and using a bit that says \"this does\nnot take an arg\" than to hardcode \"noop and bud are the commands\nwithout args\" in the code.  That hardcode was good enough when there\nwas only one thing in that special case.  Now it has two.\n\nIn a similar way, the code to special case label and reset just like\nexec may also want to become more table driven, perhaps using\nanother bit in the same new flags word to say \"this does not refer\nto commit\".  I think that can become [v2 1/N] while addition of \"bud\"\ncan be [v2 2/N] (after all, \"bud\" just does the same do_reset() with\nhardcoded argument, so \"label/reset\" must come first).\n\n\n\n\n"},{"id":"337108","messageId":"xmqqpo61lezj.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"647382ac70bfb7035345304a32d08f4e7b51cd40.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-22T22:12:48Z","receivedAt":"2018-01-22T22:12:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n>  \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n> +\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n> +\titem->arg_len = (int)(eol - item->arg);\n> +\n>  \tsaved = *end_of_object_name;\n> +\tif (item->command == TODO_MERGE && *bol == '-' &&\n> +\t    bol + 1 == end_of_object_name) {\n> +\t\titem->commit = NULL;\n> +\t\treturn 0;\n> +\t}\n> +\n>  \t*end_of_object_name = '\\0';\n>  \tstatus = get_oid(bol, &commit_oid);\n>  \t*end_of_object_name = saved;\n>  \n> -\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n> -\titem->arg_len = (int)(eol - item->arg);\n> -\n\nAssigning to \"saved\" before the added \"if we are doing merge and see\n'-', do this special thing\" is not only unnecessary, but makes the\nlogic in the non-special case harder to read.  The four things\n\"saved = *eol; *eol = 0; do_thing_using(bol); *eol = saved;\" is a\nsingle logical unit; keep them together.\n\nThis hunk may have been the most expedient way to coax \"-\" into the\nlocation where a commit object name is expected; it looks ugly, but\nfor the limited purpose of this series it should do.\n\n> @@ -2069,6 +2077,132 @@ static int do_reset(const char *name, int len)\n>  \treturn ret;\n>  }\n>  \n> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n> +\t\t    struct replay_opts *opts)\n> +{\n> +\tint merge_arg_len;\n> +\tstruct strbuf ref_name = STRBUF_INIT;\n> +\tstruct commit *head_commit, *merge_commit, *i;\n> +\tstruct commit_list *common, *j, *reversed = NULL;\n> +\tstruct merge_options o;\n> +\tint ret;\n> +\tstatic struct lock_file lock;\n> +\n> +\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n> +\t\tif (isspace(arg[merge_arg_len]))\n> +\t\t\tbreak;\n\nMental note: this scans for a whitespace, and tab is accepted\ninstead of SP, which presumably is to allow human typed string.\n\n> +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> +\t\treturn -1;\n> +\n> +\tif (commit) {\n> +\t\tconst char *message = get_commit_buffer(commit, NULL);\n> +\t\tconst char *body;\n> +\t\tint len;\n> +\n> +\t\tif (!message) {\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n> +\t\t\t\t     oid_to_hex(&commit->object.oid));\n> +\t\t}\n> +\t\twrite_author_script(message);\n> +\t\tfind_commit_subject(message, &body);\n> +\t\tlen = strlen(body);\n> +\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n> +\t\t\terror_errno(_(\"Could not write '%s'\"),\n> +\t\t\t\t    git_path_merge_msg());\n> +\t\t\tunuse_commit_buffer(commit, message);\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tunuse_commit_buffer(commit, message);\n> +\t} else {\n> +\t\tconst char *p = arg + merge_arg_len;\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tint len;\n> +\n> +\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n> +\t\twrite_author_script(buf.buf);\n> +\t\tstrbuf_reset(&buf);\n> +\n> +\t\tp += strspn(p, \" \\t\");\n\n... and this matches the above mental note.  It allows consecutive\nwhitespaces as a separator, which is sensible behaviour.\n\n> +\t\tif (*p)\n> +\t\t\tlen = strlen(p);\n> +\t\telse {\n> +\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n> +\t\t\t\t    merge_arg_len, arg);\n> +\t\t\tp = buf.buf;\n> +\t\t\tlen = buf.len;\n> +\t\t}\n\nSo... \"arg\" received by this function can be a single non-whitespace\ntoken, which is taken as the name of the branch being merged (in\nthis else clause).  Or it can also be followed by a single liner\nmessage for the merge commit.  Presumably, this is for creating a\nnew merge (i.e. \"commit==NULL\" case), and preparing a proper log\nmessage in the todo list is unrealistic, so this would be a\nreasonable compromise.  Those users who want to write proper log\nmessage could presumably follow such \"merge\" insn with a \"x git\ncommit --amend\" or something, I presume, if they really wanted to.\n\n> +\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n> +\t\t\terror_errno(_(\"Could not write '%s'\"),\n> +\t\t\t\t    git_path_merge_msg());\n> +\t\t\tstrbuf_release(&buf);\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tstrbuf_release(&buf);\n> +\t}\n\nOK.  Now we have prepared the MERGE_MSG file and are ready to commit.\n\n> +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n> +\tif (!head_commit) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error(_(\"Cannot merge without a current revision\"));\n> +\t}\n\nHmph, I would have expected to see this a lot earlier, before\ndealing with the log message.  Leftover MERGE_MSG file after an\nerror will cause unexpected fallout to the end-user experience\n(including what is shown by the shell prompt scripts), but if we do\nthis before the MERGE_MSG thing, we do not have to worry about\nerror codepath having to remove it.\n\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n> +\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> +\tif (!merge_commit) {\n> +\t\t/* fall back to non-rewritten ref or commit */\n> +\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n> +\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> +\t}\n\nOK, so \"abc\" in the example in the log message is looked up first as\na label and then we take a fallback to interpret as an object name.\n\nHopefully allowed names in \"label\" would be documented clearly in\nlater steps (I am guessing that \"a name that can be used as a branch\nname can be used as a label name and vice versa\" or something like\nthat).\n\n> +\tif (!merge_commit) {\n> +\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn -1;\n> +\t}\n> +\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n> +\t\t      git_path_merge_head(), 0);\n> +\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n\nThese two calls gave me a \"Huh?\" moment; write_message() sounds like\nit is allowed to be later updated with frills suitable for *_MSG\nfiles we place in .git/ directory (iow, it is in principle OK if\ncommented out instructions common to these files are added to the\noutput by the function), but these want exact bytes passed in the\nresult, for which wrapper.c::write_file() is more appropriate.\n\nAlternatively, perhaps write_message() can be dropped and its\ncallers can call wrapper.c::write_file() instead?  Such a clean-up\nmay require teaching the append-eol thing that write_message() wants\nto wrapper.c::write_file(), but it shouldn't be a rocket science.\n\n> +\tcommon = get_merge_bases(head_commit, merge_commit);\n> +\tfor (j = common; j; j = j->next)\n> +\t\tcommit_list_insert(j->item, &reversed);\n> +\tfree_commit_list(common);\n\nI know this is copy&pasted code from \"builtin/merge.c\", but is there\na reason to reverse the common ancestor list here?\n\n> +\tread_cache();\n> +\tinit_merge_options(&o);\n> +\to.branch1 = \"HEAD\";\n> +\to.branch2 = ref_name.buf;\n> +\to.buffer_output = 2;\n> +\n> +\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n> +\tif (ret <= 0)\n> +\t\tfputs(o.obuf.buf, stdout);\n> +...\n\nOther than these minor nits, looks quite promising.  Nicely done.\n"},{"id":"337174","messageId":"xmqqvafsh0hu.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"9878bd57cc82daf08309943305460c1e8a050518.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 3/8] sequencer: fast-forward merge commits, if possible","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-23T18:51:41Z","receivedAt":"2018-01-23T18:51:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> +\t/*\n> +\t * If HEAD is not identical to the parent of the original merge commit,\n> +\t * we cannot fast-forward.\n> +\t */\n> +\tcan_fast_forward = commit && commit->parents &&\n> +\t\t!oidcmp(&commit->parents->item->object.oid,\n> +\t\t\t&head_commit->object.oid);\n> +\n\nI think this expression and assignment should better be done much\nlater.  Are you going to update commit, commit->parents, etc. that\nare involved in the computation in the meantime???\n\n>  \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n>  \tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n>  \tif (!merge_commit) {\n> @@ -2164,6 +2172,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n>  \t\trollback_lock_file(&lock);\n>  \t\treturn -1;\n>  \t}\n> +\n> +\tif (can_fast_forward && commit->parents->next &&\n> +\t    !commit->parents->next->next &&\n> +\t    !oidcmp(&commit->parents->next->item->object.oid,\n> +\t\t    &merge_commit->object.oid)) {\n\n... Namely, here.  Because the earlier one is computing \"are we\nreplaying exactly the same commit on top of exactly the same\nstate?\", which is merely one half of \"can we fast-forward\", and\nstoring it in a variable whose name is over-promising way before it\nbecomes necessary.  The other half of \"can we fast-forward?\" logic\nis the remainder of the if() condition we see above.  IOW, when\nfully spelled, this code can fast-forward when we are replaying a\ncommit on top of exactly the same first-parent and the commit being\nreplayed is a single parent merge.\n\nWe may even want to get rid of can_fast_forward variable.\n\n> +\t\tstrbuf_release(&ref_name);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn fast_forward_to(&commit->object.oid,\n> +\t\t\t\t       &head_commit->object.oid, 0, opts);\n> +\t}\n> +\n>  \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n>  \t\t      git_path_merge_head(), 0);\n>  \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n"},{"id":"337175","messageId":"xmqqr2qggzie.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"7e6906c9-d642-ee8d-82fd-29ee6c60e308@talktalk.net","subject":"Re: [PATCH 3/8] sequencer: fast-forward merge commits, if possible","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-23T19:12:57Z","receivedAt":"2018-01-23T19:13:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood@talktalk.net> writes:\n\n> On 18/01/18 15:35, Johannes Schindelin wrote:\n>> \n>> Just like with regular `pick` commands, if we are trying to recreate a\n>> merge commit, we now test whether the parents of said commit match HEAD\n>> and the commits to be merged, and fast-forward if possible.\n>> \n>> This is not only faster, but also avoids unnecessary proliferation of\n>> new objects.\n>\n> I might have missed something but shouldn't this be checking opts->allow_ff?\n\nBecause the whole point of this mechanism is to recreate the\ntopology faithfully to the original, even if the original was a\nredundant merge (which has a side parent that is an ancestor or a\ndescendant of the first parent), we should just point at the\noriginal merge when the condition allows it, regardless of\nopts->allow_ff.\n\nI think it is a different matter if an insn to create a new merge\n(i.e. \"merge - <parent> <message>\", not \"merge <commit> <parent>\")\nshould honor opts->allow_ff; because it is not about recreating an\nexisting history but is a way to create what did not exist before,\nI think it is sensible if allow_ff option is honored.\n\n"},{"id":"337176","messageId":"xmqqh8rcgx65.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"b9cb2d915aa4ff564b1960ce0352664b1d26eecb.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 4/8] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-23T20:03:30Z","receivedAt":"2018-01-23T20:03:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> The sequencer just learned a new commands intended to recreate branch\n\ns/a //;\n\n> structure (similar in spirit to --preserve-merges, but with a\n> substantially less-broken design).\n> ...\n> @@ -2785,6 +2787,335 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n>  \tstrbuf_release(&sob);\n>  }\n>  \n> +struct labels_entry {\n> +\tstruct hashmap_entry entry;\n> +\tchar label[FLEX_ARRAY];\n> +};\n> +\n> +static int labels_cmp(const void *fndata, const struct labels_entry *a,\n> +\t\t      const struct labels_entry *b, const void *key)\n> +{\n> +\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n> +}\n\nlabel_oid() accesses state->labels hash using strihash() as the hash\nfunction, but the final comparison between the entries in the same\nhash buckets are done with case sensitivity.  It is unclear to me if\nthat is what was intended, and why.\n\n> +struct string_entry {\n> +\tstruct oidmap_entry entry;\n> +\tchar string[FLEX_ARRAY];\n> +};\n> +\n> +struct label_state {\n> +\tstruct oidmap commit2label;\n> +\tstruct hashmap labels;\n> +\tstruct strbuf buf;\n> +};\n> +\n> +static const char *label_oid(struct object_id *oid, const char *label,\n> +\t\t\t     struct label_state *state)\n> +{\n> +\tstruct labels_entry *labels_entry;\n> +\tstruct string_entry *string_entry;\n> +\tstruct object_id dummy;\n> +\tsize_t len;\n> +\tint i;\n> +\n> +\tstring_entry = oidmap_get(&state->commit2label, oid);\n> +\tif (string_entry)\n> +\t\treturn string_entry->string;\n> +\n> +\t/*\n> +\t * For \"uninteresting\" commits, i.e. commits that are not to be\n> +\t * rebased, and which can therefore not be labeled, we use a unique\n> +\t * abbreviation of the commit name. This is slightly more complicated\n> +\t * than calling find_unique_abbrev() because we also need to make\n> +\t * sure that the abbreviation does not conflict with any other\n> +\t * label.\n> +\t *\n> +\t * We disallow \"interesting\" commits to be labeled by a string that\n> +\t * is a valid full-length hash, to ensure that we always can find an\n> +\t * abbreviation for any uninteresting commit's names that does not\n> +\t * clash with any other label.\n> +\t */\n> +\tif (!label) {\n> +\t\tchar *p;\n> +\n> +\t\tstrbuf_reset(&state->buf);\n> +\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n> +\t\tlabel = p = state->buf.buf;\n> +\n> +\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n> +\n> +\t\t/*\n> +\t\t * We may need to extend the abbreviated hash so that there is\n> +\t\t * no conflicting label.\n> +\t\t */\n> +\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n> +\t\t\tsize_t i = strlen(p) + 1;\n> +\n> +\t\t\toid_to_hex_r(p, oid);\n> +\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n> +\t\t\t\tchar save = p[i];\n> +\t\t\t\tp[i] = '\\0';\n> +\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n> +\t\t\t\t\t\t\t   strihash(p), p))\n> +\t\t\t\t\tbreak;\n> +\t\t\t\tp[i] = save;\n> +\t\t\t}\n> +\t\t}\n\nIf oid->hash required full 40-hex to disambiguate, then\nfind-unique-abbrev would give 40-hex and we'd want the same \"-<num>\"\nsuffix technique employed below to make it consistently unique.  I\nwonder if organizing the function this way ...\n\n\tif (!label)\n\t\tlabel = oid-to-hex(oid);\n\n\tif (label already exists or full oid) {\n\t\tmake it unambiguous;\n\t}\n\n... allows the resulting code easier to understand and manage.\n\nA related tangent.  Does an auto-label given to \"uninteresting\"\ncommit need to be visible to end users?  I doubted it and that is\nwhy I said oid-to-hex in the above, but if it is given to end users,\nuse of find-unique-abbrev-r is perfectly fine.\n\n> +static int make_script_with_merges(struct pretty_print_context *pp,\n> +\t\t\t\t   struct rev_info *revs, FILE *out,\n> +\t\t\t\t   unsigned flags)\n> +{\n> + ...\n> +\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n> +\tconst char *p = abbr ? \"p\" : \"pick\", *l = abbr ? \"l\" : \"label\",\n> +\t\t *t = abbr ? \"t\" : \"reset\", *b = abbr ? \"b\" : \"bud\",\n> +\t\t *m = abbr ? \"m\" : \"merge\";\n\nIt would be easier to understand if these short named variables are\nreserved only for temporary use, not as constants.  It is not too\nmuch to spell \n\n\tfprintf(out, \"%s onto\\n\", cmd_label);\n\nthan\n\n\tfprintf(out, \"%s onto\\n\", l);\n\nand would save readers from head-scratching, wondering where the\nlast assignment to variable \"l\" is.\n\n> +\n> +\toidmap_init(&commit2todo, 0);\n> +\toidmap_init(&state.commit2label, 0);\n> +\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n> +\tstrbuf_init(&state.buf, 32);\n> +\n> +\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n> +\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n> +\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n> +\t\toidcpy(&entry->entry.oid, oid);\n> +\t\toidmap_put(&state.commit2label, entry);\n> +\t}\n> +\n> +\t/*\n> +\t * First phase:\n> +\t * - get onelines for all commits\n> +\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n> +\t * - label all branch tips\n> +\t */\n\nWhen an early part of a branch is merged and then the remaining part\nof the same branch is merged again, \"branch tip\" and \"2nd or later\nparents of merges\" would become different concepts.  The 2nd parent\nof an early merge is not among the branch tips.\n\nFor the purpose of the \"recreate the topology\" algorithm, I am\nimagining that you would need not just the tips but all the 2nd and\nsubsequent parents of merges, and my quick skimming tells me that\nthe following code grabs them correctly.\n"},{"id":"337177","messageId":"xmqqa7x4gwpz.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"CAPig+cSn_X-0iir-cKSd=P8p5QCxrR2qU_GBj7zDbv8zTc+AQw@mail.gmail.com","subject":"Re: [PATCH 4/8] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-23T20:13:12Z","receivedAt":"2018-01-23T20:13:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n>> +               is_octopus = to_merge && to_merge->next;\n>> +\n>> +               if (is_octopus)\n>> +                       BUG(\"Octopus merges not yet supported\");\n>\n> Is this a situation which the end-user can trigger by specifying a\n> merge with more than two parents? If so, shouldn't this be just a\n> normal error message rather than a (developer) bug message? Or, am I\n> misunderstanding?\n\nBUG() is \"we wrote code carefully so that this should not trigger;\nwe do not _expect_ the code to reach here\".  This one is expected to\ntrigger, and I agree with you that it should be die(), if the series\nis meant to be released to the general public in the current form\n(i.e. until the limitation is lifted so that it can handle an\noctopus).\n\nIf the callers are made more careful to check if there is an octopus\ninvolved and reject the request early, then seeing an octopus in\nthis location in a loop will become a BUG().\n"},{"id":"337179","messageId":"xmqq607sgway.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"71c42d6d3bb240d90071d5afdde81d1293fdf0ab.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-23T20:22:13Z","receivedAt":"2018-01-23T20:22:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 8a861c1e0d6..1d061373288 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -368,6 +368,11 @@ The commit list format can be changed by setting the configuration option\n>  rebase.instructionFormat.  A customized instruction format will automatically\n>  have the long commit hash prepended to the format.\n>  \n> +--recreate-merges::\n> +\tRecreate merge commits instead of flattening the history by replaying\n> +\tmerges. Merge conflict resolutions or manual amendments to merge\n> +\tcommits are not preserved.\n> +\n\nIt is sensible to postpone tackling \"evil merges\" in this initial\niteration of the series, and \"manual amendments ... not preserved\"\nis a reasonable thing to document.  But do we want to say a bit more\nabout conflicting merges?  \"conflict resolutions ... not preserved\"\nsounds as if it does not stop and instead record the result with\nconflict markers without even letting rerere to kick in, which\ncertainly is not the impression you wanted to give to the readers.\n\nI am imagining that it will stop and give control back to the end\nuser just like a conflicted \"pick\" would, and allow \"rebase\n--continue\" to record resolution from the working tree, and just\nlike conflicted \"pick\", it would allow rerere() to help end users\nrecall previous resolution.\n\n"},{"id":"337180","messageId":"xmqq1siggw2t.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"CA+P7+xr0FpgTUa9SgkXM1U86i8f0cParNqGkP=KNPOR_EqNHYg@mail.gmail.com","subject":"Re: [PATCH 6/8] sequencer: handle autosquash and post-rewrite for merge commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-23T20:27:06Z","receivedAt":"2018-01-23T20:27:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.keller@gmail.com> writes:\n\n>>  static int is_per_worktree_ref(const char *refname)\n>>  {\n>>         return !strcmp(refname, \"HEAD\") ||\n>> -               starts_with(refname, \"refs/bisect/\");\n>> +               starts_with(refname, \"refs/bisect/\") ||\n>> +               starts_with(refname, \"refs/rewritten/\");\n>>  }\n>\n> Would this part make more sense to move into the commit that\n> introduces writing these refs, or does it only matter once you start\n> this step here?\n\nGood spotting.  I too was wondering about multiple worktrees when I\nsaw the \"label\" thing introduced.  It probably makes sense to move\nthis to that step.\n"},{"id":"337181","messageId":"xmqqwp08fhea.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 0/8] rebase -i: offer to recreate merge commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-23T20:29:33Z","receivedAt":"2018-01-23T20:29:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> My original attempt was --preserve-merges, but that design was so\n> limited that I did not even enable it in interactive mode.\n> ...\n> There are more patches in the pipeline, based on this patch series, but\n> left for later in the interest of reviewable patch series: one mini\n> series to use the sequencer even for `git rebase -i --root`, and another\n> one to add support for octopus merges to --recreate-merges.\n\nI left comments on a handful of them, but I do not think any of them\nspotted a grave design issue to be a show stopper.  Overall, the\nseries was quite a pleasant read, even with those minor nits and\nrooms for improvements.\n\nThanks.\n"},{"id":"337226","messageId":"f1c5e283-d9c9-700d-0484-ec9c6bf76068@talktalk.net","threadId":"47635","inReplyTo":"xmqqr2qggzie.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 3/8] sequencer: fast-forward merge commits, if possible","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-01-24T10:32:43Z","receivedAt":"2018-01-24T10:32:52Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 23/01/18 19:12, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood@talktalk.net> writes:\n> \n>> On 18/01/18 15:35, Johannes Schindelin wrote:\n>>>\n>>> Just like with regular `pick` commands, if we are trying to recreate a\n>>> merge commit, we now test whether the parents of said commit match HEAD\n>>> and the commits to be merged, and fast-forward if possible.\n>>>\n>>> This is not only faster, but also avoids unnecessary proliferation of\n>>> new objects.\n>>\n>> I might have missed something but shouldn't this be checking opts->allow_ff?\n> \n> Because the whole point of this mechanism is to recreate the\n> topology faithfully to the original, even if the original was a\n> redundant merge (which has a side parent that is an ancestor or a\n> descendant of the first parent), we should just point at the\n> original merge when the condition allows it, regardless of\n> opts->allow_ff.\n\nI agree that the merge should be recreated, but I was thinking of \nsomething slightly different. Currently the sequencer uses \nopts->allow_ff to control whether a new commit with the same contents \nshould be created even if the existing one could be reused. So I was \nquerying whether we should recreate the commit when the user run 'git \nrebase --recreate-merges --no-ff' rather than just reusing it. As merges \nalso have another meaning for fast-forward the terminology gets confusing.\n\n> I think it is a different matter if an insn to create a new merge\n> (i.e. \"merge - <parent> <message>\", not \"merge <commit> <parent>\")\n> should honor opts->allow_ff; because it is not about recreating an\n> existing history but is a way to create what did not exist before,\n> I think it is sensible if allow_ff option is honored.\n\nThis is the merge sense of 'fast-forward' not the existing sequencer \nsense, without thinking about it more I'm not sure if one command line \noption for rebase is sufficient to cover both uses.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"337282","messageId":"xmqqlggndra2.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"f1c5e283-d9c9-700d-0484-ec9c6bf76068@talktalk.net","subject":"Re: [PATCH 3/8] sequencer: fast-forward merge commits, if possible","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-24T18:51:17Z","receivedAt":"2018-01-24T18:51:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood@talktalk.net> writes:\n\n> I agree that the merge should be recreated, but I was thinking of\n> something slightly different. Currently the sequencer uses\n> opts->allow_ff to control whether a new commit with the same contents\n> should be created even if the existing one could be reused.\n\nAhh, OK.  I misunderstood what you meant.  Yes, what you said makes\nsense to me.\n"},{"id":"337322","messageId":"xmqqa7x2c3w7.fsf@gitster.mtv.corp.google.com","threadId":"47635","inReplyTo":"CAPig+cQbG2s-LrAo9+7C7=dXifbWFJ3SzuNa-QePHDk7egK=jg@mail.gmail.com","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-24T22:01:44Z","receivedAt":"2018-01-24T22:01:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n>> +static int do_reset(const char *name, int len)\n>> +{\n>> +       [...]\n>> +       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n>> +               return -1;\n>> +\n>> +       for (i = 0; i < len; i++)\n>> +               if (isspace(name[i]))\n>> +                       len = i;\n>\n> What is the purpose of this loop? I could imagine that it's trying to\n> strip all whitespace from the end of 'name', however, to do that it\n> would iterate backward, not forward. (Or perhaps it's trying to\n> truncate at the first space, but then it would need to invert the\n> condition or use 'break'.) Am I missing something obvious?\n\nI must be missing the same thing.  Given that the callers of\ndo_reset(), other than the \"bug\" thing that passes the hard coded\n\"onto\", uses item->arg/item->arg_len which includes everything after\nthe insn word on the line in the todo list, I do suspect that the\nintention is to stop at the first whitespace char to avoid creating\na ref with whitespace in it, i.e. it is a bug that can be fixed with\ns/len = i/break/.\n\nThe code probably should further check the resulting string with\ncheck_ref_format() to detect strange chars and char sequences that\nmake the resulting refname invalid.  For example, you would not want\nto allow a label with two consecutive periods in it.\n\n\n"},{"id":"337668","messageId":"nycvar.QRO.7.76.6.1801291802400.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xoO4twSgYVNJ4WaTj80WHS_ViEOBOEVST1nX9UzRA4JxQ@mail.gmail.com","subject":"Re: [PATCH 9/8] [DO NOT APPLY, but squash?] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T17:02:56Z","receivedAt":"2018-01-29T17:03:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 20 Jan 2018, Jacob Keller wrote:\n\n> On Fri, Jan 19, 2018 at 12:30 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >\n> >> Good idea! I would rather do it as an introductory patch (that only\n> >> converts the existing list).\n> >>\n> >> As to `merge`: it is a bit more complicated ;-)\n> >>\n> >>       m, merge <original-merge-commit> ( <label> | \"<label>...\" ) [<oneline>]\n> >>               create a merge commit using the original merge commit's\n> >>               message (or the oneline, if \"-\" is given). Use a quoted\n> >>               list of commits to be merged for octopus merges.\n> >\n> > Is it just the message that is being reused?\n> >\n> > Aren't the trees of the original commit and its parents participate\n> > in creating the tree of the recreated merge?  One way to preserve an\n> > originally evil merge is to notice how it was made by taking the\n> > difference between the result of mechanical merge of original merge\n> > parents and the original merge result, and carry it forward when\n> > recreating the merge across new parents.  Just being curious.\n> >\n> \n> It looks like currently that only the commit is kept, with no attempt\n> to recreate evil merges.\n\nYep. I even documented that somewhere ;-)\n\nCiao,\nDscho\n"},{"id":"337692","messageId":"nycvar.QRO.7.76.6.1801292126030.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"F2A95961E128479484699EC5DDC9243D@PhilipOakley","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T20:28:24Z","receivedAt":"2018-01-29T20:28:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Thu, 18 Jan 2018, Philip Oakley wrote:\n\n> From: \"Jacob Keller\" <jacob.keller@gmail.com>\n> > On Thu, Jan 18, 2018 at 7:35 AM, Johannes Schindelin\n> > <johannes.schindelin@gmx.de> wrote:\n> > > This commit implements the commands to label, and to reset to, given\n> > > revisions. The syntax is:\n> > >\n> > >         label <name>\n> > >         reset <name>\n> > >\n> > > As a convenience shortcut, also to improve readability of the generated\n> > > todo list, a third command is introduced: bud. It simply resets to the\n> > > \"onto\" revision, i.e. the commit onto which we currently rebase.\n> > >\n> >\n> > The code looks good, but I'm a little wary of adding bud which\n> > hard-codes a specific label. I suppose it does grant a bit of\n> > readability to the resulting script... ? It doesn't seem that\n> > important compared to use using \"reset onto\"? At least when\n> > documenting this it should be made clear that the \"onto\" label is\n> > special.\n> >\n> > Thanks,\n> > Jake.\n> \n> I'd agree.\n> \n> The special 'onto' label should be fully documented, and the commit message\n> should indicate which patch actually defines it (and all its corner cases and\n> fall backs if --onto isn't explicitly given..)\n\nI hoped that the example todo lists would clarify that 'onto' is just the\nname of the first label:\n\n\tlabel onto\n\nis *literally* how all of those todo lists start. And that is why there\nare no possible concerns about any missing `--onto` argument: that\nargument is irrelevant for that label.\n\nMaybe it is just a bad name. Maybe `START` would be a better label.\n\nWhat do you think?\n\n> Likewise the choice of 'bud' should be explained with some nice\n> phraseology indicating that we are growing the new flowering from the\n> bud, otherwise the word is a bit too short and sudden for easy\n> explanation.\n\nI dropped the `bud` command.\n\nCiao,\nDscho\n"},{"id":"337694","messageId":"nycvar.QRO.7.76.6.1801292134580.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"5FC5C1765B7648F7A166363BED3AFFA3@PhilipOakley","subject":"Re: [PATCH 8/8] rebase -i: introduce --recreate-merges=no-rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T20:42:41Z","receivedAt":"2018-01-29T20:42:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Thu, 18 Jan 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> > This one is a bit tricky to explain, so let's try with a diagram:\n> >\n> >        C\n> >      /   \\\n> > A - B - E - F\n> >  \\   /\n> >    D\n> >\n> > To illustrate what this new mode is all about, let's consider what\n> > happens upon `git rebase -i --recreate-merges B`, in particular to\n> > the commit `D`. In the default mode, the new branch structure is:\n> >\n> >      --- C' --\n> >      /         \\\n> > A - B ------ E' - F'\n> >      \\    /\n> >        D'\n> >\n> > This is not really preserving the branch topology from before! The\n> > reason is that the commit `D` does not have `B` as ancestor, and\n> > therefore it gets rebased onto `B`.\n> >\n> > However, when recreating branch structure, there are legitimate use\n> > cases where one might want to preserve the branch points of commits that\n> > do not descend from the <upstream> commit that was passed to the rebase\n> > command, e.g. when a branch from core Git's `next` was merged into Git\n> > for Windows' master we will not want to rebase those commits on top of a\n> > Windows-specific commit. In the example above, the desired outcome would\n> > look like this:\n> >\n> >      --- C' --\n> >      /         \\\n> > A - B ------ E' - F'\n> >  \\        /\n> >   -- D' --\n> \n> I'm not understanding this. I see that D properly starts from A, but\n> don't see why it is now D'. Surely it's unchanged.\n\nIt is not necessarily unchanged, because this is an *interactive* rebase.\nIf you mark `D` for `reword`, for example, it may be changed.\n\nI use the label D' in the mathematical sense, to indicate that D' is\nderived from D. It may even be identical to D, but the point is that it is\nin the todo list of the interactive rebase, so it can be changed. As\nopposed to, say, A and B. Those cannot be changed in this interactive\nrebase.\n\n> Maybe it's the arc/node confusion. Maybe even spell out that the rebased\n> commits from the command are B..HEAD, but that includes D, which may not\n> be what folk had expected. (not even sure if the reflog comes into\n> determining merge-bases here..)\n> \n> I do think an exact definition is needed (e.g. via --ancestry-path or\n> its equivalent?).\n\nI don't find \"ancestry path\" any more intuitive a term than the\nmathematically correct \"uncomparable\".\n\nIf you have a better way to explain this (without devolving into\nmathematical terminology), please let's hear it.\n\nDon't get me wrong, as a mathematician I am comfortable with very precise\ndescriptions involving plenty of Greek symbols.\n\nBut this documentation, and these commit messages do not target myself. I\nknow perfectly well what I am talking about here. The target audience are\nsoftware developers who may not have a background in mathematics, who do\nnot even want to fully understand what the heck constitutes a Directed\nAcyclic Graph.\n\nSo what we need here is plain English. And I had thought that the analogy\nwith the family tree would be intuitive enough for even math haters to\nunderstand easily and quickly...\n\nCiao,\nDscho\n"},{"id":"337695","messageId":"nycvar.QRO.7.76.6.1801292146270.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAPig+cQbG2s-LrAo9+7C7=dXifbWFJ3SzuNa-QePHDk7egK=jg@mail.gmail.com","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T20:50:05Z","receivedAt":"2018-01-29T20:50:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Fri, 19 Jan 2018, Eric Sunshine wrote:\n\n> On Thu, Jan 18, 2018 at 10:35 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > [...]\n> > +static int do_reset(const char *name, int len)\n> > +{\n> > +       [...]\n> > +       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> > +               return -1;\n> > +\n> > +       for (i = 0; i < len; i++)\n> > +               if (isspace(name[i]))\n> > +                       len = i;\n> \n> What is the purpose of this loop? I could imagine that it's trying to\n> strip all whitespace from the end of 'name', however, to do that it\n> would iterate backward, not forward. (Or perhaps it's trying to\n> truncate at the first space, but then it would need to invert the\n> condition or use 'break'.) Am I missing something obvious?\n\nYes, you are missing something obvious. The idea of the `reset` command is\nthat it not only has a label, but also the oneline of the original commit:\n\n\treset branch-point sequencer: prepare for cleanup\n\nIn this instance, `branch-point` is the label. And for convenience of the\nperson editing, it also has the oneline. This came in *extremely* handy\nwhen editing the commit topology in Git for Windows, i.e. when introducing\ntopic branches or flattening them.\n\nIn the Git garden shears, I separated the two arguments via `#`:\n\n\treset branch-point # sequencer: prepare for cleanup\n\nI guess that is actually more readable, so I will introduce that into this\npatch series, too.\n\nCiao,\nDscho\n"},{"id":"337696","messageId":"nycvar.QRO.7.76.6.1801292154420.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqa7x2c3w7.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T20:55:27Z","receivedAt":"2018-01-29T20:55:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 24 Jan 2018, Junio C Hamano wrote:\n\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> \n> >> +static int do_reset(const char *name, int len)\n> >> +{\n> >> +       [...]\n> >> +       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> >> +               return -1;\n> >> +\n> >> +       for (i = 0; i < len; i++)\n> >> +               if (isspace(name[i]))\n> >> +                       len = i;\n> >\n> > What is the purpose of this loop? I could imagine that it's trying to\n> > strip all whitespace from the end of 'name', however, to do that it\n> > would iterate backward, not forward. (Or perhaps it's trying to\n> > truncate at the first space, but then it would need to invert the\n> > condition or use 'break'.) Am I missing something obvious?\n> \n> I must be missing the same thing.  Given that the callers of\n> do_reset(), other than the \"bug\" thing that passes the hard coded\n> \"onto\", uses item->arg/item->arg_len which includes everything after\n> the insn word on the line in the todo list, I do suspect that the\n> intention is to stop at the first whitespace char to avoid creating\n> a ref with whitespace in it, i.e. it is a bug that can be fixed with\n> s/len = i/break/.\n> \n> The code probably should further check the resulting string with\n> check_ref_format() to detect strange chars and char sequences that\n> make the resulting refname invalid.  For example, you would not want\n> to allow a label with two consecutive periods in it.\n\nThe code already checks that by creating a ref.\n\nNo need to go crazy and validate ref names every time we parse the todo\nlist (which is quite often), eh?\n\nCiao,\nDscho\n"},{"id":"337698","messageId":"nycvar.QRO.7.76.6.1801292200550.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAPig+cSn_X-0iir-cKSd=P8p5QCxrR2qU_GBj7zDbv8zTc+AQw@mail.gmail.com","subject":"Re: [PATCH 4/8] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:05:26Z","receivedAt":"2018-01-29T21:05:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Fri, 19 Jan 2018, Eric Sunshine wrote:\n\n> On Thu, Jan 18, 2018 at 10:35 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> \n> > structure (similar in spirit to --preserve-merges, but with a\n> > substantially less-broken design).\n> > [...]\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/sequencer.c b/sequencer.c\n> > @@ -2785,6 +2787,335 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n> > +static const char *label_oid(struct object_id *oid, const char *label,\n> > +                            struct label_state *state)\n> > +{\n> > +       [...]\n> > +       } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n> > +                   !get_oid_hex(label, &dummy)) ||\n> > +                  hashmap_get_from_hash(&state->labels,\n> > +                                        strihash(label), label)) {\n> > +               /*\n> > +                * If the label already exists, or if the label is a valid full\n> > +                * OID, we append a dash and a number to make it unique.\n> > +                */\n> > +               [...]\n> > +               for (i = 2; ; i++) {\n> \n> Why '2'? Is there some non-obvious significance to this value?\n\nI personally found it irritating to have labels \"sequencer\",\n\"sequencer-1\". It sounds *wrong* to have a \"-1\". Because it is the second\nlabel referring to the term \"sequencer\". So if there are two labels that\nboth want to be named \"sequencer\", the first one wins, and the second one\nwill be called \"sequencer-2\".\n\nHence the 2.\n\n> > +static int make_script_with_merges(struct pretty_print_context *pp,\n> > +                                  struct rev_info *revs, FILE *out,\n> > +                                  unsigned flags)\n> > +{\n> > +       [...]\n> > +               is_octopus = to_merge && to_merge->next;\n> > +\n> > +               if (is_octopus)\n> > +                       BUG(\"Octopus merges not yet supported\");\n> \n> Is this a situation which the end-user can trigger by specifying a\n> merge with more than two parents? If so, shouldn't this be just a\n> normal error message rather than a (developer) bug message? Or, am I\n> misunderstanding?\n\nYou are misunderstanding.\n\nThis is just a place-holder here. The patches to introduce support for\noctopus merges are already written. They are lined up after this here\npatch series, is all.\n\nAs such, please do not occupy your mind on the specifics or even the\nupper-case of the \"Octopus\". This line is here only as a hint for the\nreviewer that this is not yet implemented. And BUG(...) was chosen because\nthat way, we are not even tempted to waste the time of translators.\n\nSpeaking of wasting time... let's move on to further interesting code\nreviews.\n\nCiao,\nDscho\n"},{"id":"337699","messageId":"nycvar.QRO.7.76.6.1801292205551.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqa7x4gwpz.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 4/8] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:07:59Z","receivedAt":"2018-01-29T21:08:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 23 Jan 2018, Junio C Hamano wrote:\n\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> \n> >> +               is_octopus = to_merge && to_merge->next;\n> >> +\n> >> +               if (is_octopus)\n> >> +                       BUG(\"Octopus merges not yet supported\");\n> >\n> > Is this a situation which the end-user can trigger by specifying a\n> > merge with more than two parents? If so, shouldn't this be just a\n> > normal error message rather than a (developer) bug message? Or, am I\n> > misunderstanding?\n> \n> BUG() is \"we wrote code carefully so that this should not trigger;\n> we do not _expect_ the code to reach here\".  This one is expected to\n> trigger, and I agree with you that it should be die(), if the series\n> is meant to be released to the general public in the current form\n> (i.e. until the limitation is lifted so that it can handle an\n> octopus).\n> \n> If the callers are made more careful to check if there is an octopus\n> involved and reject the request early, then seeing an octopus in\n> this location in a loop will become a BUG().\n\nThis has occupied both of you for way too long.\n\nIt is *not interesting*. What *is* interesting is for example the\ndiscussion about the \"cousin commits\". And maybe both of you gentle\npersons can spend your brain cycles splendidly by trying to come up with a\nbetter term. Or by trying to beat out obvious or not-so-obvious bugs in\nthe code.\n\nSeriously, I am not interested in a discussion about BUG() vs die() as\nlong as there may be real bugs hiding.\n\nCiao,\nDscho\n"},{"id":"337700","messageId":"nycvar.QRO.7.76.6.1801292208200.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAPig+cQZTd77oqod8EZbXqroaaYb7oYbXUOW+jWFfRMrOMonPw@mail.gmail.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:09:03Z","receivedAt":"2018-01-29T21:09:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Fri, 19 Jan 2018, Eric Sunshine wrote:\n\n> On Thu, Jan 18, 2018 at 10:35 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > [...]\n> > With this patch, the goodness of the Git garden shears comes to `git\n> > rebase -i` itself. Passing the `--recreate-merges` option will generate\n> > a todo list that can be understood readily, and where it is obvious\n> > how to reorder commits. New branches can be introduced by inserting\n> > `label` commands and calling `merge - <label> <oneline>`. And once this\n> > mode has become stable and universally accepted, we can deprecate the\n> > design mistake that was `--preserve-merges`.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> > @@ -900,6 +900,7 @@ fi\n> >  if test t != \"$preserve_merges\"\n> >  then\n> >         git rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n> > +               ${recreate_merges:+--recreate-merges} \\\n> \n> If the user specifies both --preserve-merges and --recreate-merges, it\n> looks like --preserve-merges will take precedence.\n> \n> Should git-rebase.sh have a mutual-exclusion check and error out if\n> both are specified?\n\nMaybe. I welcome you to contribute such a patch once recreate-merges made\nit into the code base.\n\nIn other words: this would be premature optimization. We're not at that\nstage yet.\n\nCiao,\nDscho\n"},{"id":"337701","messageId":"nycvar.QRO.7.76.6.1801292217590.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"d069a3c7-198e-81d2-0f35-f934e110c991@philandanna.no-ip.org","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to resettherevision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:23:50Z","receivedAt":"2018-01-29T21:24:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Fri, 19 Jan 2018, Phillip Wood wrote:\n\n> \n> On 18/01/18 15:35, Johannes Schindelin wrote:\n> \n> > This idea was developed in Git for Windows' Git garden shears (that\n> > are used to maintain the \"thicket of branches\" on top of upstream\n> > Git), and this patch is part of the effort to make it available to a\n> > wider audience, as well as to make the entire process more robust (by\n> > implementing it in a safe and portable language rather than a Unix\n> > shell script).\n> > \n> > This commit implements the commands to label, and to reset to, given\n> > revisions. The syntax is:\n> > \n> > \tlabel <name>\n> > \treset <name>\n> \n> If I've understood the code below correctly then reset will clobber\n> untracked files, this is the opposite behaviour to what happens when\n> tries to checkout <onto> at the start of a rebase - then it will fail if\n> untracked files would be overwritten.\n\nThis would be completely unintentional, I will verify that untracked files\nare not clobbered.\n\nHowever, in practice this should not happen because the intended use case\nis for revisions to be labeled *before* checking them out at a later\nstage. Therefore, the files that would be clobbered would already have\nbeen tracked in the revision when it was labeled, and I do not quite see\nhow those files could become untracked without playing sloppy exec games\nin between.\n\n> > Internally, the `label <name>` command creates the ref\n> > `refs/rewritten/<name>`. This makes it possible to work with the labeled\n> > revisions interactively, or in a scripted fashion (e.g. via the todo\n> > list command `exec`).\n> \n> If a user has two work trees and runs a rebase in each with the same\n> label name, they'll clobber each other. I'd suggest storing them under\n> refs/rewritten/<branch-name or detached HEAD SHA> instead. If the user\n> tries to rebase a second worktree with the same detached HEAD as an\n> existing rebase then refuse to start.\n\nThat is why a later patch marks those refs/rewritten/ refs as\nworktree-local.\n\n> > +static int do_label(const char *name, int len)\n> > +{\n> > +\tstruct ref_store *refs = get_main_ref_store();\n> > +\tstruct ref_transaction *transaction;\n> > +\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n> > +\tstruct strbuf msg = STRBUF_INIT;\n> > +\tint ret = 0;\n> > +\tstruct object_id head_oid;\n> > +\n> > +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> > +\tstrbuf_addf(&msg, \"label '%.*s'\", len, name);\n> \n> The other reflog messages below have a (rebase -i) prefix\n\nGood point. I changed it to \"rebase -i (label)\".\n\n> > +\ttransaction = ref_store_transaction_begin(refs, &err);\n> > +\tif (!transaction ||\n> > +\t    get_oid(\"HEAD\", &head_oid) ||\n> > +\t    ref_transaction_update(transaction, ref_name.buf, &head_oid, NULL,\n> > +\t\t\t\t   0, msg.buf, &err) < 0 ||\n> > +\t    ref_transaction_commit(transaction, &err)) {\n> > +\t\terror(\"%s\", err.buf);\n> \n> if get_oid() fails then err is empty so there wont be an message after\n> the 'error: '\n\nYep, that would be nasty. Fixed.\n\n> > +static int do_reset(const char *name, int len)\n> > +{\n> > +\tstruct strbuf ref_name = STRBUF_INIT;\n> > +\tstruct object_id oid;\n> > +\tstruct lock_file lock = LOCK_INIT;\n> > +\tstruct tree_desc desc;\n> > +\tstruct tree *tree;\n> > +\tstruct unpack_trees_options opts;\n> > +\tint ret = 0, i;\n> > +\n> > +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> > +\t\treturn -1;\n> > +\n> > +\tfor (i = 0; i < len; i++)\n> > +\t\tif (isspace(name[i]))\n> > +\t\t\tlen = i;\n> \n> If name starts with any white space then I think this effectively\n> truncates name to a bunch of white space which doesn't sound right. I'm\n> not sure how this is being called, but it might be better to clean up\n> name when the to-do list is parsed instead.\n\nThe left-trimming of the name was already performed as part of the todo\nlist parsing.\n\nAnd we are not really right-trimming here. We are splitting a line of the\nform\n\n\treset <label> <oneline>\n\nIn fact, after reflecting about it, I changed the code so that it would\nnow even read:\n\n\treset <label> # <oneline>\n\nSo the code really is doing the intended thing here.\n\nCiao,\nDscho\n"},{"id":"337702","messageId":"nycvar.QRO.7.76.6.1801292224180.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xpj503VKEf4jzYdbk33aAvaMQ8+T1bQ3EB0T+AWEU3pKA@mail.gmail.com","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to resettherevision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:25:12Z","receivedAt":"2018-01-29T21:25:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Fri, 19 Jan 2018, Jacob Keller wrote:\n\n> On Fri, Jan 19, 2018 at 10:55 AM, Phillip Wood\n> <phillip.wood@talktalk.net> wrote:\n> > On 19/01/18 12:24, Phillip Wood wrote:\n> >>\n> >> On 18/01/18 15:35, Johannes Schindelin wrote:\n> >>>\n> >>> Internally, the `label <name>` command creates the ref\n> >>> `refs/rewritten/<name>`. This makes it possible to work with the labeled\n> >>> revisions interactively, or in a scripted fashion (e.g. via the todo\n> >>> list command `exec`).\n> >>\n> >> If a user has two work trees and runs a rebase in each with the same\n> >> label name, they'll clobber each other. I'd suggest storing them under\n> >> refs/rewritten/<branch-name or detached HEAD SHA> instead. If the user\n> >> tries to rebase a second worktree with the same detached HEAD as an\n> >> existing rebase then refuse to start.\n> >>\n> >\n> > Ah this isn't a concern after all as patch 5 makes refs/rewritten local\n> > to the worktree. Perhaps you could move that part of patch 5 here or add\n> > a note to the commit message that it will become worktree local later in\n> > the series\n> >\n> > Best Wishes\n> >\n> > Phillip\n> \n> I'd rather it be included here as well.\n\nBut it would have been really easy to overlook in here. I really want this\nto be a separate commit, also to have a chance to get this done\n*differently* if somebody comes up with a splendid idea how to do that\n(because hard-coding feels quite dirty).\n\nCiao,\nDscho\n"},{"id":"337703","messageId":"nycvar.QRO.7.76.6.1801292227200.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801292224180.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to resettherevision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:29:11Z","receivedAt":"2018-01-29T21:29:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 29 Jan 2018, Johannes Schindelin wrote:\n\n> On Fri, 19 Jan 2018, Jacob Keller wrote:\n> \n> > On Fri, Jan 19, 2018 at 10:55 AM, Phillip Wood\n> > <phillip.wood@talktalk.net> wrote:\n> > > On 19/01/18 12:24, Phillip Wood wrote:\n> > >>\n> > >> On 18/01/18 15:35, Johannes Schindelin wrote:\n> > >>>\n> > >>> Internally, the `label <name>` command creates the ref\n> > >>> `refs/rewritten/<name>`. This makes it possible to work with the labeled\n> > >>> revisions interactively, or in a scripted fashion (e.g. via the todo\n> > >>> list command `exec`).\n> > >>\n> > >> If a user has two work trees and runs a rebase in each with the same\n> > >> label name, they'll clobber each other. I'd suggest storing them under\n> > >> refs/rewritten/<branch-name or detached HEAD SHA> instead. If the user\n> > >> tries to rebase a second worktree with the same detached HEAD as an\n> > >> existing rebase then refuse to start.\n> > >>\n> > >\n> > > Ah this isn't a concern after all as patch 5 makes refs/rewritten local\n> > > to the worktree. Perhaps you could move that part of patch 5 here or add\n> > > a note to the commit message that it will become worktree local later in\n> > > the series\n> > >\n> > > Best Wishes\n> > >\n> > > Phillip\n> > \n> > I'd rather it be included here as well.\n> \n> But it would have been really easy to overlook in here. I really want this\n> to be a separate commit, also to have a chance to get this done\n> *differently* if somebody comes up with a splendid idea how to do that\n> (because hard-coding feels quite dirty).\n\nBTW there is an additional good reason why the patch to make\nrefs/rewritten/* worktree-local is so far away: that is the first time in\nthe patch series when we can test this really effectively; at that stage\nwe can easily just add to t3430 because all the building blocks for\n`rebase -i --recreate-merges` are in place.\n\nCiao,\nDscho\n"},{"id":"337705","messageId":"nycvar.QRO.7.76.6.1801292230550.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xr4KtR4q8Y=-+pv2TzvP009zRVR6a_zh2GOZXt_LXrFOg@mail.gmail.com","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:41:03Z","receivedAt":"2018-01-29T21:41:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake & Phillip,\n\nOn Sat, 20 Jan 2018, Jacob Keller wrote:\n\n> On Fri, Jan 19, 2018 at 6:45 AM, Phillip Wood <phillip.wood@talktalk.net> wrote:\n> > On 18/01/18 15:35, Johannes Schindelin wrote:\n> >>\n> >> This patch is part of the effort to reimplement `--preserve-merges` with\n> >> a substantially improved design, a design that has been developed in the\n> >> Git for Windows project to maintain the dozens of Windows-specific patch\n> >> series on top of upstream Git.\n> >>\n> >> The previous patch implemented the `label`, `bud` and `reset` commands\n> >> to label commits and to reset to a labeled commits. This patch adds the\n> >> `merge` command, with the following syntax:\n> >>\n> >>       merge <commit> <rev> <oneline>\n> >\n> > I'm concerned that this will be confusing for users. All of the other\n> > rebase commands replay the changes in the commit hash immediately\n> > following the command name. This command instead uses the first commit\n> > to specify the message which is different to both 'git merge' and the\n> > existing rebase commands. I wonder if it would be clearer to have 'merge\n> > -C <commit> <rev> ...' instead so it's clear which argument specifies\n> > the message and which the remote head to merge. It would also allow for\n> > 'merge -c <commit> <rev> ...' in the future for rewording an existing\n> > merge message and also avoid the slightly odd 'merge - <rev> ...'. Where\n> > it's creating new merges I'm not sure it's a good idea to encourage\n> > people to only have oneline commit messages by making it harder to edit\n> > them, perhaps it could take another argument to mean open the editor or\n> > not, though as Jake said I guess it's not that common.\n> \n> I actually like the idea of re-using commit message options like -C,\n> -c,  and -m, so we could do:\n> \n> merge -C <commit> ... to take message from commit\n\nThat is exactly how the Git garden shears do it.\n\nI found it not very readable. That is why I wanted to get away from it in\n--recreate-merges.\n\n> merge -c <commit> ...  to take the message from commit and open editor to edit\n> merge -m \"<message>\" ... to take the message from the quoted test\n> merge ... to merge and open commit editor with default message\n> \n> This also, I think, allows us to not need to put the oneline on the\n> end, meaning we wouldn't have to quote the parent commit arguments\n> since we could use option semantics?\n\nThe oneline is there primarily to give you, the reader, a clue when\nreading and editing the todo list.\n\nReusing it for the `merge -` command was only an afterthought.\n\n> > One thought that just struck me - if a merge or reset command specifies\n> > an invalid label is it rescheduled so that it's still in the to-do list\n> > when the user edits it after rebase stops?\n\nIt is not rescheduled, because the command already failed, so we know it\nis bad.\n\nYou have to go edit the todo list, possibly after copying the faulty\ncommand from `git status`' output.\n\n> > In the future it might be nice if the label, reset and merge commands\n> > were validated when the to-do list is parsed so that the user gets\n> > immediate feedback if they try to create a label that is not a valid\n> > ref name or that they have a typo in a name given to reset or merge\n> > rather than the rebase stopping later.\n\nThere are too many possible errors to make this fool-proof. What if the\nref name is valid, but there was no `label` command yet?  What if there\n*has* been a `label` command but it is now stuck in the `done` file (which\nwe do not parse, ever)? What if the user specified two label commands with\nthe same label?\n\nIt sounds like an exercise in futility to try to catch these things in the\nparser.\n\nAnd keep in mind that the parser is used\n\n- when shortening the commit names\n- when checking the todo list for accidentally dropped picks\n- when skipping unnecessary picks\n- when rearranging fixup!/squash! commands\n- when adding `exec` commands specified via `-x`\n\nI am not sure that I would come out in favor of trying to catch ref name\nerrors during parsing time if I wanted to balance bang vs buck.\n\nCiao,\nDscho\n"},{"id":"337706","messageId":"nycvar.QRO.7.76.6.1801292245030.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"7e6906c9-d642-ee8d-82fd-29ee6c60e308@talktalk.net","subject":"Re: [PATCH 3/8] sequencer: fast-forward merge commits, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:47:01Z","receivedAt":"2018-01-29T21:47:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Fri, 19 Jan 2018, Phillip Wood wrote:\n\n> On 18/01/18 15:35, Johannes Schindelin wrote:\n> > \n> > Just like with regular `pick` commands, if we are trying to recreate a\n> > merge commit, we now test whether the parents of said commit match HEAD\n> > and the commits to be merged, and fast-forward if possible.\n> > \n> > This is not only faster, but also avoids unnecessary proliferation of\n> > new objects.\n> \n> I might have missed something but shouldn't this be checking\n> opts->allow_ff?\n\nGood point. This is the type of review for which I was hoping.\n\n> Another possible optimization is that if the parent branches have only\n> reworded commits or some commits that have been squashed but no other\n> changes then their trees will be the same as in the original merge\n> commit and so could be reused without calling merge_recursive().\n\nTrue. It is also a bit involved to check this condition, and I am not sure\nthat it is worth the effort for my use case.\n\nSo I would invite you to work on this after this patch series settles, if\nyou are interested.\n\nCiao,\nDscho\n"},{"id":"337707","messageId":"nycvar.QRO.7.76.6.1801292251240.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqmv19ppen.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 0/8] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T21:53:54Z","receivedAt":"2018-01-29T21:54:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 19 Jan 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Think of --recreate-merges as \"--preserve-merges done right\". It\n> > introduces new verbs for the todo list, `label`, `reset` and `merge`.\n> > For a commit topology like this:\n> >\n> >             A - B - C\n> >               \\   /\n> >                 D\n> >\n> > the generated todo list would look like this:\n> >\n> >             # branch D\n> >             pick 0123 A\n> >             label branch-point\n> >             pick 1234 D\n> >             label D\n> >\n> >             reset branch-point\n> >             pick 2345 B\n> >             merge 3456 D C\n> \n> Yup.  I've seen this design talked about on list in the past, and\n> I've always felt that this is \"sequencer done right\".\n> \n> At the first glance, it may feel somewhat unsatisfying that \"merge\"\n> has to say effects of which commits should be reflected in the\n> result and which commot to take the log message from, i.e.\n> (recreated)D is merged to form the resulting tree, and 3456=C is\n> used for the log, to recreate C in the above example, while \"pick\"\n> always uses the same commit for both, i.e. recreated B inherits both\n> the changes and log message from the original B=2345 (or depending\n> on the readers' point of view, \"merge\" is allowed to use two\n> different commits, while \"pick\" is always limited to the same one).\n> \n> But I think this distinction is probably fundamental and I am not\n> opposed to it at all.  The result of \"pick\" has only one parent, and\n> the parent is determined only by the previous actions and not by\n> anything on the \"pick\" line in the todo list.  But the result of\n> \"merge\" has to record all the other parents, and only the first\n> parent is determined implicitly by the previous actions.  We need to\n> tell the \"merge\" command about \"3456=C\" in order to recreate the\n> effect of original merge commit (i.e. changes between B and C) as\n> well as its log message, and we also need to tell it about label \"D\"\n> that it is the \"other parent\" that need to be recorded.\n\nYes, this was the hard lesson of the failed preserve-merges design.\n\n> Obviously \"merge\" command syntax should allow recreating an octopus,\n> so whenever I said \"two\" in the above, I meant \"N\".  The original\n> merge commit is needed so that the effect to replay (roughly: a\n> patch going to the original merge result from its first parent) can\n> be learned from the existing history, and all the other \"N-1\"\n> parents needs to be given (and they must have been already created\n> in the todo list) so that the resulting recreated merge can be\n> recorded with them as parents (in addition to the first parent that\n> is implicitly given as the result of all the previous steps).\n\nI have two more patch series lined up after this one, the first one\nimplements --root via the sequencer, and the second one indeed extends\n`merge` to handle octopus commits.\n\n> One interesting (and probably useful) thing to notice is that if A\n> were not rebased in the above sample picture, and only B were the\n> one that was tweaked, then a recreated C may use the same original D\n> as its side parent, and the mechanism outlined above naturally can\n> support it by allowing an un-rewritten commit to be given as a side\n> parent when \"merge\" is redoing C.\n\nI think that you will get a kick out of reading the commit message of the\nlast commit, as it does talk about the problematic C: it *would* be\nrebased by default.\n\nIn the next iteration I will actually switch around the default from\nrebase-cousins to no-rebase-cousins for that reason.\n\nCiao,\nDscho\n"},{"id":"337708","messageId":"nycvar.QRO.7.76.6.1801292256000.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqwp09lh6b.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:00:06Z","receivedAt":"2018-01-29T22:00:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 22 Jan 2018, Junio C Hamano wrote:\n\n> Jacob Keller <jacob.keller@gmail.com> writes:\n> \n> > The code looks good, but I'm a little wary of adding bud which\n> > hard-codes a specific label. I suppose it does grant a bit of\n> > readability to the resulting script... ? It doesn't seem that\n> > important compared to use using \"reset onto\"? At least when\n> > documenting this it should be made clear that the \"onto\" label is\n> > special.\n> \n> I do not think we would mind \"bud\" too much in the end result, but\n> the change in 1/8 is made harder to read than necessary with it.  It\n> is the only thing that needs \"a single-letter command name may now\n> not have any argument after it\" change to the parser among the three\n> things being added here, and it also needs to be added to the list\n> of special commands without arguments.\n> \n> It would have been easier to reason about if addition of \"bud\" was\n> in its own patch done after label and reset are added.  And if done\n> as a separate step, perhaps it would have been easier to realize\n> that it would be a more future-proof solution for handling the\n> \"special\" ness of BUD to add a new \"unsigned flags\" word to\n> todo_command_info[] structure and using a bit that says \"this does\n> not take an arg\" than to hardcode \"noop and bud are the commands\n> without args\" in the code.  That hardcode was good enough when there\n> was only one thing in that special case.  Now it has two.\n\nI dropped the `bud` command. It did come in handy when I truly recreated\nbranch structure from a way-too-long topic branch, but that is probably a\nrare use case, and not worth spending so much air time on.\n\n> In a similar way, the code to special case label and reset just like\n> exec may also want to become more table driven, perhaps using\n> another bit in the same new flags word to say \"this does not refer\n> to commit\".  I think that can become [v2 1/N] while addition of \"bud\"\n> can be [v2 2/N] (after all, \"bud\" just does the same do_reset() with\n> hardcoded argument, so \"label/reset\" must come first).\n\nThe downside of such a table-driven approach is readability, of course. It\nbecomes *less* readable because all of a sudden you have to jump back and\nforth between the parsing code and the table (and then you also have to\nkeep the table header in sight).\n\nI have had enough problems with such table-driven approaches in the past,\neven in Git's own source code. Exhibit A: t0027. I do not wish upon my\nworst enemies having to investigate problems in that script, for in those\ntables despair awaits ye who dare enter.\n\nSo I respectfully decline to go into that direction in the sequencer.\n\nCiao,\nDscho\n"},{"id":"337709","messageId":"nycvar.QRO.7.76.6.1801292306510.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqpo61lezj.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:15:50Z","receivedAt":"2018-01-29T22:16:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 22 Jan 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> >  \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n> > +\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n> > +\titem->arg_len = (int)(eol - item->arg);\n> > +\n> >  \tsaved = *end_of_object_name;\n> > +\tif (item->command == TODO_MERGE && *bol == '-' &&\n> > +\t    bol + 1 == end_of_object_name) {\n> > +\t\titem->commit = NULL;\n> > +\t\treturn 0;\n> > +\t}\n> > +\n> >  \t*end_of_object_name = '\\0';\n> >  \tstatus = get_oid(bol, &commit_oid);\n> >  \t*end_of_object_name = saved;\n> >  \n> > -\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n> > -\titem->arg_len = (int)(eol - item->arg);\n> > -\n> \n> Assigning to \"saved\" before the added \"if we are doing merge and see\n> '-', do this special thing\" is not only unnecessary, but makes the\n> logic in the non-special case harder to read.  The four things\n> \"saved = *eol; *eol = 0; do_thing_using(bol); *eol = saved;\" is a\n> single logical unit; keep them together.\n\nTrue. This was a sloppily resolved merge conflict in one of the many\nrewrites, I guess.\n\n> > +\t\tif (*p)\n> > +\t\t\tlen = strlen(p);\n> > +\t\telse {\n> > +\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n> > +\t\t\t\t    merge_arg_len, arg);\n> > +\t\t\tp = buf.buf;\n> > +\t\t\tlen = buf.len;\n> > +\t\t}\n> \n> So... \"arg\" received by this function can be a single non-whitespace\n> token, which is taken as the name of the branch being merged (in\n> this else clause).  Or it can also be followed by a single liner\n> message for the merge commit.  Presumably, this is for creating a\n> new merge (i.e. \"commit==NULL\" case), and preparing a proper log\n> message in the todo list is unrealistic, so this would be a\n> reasonable compromise.  Those users who want to write proper log\n> message could presumably follow such \"merge\" insn with a \"x git\n> commit --amend\" or something, I presume, if they really wanted to.\n\nPrecisely.\n\n> > +\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n> > +\t\t\terror_errno(_(\"Could not write '%s'\"),\n> > +\t\t\t\t    git_path_merge_msg());\n> > +\t\t\tstrbuf_release(&buf);\n> > +\t\t\trollback_lock_file(&lock);\n> > +\t\t\treturn -1;\n> > +\t\t}\n> > +\t\tstrbuf_release(&buf);\n> > +\t}\n> \n> OK.  Now we have prepared the MERGE_MSG file and are ready to commit.\n> \n> > +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n> > +\tif (!head_commit) {\n> > +\t\trollback_lock_file(&lock);\n> > +\t\treturn error(_(\"Cannot merge without a current revision\"));\n> > +\t}\n> \n> Hmph, I would have expected to see this a lot earlier, before\n> dealing with the log message.  Leftover MERGE_MSG file after an\n> error will cause unexpected fallout to the end-user experience\n> (including what is shown by the shell prompt scripts), but if we do\n> this before the MERGE_MSG thing, we do not have to worry about\n> error codepath having to remove it.\n\nFixed.\n\n> > +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n> > +\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> > +\tif (!merge_commit) {\n> > +\t\t/* fall back to non-rewritten ref or commit */\n> > +\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n> > +\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> > +\t}\n> \n> OK, so \"abc\" in the example in the log message is looked up first as\n> a label and then we take a fallback to interpret as an object name.\n\nYes. And auto-generated labels are guaranteed not to be full hex hashes\nfor that reason.\n\n> Hopefully allowed names in \"label\" would be documented clearly in\n> later steps (I am guessing that \"a name that can be used as a branch\n> name can be used as a label name and vice versa\" or something like\n> that).\n\nWell, I thought that it would suffice to say that these labels are\navailable as refs/rewritten/<label>. It kind of goes without saying that\nthose need to be valid ref names, then?\n\n> > +\tif (!merge_commit) {\n> > +\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n> > +\t\tstrbuf_release(&ref_name);\n> > +\t\trollback_lock_file(&lock);\n> > +\t\treturn -1;\n> > +\t}\n> > +\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n> > +\t\t      git_path_merge_head(), 0);\n> > +\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n> \n> These two calls gave me a \"Huh?\" moment; write_message() sounds like\n> it is allowed to be later updated with frills suitable for *_MSG\n> files we place in .git/ directory (iow, it is in principle OK if\n> commented out instructions common to these files are added to the\n> output by the function), but these want exact bytes passed in the\n> result, for which wrapper.c::write_file() is more appropriate.\n\nI agree that write_message() is not a good name, but write_file() was\nalready taken. I do not think wrapper.c:write_file() is more appropriate,\nas it has *also* misnamed: it *always completes the line*. In other words,\nit is not write_file(), it is write_line_to_file(). And not even that, as\nit takes a printf()-type format.\n\nNo, write_message(), even if not named appropriately, is the functionality\nI want.\n\n> Alternatively, perhaps write_message() can be dropped and its\n> callers can call wrapper.c::write_file() instead?  Such a clean-up\n> may require teaching the append-eol thing that write_message() wants\n> to wrapper.c::write_file(), but it shouldn't be a rocket science.\n\nwrite_file() does different things than write_message(). I think this\nsuggestion to congeal them into one is quite overzealous.\n\nAnd would also lead us astray.\n\n> > +\tcommon = get_merge_bases(head_commit, merge_commit);\n> > +\tfor (j = common; j; j = j->next)\n> > +\t\tcommit_list_insert(j->item, &reversed);\n> > +\tfree_commit_list(common);\n> \n> I know this is copy&pasted code from \"builtin/merge.c\", but is there\n> a reason to reverse the common ancestor list here?\n\nYes. You explained the reason yourself: this is `git merge`'s behavior. To\nrecreate it in the sequencer, the list of common ancestors has to be\nreversed here, too.\n\nCiao,\nDscho\n"},{"id":"337710","messageId":"nycvar.QRO.7.76.6.1801292316180.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqvafsh0hu.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 3/8] sequencer: fast-forward merge commits, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:18:36Z","receivedAt":"2018-01-29T22:18:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 23 Jan 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > +\t/*\n> > +\t * If HEAD is not identical to the parent of the original merge commit,\n> > +\t * we cannot fast-forward.\n> > +\t */\n> > +\tcan_fast_forward = commit && commit->parents &&\n> > +\t\t!oidcmp(&commit->parents->item->object.oid,\n> > +\t\t\t&head_commit->object.oid);\n> > +\n> \n> I think this expression and assignment should better be done much\n> later.  Are you going to update commit, commit->parents, etc. that\n> are involved in the computation in the meantime???\n\nNo, it is in the exact right spot.\n\nWhat you are missing is that there will be some more code here, to support\noctopus merges.\n\nThat's why it is exactly the right spot.\n\n> > @@ -2164,6 +2172,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n> >  \t\trollback_lock_file(&lock);\n> >  \t\treturn -1;\n> >  \t}\n> > +\n> > +\tif (can_fast_forward && commit->parents->next &&\n> > +\t    !commit->parents->next->next &&\n> > +\t    !oidcmp(&commit->parents->next->item->object.oid,\n> > +\t\t    &merge_commit->object.oid)) {\n> \n> ... Namely, here.\n\n... which the octopus merge code will never reach. That's why this is the\nwrong spot for that initial can_fast_forward assignment.\n\n> Because the earlier one is computing \"are we\n> replaying exactly the same commit on top of exactly the same\n> state?\", which is merely one half of \"can we fast-forward\", and\n> storing it in a variable whose name is over-promising way before it\n> becomes necessary.  The other half of \"can we fast-forward?\" logic\n> is the remainder of the if() condition we see above.  IOW, when\n> fully spelled, this code can fast-forward when we are replaying a\n> commit on top of exactly the same first-parent and the commit being\n> replayed is a single parent merge.\n> \n> We may even want to get rid of can_fast_forward variable.\n\nAbsolutely not.\n\nCiao,\nDscho\n"},{"id":"337748","messageId":"nycvar.QRO.7.76.6.1801292323260.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqh8rcgx65.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 4/8] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:37:58Z","receivedAt":"2018-01-29T22:40:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 23 Jan 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > structure (similar in spirit to --preserve-merges, but with a\n> > substantially less-broken design).\n> > ...\n> > @@ -2785,6 +2787,335 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n> >  \tstrbuf_release(&sob);\n> >  }\n> >  \n> > +struct labels_entry {\n> > +\tstruct hashmap_entry entry;\n> > +\tchar label[FLEX_ARRAY];\n> > +};\n> > +\n> > +static int labels_cmp(const void *fndata, const struct labels_entry *a,\n> > +\t\t      const struct labels_entry *b, const void *key)\n> > +{\n> > +\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n> > +}\n> \n> label_oid() accesses state->labels hash using strihash() as the hash\n> function, but the final comparison between the entries in the same\n> hash buckets are done with case sensitivity.  It is unclear to me if\n> that is what was intended, and why.\n\nHeh... you were almost there with your analysis. strihash() is needed for\ncase-insensitive comparisons, and labels are... ref names.\n\nSo the idea (which I implemented only partially) was to make the labels\ncase-insensitive based on `ignore_case`.\n\nI think the best way forward will be to copy\nmerge-recursive.c:path_hash().\n\n> > +struct string_entry {\n> > +\tstruct oidmap_entry entry;\n> > +\tchar string[FLEX_ARRAY];\n> > +};\n> > +\n> > +struct label_state {\n> > +\tstruct oidmap commit2label;\n> > +\tstruct hashmap labels;\n> > +\tstruct strbuf buf;\n> > +};\n> > +\n> > +static const char *label_oid(struct object_id *oid, const char *label,\n> > +\t\t\t     struct label_state *state)\n> > +{\n> > +\tstruct labels_entry *labels_entry;\n> > +\tstruct string_entry *string_entry;\n> > +\tstruct object_id dummy;\n> > +\tsize_t len;\n> > +\tint i;\n> > +\n> > +\tstring_entry = oidmap_get(&state->commit2label, oid);\n> > +\tif (string_entry)\n> > +\t\treturn string_entry->string;\n> > +\n> > +\t/*\n> > +\t * For \"uninteresting\" commits, i.e. commits that are not to be\n> > +\t * rebased, and which can therefore not be labeled, we use a unique\n> > +\t * abbreviation of the commit name. This is slightly more complicated\n> > +\t * than calling find_unique_abbrev() because we also need to make\n> > +\t * sure that the abbreviation does not conflict with any other\n> > +\t * label.\n> > +\t *\n> > +\t * We disallow \"interesting\" commits to be labeled by a string that\n> > +\t * is a valid full-length hash, to ensure that we always can find an\n> > +\t * abbreviation for any uninteresting commit's names that does not\n> > +\t * clash with any other label.\n> > +\t */\n> > +\tif (!label) {\n> > +\t\tchar *p;\n> > +\n> > +\t\tstrbuf_reset(&state->buf);\n> > +\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n> > +\t\tlabel = p = state->buf.buf;\n> > +\n> > +\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n> > +\n> > +\t\t/*\n> > +\t\t * We may need to extend the abbreviated hash so that there is\n> > +\t\t * no conflicting label.\n> > +\t\t */\n> > +\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n> > +\t\t\tsize_t i = strlen(p) + 1;\n> > +\n> > +\t\t\toid_to_hex_r(p, oid);\n> > +\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n> > +\t\t\t\tchar save = p[i];\n> > +\t\t\t\tp[i] = '\\0';\n> > +\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n> > +\t\t\t\t\t\t\t   strihash(p), p))\n> > +\t\t\t\t\tbreak;\n> > +\t\t\t\tp[i] = save;\n> > +\t\t\t}\n> > +\t\t}\n> \n> If oid->hash required full 40-hex to disambiguate, then\n> find-unique-abbrev would give 40-hex and we'd want the same \"-<num>\"\n> suffix technique employed below to make it consistently unique.  I\n> wonder if organizing the function this way ...\n> \n> \tif (!label)\n> \t\tlabel = oid-to-hex(oid);\n> \n> \tif (label already exists or full oid) {\n> \t\tmake it unambiguous;\n> \t}\n\nIt was hard to miss, I agree. The first arm of the if() is not trying to\nmake a label unambiguous by adding `-<num>`, but by extending the\nabbreviated 40-hex until it is unique. (And we guarantee that there is a\nsolution, as we do not allow valid 40-hex strings to be used as label.)\n\nThe reason: if no `label` is provided, we want a valid raw (possibly\nabbreviated) commit name. If `label` is provided, we want a valid ref name\n(possibly made unique by appending `-<num>`).\n\nDifferent beasts. Cannot be put into the same if() arm.\n\n> A related tangent.  Does an auto-label given to \"uninteresting\"\n> commit need to be visible to end users?\n\nYes. The reason for this is the `merge`/`reset` commands when *not*\nrebasing cousins. Because in that case, we have to refer to commits that\nwe could not possibly have `label`ed, because they were never the current\nrevision. Therefore we cannot assign <name>-<num> labels, but have to use\nthe abbreviated commit hash.\n\n> > +static int make_script_with_merges(struct pretty_print_context *pp,\n> > +\t\t\t\t   struct rev_info *revs, FILE *out,\n> > +\t\t\t\t   unsigned flags)\n> > +{\n> > + ...\n> > +\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n> > +\tconst char *p = abbr ? \"p\" : \"pick\", *l = abbr ? \"l\" : \"label\",\n> > +\t\t *t = abbr ? \"t\" : \"reset\", *b = abbr ? \"b\" : \"bud\",\n> > +\t\t *m = abbr ? \"m\" : \"merge\";\n> \n> It would be easier to understand if these short named variables are\n> reserved only for temporary use, not as constants.  It is not too\n> much to spell \n> \n> \tfprintf(out, \"%s onto\\n\", cmd_label);\n> \n> than\n> \n> \tfprintf(out, \"%s onto\\n\", l);\n> \n> and would save readers from head-scratching, wondering where the\n> last assignment to variable \"l\" is.\n\nSure.\n\n> > +\toidmap_init(&commit2todo, 0);\n> > +\toidmap_init(&state.commit2label, 0);\n> > +\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n> > +\tstrbuf_init(&state.buf, 32);\n> > +\n> > +\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n> > +\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n> > +\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n> > +\t\toidcpy(&entry->entry.oid, oid);\n> > +\t\toidmap_put(&state.commit2label, entry);\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * First phase:\n> > +\t * - get onelines for all commits\n> > +\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n> > +\t * - label all branch tips\n> > +\t */\n> \n> When an early part of a branch is merged and then the remaining part\n> of the same branch is merged again, \"branch tip\" and \"2nd or later\n> parents of merges\" would become different concepts.  The 2nd parent\n> of an early merge is not among the branch tips.\n> \n> For the purpose of the \"recreate the topology\" algorithm, I am\n> imagining that you would need not just the tips but all the 2nd and\n> subsequent parents of merges, and my quick skimming tells me that\n> the following code grabs them correctly.\n\nYes. I have used an earlier iteration of this patch series to create the\nmerging-rebase of Git for Windows v2.16.0 and that has a couple of really\ngood corner cases to exercise this code.\n\nCiao,\nDscho\n"},{"id":"337750","messageId":"nycvar.QRO.7.76.6.1801292352290.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqwp08fhea.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 0/8] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:53:35Z","receivedAt":"2018-01-29T22:53:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 23 Jan 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > My original attempt was --preserve-merges, but that design was so\n> > limited that I did not even enable it in interactive mode.\n> > ...\n> > There are more patches in the pipeline, based on this patch series, but\n> > left for later in the interest of reviewable patch series: one mini\n> > series to use the sequencer even for `git rebase -i --root`, and another\n> > one to add support for octopus merges to --recreate-merges.\n> \n> I left comments on a handful of them, but I do not think any of them\n> spotted a grave design issue to be a show stopper.  Overall, the\n> series was quite a pleasant read, even with those minor nits and\n> rooms for improvements.\n\nI objected to a couple obviously problematic suggestions, and I\nimplemented all others (even those to which I did not respond\nspecifically).\n\nCiao,\nDscho\n"},{"id":"337751","messageId":"474e71f85c613d3c5fe38acd505fa23960afd36b.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 01/10] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:54:37Z","receivedAt":"2018-01-29T22:54:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Stefan Beller <stefanbeller@gmail.com>\n\nUp to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we are about to introduce commands that take different\narguments, clarify each command by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex d47bd29593a..fcedece1860 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,13 +155,13 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.16.1.windows.1\n\n\n"},{"id":"337752","messageId":"cover.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1516225925.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 00/10] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:54:32Z","receivedAt":"2018-01-29T22:54:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, I dreamt of an interactive rebase that would not\nflatten branch structure, but instead recreate the commit topology\nfaithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --recreate-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge 3456 D C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --recreate-merges.\n\nChanges since v1:\n\n- reintroduced \"sequencer: make refs generated by the `label` command\n  worktree-local\" (which was squashed into \"sequencer: handle autosquash\n  and post-rewrite for merge commands\" by accident)\n\n- got rid of the universally-hated `bud` command\n\n- as per Stefan's suggestion, the help blurb at the end of the todo list\n  now lists the syntax\n\n- the no-rebase-cousins mode was made the default; This not only reflects\n  the experience won from those years of using the Git garden shears, but\n  was also deemed the better default in the discussion on the PR at\n  https://github.com/git/git/pull/447\n\n- I tried to clarify the role of the `onto` label in the commit message of\n  `rebase-helper --make-script: introduce a flag to recreate merges`\n\n- fixed punctuation at the end of error(...) messages, and incorrect\n  upper-case at the start\n\n- changed the generated todo lists to separate the label and the oneline in\n  the `reset` command with a `#`, for readability\n\n- dropped redundant paragraph in the commit message that talked about\n  support for octopus merges\n\n- avoided empty error message when HEAD could not be read during do_label()\n\n- merge commits are fast-forwarded only unless --force-rebase was passed\n\n- do_merge() now errors out a lot earlier when HEAD could not be parsed\n\n- the one-letter variables to hold either abbreviated or full todo list\n  instructions in make_script_recreating_merges() were renamed to clearer\n  names\n\n- The description of rebase's --recreate-merge option has been reworded;\n  Hopefully it is a lot more clear now.\n\n\nJohannes Schindelin (9):\n  sequencer: introduce new commands to reset the revision\n  sequencer: introduce the `merge` command\n  sequencer: fast-forward merge commits, if possible\n  rebase-helper --make-script: introduce a flag to recreate merges\n  rebase: introduce the --recreate-merges option\n  sequencer: make refs generated by the `label` command worktree-local\n  sequencer: handle autosquash and post-rewrite for merge commands\n  pull: accept --rebase=recreate to recreate the branch topology\n  rebase -i: introduce --recreate-merges=[no-]rebase-cousins\n\nStefan Beller (1):\n  git-rebase--interactive: clarify arguments\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   5 +-\n Documentation/git-rebase.txt           |  14 +-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |   2 +\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |  22 +-\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 699 ++++++++++++++++++++++++++++++++-\n sequencer.h                            |   7 +\n t/t3430-rebase-recreate-merges.sh      | 208 ++++++++++\n 13 files changed, 988 insertions(+), 27 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\n\nbase-commit: 5be1f00a9a701532232f57958efab4be8c959a29\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v2\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v2\n\nInterdiff vs v1:\n diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n index ac07a5c3fc9..0e6d020d924 100644\n --- a/Documentation/git-rebase.txt\n +++ b/Documentation/git-rebase.txt\n @@ -371,12 +371,13 @@ have the long commit hash prepended to the format.\n  --recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n  \tRecreate merge commits instead of flattening the history by replaying\n  \tmerges. Merge conflict resolutions or manual amendments to merge\n -\tcommits are not preserved.\n +\tcommits are not recreated automatically, but have to be recreated\n +\tmanually.\n  +\n -By default, or when `rebase-cousins` was specified, commits which do not have\n -`<upstream>` as direct ancestor are rebased onto `<upstream>` (or `<onto>`,\n -if specified). If the `rebase-cousins` mode is turned off, such commits will\n -retain their original branch point.\n +By default, or when `no-rebase-cousins` was specified, commits which do not\n +have `<upstream>` as direct ancestor keep their original branch point.\n +If the `rebase-cousins` mode is turned on, such commits are rebased onto\n +`<upstream>` (or `<onto>`, if specified).\n  \n  -p::\n  --preserve-merges::\n diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\n index ef08fef4d14..cea99cb3235 100644\n --- a/builtin/rebase--helper.c\n +++ b/builtin/rebase--helper.c\n @@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  {\n  \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n  \tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n -\tint abbreviate_commands = 0, no_rebase_cousins = -1;\n +\tint abbreviate_commands = 0, rebase_cousins = -1;\n  \tenum {\n  \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n  \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n @@ -23,7 +23,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  \t\tOPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n  \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n  \t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n -\t\tOPT_BOOL(0, \"no-rebase-cousins\", &no_rebase_cousins,\n +\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n  \t\t\t N_(\"keep original branch points of cousins\")),\n  \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n  \t\t\t\tCONTINUE),\n @@ -59,10 +59,10 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n  \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n  \tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n -\tflags |= no_rebase_cousins > 0 ? TODO_LIST_NO_REBASE_COUSINS : 0;\n +\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n  \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n  \n -\tif (no_rebase_cousins >= 0&& !recreate_merges)\n +\tif (rebase_cousins >= 0 && !recreate_merges)\n  \t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n  \t\t\t  \"--recreate-merges\"));\n  \n diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n index 23184c77e88..5e21e4cf269 100644\n --- a/git-rebase--interactive.sh\n +++ b/git-rebase--interactive.sh\n @@ -155,17 +155,19 @@ reschedule_last_action () {\n  append_todo_help () {\n  \tgettext \"\n  Commands:\n -p, pick = use commit\n -r, reword = use commit, but edit the commit message\n -e, edit = use commit, but stop for amending\n -s, squash = use commit, but meld into previous commit\n -f, fixup = like \\\"squash\\\", but discard this commit's log message\n -x, exec = run command (the rest of the line) using shell\n -d, drop = remove commit\n -l, label = label current HEAD with a name\n -t, reset = reset HEAD to a label\n -b, bud = reset HEAD to the revision labeled 'onto'\n -m, merge = create a merge commit using a given commit's message\n +p, pick <commit> = use commit\n +r, reword <commit> = use commit, but edit the commit message\n +e, edit <commit> = use commit, but stop for amending\n +s, squash <commit> = use commit, but meld into previous commit\n +f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n +x, exec <commit> = run command (the rest of the line) using shell\n +d, drop <commit> = remove commit\n +l, label <label> = label current HEAD with a name\n +t, reset <label> = reset HEAD to a label\n +m, merge <original-merge-commit> ( <label> | \\\"<label>...\\\" ) [<oneline>]\n +.       create a merge commit using the original merge commit's\n +.       message (or the oneline, if \"-\" is given). Use a quoted\n +.       list of commits to be merged for octopus merges.\n  \n  These lines can be re-ordered; they are executed from top to bottom.\n  \" | git stripspace --comment-lines >>\"$todo\"\n @@ -901,7 +903,7 @@ if test t != \"$preserve_merges\"\n  then\n  \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n  \t\t${recreate_merges:+--recreate-merges} \\\n -\t\t${no_rebase_cousins:+--no-rebase-cousins} \\\n +\t\t${rebase_cousins:+--rebase-cousins} \\\n  \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n  \tdie \"$(gettext \"Could not generate todo list\")\"\n  else\n diff --git a/git-rebase.sh b/git-rebase.sh\n index 3403b1416a8..58d778a2da0 100755\n --- a/git-rebase.sh\n +++ b/git-rebase.sh\n @@ -88,7 +88,7 @@ state_dir=\n  # One of {'', continue, skip, abort}, as parsed from command line\n  action=\n  recreate_merges=\n -no_rebase_cousins=\n +rebase_cousins=\n  preserve_merges=\n  autosquash=\n  keep_empty=\n @@ -272,8 +272,8 @@ do\n  \t--recreate-merges=*)\n  \t\trecreate_merges=t\n  \t\tcase \"${1#*=}\" in\n -\t\trebase-cousins) no_rebase_cousins=;;\n -\t\tno-rebase-cousins) no_rebase_cousins=t;;\n +\t\trebase-cousins) rebase_cousins=t;;\n +\t\tno-rebase-cousins) rebase_cousins=;;\n  \t\t*) die \"Unknown mode: $1\";;\n  \t\tesac\n  \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n diff --git a/sequencer.c b/sequencer.c\n index 2b4e6b12321..cd2f2ae5d53 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -780,7 +780,6 @@ enum todo_command {\n  \tTODO_EXEC,\n  \tTODO_LABEL,\n  \tTODO_RESET,\n -\tTODO_BUD,\n  \tTODO_MERGE,\n  \t/* commands that do nothing but are counted for reporting progress */\n  \tTODO_NOOP,\n @@ -802,7 +801,6 @@ static struct {\n  \t{ 'x', \"exec\" },\n  \t{ 'l', \"label\" },\n  \t{ 't', \"reset\" },\n -\t{ 'b', \"bud\" },\n  \t{ 'm', \"merge\" },\n  \t{ 0,   \"noop\" },\n  \t{ 'd', \"drop\" },\n @@ -1285,7 +1283,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n  \tpadding = strspn(bol, \" \\t\");\n  \tbol += padding;\n  \n -\tif (item->command == TODO_NOOP || item->command == TODO_BUD) {\n +\tif (item->command == TODO_NOOP) {\n  \t\tif (bol != eol)\n  \t\t\treturn error(_(\"%s does not accept arguments: '%s'\"),\n  \t\t\t\t     command_to_string(item->command), bol);\n @@ -1311,13 +1309,13 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n  \titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n  \titem->arg_len = (int)(eol - item->arg);\n  \n -\tsaved = *end_of_object_name;\n  \tif (item->command == TODO_MERGE && *bol == '-' &&\n  \t    bol + 1 == end_of_object_name) {\n  \t\titem->commit = NULL;\n  \t\treturn 0;\n  \t}\n  \n +\tsaved = *end_of_object_name;\n  \t*end_of_object_name = '\\0';\n  \tstatus = get_oid(bol, &commit_oid);\n  \t*end_of_object_name = saved;\n @@ -1969,7 +1967,7 @@ static int safe_append(const char *filename, const char *fmt, ...)\n  \t}\n  \tif (commit_lock_file(&lock) < 0) {\n  \t\trollback_lock_file(&lock);\n -\t\treturn error(_(\"failed to finalize '%s'.\"), filename);\n +\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n  \t}\n  \n  \treturn 0;\n @@ -1985,14 +1983,18 @@ static int do_label(const char *name, int len)\n  \tstruct object_id head_oid;\n  \n  \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n -\tstrbuf_addf(&msg, \"label '%.*s'\", len, name);\n +\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n  \n  \ttransaction = ref_store_transaction_begin(refs, &err);\n -\tif (!transaction ||\n -\t    get_oid(\"HEAD\", &head_oid) ||\n -\t    ref_transaction_update(transaction, ref_name.buf, &head_oid, NULL,\n -\t\t\t\t   0, msg.buf, &err) < 0 ||\n -\t    ref_transaction_commit(transaction, &err)) {\n +\tif (!transaction) {\n +\t\terror(\"%s\", err.buf);\n +\t\tret = -1;\n +\t} else if (get_oid(\"HEAD\", &head_oid)) {\n +\t\terror(_(\"could not read HEAD\"));\n +\t\tret = -1;\n +\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n +\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n +\t\t   ref_transaction_commit(transaction, &err)) {\n  \t\terror(\"%s\", err.buf);\n  \t\tret = -1;\n  \t}\n @@ -2021,6 +2023,7 @@ static int do_reset(const char *name, int len)\n  \tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n  \t\treturn -1;\n  \n +\t/* Determine the length of the label */\n  \tfor (i = 0; i < len; i++)\n  \t\tif (isspace(name[i]))\n  \t\t\tlen = i;\n @@ -2045,7 +2048,7 @@ static int do_reset(const char *name, int len)\n  \n  \tread_cache_unmerged();\n  \tif (!fill_tree_descriptor(&desc, &oid)) {\n -\t\terror(_(\"Failed to find tree of %s.\"), oid_to_hex(&oid));\n +\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n  \t\trollback_lock_file(&lock);\n  \t\tfree((void *)desc.buffer);\n  \t\tstrbuf_release(&ref_name);\n @@ -2097,6 +2100,12 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n  \t\treturn -1;\n  \n +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n +\tif (!head_commit) {\n +\t\trollback_lock_file(&lock);\n +\t\treturn error(_(\"cannot merge without a current revision\"));\n +\t}\n +\n  \tif (commit) {\n  \t\tconst char *message = get_commit_buffer(commit, NULL);\n  \t\tconst char *body;\n @@ -2111,7 +2120,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\tfind_commit_subject(message, &body);\n  \t\tlen = strlen(body);\n  \t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n -\t\t\terror_errno(_(\"Could not write '%s'\"),\n +\t\t\terror_errno(_(\"could not write '%s'\"),\n  \t\t\t\t    git_path_merge_msg());\n  \t\t\tunuse_commit_buffer(commit, message);\n  \t\t\trollback_lock_file(&lock);\n @@ -2138,7 +2147,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\t}\n  \n  \t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n -\t\t\terror_errno(_(\"Could not write '%s'\"),\n +\t\t\terror_errno(_(\"could not write '%s'\"),\n  \t\t\t\t    git_path_merge_msg());\n  \t\t\tstrbuf_release(&buf);\n  \t\t\trollback_lock_file(&lock);\n @@ -2147,17 +2156,11 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\tstrbuf_release(&buf);\n  \t}\n  \n -\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n -\tif (!head_commit) {\n -\t\trollback_lock_file(&lock);\n -\t\treturn error(_(\"Cannot merge without a current revision\"));\n -\t}\n -\n  \t/*\n  \t * If HEAD is not identical to the parent of the original merge commit,\n  \t * we cannot fast-forward.\n  \t */\n -\tcan_fast_forward = commit && commit->parents &&\n +\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n  \t\t!oidcmp(&commit->parents->item->object.oid,\n  \t\t\t&head_commit->object.oid);\n  \n @@ -2411,8 +2414,6 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\tres = do_label(item->arg, item->arg_len);\n  \t\telse if (item->command == TODO_RESET)\n  \t\t\tres = do_reset(item->arg, item->arg_len);\n -\t\telse if (item->command == TODO_BUD)\n -\t\t\tres = do_reset(\"onto\", 4);\n  \t\telse if (item->command == TODO_MERGE) {\n  \t\t\tres = do_merge(item->commit,\n  \t\t\t\t       item->arg, item->arg_len, opts);\n @@ -2905,7 +2906,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\t\t\t   unsigned flags)\n  {\n  \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n -\tint no_rebase_cousins = flags & TODO_LIST_NO_REBASE_COUSINS;\n +\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n  \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n  \tstruct strbuf label = STRBUF_INIT;\n  \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n @@ -2918,9 +2919,10 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n  \n  \tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n -\tconst char *p = abbr ? \"p\" : \"pick\", *l = abbr ? \"l\" : \"label\",\n -\t\t *t = abbr ? \"t\" : \"reset\", *b = abbr ? \"b\" : \"bud\",\n -\t\t *m = abbr ? \"m\" : \"merge\";\n +\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n +\t\t*cmd_label = abbr ? \"l\" : \"label\",\n +\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n +\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n  \n  \toidmap_init(&commit2todo, 0);\n  \toidmap_init(&state.commit2label, 0);\n @@ -2961,7 +2963,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\t\tstrbuf_reset(&buf);\n  \t\t\tif (!keep_empty && is_original_commit_empty(commit))\n  \t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n -\t\t\tstrbuf_addf(&buf, \"%s %s %s\", p,\n +\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n  \t\t\t\t    oid_to_hex(&commit->object.oid),\n  \t\t\t\t    oneline.buf);\n  \n @@ -2995,7 +2997,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\t\t\t*(char *)p1 = '-';\n  \n  \t\tstrbuf_reset(&buf);\n -\t\tstrbuf_addf(&buf, \"%s %s\", m, oid_to_hex(&commit->object.oid));\n +\t\tstrbuf_addf(&buf, \"%s %s\",\n +\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n  \n  \t\t/* label the tip of merged branch */\n  \t\toid = &to_merge->item->object.oid;\n @@ -3046,7 +3049,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t * gathering commits not yet shown, reversing the list on the fly,\n  \t * then outputting that list (labeling revisions as needed).\n  \t */\n -\tfprintf(out, \"%s onto\\n\", l);\n +\tfprintf(out, \"%s onto\\n\", cmd_label);\n  \tfor (iter = tips; iter; iter = iter->next) {\n  \t\tstruct commit_list *list = NULL, *iter2;\n  \n @@ -3071,7 +3074,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\t}\n  \n  \t\tif (!commit)\n -\t\t\tfprintf(out, \"%s\\n\", b);\n +\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n  \t\telse {\n  \t\t\tconst char *to = NULL;\n  \n @@ -3079,17 +3082,17 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\t\t\t\t   &commit->object.oid);\n  \t\t\tif (entry)\n  \t\t\t\tto = entry->string;\n -\t\t\telse if (no_rebase_cousins)\n +\t\t\telse if (!rebase_cousins)\n  \t\t\t\tto = label_oid(&commit->object.oid, NULL,\n  \t\t\t\t\t       &state);\n  \n -\t\t\tif (!to || !strcmp(\"onto\", to))\n -\t\t\t\tfprintf(out, \"%s\\n\", b);\n +\t\t\tif (!to || !strcmp(to, \"onto\"))\n +\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n  \t\t\telse {\n  \t\t\t\tstrbuf_reset(&oneline);\n  \t\t\t\tpretty_print_commit(pp, commit, &oneline);\n -\t\t\t\tfprintf(out, \"%s %s %s\\n\",\n -\t\t\t\t\tt, to, oneline.buf);\n +\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n +\t\t\t\t\tcmd_reset, to, oneline.buf);\n  \t\t\t}\n  \t\t}\n  \n @@ -3101,7 +3104,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n  \t\t\tentry = oidmap_get(&state.commit2label, oid);\n  \t\t\tif (entry)\n -\t\t\t\tfprintf(out, \"%s %s\\n\", l, entry->string);\n +\t\t\t\tfprintf(out, \"%s %s\\n\",\n +\t\t\t\t\tcmd_label, entry->string);\n  \t\t\toidset_insert(&shown, oid);\n  \t\t}\n  \n diff --git a/sequencer.h b/sequencer.h\n index 9530dba3cba..deebc6e3258 100644\n --- a/sequencer.h\n +++ b/sequencer.h\n @@ -51,12 +51,10 @@ int sequencer_remove_state(struct replay_opts *opts);\n  #define TODO_LIST_RECREATE_MERGES (1U << 3)\n  /*\n   * When recreating merges, commits that do have the base commit as ancestor\n - * (\"cousins\") are rebased onto the new base by default. If those commits\n - * should keep their original branch point, this flag needs to be passed.\n - *\n - * This flag only makes sense when <base> and <onto> are different.\n + * (\"cousins\") are *not* rebased onto the new base by default. If those\n + * commits should be rebased onto the new base, this flag needs to be passed.\n   */\n -#define TODO_LIST_NO_REBASE_COUSINS (1U << 4)\n +#define TODO_LIST_REBASE_COUSINS (1U << 4)\n  int sequencer_make_script(FILE *out, int argc, const char **argv,\n  \t\t\t  unsigned flags);\n  \n diff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\n index 22930e470a4..ab51b584ff9 100755\n --- a/t/t3430-rebase-recreate-merges.sh\n +++ b/t/t3430-rebase-recreate-merges.sh\n @@ -54,11 +54,11 @@ pick D\n  label onebranch\n  \n  # second\n -bud\n +reset onto\n  pick B\n  label second\n  \n -bud\n +reset onto\n  merge H second\n  merge - onebranch Merge the topic branch 'onebranch'\n  EOF\n @@ -93,18 +93,18 @@ test_expect_success 'generate correct todo list' '\n  \tcat >expect <<-\\EOF &&\n  \tlabel onto\n  \n -\tbud\n +\treset onto\n  \tpick d9df450 B\n  \tlabel E\n  \n -\tbud\n +\treset onto\n  \tpick 5dee784 C\n  \tlabel branch-point\n  \tpick ca2c861 F\n  \tpick 088b00a G\n  \tlabel H\n  \n -\treset branch-point C\n +\treset branch-point # C\n  \tpick 12bd07b D\n  \tmerge 2051b56 E E\n  \tmerge 233d48a H H\n @@ -143,7 +143,7 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n  \tEOF\n  '\n  \n -test_expect_success 'rebase cousins unless told not to' '\n +test_expect_success 'do not rebase cousins unless asked for' '\n  \twrite_script copy-editor.sh <<-\\EOF &&\n  \tcp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n  \tEOF\n @@ -152,10 +152,10 @@ test_expect_success 'rebase cousins unless told not to' '\n  \tgit checkout -b cousins master &&\n  \tbefore=\"$(git rev-parse --verify HEAD)\" &&\n  \ttest_tick &&\n -\tgit rebase -i --recreate-merges=no-rebase-cousins HEAD^ &&\n +\tgit rebase -i --recreate-merges HEAD^ &&\n  \ttest_cmp_rev HEAD $before &&\n  \ttest_tick &&\n -\tgit rebase -i --recreate-merges HEAD^ &&\n +\tgit rebase -i --recreate-merges=rebase-cousins HEAD^ &&\n  \ttest_cmp_graph HEAD^.. <<-\\EOF\n  \t*   Merge the topic branch '\\''onebranch'\\''\n  \t|\\\n-- \n2.16.1.windows.1\n\n"},{"id":"337753","messageId":"42839735d7aa3e56411cb68fd645349e09239777.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 03/10] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:54:46Z","receivedAt":"2018-01-29T22:54:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label` and `reset` commands to label\ncommits and to reset to a labeled commits. This patch adds the `merge`\ncommand, with the following syntax:\n\n\tmerge <commit> <rev> <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the to-be-created merge\ncommit.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list:\n\n\tlabel onto\n\n\t# Branch abc\n\treset onto\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\treset onto\n\tpick cafecafe And now for something completely different\n\tmerge baaabaaa abc Merge the branch 'abc' into master\n\nTo support creating *new* merges, i.e. without copying the commit\nmessage from an existing commit, use the special value `-` as <commit>\nparameter (in which case the text after the <rev> parameter is used as\ncommit message):\n\n\tmerge - abc This will be the actual commit message of the merge\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in a separate\npatch series, support for merges using strategies other than the\nrecursive merge is left for the future.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   4 ++\n sequencer.c                | 146 +++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 146 insertions(+), 4 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 7e5281e74aa..d6fd30f6c09 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -164,6 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n l, label <label> = label current HEAD with a name\n t, reset <label> = reset HEAD to a label\n+m, merge <original-merge-commit> ( <label> | \\\"<label>...\\\" ) [<oneline>]\n+.       create a merge commit using the original merge commit's\n+.       message (or the oneline, if \"-\" is given). Use a quoted\n+.       list of commits to be merged for octopus merges.\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 92ca8d2adee..dfc9f9e13cd 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -778,6 +778,7 @@ enum todo_command {\n \tTODO_EXEC,\n \tTODO_LABEL,\n \tTODO_RESET,\n+\tTODO_MERGE,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -798,6 +799,7 @@ static struct {\n \t{ 'x', \"exec\" },\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n+\t{ 'm', \"merge\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1302,14 +1304,20 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t}\n \n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n+\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n+\titem->arg_len = (int)(eol - item->arg);\n+\n+\tif (item->command == TODO_MERGE && *bol == '-' &&\n+\t    bol + 1 == end_of_object_name) {\n+\t\titem->commit = NULL;\n+\t\treturn 0;\n+\t}\n+\n \tsaved = *end_of_object_name;\n \t*end_of_object_name = '\\0';\n \tstatus = get_oid(bol, &commit_oid);\n \t*end_of_object_name = saved;\n \n-\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n-\titem->arg_len = (int)(eol - item->arg);\n-\n \tif (status < 0)\n \t\treturn -1;\n \n@@ -2072,6 +2080,132 @@ static int do_reset(const char *name, int len)\n \treturn ret;\n }\n \n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    struct replay_opts *opts)\n+{\n+\tint merge_arg_len;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *common, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint ret;\n+\tstatic struct lock_file lock;\n+\n+\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n+\t\tif (isspace(arg[merge_arg_len]))\n+\t\t\tbreak;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"cannot merge without a current revision\"));\n+\t}\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t     oid_to_hex(&commit->object.oid));\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tunuse_commit_buffer(commit, message);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tunuse_commit_buffer(commit, message);\n+\t} else {\n+\t\tconst char *p = arg + merge_arg_len;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tp += strspn(p, \" \\t\");\n+\t\tif (*p)\n+\t\t\tlen = strlen(p);\n+\t\telse {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tstrbuf_release(&buf);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\tif (!merge_commit) {\n+\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tcommon = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(common);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n+\t\t\t     merge_arg_len, arg);\n+\t}\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error(_(\"merge: Unable to write new index file\"));\n+\t}\n+\trollback_lock_file(&lock);\n+\n+\tret = run_git_commit(git_path_merge_msg(), opts, 0);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2259,6 +2393,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_label(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len);\n+\t\telse if (item->command == TODO_MERGE)\n+\t\t\tres = do_merge(item->commit,\n+\t\t\t\t       item->arg, item->arg_len, opts);\n \t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n@@ -2758,7 +2895,8 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n-\t\t}\n+\t\t} else if (item->command == TODO_MERGE)\n+\t\t\tstrbuf_addstr(&buf, \" -\");\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.16.1.windows.1\n\n\n"},{"id":"337754","messageId":"36281a14bc9f9a53e6c1bead561e3c93744047a8.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 02/10] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:54:41Z","receivedAt":"2018-01-29T22:55:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming commits, we will teach the sequencer to recreate merges.\nThis will be done in a very different way from the unfortunate design of\n`git rebase --preserve-merges` (which does not allow for reordering\ncommits, or changing the branch topology).\n\nThe main idea is to introduce new todo list commands, to support\nlabeling the current revision with a given name, resetting the current\nrevision to a previous state, merging labeled revisions.\n\nThis idea was developed in Git for Windows' Git garden shears (that are\nused to maintain the \"thicket of branches\" on top of upstream Git), and\nthis patch is part of the effort to make it available to a wider\naudience, as well as to make the entire process more robust (by\nimplementing it in a safe and portable language rather than a Unix shell\nscript).\n\nThis commit implements the commands to label, and to reset to, given\nrevisions. The syntax is:\n\n\tlabel <name>\n\treset <name>\n\nInternally, the `label <name>` command creates the ref\n`refs/rewritten/<name>`. This makes it possible to work with the labeled\nrevisions interactively, or in a scripted fashion (e.g. via the todo\nlist command `exec`).\n\nLater in this patch series, we will mark the `refs/rewritten/` refs as\nworktree-local, to allow for interactive rebases to be run in parallel in\nworktrees linked to the same repository.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   2 +\n sequencer.c                | 180 ++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 179 insertions(+), 3 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex fcedece1860..7e5281e74aa 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n+l, label <label> = label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 4d3f60594cb..92ca8d2adee 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -21,6 +21,8 @@\n #include \"log-tree.h\"\n #include \"wt-status.h\"\n #include \"hashmap.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -116,6 +118,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `merge` command.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -767,6 +776,8 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -785,6 +796,8 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1253,7 +1266,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\tif (skip_prefix(bol, todo_command_info[i].str, &bol)) {\n \t\t\titem->command = i;\n \t\t\tbreak;\n-\t\t} else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {\n+\t\t} else if ((bol + 1 == eol || bol[1] == ' ') &&\n+\t\t\t   *bol == todo_command_info[i].c) {\n \t\t\tbol++;\n \t\t\titem->command = i;\n \t\t\tbreak;\n@@ -1279,7 +1293,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -1919,6 +1934,144 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename, 0);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n+\t\treturn error_errno(_(\"could not read '%s'\"), filename);\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t} else if (get_oid(\"HEAD\", &head_oid)) {\n+\t\terror(_(\"could not read HEAD\"));\n+\t\tret = -1;\n+\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n+\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t   ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static int do_reset(const char *name, int len)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\t/* Determine the length of the label */\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = &the_index;\n+\topts.dst_index = &the_index;\n+\topts.fn = oneway_merge;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.reset = 1;\n+\n+\tread_cache_unmerged();\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret) {\n+\t\tstruct strbuf msg = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&msg, \"(rebase -i) reset '%.*s'\", len, name);\n+\t\tret = update_ref(msg.buf, \"HEAD\", &oid, NULL, 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n+\t\tstrbuf_release(&msg);\n+\t}\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2102,7 +2255,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n-\t\t} else if (!is_noop(item->command))\n+\t\t} else if (item->command == TODO_LABEL)\n+\t\t\tres = do_label(item->arg, item->arg_len);\n+\t\telse if (item->command == TODO_RESET)\n+\t\t\tres = do_reset(item->arg, item->arg_len);\n+\t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\n@@ -2207,6 +2364,23 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t}\n \t\tapply_autostash(opts);\n \n+\t\tstrbuf_reset(&buf);\n+\t\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0)\n+\t\t    > 0) {\n+\t\t\tchar *p = buf.buf;\n+\t\t\twhile (*p) {\n+\t\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\t\tif (eol)\n+\t\t\t\t\t*eol = '\\0';\n+\t\t\t\tif (delete_ref(\"(rebase -i) cleanup\",\n+\t\t\t\t\t       p, NULL, 0) < 0)\n+\t\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\t\tif (!eol)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp = eol + 1;\n+\t\t\t}\n+\t\t}\n+\n \t\tfprintf(stderr, \"Successfully rebased and updated %s.\\n\",\n \t\t\thead_ref.buf);\n \n-- \n2.16.1.windows.1\n\n\n"},{"id":"337755","messageId":"2fdde64f9de580cde654988931e53a36a30f36bf.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 04/10] sequencer: fast-forward merge commits, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:54:49Z","receivedAt":"2018-01-29T22:55:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to recreate a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 21 ++++++++++++++++++++-\n 1 file changed, 20 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex dfc9f9e13cd..df61e7883b3 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2088,7 +2088,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *common, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint ret;\n+\tint can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \n \tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n@@ -2154,6 +2154,14 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\tstrbuf_release(&buf);\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the parent of the original merge commit,\n+\t * we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n \tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n \tif (!merge_commit) {\n@@ -2167,6 +2175,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\trollback_lock_file(&lock);\n \t\treturn -1;\n \t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn fast_forward_to(&commit->object.oid,\n+\t\t\t\t       &head_commit->object.oid, 0, opts);\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.16.1.windows.1\n\n\n"},{"id":"337756","messageId":"a28a65007856eecfea1d44072b3190399b7509d3.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 05/10] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:54:53Z","receivedAt":"2018-01-29T22:55:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --recreate-merges option. For a\ncommit topology like this (where the HEAD points to C):\n\n\t- A - B - C\n\t    \\   /\n\t      D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge 3456 D C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch in this patch series.\n\nAs a special, hard-coded label, all merge-recreating todo lists start with\nthe command `label onto` so that we can later always refer to the revision\nonto which everything is rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 346 ++++++++++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 348 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex 7daee544b7b..a34ab5c0655 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -22,6 +22,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n+\t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -55,6 +56,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex df61e7883b3..d5af315a440 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2786,6 +2788,338 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, we append a dash and a number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n+\t\t*cmd_label = abbr ? \"l\" : \"label\",\n+\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n+\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tif ((commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_original_commit_empty(commit))\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s %s\",\n+\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", cmd_label);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(to, \"onto\"))\n+\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n+\t\t\t\t\tcmd_reset, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\",\n+\t\t\t\t\tcmd_label, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -2796,11 +3130,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint recreate_merges = flags & TODO_LIST_RECREATE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_pick = 1;\n+\tif (recreate_merges)\n+\t\trevs.cherry_mark = 1;\n+\telse {\n+\t\trevs.max_parents = 1;\n+\t\trevs.cherry_pick = 1;\n+\t}\n \trevs.limited = 1;\n \trevs.reverse = 1;\n \trevs.right_only = 1;\n@@ -2824,6 +3163,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (recreate_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tstrbuf_reset(&buf);\n \t\tif (!keep_empty && is_original_commit_empty(commit))\ndiff --git a/sequencer.h b/sequencer.h\nindex 81f6d7d393f..11d1ac925ef 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -48,6 +48,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_RECREATE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.16.1.windows.1\n\n\n"},{"id":"337758","messageId":"de018eb9e0799cfe3a1b8c7fb364de41cb161229.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 07/10] sequencer: make refs generated by the `label` command worktree-local","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:54:59Z","receivedAt":"2018-01-29T22:55:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                            |  3 ++-\n t/t3430-rebase-recreate-merges.sh | 14 ++++++++++++++\n 2 files changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 20ba82b4343..e8b84c189ff 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex b5ea4130bb5..5295bb03dc0 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,4 +143,18 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n-- \n2.16.1.windows.1\n\n\n"},{"id":"337759","messageId":"9f0b001eca49d0cd3c8baf9230e49330034fab4b.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 06/10] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:54:56Z","receivedAt":"2018-01-29T22:55:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt at an answer was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto flatten the branch topology or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--recreate-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge - <label> <oneline>`. And once this\nmode has become stable and universally accepted, we can deprecate the\ndesign mistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |   9 +-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 ++\n t/t3430-rebase-recreate-merges.sh      | 146 +++++++++++++++++++++++++++++++++\n 5 files changed, 162 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 8a861c1e0d6..e9da7e26329 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -368,6 +368,12 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+--recreate-merges::\n+\tRecreate merge commits instead of flattening the history by replaying\n+\tmerges. Merge conflict resolutions or manual amendments to merge\n+\tcommits are not recreated automatically, but have to be recreated\n+\tmanually.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -770,7 +776,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+--recreate-merges for a more faithful representation.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3683c772c55..6893c3adabc 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2008,7 +2008,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--recreate-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex d6fd30f6c09..97b7954f7d3 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -902,6 +902,7 @@ fi\n if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${recreate_merges:+--recreate-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex fd72a35c65b..d69bc7d0e0d 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+recreate-merges!   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -86,6 +87,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+recreate_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -262,6 +264,10 @@ do\n \t--keep-empty)\n \t\tkeep_empty=yes\n \t\t;;\n+\t--recreate-merges)\n+\t\trecreate_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nnew file mode 100755\nindex 00000000000..b5ea4130bb5\n--- /dev/null\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -0,0 +1,146 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2017 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --recreate-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+cat >script-from-scratch <<\\EOF\n+label onto\n+\n+# onebranch\n+pick G\n+pick D\n+label onebranch\n+\n+# second\n+reset onto\n+pick B\n+label second\n+\n+reset onto\n+merge H second\n+merge - onebranch Merge the topic branch 'onebranch'\n+EOF\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'create completely different structure' '\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\treset onto\n+\tpick d9df450 B\n+\tlabel E\n+\n+\treset onto\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point # C\n+\tpick 12bd07b D\n+\tmerge 2051b56 E E\n+\tmerge 233d48a H H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.16.1.windows.1\n\n\n"},{"id":"337760","messageId":"7449a6b30f15a00b80924b732cb6998cbee99e5e.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 08/10] sequencer: handle autosquash and post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:55:02Z","receivedAt":"2018-01-29T22:55:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --recreate-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits. And the\ninteractive rebase also supports the autosquash mode, where commits\nwhose oneline is of the form `fixup! <oneline>` or `squash! <oneline>`\nare rearranged to amend commits whose oneline they match.\n\nThis patch implements the post-rewrite and autosquash handling for the\n`merge` command we just introduced. The other commands that were added\nrecently (`label` and `reset`) do not create new commits, therefore\npost-rewrite & autosquash do not need to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c                       | 10 +++++++---\n t/t3430-rebase-recreate-merges.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 32 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex d5af315a440..4cc73775394 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2414,10 +2414,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_label(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len);\n-\t\telse if (item->command == TODO_MERGE)\n+\t\telse if (item->command == TODO_MERGE) {\n \t\t\tres = do_merge(item->commit,\n \t\t\t\t       item->arg, item->arg_len, opts);\n-\t\telse if (!is_noop(item->command))\n+\t\t\tif (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n+\t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\n@@ -3560,7 +3563,8 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (item->command >= TODO_EXEC &&\n+\t\t    (item->command != TODO_MERGE || !item->commit)) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 5295bb03dc0..2eeda0c512b 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -157,4 +157,29 @@ test_expect_success 'refs/rewritten/* is worktree-local' '\n \ttest_cmp_rev HEAD \"$(cat wt/b)\"\n '\n \n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash --recreate-merges HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.16.1.windows.1\n\n\n"},{"id":"337761","messageId":"b944d8674039072972089e09bb10b9797f1f7070.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 09/10] pull: accept --rebase=recreate to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:55:06Z","receivedAt":"2018-01-29T22:55:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `recreate` mode simply passes the\n`--recreate-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  5 ++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       |  2 ++\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 25 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0e25b2c92b3..da41ab246dc 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2607,6 +2611,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..b4f9f057ea9 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,16 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|recreate|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to recreate, rebase with the `--recreate-merges` option passed\n+to `git rebase` so that locally created merge commits will not be flattened.\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 511dbbe0f6e..e33c84e0345 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_RECREATE,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"recreate\", returns REBASE_RECREATE. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"recreate\"))\n+\t\treturn REBASE_RECREATE;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|recreate|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -798,7 +802,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_RECREATE)\n+\t\targv_array_push(&args, \"--recreate-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d95bf904c3b..b7d0f7ce596 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"recreate\"))\n+\t\t\t\tinfo->rebase = NORMAL_REBASE;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 6893c3adabc..6f98c96fee9 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2182,7 +2182,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true recreate preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.16.1.windows.1\n\n\n"},{"id":"337762","messageId":"0fc047807559b6f105494d1fc2bda4a48863835e.1517266437.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 10/10] rebase -i: introduce --recreate-merges=[no-]rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-29T22:55:12Z","receivedAt":"2018-01-29T22:55:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This one is a bit tricky to explain, so let's try with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nTo illustrate what this new mode is all about, let's consider what\nhappens upon `git rebase -i --recreate-merges B`, in particular to\nthe commit `D`. So far, the new branch structure would be:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nThis is unintuitive behavior. Even worse, when recreating branch\nstructure, most use cases would appear to want cousins *not* to be\nrebased onto the new base commit. For example, Git for Windows (the\nheaviest user of the Git garden shears, which served as the blueprint\nfor --recreate-merges) frequently merges branches from `next` early, and\nthese branches certainly do *not* want to be rebased. In the example\nabove, the desired outcome would look like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and let's not rebase them by default, introducing the new\n\"rebase-cousins\" mode for use cases where they should be rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt      |  7 ++++++-\n builtin/rebase--helper.c          |  9 ++++++++-\n git-rebase--interactive.sh        |  1 +\n git-rebase.sh                     | 12 +++++++++++-\n sequencer.c                       |  4 ++++\n sequencer.h                       |  6 ++++++\n t/t3430-rebase-recreate-merges.sh | 23 +++++++++++++++++++++++\n 7 files changed, 59 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex e9da7e26329..0e6d020d924 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -368,11 +368,16 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n---recreate-merges::\n+--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tRecreate merge commits instead of flattening the history by replaying\n \tmerges. Merge conflict resolutions or manual amendments to merge\n \tcommits are not recreated automatically, but have to be recreated\n \tmanually.\n++\n+By default, or when `no-rebase-cousins` was specified, commits which do not\n+have `<upstream>` as direct ancestor keep their original branch point.\n+If the `rebase-cousins` mode is turned on, such commits are rebased onto\n+`<upstream>` (or `<onto>`, if specified).\n \n -p::\n --preserve-merges::\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex a34ab5c0655..cea99cb3235 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -23,6 +23,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n+\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,8 +59,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n+\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (rebase_cousins >= 0 && !recreate_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--recreate-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 97b7954f7d3..5e21e4cf269 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -903,6 +903,7 @@ if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${recreate_merges:+--recreate-merges} \\\n+\t\t${rebase_cousins:+--rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex d69bc7d0e0d..58d778a2da0 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-recreate-merges!   try to recreate merges instead of skipping them\n+recreate-merges?   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -88,6 +88,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n recreate_merges=\n+rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -268,6 +269,15 @@ do\n \t\trecreate_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--recreate-merges=*)\n+\t\trecreate_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) rebase_cousins=t;;\n+\t\tno-rebase-cousins) rebase_cousins=;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex 4cc73775394..cd2f2ae5d53 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2906,6 +2906,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3081,6 +3082,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (!rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(to, \"onto\"))\n \t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\ndiff --git a/sequencer.h b/sequencer.h\nindex 11d1ac925ef..deebc6e3258 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -49,6 +49,12 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_RECREATE_MERGES (1U << 3)\n+/*\n+ * When recreating merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are *not* rebased onto the new base by default. If those\n+ * commits should be rebased onto the new base, this flag needs to be passed.\n+ */\n+#define TODO_LIST_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 2eeda0c512b..ab51b584ff9 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,6 +143,29 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase cousins unless asked for' '\n+\twrite_script copy-editor.sh <<-\\EOF &&\n+\tcp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tEOF\n+\n+\ttest_config sequence.editor \\\"\"$PWD\"/copy-editor.sh\\\" &&\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges=rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.16.1.windows.1\n"},{"id":"337774","messageId":"CAPig+cRjU6niXpT2FrDWZ0x1HmGf1ojVZj3uk2qXEGe-S7i_HQ@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801292146270.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 1/8] sequencer: introduce new commands to reset the revision","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-01-30T07:12:15Z","receivedAt":"2018-01-30T07:12:21Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jan 29, 2018 at 3:50 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Fri, 19 Jan 2018, Eric Sunshine wrote:\n>> On Thu, Jan 18, 2018 at 10:35 AM, Johannes Schindelin\n>> <johannes.schindelin@gmx.de> wrote:\n>> > +static int do_reset(const char *name, int len)\n>> > +{\n>> > +       for (i = 0; i < len; i++)\n>> > +               if (isspace(name[i]))\n>> > +                       len = i;\n>>\n>> What is the purpose of this loop? I could imagine that it's trying to\n>> strip all whitespace from the end of 'name', however, to do that it\n>> would iterate backward, not forward. (Or perhaps it's trying to\n>> truncate at the first space, but then it would need to invert the\n>> condition or use 'break'.) Am I missing something obvious?\n>\n> Yes, you are missing something obvious. The idea of the `reset` command is\n> that it not only has a label, but also the oneline of the original commit:\n>\n>         reset branch-point sequencer: prepare for cleanup\n>\n> In this instance, `branch-point` is the label. And for convenience of the\n> person editing, it also has the oneline.\n\nNo, that's not what I was missing. What I was missing was that\nassigning 'i' to 'len' also causes the loop to terminate. It's\nembarrassing how long I had to stare at this loop to see that, and I\nsuspect that's what fooled a couple other reviewers, as well, since\nidiomatic loops don't normally muck with the termination condition in\nquite that fashion (and is why I suggested that a 'break' might be\nmissing).\n\nHad the loop been a bit more idiomatic:\n\n    for (i = 0; i < len; i++)\n        if (isspace(name[i]))\n            break;\n    len = i;\n\nthen the question would never have arisen. Anyhow, it's a minor point\nin the greater scheme of the patch series.\n\n> In the Git garden shears, I separated the two arguments via `#`:\n>\n>         reset branch-point # sequencer: prepare for cleanup\n>\n> I guess that is actually more readable, so I will introduce that into this\n> patch series, too.\n\nGiven my termination-condition blindness, the extra \"#\" would not have\nhelped me understand the loop any better.\n\nHaving now played with the feature a tiny bit, I don't have a strong\nopinion about the \"#\" other than to note that it seems inconsistent\nwith other commands which don't use \"#\" as a separator.\n"},{"id":"337775","messageId":"CAPig+cSkzetewFio+4ECnXiizH_KFGEZPZVdzHYGcKL_yvLcSg@mail.gmail.com","threadId":"47635","inReplyTo":"36281a14bc9f9a53e6c1bead561e3c93744047a8.1517266437.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 02/10] sequencer: introduce new commands to reset the revision","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-01-30T08:06:18Z","receivedAt":"2018-01-30T08:06:25Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jan 29, 2018 at 5:54 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> [...]\n> This commit implements the commands to label, and to reset to, given\n> revisions. The syntax is:\n>\n>         label <name>\n>         reset <name>\n> [...]\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/sequencer.c b/sequencer.c\n> @@ -1253,7 +1266,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>                 if (skip_prefix(bol, todo_command_info[i].str, &bol)) {\n>                         item->command = i;\n>                         break;\n> -               } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {\n> +               } else if ((bol + 1 == eol || bol[1] == ' ') &&\n> +                          *bol == todo_command_info[i].c) {\n\nThis adds support for commands which have no arguments, however, now\nthat the \"bud\" command has been retired, this can go away too, right?\n\n>                         bol++;\n>                         item->command = i;\n>                         break;\n> @@ -1919,6 +1934,144 @@ static int do_exec(const char *command_line)\n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> +       va_list ap;\n> +       struct lock_file lock = LOCK_INIT;\n> +       int fd = hold_lock_file_for_update(&lock, filename, 0);\n> +       struct strbuf buf = STRBUF_INIT;\n> +\n> +       if (fd < 0)\n> +               return error_errno(_(\"could not lock '%s'\"), filename);\n\nMinor: unable_to_lock_message() can provide a more detailed\nexplanation of the failure.\n\n> +\n> +       if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n> +               return error_errno(_(\"could not read '%s'\"), filename);\n> +       strbuf_complete(&buf, '\\n');\n> +       va_start(ap, fmt);\n> +       strbuf_vaddf(&buf, fmt, ap);\n> +       va_end(ap);\n\nWould it make sense to also\n\n    strbuf_complete(&buf, '\\n')\n\nhere, as well, to be a bit more robust against lazy callers?\n\n> +\n> +       if (write_in_full(fd, buf.buf, buf.len) < 0) {\n> +               rollback_lock_file(&lock);\n> +               return error_errno(_(\"could not write to '%s'\"), filename);\n\nReading lockfile.h & tempfile.c, I see that rollback_lock_file()\nclobbers write_in_full()'s errno before error_errno() is called.\n\n> +       }\n> +       if (commit_lock_file(&lock) < 0) {\n> +               rollback_lock_file(&lock);\n> +               return error(_(\"failed to finalize '%s'\"), filename);\n> +       }\n> +\n> +       return 0;\n> +}\n> +\n> +static int do_reset(const char *name, int len)\n> +{\n> +       [...]\n> +       strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +       if (get_oid(ref_name.buf, &oid) &&\n> +           get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> +               error(_(\"could not read '%s'\"), ref_name.buf);\n\nChecking my understanding: The two get_oid() calls allow the argument\nto 'reset' to be a label created with the 'label' command or any other\nway to name an object, right? If so, then I wonder if the error\ninvocation should instead be:\n\n    error(_(\"could not read '%.*s'\"), len, name);\n\n> +               rollback_lock_file(&lock);\n> +               strbuf_release(&ref_name);\n> +               return -1;\n> +       }\n"},{"id":"337795","messageId":"CAGZ79kYVa1HFG=tP6gJjH_epC_kwNSwjWmQ2gQ+JWzSksY6fSA@mail.gmail.com","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 00/10] rebase -i: offer to recreate merge commits","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-30T18:47:04Z","receivedAt":"2018-01-30T18:47:11Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jan 29, 2018 at 2:54 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> Once upon a time, I dreamt of an interactive rebase that would not\n> flatten branch structure, but instead recreate the commit topology\n> faithfully.\n>\n> My original attempt was --preserve-merges, but that design was so\n> limited that I did not even enable it in interactive mode.\n>\n> Subsequently, it *was* enabled in interactive mode, with the predictable\n> consequences: as the --preserve-merges design does not allow for\n> specifying the parents of merge commits explicitly, all the new commits'\n> parents are defined *implicitly* by the previous commit history, and\n> hence it is *not possible to even reorder commits*.\n>\n> This design flaw cannot be fixed. Not without a complete re-design, at\n> least. This patch series offers such a re-design.\n>\n> Think of --recreate-merges as \"--preserve-merges done right\". It\n> introduces new verbs for the todo list, `label`, `reset` and `merge`.\n> For a commit topology like this:\n>\n>             A - B - C\n>               \\   /\n>                 D\n>\n> the generated todo list would look like this:\n>\n>             # branch D\n>             pick 0123 A\n>             label branch-point\n>             pick 1234 D\n>             label D\n>\n>             reset branch-point\n>             pick 2345 B\n>             merge 3456 D C\n>\n> There are more patches in the pipeline, based on this patch series, but\n> left for later in the interest of reviewable patch series: one mini\n> series to use the sequencer even for `git rebase -i --root`, and another\n> one to add support for octopus merges to --recreate-merges.\n>\n> Changes since v1:\n>\n> - reintroduced \"sequencer: make refs generated by the `label` command\n>   worktree-local\" (which was squashed into \"sequencer: handle autosquash\n>   and post-rewrite for merge commands\" by accident)\n>\n> - got rid of the universally-hated `bud` command\n\nSorry if you got the impression for that. Maybe I was imprecise.\nI had no strong opinion one way or another, I merely pointed out the\ncollision in abbreviation letters with the potential new 'break', IIRC.\n\n'bud' was a special case for resetting to a specific revision\n(and labeling it?)\n\nMaybe we can have default labels, such that there is no need to reset\nto the first revision manually, but can just use these defaults in the merge.\n(I haven't thought about this in the big picture, just food for thought)\n\n\n>\n> - as per Stefan's suggestion, the help blurb at the end of the todo list\n>   now lists the syntax\n>\n> - the no-rebase-cousins mode was made the default; This not only reflects\n>   the experience won from those years of using the Git garden shears, but\n>   was also deemed the better default in the discussion on the PR at\n>   https://github.com/git/git/pull/447\n>\n> - I tried to clarify the role of the `onto` label in the commit message of\n>   `rebase-helper --make-script: introduce a flag to recreate merges`\n>\n> - fixed punctuation at the end of error(...) messages, and incorrect\n>   upper-case at the start\n>\n> - changed the generated todo lists to separate the label and the oneline in\n>   the `reset` command with a `#`, for readability\n>\n> - dropped redundant paragraph in the commit message that talked about\n>   support for octopus merges\n>\n> - avoided empty error message when HEAD could not be read during do_label()\n>\n> - merge commits are fast-forwarded only unless --force-rebase was passed\n>\n> - do_merge() now errors out a lot earlier when HEAD could not be parsed\n>\n> - the one-letter variables to hold either abbreviated or full todo list\n>   instructions in make_script_recreating_merges() were renamed to clearer\n>   names\n>\n> - The description of rebase's --recreate-merge option has been reworded;\n>   Hopefully it is a lot more clear now.\n>\n>\n> Johannes Schindelin (9):\n>   sequencer: introduce new commands to reset the revision\n>   sequencer: introduce the `merge` command\n>   sequencer: fast-forward merge commits, if possible\n>   rebase-helper --make-script: introduce a flag to recreate merges\n>   rebase: introduce the --recreate-merges option\n>   sequencer: make refs generated by the `label` command worktree-local\n>   sequencer: handle autosquash and post-rewrite for merge commands\n>   pull: accept --rebase=recreate to recreate the branch topology\n>   rebase -i: introduce --recreate-merges=[no-]rebase-cousins\n>\n> Stefan Beller (1):\n>   git-rebase--interactive: clarify arguments\n\nNo need to honor me with authorship, as I just wrote\nthat patch in a quick hurry to express the idea.\nBut this is fine, too.\n\nThe interdiff looks good to me, I'll review the patches now.\n\nThanks,\nStefan\n"},{"id":"337802","messageId":"CAGZ79kYK9H88HRLKBFs+OatYMVVqmc1WOsTbaUbaK9yW0Sqqrg@mail.gmail.com","threadId":"47635","inReplyTo":"36281a14bc9f9a53e6c1bead561e3c93744047a8.1517266437.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 02/10] sequencer: introduce new commands to reset the revision","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-30T20:17:01Z","receivedAt":"2018-01-30T20:17:08Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jan 29, 2018 at 2:54 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> In the upcoming commits, we will teach the sequencer to recreate merges.\n> This will be done in a very different way from the unfortunate design of\n> `git rebase --preserve-merges` (which does not allow for reordering\n> commits, or changing the branch topology).\n>\n> The main idea is to introduce new todo list commands, to support\n> labeling the current revision with a given name, resetting the current\n> revision to a previous state, merging labeled revisions.\n>\n> This idea was developed in Git for Windows' Git garden shears (that are\n> used to maintain the \"thicket of branches\" on top of upstream Git), and\n> this patch is part of the effort to make it available to a wider\n> audience, as well as to make the entire process more robust (by\n> implementing it in a safe and portable language rather than a Unix shell\n> script).\n>\n> This commit implements the commands to label, and to reset to, given\n> revisions. The syntax is:\n>\n>         label <name>\n>         reset <name>\n>\n> Internally, the `label <name>` command creates the ref\n> `refs/rewritten/<name>`. This makes it possible to work with the labeled\n> revisions interactively, or in a scripted fashion (e.g. via the todo\n> list command `exec`).\n>\n> Later in this patch series, we will mark the `refs/rewritten/` refs as\n> worktree-local, to allow for interactive rebases to be run in parallel in\n> worktrees linked to the same repository.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  git-rebase--interactive.sh |   2 +\n>  sequencer.c                | 180 ++++++++++++++++++++++++++++++++++++++++++++-\n>  2 files changed, 179 insertions(+), 3 deletions(-)\n>\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index fcedece1860..7e5281e74aa 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n>  f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n>  x, exec <commit> = run command (the rest of the line) using shell\n>  d, drop <commit> = remove commit\n> +l, label <label> = label current HEAD with a name\n> +t, reset <label> = reset HEAD to a label\n>\n>  These lines can be re-ordered; they are executed from top to bottom.\n>  \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 4d3f60594cb..92ca8d2adee 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -21,6 +21,8 @@\n>  #include \"log-tree.h\"\n>  #include \"wt-status.h\"\n>  #include \"hashmap.h\"\n> +#include \"unpack-trees.h\"\n> +#include \"worktree.h\"\n>\n>  #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>\n> @@ -116,6 +118,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n>  static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n>  static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n>         \"rebase-merge/rewritten-pending\")\n> +\n> +/*\n> + * The path of the file listing refs that need to be deleted after the rebase\n> + * finishes. This is used by the `merge` command.\n> + */\n\nSo this file contains (label -> commit), which is appended in do_label,\nit uses refs to store the commits in refs/rewritten.\nWe do not have to worry about the contents of that file getting too long,\nor label re-use, because the directory containing all these helper files will\nbe deleted upon successful rebase in `sequencer_remove_state()`.\n\n\n\n> +static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n> +\n>  /*\n>   * The following files are written by git-rebase just after parsing the\n>   * command-line (and are only consumed, not modified, by the sequencer).\n> @@ -767,6 +776,8 @@ enum todo_command {\n>         TODO_SQUASH,\n>         /* commands that do something else than handling a single commit */\n>         TODO_EXEC,\n> +       TODO_LABEL,\n> +       TODO_RESET,\n>         /* commands that do nothing but are counted for reporting progress */\n>         TODO_NOOP,\n>         TODO_DROP,\n> @@ -785,6 +796,8 @@ static struct {\n>         { 'f', \"fixup\" },\n>         { 's', \"squash\" },\n>         { 'x', \"exec\" },\n> +       { 'l', \"label\" },\n> +       { 't', \"reset\" },\n>         { 0,   \"noop\" },\n>         { 'd', \"drop\" },\n>         { 0,   NULL }\n> @@ -1253,7 +1266,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>                 if (skip_prefix(bol, todo_command_info[i].str, &bol)) {\n>                         item->command = i;\n>                         break;\n> -               } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {\n> +               } else if ((bol + 1 == eol || bol[1] == ' ') &&\n> +                          *bol == todo_command_info[i].c) {\n>                         bol++;\n>                         item->command = i;\n>                         break;\n> @@ -1279,7 +1293,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>                 return error(_(\"missing arguments for %s\"),\n>                              command_to_string(item->command));\n>\n> -       if (item->command == TODO_EXEC) {\n> +       if (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n> +           item->command == TODO_RESET) {\n>                 item->commit = NULL;\n>                 item->arg = bol;\n>                 item->arg_len = (int)(eol - bol);\n> @@ -1919,6 +1934,144 @@ static int do_exec(const char *command_line)\n>         return status;\n>  }\n>\n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> +       va_list ap;\n> +       struct lock_file lock = LOCK_INIT;\n> +       int fd = hold_lock_file_for_update(&lock, filename, 0);\n> +       struct strbuf buf = STRBUF_INIT;\n> +\n> +       if (fd < 0)\n> +               return error_errno(_(\"could not lock '%s'\"), filename);\n> +\n> +       if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n> +               return error_errno(_(\"could not read '%s'\"), filename);\n> +       strbuf_complete(&buf, '\\n');\n> +       va_start(ap, fmt);\n> +       strbuf_vaddf(&buf, fmt, ap);\n> +       va_end(ap);\n> +\n> +       if (write_in_full(fd, buf.buf, buf.len) < 0) {\n> +               rollback_lock_file(&lock);\n> +               return error_errno(_(\"could not write to '%s'\"), filename);\n> +       }\n> +       if (commit_lock_file(&lock) < 0) {\n> +               rollback_lock_file(&lock);\n> +               return error(_(\"failed to finalize '%s'\"), filename);\n> +       }\n> +\n> +       return 0;\n> +}\n> +\n> +static int do_label(const char *name, int len)\n> +{\n> +       struct ref_store *refs = get_main_ref_store();\n> +       struct ref_transaction *transaction;\n> +       struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n> +       struct strbuf msg = STRBUF_INIT;\n> +       int ret = 0;\n> +       struct object_id head_oid;\n> +\n> +       strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +       strbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n> +\n> +       transaction = ref_store_transaction_begin(refs, &err);\n> +       if (!transaction) {\n> +               error(\"%s\", err.buf);\n> +               ret = -1;\n> +       } else if (get_oid(\"HEAD\", &head_oid)) {\n> +               error(_(\"could not read HEAD\"));\n> +               ret = -1;\n> +       } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n> +                                         NULL, 0, msg.buf, &err) < 0 ||\n> +                  ref_transaction_commit(transaction, &err)) {\n> +               error(\"%s\", err.buf);\n> +               ret = -1;\n> +       }\n> +       ref_transaction_free(transaction);\n> +       strbuf_release(&err);\n> +       strbuf_release(&msg);\n> +\n> +       if (!ret)\n> +               ret = safe_append(rebase_path_refs_to_delete(),\n> +                                 \"%s\\n\", ref_name.buf);\n> +       strbuf_release(&ref_name);\n> +\n> +       return ret;\n> +}\n> +\n> +static int do_reset(const char *name, int len)\n> +{\n> +       struct strbuf ref_name = STRBUF_INIT;\n> +       struct object_id oid;\n> +       struct lock_file lock = LOCK_INIT;\n> +       struct tree_desc desc;\n> +       struct tree *tree;\n> +       struct unpack_trees_options opts;\n> +       int ret = 0, i;\n> +\n> +       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> +               return -1;\n> +\n> +       /* Determine the length of the label */\n> +       for (i = 0; i < len; i++)\n> +               if (isspace(name[i]))\n> +                       len = i;\n> +\n> +       strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +       if (get_oid(ref_name.buf, &oid) &&\n> +           get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> +               error(_(\"could not read '%s'\"), ref_name.buf);\n> +               rollback_lock_file(&lock);\n> +               strbuf_release(&ref_name);\n> +               return -1;\n> +       }\n> +\n> +       memset(&opts, 0, sizeof(opts));\n> +       opts.head_idx = 1;\n> +       opts.src_index = &the_index;\n> +       opts.dst_index = &the_index;\n> +       opts.fn = oneway_merge;\n> +       opts.merge = 1;\n> +       opts.update = 1;\n> +       opts.reset = 1;\n> +\n> +       read_cache_unmerged();\n\nIn read-tree.c merge.c and pull.c we guard this conditionally\nand use die_resolve_conflict to bail out. In am.c we do not.\n\nI think we'd want to guard it here, too?\n\nConstructing an instruction sheet that produces a merge\nconflict just before the reset is a bit artificial, but still:\n\n    label onto\n    pick abc\n    exec false # run \"git merge out-of-rebase-merge\"\n        # manually to produce a conflict\n    reset onto # we want to stop here telling the user to fix it.\n\n> +       if (!fill_tree_descriptor(&desc, &oid)) {\n> +               error(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n> +               rollback_lock_file(&lock);\n> +               free((void *)desc.buffer);\n> +               strbuf_release(&ref_name);\n> +               return -1;\n> +       }\n> +\n> +       if (unpack_trees(1, &desc, &opts)) {\n> +               rollback_lock_file(&lock);\n> +               free((void *)desc.buffer);\n> +               strbuf_release(&ref_name);\n> +               return -1;\n> +       }\n> +\n> +       tree = parse_tree_indirect(&oid);\n> +       prime_cache_tree(&the_index, tree);\n> +\n> +       if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n> +               ret = error(_(\"could not write index\"));\n> +       free((void *)desc.buffer);\n\nFor most newer structs we have a {release, clear, free}_X,\nbut for tree_desc's this seems to be the convention to avoid memleaks.\n\nThanks,\nStefan\n"},{"id":"337827","messageId":"xmqqo9lbrput.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 00/10] rebase -i: offer to recreate merge commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-30T21:36:26Z","receivedAt":"2018-01-30T21:36:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Changes since v1:\n>\n> - reintroduced \"sequencer: make refs generated by the `label` command\n>   worktree-local\" (which was squashed into \"sequencer: handle autosquash\n>   and post-rewrite for merge commands\" by accident)\n\nGood.\n\n> - got rid of the universally-hated `bud` command\n\nUniversally is a bit too strong a word, unless you want to hint that\nyou are specifically ignoring my input ;-).\n\n> - the no-rebase-cousins mode was made the default\n\nAlthough I lack first-hand experience with this implementation, this\ndesign decision matches my instinct.\n\nMay comment on individual patches separately, later.\n\nThanks.\n"},{"id":"337952","messageId":"nycvar.QRO.7.76.6.1801311407190.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAGZ79kYVa1HFG=tP6gJjH_epC_kwNSwjWmQ2gQ+JWzSksY6fSA@mail.gmail.com","subject":"Re: [PATCH v2 00/10] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-31T13:08:05Z","receivedAt":"2018-01-31T13:08:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Tue, 30 Jan 2018, Stefan Beller wrote:\n\n> > - got rid of the universally-hated `bud` command\n> \n> Sorry if you got the impression for that. Maybe I was imprecise.\n\nYou were not the most vocal voice. Anyway, `bud` is gone now.\n\n> > Stefan Beller (1):\n> >   git-rebase--interactive: clarify arguments\n> \n> No need to honor me with authorship, as I just wrote\n> that patch in a quick hurry to express the idea.\n\nYou wrote it.\n\n> The interdiff looks good to me, I'll review the patches now.\n\nThanks,\nDscho\n"},{"id":"337953","messageId":"nycvar.QRO.7.76.6.1801311411040.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAGZ79kYK9H88HRLKBFs+OatYMVVqmc1WOsTbaUbaK9yW0Sqqrg@mail.gmail.com","subject":"Re: [PATCH v2 02/10] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-31T13:21:44Z","receivedAt":"2018-01-31T13:21:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Tue, 30 Jan 2018, Stefan Beller wrote:\n\n> On Mon, Jan 29, 2018 at 2:54 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > @@ -116,6 +118,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n> >  static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n> >  static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n> >         \"rebase-merge/rewritten-pending\")\n> > +\n> > +/*\n> > + * The path of the file listing refs that need to be deleted after the rebase\n> > + * finishes. This is used by the `merge` command.\n> > + */\n\nWhoops. The comment \"This is used by the `merge` command`\" is completely\nwrong. Will fix.\n\n> So this file contains (label -> commit),\n\nOnly `label`. No `commit`.\n\n> which is appended in do_label, it uses refs to store the commits in\n> refs/rewritten.  We do not have to worry about the contents of that file\n> getting too long, or label re-use, because the directory containing all\n> these helper files will be deleted upon successful rebase in\n> `sequencer_remove_state()`.\n\nYes.\n\n> > +static int do_reset(const char *name, int len)\n> > +{\n> > +       struct strbuf ref_name = STRBUF_INIT;\n> > +       struct object_id oid;\n> > +       struct lock_file lock = LOCK_INIT;\n> > +       struct tree_desc desc;\n> > +       struct tree *tree;\n> > +       struct unpack_trees_options opts;\n> > +       int ret = 0, i;\n> > +\n> > +       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> > +               return -1;\n> > +\n> > +       /* Determine the length of the label */\n> > +       for (i = 0; i < len; i++)\n> > +               if (isspace(name[i]))\n> > +                       len = i;\n> > +\n> > +       strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> > +       if (get_oid(ref_name.buf, &oid) &&\n> > +           get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> > +               error(_(\"could not read '%s'\"), ref_name.buf);\n> > +               rollback_lock_file(&lock);\n> > +               strbuf_release(&ref_name);\n> > +               return -1;\n> > +       }\n> > +\n> > +       memset(&opts, 0, sizeof(opts));\n> > +       opts.head_idx = 1;\n> > +       opts.src_index = &the_index;\n> > +       opts.dst_index = &the_index;\n> > +       opts.fn = oneway_merge;\n> > +       opts.merge = 1;\n> > +       opts.update = 1;\n> > +       opts.reset = 1;\n> > +\n> > +       read_cache_unmerged();\n> \n> In read-tree.c merge.c and pull.c we guard this conditionally\n> and use die_resolve_conflict to bail out. In am.c we do not.\n> \n> I think we'd want to guard it here, too?\n\nYes.\n\n> Constructing an instruction sheet that produces a merge\n> conflict just before the reset is a bit artificial, but still:\n> \n>     label onto\n>     pick abc\n>     exec false # run \"git merge out-of-rebase-merge\"\n>         # manually to produce a conflict\n\nThis would fail already, as `exec` tests for a clean index after the\noperation ran.\n\n>     reset onto # we want to stop here telling the user to fix it.\n\nBut you are absolutely right that we still need to fix it.\n\n> > +       if (!fill_tree_descriptor(&desc, &oid)) {\n> > +               error(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n> > +               rollback_lock_file(&lock);\n> > +               free((void *)desc.buffer);\n> > +               strbuf_release(&ref_name);\n> > +               return -1;\n> > +       }\n> > +\n> > +       if (unpack_trees(1, &desc, &opts)) {\n> > +               rollback_lock_file(&lock);\n> > +               free((void *)desc.buffer);\n> > +               strbuf_release(&ref_name);\n> > +               return -1;\n> > +       }\n> > +\n> > +       tree = parse_tree_indirect(&oid);\n> > +       prime_cache_tree(&the_index, tree);\n> > +\n> > +       if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n> > +               ret = error(_(\"could not write index\"));\n> > +       free((void *)desc.buffer);\n> \n> For most newer structs we have a {release, clear, free}_X,\n> but for tree_desc's this seems to be the convention to avoid memleaks.\n\nYep, this code is just copy-edited from elsewhere. It seemed to be\ndifferent enough from the (very generic) use case in builtin/reset.c that\nI did not think refactoring this into a convenience function in\nunpack-trees.[ch] would make sense.\n\nCiao,\nDscho\n"},{"id":"337954","messageId":"nycvar.QRO.7.76.6.1801311424270.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqo9lbrput.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 00/10] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-31T13:29:42Z","receivedAt":"2018-01-31T13:29:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 30 Jan 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Changes since v1:\n> >\n> > - reintroduced \"sequencer: make refs generated by the `label` command\n> >   worktree-local\" (which was squashed into \"sequencer: handle autosquash\n> >   and post-rewrite for merge commands\" by accident)\n> \n> Good.\n> \n> > - got rid of the universally-hated `bud` command\n> \n> Universally is a bit too strong a word, unless you want to hint that\n> you are specifically ignoring my input ;-).\n\nIn the interest of comic effect, I exaggerated a little.\n\n> > - the no-rebase-cousins mode was made the default\n> \n> Although I lack first-hand experience with this implementation, this\n> design decision matches my instinct.\n\nExcellent.\n\n> May comment on individual patches separately, later.\n\nI think I may want to introduce a bigger change, still. I forgot who\nexactly came up with the suggestion to use `merge -C <original-commit>\n<to-merge>` (I think it was Jake), and I reacted too forcefully in\nrejecting it.\n\nThis design had been my original design in the Git garden shears, and I\ndid not like it because it felt clunky and it also broke the style of\n<command> <commit>.\n\nBut the longer I think about this, the more I come to the conclusion that\nI was wrong, and that the -C way is the way that leaves the door open to\nthe pretty elegant `-c <commit>` (imitating `git commit`'s option to\nborrow the commit message from elsewhere but still allowing to edit it).\n\nAnd it also leaves open the door to just write `merge <to-merge>` and have\nthe sequencer come up with a default merge message that the user can then\nedit.\n\nI'll probably refrain from implementing support for -m because I do not\nwant to implement the dequoting.\n\nCiao,\nDscho\n"},{"id":"337955","messageId":"nycvar.QRO.7.76.6.1801311441430.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801292230550.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-01-31T13:48:48Z","receivedAt":"2018-01-31T13:49:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake & Phillip,\n\nOn Mon, 29 Jan 2018, Johannes Schindelin wrote:\n\n> On Sat, 20 Jan 2018, Jacob Keller wrote:\n> \n> > On Fri, Jan 19, 2018 at 6:45 AM, Phillip Wood <phillip.wood@talktalk.net> wrote:\n> > > On 18/01/18 15:35, Johannes Schindelin wrote:\n> > >>\n> > >> This patch adds the `merge` command, with the following syntax:\n> > >>\n> > >>       merge <commit> <rev> <oneline>\n> > >\n> > > I'm concerned that this will be confusing for users. All of the other\n> > > rebase commands replay the changes in the commit hash immediately\n> > > following the command name. This command instead uses the first\n> > > commit to specify the message which is different to both 'git merge'\n> > > and the existing rebase commands. I wonder if it would be clearer to\n> > > have 'merge -C <commit> <rev> ...' instead so it's clear which\n> > > argument specifies the message and which the remote head to merge.\n> > > It would also allow for 'merge -c <commit> <rev> ...' in the future\n> > > for rewording an existing merge message and also avoid the slightly\n> > > odd 'merge - <rev> ...'. Where it's creating new merges I'm not sure\n> > > it's a good idea to encourage people to only have oneline commit\n> > > messages by making it harder to edit them, perhaps it could take\n> > > another argument to mean open the editor or not, though as Jake said\n> > > I guess it's not that common.\n> > \n> > I actually like the idea of re-using commit message options like -C,\n> > -c,  and -m, so we could do:\n> > \n> > merge -C <commit> ... to take message from commit\n> \n> That is exactly how the Git garden shears do it.\n> \n> I found it not very readable. That is why I wanted to get away from it in\n> --recreate-merges.\n\nI made up my mind. Even if it is not very readable, it is still better\nthan the `merge A B` where the order of A and B magically determines their\nrespective roles.\n\n> > merge -c <commit> ...  to take the message from commit and open editor to edit\n> > merge -m \"<message>\" ... to take the message from the quoted test\n> > merge ... to merge and open commit editor with default message\n\nI will probably implement -c, but not -m, and will handle the absence of\nthe -C and -c options to construct a default merge message which can then\nbe edited.\n\nThe -m option just opens such a can of worms with dequoting, that's why I\ndo not want to do that.\n\nBTW I am still trying to figure out how to present the oneline of the\ncommit to merge (which is sometimes really helpful because the label might\nbe less than meaningful) while *still* allowing for octopus merges.\n\nSo far, what I have is this:\n\n\tmerge <original> <to-merge> <oneline>\n\nand for octopus:\n\n\tmerge <original> \"<to-merge> <to-merge2>...\" <oneline>...\n\nI think with the -C syntax, it would become something like\n\n\tmerge -C <original> <to-merge> # <oneline>\n\nand\n\n\tmerge -C <original> <to-merge> <to-merge2>...\n\t# Merging: <oneline>\n\t# Merging: <oneline2>\n\t# ...\n\nThe only qualm I have about this is that `#` really *is* a valid ref name.\n(Seriously, it is...). So that would mean that I'd have to disallow `#`\nas a label specificially.\n\nThoughts?\n\nCiao,\nDscho\n"},{"id":"337968","messageId":"640c350d-090c-ad0f-e6be-fc577bd184c2@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801311441430.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-01-31T17:58:53Z","receivedAt":"2018-01-31T17:59:03Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 31/01/18 13:48, Johannes Schindelin wrote:\n> Hi Jake & Phillip,\n> \n> On Mon, 29 Jan 2018, Johannes Schindelin wrote:\n> \n>> On Sat, 20 Jan 2018, Jacob Keller wrote:\n>>\n>>> On Fri, Jan 19, 2018 at 6:45 AM, Phillip Wood <phillip.wood@talktalk.net> wrote:\n>>>> On 18/01/18 15:35, Johannes Schindelin wrote:\n>>>>>\n>>>>> This patch adds the `merge` command, with the following syntax:\n>>>>>\n>>>>>       merge <commit> <rev> <oneline>\n>>>>\n>>>> I'm concerned that this will be confusing for users. All of the other\n>>>> rebase commands replay the changes in the commit hash immediately\n>>>> following the command name. This command instead uses the first\n>>>> commit to specify the message which is different to both 'git merge'\n>>>> and the existing rebase commands. I wonder if it would be clearer to\n>>>> have 'merge -C <commit> <rev> ...' instead so it's clear which\n>>>> argument specifies the message and which the remote head to merge.\n>>>> It would also allow for 'merge -c <commit> <rev> ...' in the future\n>>>> for rewording an existing merge message and also avoid the slightly\n>>>> odd 'merge - <rev> ...'. Where it's creating new merges I'm not sure\n>>>> it's a good idea to encourage people to only have oneline commit\n>>>> messages by making it harder to edit them, perhaps it could take\n>>>> another argument to mean open the editor or not, though as Jake said\n>>>> I guess it's not that common.\n>>>\n>>> I actually like the idea of re-using commit message options like -C,\n>>> -c,  and -m, so we could do:\n>>>\n>>> merge -C <commit> ... to take message from commit\n>>\n>> That is exactly how the Git garden shears do it.\n>>\n>> I found it not very readable. That is why I wanted to get away from it in\n>> --recreate-merges.\n> \n> I made up my mind. Even if it is not very readable, it is still better\n> than the `merge A B` where the order of A and B magically determines their\n> respective roles.\n> \n>>> merge -c <commit> ...  to take the message from commit and open editor to edit\n>>> merge -m \"<message>\" ... to take the message from the quoted test\n>>> merge ... to merge and open commit editor with default message\n> \n> I will probably implement -c, but not -m, and will handle the absence of\n> the -C and -c options to construct a default merge message which can then\n> be edited.\n\nThat sounds like a good plan (-c can always be added later), I'm really\npleased you changed your mind on this, having the -C may be a bit ugly\nbut I think it is valuable to have some way of distinguishing the\nmessage commit from the merge heads.\n\n> The -m option just opens such a can of worms with dequoting, that's why I\n> do not want to do that.\n> \n> BTW I am still trying to figure out how to present the oneline of the\n> commit to merge (which is sometimes really helpful because the label might\n> be less than meaningful) while *still* allowing for octopus merges.\n> \n> So far, what I have is this:\n> \n> \tmerge <original> <to-merge> <oneline>\n> \n> and for octopus:\n> \n> \tmerge <original> \"<to-merge> <to-merge2>...\" <oneline>...\n> \n> I think with the -C syntax, it would become something like\n> \n> \tmerge -C <original> <to-merge> # <oneline>\n> \n> and\n> \n> \tmerge -C <original> <to-merge> <to-merge2>...\n> \t# Merging: <oneline>\n> \t# Merging: <oneline2>\n> \t# ...\n> \n> The only qualm I have about this is that `#` really *is* a valid ref name.\n> (Seriously, it is...). So that would mean that I'd have to disallow `#`\n> as a label specificially.\n> \n> Thoughts?\n\nAs ':' is not a valid ref if you want a separator you could have\n\n\tmerge -C <original> <to-merge> : <oneline>\n\npersonally I'm not sure what value having a separator adds in this case.\nI think in the octopus case have a separate comment line for the subject\nof each merge head is a good idea - maybe the two head merge could just\nhave the subject of the remote head in a comment below. I wonder if\nhaving the subject of the commit that is going to be used for the\nmessage may be a useful prompt in some cases but that's just making\nthings more complicated.\n\nBest Wishes\n\nPhillip\n\n> Ciao,\n> Dscho\n> \n\n"},{"id":"337969","messageId":"cba8d5dc-f42d-8412-c471-edb5a860b3ae@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801311411040.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v2 02/10] sequencer: introduce new commands to reset therevision","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-01-31T18:02:20Z","receivedAt":"2018-01-31T18:02:28Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 31/01/18 13:21, Johannes Schindelin wrote:\n> \n> Hi Stefan,\n> \n> On Tue, 30 Jan 2018, Stefan Beller wrote:\n> \n>> On Mon, Jan 29, 2018 at 2:54 PM, Johannes Schindelin\n>> <johannes.schindelin@gmx.de> wrote:\n>>> @@ -116,6 +118,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n>>>  static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n>>>  static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n>>>         \"rebase-merge/rewritten-pending\")\n>>> +\n>>> +/*\n>>> + * The path of the file listing refs that need to be deleted after the rebase\n>>> + * finishes. This is used by the `merge` command.\n>>> + */\n> \n> Whoops. The comment \"This is used by the `merge` command`\" is completely\n> wrong. Will fix.\n> \n>> So this file contains (label -> commit),\n> \n> Only `label`. No `commit`.\n> \n>> which is appended in do_label, it uses refs to store the commits in\n>> refs/rewritten.  We do not have to worry about the contents of that file\n>> getting too long, or label re-use, because the directory containing all\n>> these helper files will be deleted upon successful rebase in\n>> `sequencer_remove_state()`.\n> \n> Yes.\n>\nIt might be a good idea to have 'git rebase --abort' delete the refs as\nwell as the file though\n\nBest Wishes\n\nPhillip\n"},{"id":"338005","messageId":"CA+P7+xor7xf5WPSeT+p4cGiaCnAazGHFHg5ago=fWVe9d2sk+w@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801311424270.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v2 00/10] rebase -i: offer to recreate merge commits","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-02-01T06:37:12Z","receivedAt":"2018-02-01T06:37:39Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Wed, Jan 31, 2018 at 5:29 AM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> I think I may want to introduce a bigger change, still. I forgot who\n> exactly came up with the suggestion to use `merge -C <original-commit>\n> <to-merge>` (I think it was Jake), and I reacted too forcefully in\n> rejecting it.\n>\n\nI believe someone else suggested it, but I replied that I liked it.\n\n> This design had been my original design in the Git garden shears, and I\n> did not like it because it felt clunky and it also broke the style of\n> <command> <commit>.\n>\n\nI agree it's a bit weird it breaks the style of \"<command> <commit>\",\nbut on some level merge does this anyways as it's the first one to\ntake more than one argument.\n\n> But the longer I think about this, the more I come to the conclusion that\n> I was wrong, and that the -C way is the way that leaves the door open to\n> the pretty elegant `-c <commit>` (imitating `git commit`'s option to\n> borrow the commit message from elsewhere but still allowing to edit it).\n>\n\nThe other reason I liked this, is that it matches merge syntax on the\ncommand line, so users don't need to learn a special new syntax for\nthe todo file.\n\n> And it also leaves open the door to just write `merge <to-merge>` and have\n> the sequencer come up with a default merge message that the user can then\n> edit.\n\nI like that we could completely forgo the -C and -c in order to allow\nthe normal default merge commit message that is auto generated as\nwell.\n\n>\n> I'll probably refrain from implementing support for -m because I do not\n> want to implement the dequoting.\n>\n\nYea, I don't think that is necessary either.\n\nThanks,\nJake\n\n> Ciao,\n> Dscho\n"},{"id":"338006","messageId":"CA+P7+xoau5Zq0ZjR5LXPocADU1AM9nW0d1Gsk8+_s4_MMu9uOg@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1801311441430.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 2/8] sequencer: introduce the `merge` command","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-02-01T06:40:07Z","receivedAt":"2018-02-01T06:40:33Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Wed, Jan 31, 2018 at 5:48 AM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Hi Jake & Phillip,\n>\n> On Mon, 29 Jan 2018, Johannes Schindelin wrote:\n>\n>> On Sat, 20 Jan 2018, Jacob Keller wrote:\n>>\n>> > On Fri, Jan 19, 2018 at 6:45 AM, Phillip Wood <phillip.wood@talktalk.net> wrote:\n>> > > On 18/01/18 15:35, Johannes Schindelin wrote:\n>> > >>\n>> > >> This patch adds the `merge` command, with the following syntax:\n>> > >>\n>> > >>       merge <commit> <rev> <oneline>\n>> > >\n>> > > I'm concerned that this will be confusing for users. All of the other\n>> > > rebase commands replay the changes in the commit hash immediately\n>> > > following the command name. This command instead uses the first\n>> > > commit to specify the message which is different to both 'git merge'\n>> > > and the existing rebase commands. I wonder if it would be clearer to\n>> > > have 'merge -C <commit> <rev> ...' instead so it's clear which\n>> > > argument specifies the message and which the remote head to merge.\n>> > > It would also allow for 'merge -c <commit> <rev> ...' in the future\n>> > > for rewording an existing merge message and also avoid the slightly\n>> > > odd 'merge - <rev> ...'. Where it's creating new merges I'm not sure\n>> > > it's a good idea to encourage people to only have oneline commit\n>> > > messages by making it harder to edit them, perhaps it could take\n>> > > another argument to mean open the editor or not, though as Jake said\n>> > > I guess it's not that common.\n>> >\n>> > I actually like the idea of re-using commit message options like -C,\n>> > -c,  and -m, so we could do:\n>> >\n>> > merge -C <commit> ... to take message from commit\n>>\n>> That is exactly how the Git garden shears do it.\n>>\n>> I found it not very readable. That is why I wanted to get away from it in\n>> --recreate-merges.\n>\n> I made up my mind. Even if it is not very readable, it is still better\n> than the `merge A B` where the order of A and B magically determines their\n> respective roles.\n>\n>> > merge -c <commit> ...  to take the message from commit and open editor to edit\n>> > merge -m \"<message>\" ... to take the message from the quoted test\n>> > merge ... to merge and open commit editor with default message\n>\n> I will probably implement -c, but not -m, and will handle the absence of\n> the -C and -c options to construct a default merge message which can then\n> be edited.\n>\n> The -m option just opens such a can of worms with dequoting, that's why I\n> do not want to do that.\n>\n\nI agree, I don't see a need for \"-m\".\n\n> BTW I am still trying to figure out how to present the oneline of the\n> commit to merge (which is sometimes really helpful because the label might\n> be less than meaningful) while *still* allowing for octopus merges.\n>\n> So far, what I have is this:\n>\n>         merge <original> <to-merge> <oneline>\n>\n> and for octopus:\n>\n>         merge <original> \"<to-merge> <to-merge2>...\" <oneline>...\n>\n> I think with the -C syntax, it would become something like\n>\n>         merge -C <original> <to-merge> # <oneline>\n>\n\nI like this, especially given you added the \"#\" for one of the other\nnew commands as well, (reset I think?)\n\n> and\n>\n>         merge -C <original> <to-merge> <to-merge2>...\n>         # Merging: <oneline>\n>         # Merging: <oneline2>\n>         # ...\n>\n\nI really like this, since you can show each oneline for all the\nto-merges for an octopus.\n\n> The only qualm I have about this is that `#` really *is* a valid ref name.\n> (Seriously, it is...). So that would mean that I'd have to disallow `#`\n> as a label specifically.\n>\n> Thoughts?\n>\n\nI think it's fine to disallow # as a label.\n\nThanks,\nJake\n\n> Ciao,\n> Dscho\n"},{"id":"338588","messageId":"87k1vpqq85.fsf@javad.com","threadId":"47635","inReplyTo":"71c42d6d3bb240d90071d5afdde81d1293fdf0ab.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-07T06:16:26Z","receivedAt":"2018-02-07T06:16:38Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n[...]\n\n> +--recreate-merges::\n> +\tRecreate merge commits instead of flattening the history by replaying\n> +\tmerges. Merge conflict resolutions or manual amendments to merge\n> +\tcommits are not preserved.\n\nI wonder why you guys still hold on replaying \"merge-the-operation\"\ninstead of replaying \"merge-the-result\"? The latter, the merge commit\nitself, no matter how exactly it was created in the first place, is the\nmost valuable thing git keeps about the merge, and you silently drop it\nentirely! OTOH, git keeps almost no information about\n\"merge-the-operation\", so it's virtually impossible to reliably replay\nthe operation automatically, and yet you try to.\n\nIMHO that was severe mistake in the original --preserve-merges, and you\nbring with you to this new --recreate-merges... It's sad. Even more sad\nas solution is already known for years:\n\n    bc00341838a8faddcd101da9e746902994eef38a\n    Author: Johannes Sixt <j6t@kdbg.org>\n    Date:   Sun Jun 16 15:50:42 2013 +0200\n    \n        rebase -p --first-parent: redo merge by cherry-picking first-parent change\n\nand it works like a charm.\n\n-- Sergey\n\n"},{"id":"338591","messageId":"CA+P7+xqs=cEC+9LJ9VB4XZU-oS7CX6eQSNSGfPeg-whRCwU1Lg@mail.gmail.com","threadId":"47635","inReplyTo":"87k1vpqq85.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-02-07T07:26:11Z","receivedAt":"2018-02-07T07:26:37Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Tue, Feb 6, 2018 at 10:16 PM, Sergey Organov <sorganov@gmail.com> wrote:\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>\n> [...]\n>\n>> +--recreate-merges::\n>> +     Recreate merge commits instead of flattening the history by replaying\n>> +     merges. Merge conflict resolutions or manual amendments to merge\n>> +     commits are not preserved.\n>\n> I wonder why you guys still hold on replaying \"merge-the-operation\"\n> instead of replaying \"merge-the-result\"? The latter, the merge commit\n> itself, no matter how exactly it was created in the first place, is the\n> most valuable thing git keeps about the merge, and you silently drop it\n> entirely! OTOH, git keeps almost no information about\n> \"merge-the-operation\", so it's virtually impossible to reliably replay\n> the operation automatically, and yet you try to.\n>\n\nI'm not sure I follow what you mean here?\n\nYou mean that you'd want this to actually attempt to re-create the\noriginal merge including conflict resolutions by taking the contents\nof the result?\n\nHow do you handle if that result has conflicts? What UX do you present\nto the user to handle such conflicts? I don't think the normal 3-way\nconflicts would even be possible in this case?\n\nThanks,\nJake\n\n> IMHO that was severe mistake in the original --preserve-merges, and you\n> bring with you to this new --recreate-merges... It's sad. Even more sad\n> as solution is already known for years:\n>\n>     bc00341838a8faddcd101da9e746902994eef38a\n>     Author: Johannes Sixt <j6t@kdbg.org>\n>     Date:   Sun Jun 16 15:50:42 2013 +0200\n>\n>         rebase -p --first-parent: redo merge by cherry-picking first-parent change\n>\n> and it works like a charm.\n>\n> -- Sergey\n>\n"},{"id":"338592","messageId":"5b41a285-5c3d-52c8-23e0-3d8421bb3051@kdbg.org","threadId":"47635","inReplyTo":"87k1vpqq85.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2018-02-07T07:27:15Z","receivedAt":"2018-02-07T07:27:21Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 07.02.2018 um 07:16 schrieb Sergey Organov:\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> [...]\n> \n>> +--recreate-merges::\n>> +\tRecreate merge commits instead of flattening the history by replaying\n>> +\tmerges. Merge conflict resolutions or manual amendments to merge\n>> +\tcommits are not preserved.\n> \n> I wonder why you guys still hold on replaying \"merge-the-operation\"\n> instead of replaying \"merge-the-result\"? The latter, the merge commit\n> itself, no matter how exactly it was created in the first place, is the\n> most valuable thing git keeps about the merge, and you silently drop it\n> entirely! OTOH, git keeps almost no information about\n> \"merge-the-operation\", so it's virtually impossible to reliably replay\n> the operation automatically, and yet you try to.\n\nVery well put. I share your concerns.\n\n-- Hannes\n\n> IMHO that was severe mistake in the original --preserve-merges, and you\n> bring with you to this new --recreate-merges... It's sad. Even more sad\n> as solution is already known for years:\n> \n>      bc00341838a8faddcd101da9e746902994eef38a\n>      Author: Johannes Sixt <j6t@kdbg.org>\n>      Date:   Sun Jun 16 15:50:42 2013 +0200\n>      \n>          rebase -p --first-parent: redo merge by cherry-picking first-parent change\n> \n> and it works like a charm.\n> \n> -- Sergey\n"},{"id":"338598","messageId":"876079qggl.fsf@javad.com","threadId":"47635","inReplyTo":"CA+P7+xqs=cEC+9LJ9VB4XZU-oS7CX6eQSNSGfPeg-whRCwU1Lg@mail.gmail.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-07T09:47:22Z","receivedAt":"2018-02-07T09:47:33Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Jacob Keller <jacob.keller@gmail.com> writes:\n\n> On Tue, Feb 6, 2018 at 10:16 PM, Sergey Organov <sorganov@gmail.com> wrote:\n>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>>\n>> [...]\n>>\n>>> +--recreate-merges::\n>>> +     Recreate merge commits instead of flattening the history by replaying\n>>> +     merges. Merge conflict resolutions or manual amendments to merge\n>>> +     commits are not preserved.\n>>\n>> I wonder why you guys still hold on replaying \"merge-the-operation\"\n>> instead of replaying \"merge-the-result\"? The latter, the merge commit\n>> itself, no matter how exactly it was created in the first place, is the\n>> most valuable thing git keeps about the merge, and you silently drop it\n>> entirely! OTOH, git keeps almost no information about\n>> \"merge-the-operation\", so it's virtually impossible to reliably replay\n>> the operation automatically, and yet you try to.\n>>\n>\n> I'm not sure I follow what you mean here?\n>\n> You mean that you'd want this to actually attempt to re-create the\n> original merge including conflict resolutions by taking the contents\n> of the result?\n\nI mean just cherry-pick the merge the same way all other commits are\nessentially cherry-picked during rebase. That's what Johannes Sixt did\nin his patch I was reffering to.\n\n> How do you handle if that result has conflicts? What UX do you present\n> to the user to handle such conflicts? I don't think the normal 3-way\n> conflicts would even be possible in this case?\n\nNo problem here. It goes exactly the same way as for non-merge commits\nthat are being rebased. You can try it right now using\n\n$ git cherry-pick -m1 <merge_commit>\n\nthat will induce conflicts.\n\nThe (somewhat tricky) functional difference is only in recording correct\nadditional parents to the final commit, but that part is hidden from the\nuser.\n\n-- Sergey\n"},{"id":"338639","messageId":"nycvar.QRO.7.76.6.1802071818240.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87k1vpqq85.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-07T17:36:06Z","receivedAt":"2018-02-07T17:36:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 7 Feb 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> [...]\n> \n> > +--recreate-merges::\n> > +\tRecreate merge commits instead of flattening the history by replaying\n> > +\tmerges. Merge conflict resolutions or manual amendments to merge\n> > +\tcommits are not preserved.\n> \n> I wonder why you guys still hold on replaying \"merge-the-operation\"\n> instead of replaying \"merge-the-result\"?\n\nThis misses the point of rebasing: you want to replay the changes.\n\n> The latter, the merge commit itself, no matter how exactly it was\n> created in the first place, is the most valuable thing git keeps about\n> the merge, and you silently drop it entirely!\n\nYou miss another very crucial point. I don't blame you, as you certainly\nhave not used the Git garden shears for years.\n\nLet me explain the scenario which comes up plenty of times in my work with\nGit for Windows. We have a thicket of some 70 branches on top of git.git's\nlatest release. These branches often include fixup! and squash! commits\nand even more complicated constructs that rebase cannot handle at all at\nthe moment, such as reorder-before! and reorder-after! (for commits that\nreally need to go into a different branch).\n\nEven if you do not have such a complicated setup, it is quite possible\nthat you need to include a commit in your development that needs to be\ndropped before contributing your work. Think e.g. removing the `-O2` flag\nwhen compiling with GCC because GDB gets utterly confused with executables\ncompiled with `-O2` while single-stepping. This could be an initial commit\ncalled `TO-DROP` or some such.\n\nAnd guess what happens if you drop that `pick` line in your todo list and\nthen the `merge` command simply tries to re-create the original merge\ncommit's changes?\n\nExactly. The merge will become an evil merge, and will introduce that very\nmuch not-wanted and therefore-dropped changes.\n\n> OTOH, git keeps almost no information about \"merge-the-operation\", so\n> it's virtually impossible to reliably replay the operation\n> automatically, and yet you try to.\n\nThat is true. However, the intended use case is not to allow you to\nrecreate funny merges. Its use case is to allow you to recreate merges.\n\nAt a later stage, I might introduce support to detect `-s ours` merges,\nbecause they are easy to detect. But even then, it will be an opt-in.\n\n> IMHO that was severe mistake in the original --preserve-merges, and you\n> bring with you to this new --recreate-merges... It's sad.\n\nPlease refrain from drawing this discussion into an emotional direction.\nThat is definitely not helpful.\n\n> Even more sad as solution is already known for years:\n> \n>     bc00341838a8faddcd101da9e746902994eef38a\n>     Author: Johannes Sixt <j6t@kdbg.org>\n>     Date:   Sun Jun 16 15:50:42 2013 +0200\n>     \n>         rebase -p --first-parent: redo merge by cherry-picking first-parent change\n> \n> and it works like a charm.\n\nIt might work for you, as you probably used --preserve-merges, and dealt\nwith the fact that you could neither drop nor reorder commits.\n\nSo --preserve-merges --first-parent is probably what you were looking for.\n\nInstead, --recreate-merges is all about allowing the same level of freedom\nas with regular interactive rebases, but recreating the original commit\ntopology (and allowing to change it, too).\n\nTherefore, I think that it would be even harmful to allow\n--recreate-merges --first-parent *because it would cause evil merges*!\n\nAnd I totally could see myself being vexed again about options that worked\nperfectly well (just like --preserve-merges) being completely messed up by\nallowing it to be combined with options *that they cannot work with* (just\nlike --preserve-merges --interactive, a *huge* mistake causing so many\nannoying \"bug\" reports: I *never intended it that way because I knew it\nwould not work as users expect*).\n\nSo no, I do not think that --recreate-merges --first-parent is a good idea\nat all. Unless you try to do that non-interactively only, *and disallow it\nin interactive mode*. Because the entire point of the interactive rebase\nis to allow reordering and dropping commits, in --recreate-merges even\nmoving, introducing and dropping merge commits. The --first-parent option\nflies in the face of this idea.\n\nCiao,\nJohannes\n"},{"id":"338677","messageId":"1518044326.7000.4.camel@gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802071818240.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Øyvind Rønningstad","fromEmail":"ronningstad@gmail.com","sentAt":"2018-02-07T22:58:46Z","receivedAt":"2018-02-07T22:58:55Z","isPatch":true,"sender":{"key":"ronningstad@gmail.com","avatar":null},"body":"edit: Sending again, hopefully without HTML :). Sorry for spamming.\n\nHi, I think --recreate-merges is a very exciting feature.\n\nI've also been puzzled by why we can't just pick merge commits directly\nincluding\nconflict resolutions, so allow me to join the discussion.\n\nOn Wed, Feb 7, 2018 at 6:36 PM, Johannes Schindelin <Johannes.Schindeli\nn@gmx.de> wrote:\n>\n> Hi,\n>\n> [...]\n>\n> And guess what happens if you drop that `pick` line in your todo list\nand\n> then the `merge` command simply tries to re-create the original merge\n> commit's changes?\n>\n> Exactly. The merge will become an evil merge, and will introduce that\nvery\n> much not-wanted and therefore-dropped changes.\n\nI think I understand. Evil merges happen when we change the branch\nthat is not the mainline..? Is there any reason why the following\nwouldn't work?\n\nImagine rebase is about to pick a merge commit, and we have edited at\nleast one\ncommit in each branch to be merged.\n\n1. apply patch mainline_orig..merge_orig\n2. apply patch branch1_orig..branch1\n...\nN. apply patch branchN_orig..branchN\nN+1. Commit merge\n\nI do see complications, like the fact that steps 2-N can be done in any\norder, with\npossibly quite different results. Moving commits from one branch to\nanother might\nnot work very well. And what to do when you remove branches or create\nnew ones?\n\nThese problems might be prohibitive, but picking merge commits seems\nlike\nsomething that should be possible to do.\n\n>\n> [...]\n>\n> So --preserve-merges --first-parent is probably what you were looking\nfor.\n\nI want this as well :). I don't quite see the risk if it's not used\nwith --interactive.\n\n> [...]\n>\n> So no, I do not think that --recreate-merges --first-parent is a good\nidea\n> at all. Unless you try to do that non-interactively only, *and\ndisallow it\n> in interactive mode*. Because the entire point of the interactive\nrebase\n> is to allow reordering and dropping commits, in --recreate-merges\neven\n> moving, introducing and dropping merge commits. The --first-parent\noption\n> flies in the face of this idea.\n\nFWIW I'd be totally fine with disallowing it in --interactive. It would\nbe incredibly useful \ne.g. with pull --rebase in merge-based workflows.\n\nBTW what is the difference between --recreate-merges and --preserve-\nmerges when\n--interactive is not present? I apologize if you have explained this\nsomewhere\nelse in the patch series.\n\n>\n> Ciao,\n> Johannes\n\nThanks,\nØyvind\n"},{"id":"338680","messageId":"xmqqpo5g5qd3.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"1518044326.7000.4.camel@gmail.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-02-07T23:31:20Z","receivedAt":"2018-02-07T23:31:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Øyvind Rønningstad <ronningstad@gmail.com> writes:\n\n>> So no, I do not think that --recreate-merges --first-parent is a good\n> idea\n>> at all. Unless you try to do that non-interactively only, *and\n> disallow it\n>> in interactive mode*.\n\nCorrect.  If the original side branch has commits A, B and C, you\nare rebuilding the topic to have only A and C but not B and then\nrecreate the merge of that rebuilt topic, then you absolutely do not\nwant \"cherry-pick -m1\" of the original merge when recreating the\nmerge, as that would resurrect the effect of having B.  The same\nargument applies if you rebuilt the topic with A and C and then a\nnew commit D.  \"cherry-pick -m1\" of the original would do a wrong\nthing.\n\nWhen there is no such fixing up, \"cherry-pick -m1\" is the right\nthing to do, though, so it probably makes sense to pick merges that\nway when the side topic being merged consists of the same commits as\nthe original.  I do not think that the code structure in the topic\nas posted makes it impossible (or unnecessarily hard) to give an\nenhancement like that in the future as a follow-up series.\n"},{"id":"338700","messageId":"nycvar.QRO.7.76.6.1802081317320.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqqpo5g5qd3.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-08T12:34:18Z","receivedAt":"2018-02-08T12:34:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 7 Feb 2018, Junio C Hamano wrote:\n\n> Øyvind Rønningstad <ronningstad@gmail.com> writes:\n> \n> >> So no, I do not think that --recreate-merges --first-parent is a good\n> > idea\n> >> at all. Unless you try to do that non-interactively only, *and\n> > disallow it\n> >> in interactive mode*.\n> \n> Correct.  If the original side branch has commits A, B and C, you\n> are rebuilding the topic to have only A and C but not B and then\n> recreate the merge of that rebuilt topic, then you absolutely do not\n> want \"cherry-pick -m1\" of the original merge when recreating the\n> merge, as that would resurrect the effect of having B.  The same\n> argument applies if you rebuilt the topic with A and C and then a\n> new commit D.  \"cherry-pick -m1\" of the original would do a wrong\n> thing.\n> \n> When there is no such fixing up, \"cherry-pick -m1\" is the right\n> thing to do, though, so it probably makes sense to pick merges that\n> way when the side topic being merged consists of the same commits as\n> the original.\n\nPlease note that there are a lot of conditions of \"fixing up\". A lot more\nthan just dropping, reordering or adding `pick`s.\n\n> I do not think that the code structure in the topic as posted makes it\n> impossible (or unnecessarily hard) to give an enhancement like that in\n> the future as a follow-up series.\n\nJust to give you one concrete example: when I recently rebased some\npatches (no reording or dropping involved here!) and one of the picks\nfailed with merge conflicts, I realized that that particular commit\nintroduced incorrect formatting and fixed that right away (verifying that\nno other commits introduced incorrect formatting, of course).\n\nWith your new cute idea to magically cherry-pick -m1, this change would\nhave been magically dropped from the subsequent merge commits!\n\nAnd let me pick a bit on the statement \"I do not think that ... makes it\nimpossible (or unnecessarily hard) ...\": I absolutely agree. I absolutely\nagree that it is not impossible or unnecessarily hard to introduce\nfeatures *that are confusing the users because they are inconsistent with\nthe expectations how such a command should operate*.\n\nSo the question is not so much whether we can introduce a feature that\nmakes no sense. Of course we can, we are decent software developers.\n\nThe question is: will this be confusing, inconsistent behavior that\nviolates the Principle of Least Surprise?\n\nIn that respect, introducing conditional code that would `cherry-pick -m1`\nwhen the todo list is unchanged so far (and only then) is an absolute no,\nno, no. It would be all three: confusing, inconsistent and violating the\nPrinciple of Least Surprise.\n\nSo how about introducing support for `--recreate-merges --first-parent`\nand allowing to combine it with `--interactive`? Also violating all three.\n\nI can see how you *could* argue that `--recreate-merges --first-parent` is\na Good Thing. I really can. It would even recreate evil merges.\n\nBut in interactive mode?\n\nNope. It would cause all kind of pain, not the least on me, because I know\nhow many people ask me about `--preserve-merges --interactive` and its\nconfusing and inconsistent behavior that violates the Principle of Least\nSurprise.\n\nSo as long as y'all don't go anywhere near \"oh, let's just introduce\n--recreate-merges --first-parent and *then also support it in\n--interactive because we can even if it hurts the user experience\", I\nagree that it could be a good follow-up patch series.\n\nTaking a step back, I have to wonder, though, why we stumble over our feet\ntrying to cross bridges that are one farther than the one we currently\nhave to cross.\n\nBy now, it should be clear why the default mode of --recreate-merges\n*cannot* be --first-parent.\n\nAnd before --recreate-merges is said and done, we should maybe work on\ngetting it said and done, rather than musing about what comes next? I, for\none, would really like to focus my time on getting *this* patch series\nreviewed and included.\n\nThanks,\nJohannes"},{"id":"338810","messageId":"87o9kyitf7.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802071818240.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-09T06:11:24Z","receivedAt":"2018-02-09T06:11:33Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi,\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> On Wed, 7 Feb 2018, Sergey Organov wrote:\n>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>> > +--recreate-merges::\n>> > +\tRecreate merge commits instead of flattening the history by replaying\n>> > +\tmerges. Merge conflict resolutions or manual amendments to merge\n>> > +\tcommits are not preserved.\n>> \n>> I wonder why you guys still hold on replaying \"merge-the-operation\"\n>> instead of replaying \"merge-the-result\"?\n>\n> This misses the point of rebasing: you want to replay the changes.\n\nWhat this comment has to do with the statement to which it's supposed to\nbe a reply? Sounds like topic change to me. Please clarify if it isn't.\n\n>\n>> The latter, the merge commit itself, no matter how exactly it was\n>> created in the first place, is the most valuable thing git keeps about\n>> the merge, and you silently drop it entirely!\n>\n> You miss another very crucial point.\n\nWhat was the first crucial point I miss? Do you rather agree that the\npoint you are replying to with this is very crucial one as well?\n\n> I don't blame you, as you certainly have not used the Git garden\n> shears for years.\n\nThanks a lot!\n\n> Let me explain the scenario which comes up plenty of times in my work with\n> Git for Windows. We have a thicket of some 70 branches on top of git.git's\n> latest release. These branches often include fixup! and squash! commits\n> and even more complicated constructs that rebase cannot handle at all at\n> the moment, such as reorder-before! and reorder-after! (for commits that\n> really need to go into a different branch).\n\nI sympathize, but a solution that breaks even in simple cases can't be\nused reliably to solve more complex problems, sorry. Being so deep\ninto your problems, I think you maybe just aren't seeing forest for the\ntrees [1].\n\n> Even if you do not have such a complicated setup, it is quite possible\n> that you need to include a commit in your development that needs to be\n> dropped before contributing your work. Think e.g. removing the `-O2` flag\n> when compiling with GCC because GDB gets utterly confused with executables\n> compiled with `-O2` while single-stepping. This could be an initial commit\n> called `TO-DROP` or some such.\n>\n> And guess what happens if you drop that `pick` line in your todo list and\n> then the `merge` command simply tries to re-create the original merge\n> commit's changes?\n>\n> Exactly. The merge will become an evil merge, and will introduce that very\n> much not-wanted and therefore-dropped changes.\n\nOkay, Houston, we've had a problem here.\n\nI'm sure you'll be able to come-up with suitable solution once you start\nto think about it positively, but automatic unguided silent re-merge is\nstill not the right answer, for the same reason of distortion of user\nchanges.\n\nAs for \"evil merges\"... I don't want to get too far from original\nsubject to even start discussing this.\n\n>> OTOH, git keeps almost no information about \"merge-the-operation\", so\n>> it's virtually impossible to reliably replay the operation\n>> automatically, and yet you try to.\n>\n> That is true. However, the intended use case is not to allow you to\n> recreate funny merges. Its use case is to allow you to recreate\n> merges.\n\nThen it at least should behave accordingly, e.g., stop after every such\noccurrence, for user assistance. As an example, see what rerere does\nwhen it fires, even though it's much more reliable than this blind\nre-merge.\n\nBut the actual problem here is that almost any merge but those made with\npure \"git merge\", no options, no conflicts, no edits, no nothing,\nbecomes \"funny\" and is being destroyed, sometimes in a weird way, by\nsilently creating something different instead of original.\n\n> At a later stage, I might introduce support to detect `-s ours` merges,\n> because they are easy to detect. But even then, it will be an opt-in.\n\nSo you are going to fix one particular case that is \"easy to detect\"\n(and fix). Does it mean you do realize it's a problem, but fail to see that\nit's _fundamental_ problem with current approach?\n\nI think you start from the wrong end. I think that any merge should be\nmade reproducible first (with possible guidance from the user when\nrequired, as usual), and then advanced features for complex history\ntweaking should come, not the other way around.\n\nI feel that any solution that fails to exactly reproduce original\nhistory, unless it is to be actually changed, is flawed, and we will\ncontinue to hit our heads on sharp corners like \"merge -s ours\", most of\nwhich will be not that simple to detect and fix, for an unforeseeable\nfuture.\n\n>\n>> IMHO that was severe mistake in the original --preserve-merges, and you\n>> bring with you to this new --recreate-merges... It's sad.\n>\n> Please refrain from drawing this discussion into an emotional direction.\n> That is definitely not helpful.\n\nI don't actually blame anybody for that original implementation in the\nfirst place. It was made based on the actual needs, and that's perfectly\nfine with me, however quick-and-dirty it was. Keeping it quick-and-dirty\nfor years isn't that fine though.\n\nAnd I do get somewhat emotional seeing this, all right. Look what you\ndid. We had one pet that strikes badly (with corresponding warning label\nput on it in the manual), and now we have another one that would strike\nas bad! I'm even afraid that I'm unfortunately not that young anymore to\nget /properly/ emotional about it.\n\n>> Even more sad as solution is already known for years:\n>> \n>>     bc00341838a8faddcd101da9e746902994eef38a\n>>     Author: Johannes Sixt <j6t@kdbg.org>\n>>     Date:   Sun Jun 16 15:50:42 2013 +0200\n>>     \n>>         rebase -p --first-parent: redo merge by cherry-picking first-parent change\n>> \n>> and it works like a charm.\n>\n> It might work for you, as you probably used --preserve-merges, and dealt\n> with the fact that you could neither drop nor reorder commits.\n>\n> So --preserve-merges --first-parent is probably what you were looking\n> for.\n\nNo. What I'm looking for is for my history to be kept as intact as\npossible during rebase, unless I explicitly ask to change it, be it\n--preserve-merges, or --recreate-merges, interactive or not. Is it too\nmuch to ask for?\n\nAnd no, I don't think --preserve-merges --first-parent is the right\nanswer either, in general, even though it did suit most of my purposes\nindeed (in fact I simply patched --preserve-merges in my local git).\n\n> Instead, --recreate-merges is all about allowing the same level of\n> freedom as with regular interactive rebases, but recreating the\n> original commit topology (and allowing to change it, too).\n\nThat's a very good thing and a very nice job as a whole, sure! If it\nweren't I'd not even bother to raise this topic. But provided you\nrealize what problem \"--preserve-merges --first-parent\" would solve for\nnon-interactive use, you should realize that you have exactly the same\nproblem unsolved with the new --recreate-merges.\n\n> Therefore, I think that it would be even harmful to allow\n> --recreate-merges --first-parent *because it would cause evil merges*!\n\nOnce again, for me it seems you are thinking about it from the wrong\nend, and this indeed won't work, but for different reasons than you\nthink.\n\n[And please, stop frightening us with those \"evil merge\" thingy!]\n\n> And I totally could see myself being vexed again about options that worked\n> perfectly well (just like --preserve-merges) being completely messed up by\n> allowing it to be combined with options *that they cannot work with* (just\n> like --preserve-merges --interactive, a *huge* mistake causing so many\n> annoying \"bug\" reports: I *never intended it that way because I knew it\n> would not work as users expect*).\n\nIMHO it's a minor problem. At least there was a warning there in the\nmanual, and nobody actually claimed reliable support for the feature.\n\n> So no, I do not think that --recreate-merges --first-parent is a good idea\n> at all. Unless you try to do that non-interactively only, *and disallow it\n> in interactive mode*. Because the entire point of the interactive rebase\n> is to allow reordering and dropping commits, in --recreate-merges even\n> moving, introducing and dropping merge commits. The --first-parent option\n> flies in the face of this idea.\n\nForget about --first-parent, please! The patch that introduced it was\nonly meant to show the general way of replaying a merge. Actual\nsuggestion is to implement proper support right with --preserve-merges,\nor call it --recreate-merges, or --keep-topology, or\n--no-flatten-history, whatever [2].\n\n-- Sergey\n\n[1] Actually I wonder how, with such a complex setup, you seem to never\nstep onto the problem I regularly have? Don't you ever have sub-topic\nbranches on top of those 70? How do you ensure none of the merges you\nare rebasing are \"funny\", as you call them?\n\n[2] Proper implementation of this will likely need some tweaks to the\ntodo list, such as \"rm\" command for a commit to be removed, explicit\ndistinguish between \"merge-redo\" and \"merge-pick\" (or even \"merge-dwim\")\nin the list, or something like that. But to start actually thinking\nabout implementation, we need to agree that silent dropping user changes\nis a huge problem, be it \"evil merge\" or something else that sounds even\nmore \"dangerous\".\n"},{"id":"338811","messageId":"874lmqirma.fsf@javad.com","threadId":"47635","inReplyTo":"71c42d6d3bb240d90071d5afdde81d1293fdf0ab.1516225925.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-09T06:50:21Z","receivedAt":"2018-02-09T06:50:29Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n[...]\n\n> With this patch, the goodness of the Git garden shears comes to `git\n> rebase -i` itself. Passing the `--recreate-merges` option will generate\n> a todo list that can be understood readily, and where it is obvious\n> how to reorder commits. New branches can be introduced by inserting\n> `label` commands and calling `merge - <label> <oneline>`. And once this\n> mode has become stable and universally accepted, we can deprecate the\n> design mistake that was `--preserve-merges`.\n\nThis doesn't explain why you introduced this new --recreate-merges. Why\ndidn't you rather fix --preserve-merges to generate and use new todo\nlist format?\n\nIt doesn't seem likely that todo list created by one Git version is to\nbe ever used by another, right? Is there some hidden reason here? Some\ntools outside of Git that use old todo list format, maybe?\n\nThen, if new option indeed required, please look at the resulting manual:\n\n--recreate-merges::\n\tRecreate merge commits instead of flattening the history by replaying\n\tmerges. Merge conflict resolutions or manual amendments to merge\n\tcommits are not preserved.\n\n-p::\n--preserve-merges::\n\tRecreate merge commits instead of flattening the history by replaying\n\tcommits a merge commit introduces. Merge conflict resolutions or manual\n\tamendments to merge commits are not preserved.\n\n\nDon't you think more explanations are needed there in the manual on\nwhy do we have 2 separate options with almost the same yet subtly\ndifferent description? Is this subtle difference even important? How?\n\nI also have trouble making sense of \"Recreate merge commits instead of\nflattening the history by replaying merges.\" Is it \"<Recreate merge\ncommits by replaying merges> instead of <flattening the history>\" or is it\nrather \"<Recreate merge commits> instead of <flattening the history by\nreplaying merges>?\n\n-- Sergey\n"},{"id":"338814","messageId":"ec5c7aa2-b36b-aca8-d82f-9d131ac83b41@kdbg.org","threadId":"47635","inReplyTo":"87o9kyitf7.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2018-02-09T07:13:07Z","receivedAt":"2018-02-09T07:13:15Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 09.02.2018 um 07:11 schrieb Sergey Organov:\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>> Let me explain the scenario which comes up plenty of times in my work with\n>> Git for Windows. We have a thicket of some 70 branches on top of git.git's\n>> latest release. These branches often include fixup! and squash! commits\n>> and even more complicated constructs that rebase cannot handle at all at\n>> the moment, such as reorder-before! and reorder-after! (for commits that\n>> really need to go into a different branch).\n> \n> I sympathize, but a solution that breaks even in simple cases can't be\n> used reliably to solve more complex problems, sorry. Being so deep\n> into your problems, I think you maybe just aren't seeing forest for the\n> trees [1].\n\nHold your horses! Dscho has a point here. --preserve-merges \n--first-parent works only as long as you don't tamper with the side \nbranches. If you make changes in the side branches during the same \nrebase operation, this --first-parent mode would undo that change. (And, \nyes, its result would be called an \"evil merge\", and that scary name \n_should_ frighten you!)\n\n-- Hannes\n"},{"id":"338992","messageId":"nycvar.QRO.7.76.6.1802102030570.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqq607sgway.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-10T19:31:33Z","receivedAt":"2018-02-10T19:31:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 23 Jan 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> > index 8a861c1e0d6..1d061373288 100644\n> > --- a/Documentation/git-rebase.txt\n> > +++ b/Documentation/git-rebase.txt\n> > @@ -368,6 +368,11 @@ The commit list format can be changed by setting the configuration option\n> >  rebase.instructionFormat.  A customized instruction format will automatically\n> >  have the long commit hash prepended to the format.\n> >  \n> > +--recreate-merges::\n> > +\tRecreate merge commits instead of flattening the history by replaying\n> > +\tmerges. Merge conflict resolutions or manual amendments to merge\n> > +\tcommits are not preserved.\n> > +\n> \n> It is sensible to postpone tackling \"evil merges\" in this initial\n> iteration of the series, and \"manual amendments ... not preserved\"\n> is a reasonable thing to document.  But do we want to say a bit more\n> about conflicting merges?  \"conflict resolutions ... not preserved\"\n> sounds as if it does not stop and instead record the result with\n> conflict markers without even letting rerere to kick in, which\n> certainly is not the impression you wanted to give to the readers.\n> \n> I am imagining that it will stop and give control back to the end\n> user just like a conflicted \"pick\" would, and allow \"rebase\n> --continue\" to record resolution from the working tree, and just\n> like conflicted \"pick\", it would allow rerere() to help end users\n> recall previous resolution.\n\nThis is my current version:\n\n--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n        Recreate merge commits instead of flattening the history by replaying\n        merges. Merge conflict resolutions or manual amendments to merge\n        commits are not recreated automatically, but have to be recreated\n        manually.\n\nCiao,\nDscho\n"},{"id":"338996","messageId":"nycvar.QRO.7.76.6.1802102034280.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAPig+cSkzetewFio+4ECnXiizH_KFGEZPZVdzHYGcKL_yvLcSg@mail.gmail.com","subject":"Re: [PATCH v2 02/10] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-10T20:58:07Z","receivedAt":"2018-02-10T20:58:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Tue, 30 Jan 2018, Eric Sunshine wrote:\n\n> On Mon, Jan 29, 2018 at 5:54 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > [...]\n> > This commit implements the commands to label, and to reset to, given\n> > revisions. The syntax is:\n> >\n> >         label <name>\n> >         reset <name>\n> > [...]\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/sequencer.c b/sequencer.c\n> > @@ -1253,7 +1266,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n> >                 if (skip_prefix(bol, todo_command_info[i].str, &bol)) {\n> >                         item->command = i;\n> >                         break;\n> > -               } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {\n> > +               } else if ((bol + 1 == eol || bol[1] == ' ') &&\n> > +                          *bol == todo_command_info[i].c) {\n> \n> This adds support for commands which have no arguments, however, now\n> that the \"bud\" command has been retired, this can go away too, right?\n\nGood point. Fixed.\n\n> >                         bol++;\n> >                         item->command = i;\n> >                         break;\n> > @@ -1919,6 +1934,144 @@ static int do_exec(const char *command_line)\n> > +static int safe_append(const char *filename, const char *fmt, ...)\n> > +{\n> > +       va_list ap;\n> > +       struct lock_file lock = LOCK_INIT;\n> > +       int fd = hold_lock_file_for_update(&lock, filename, 0);\n> > +       struct strbuf buf = STRBUF_INIT;\n> > +\n> > +       if (fd < 0)\n> > +               return error_errno(_(\"could not lock '%s'\"), filename);\n> \n> Minor: unable_to_lock_message() can provide a more detailed\n> explanation of the failure.\n\nThat is true. Due to its awkward signature (returning void, using a\nstrbuf), it would add a whopping 4 lines, too.\n\nThere is a better solution, though, adding only one line: passing\nLOCK_REPORT_ON_ERROR as flag to hold_lock_file_for_update().\n\n> > +\n> > +       if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n> > +               return error_errno(_(\"could not read '%s'\"), filename);\n> > +       strbuf_complete(&buf, '\\n');\n> > +       va_start(ap, fmt);\n> > +       strbuf_vaddf(&buf, fmt, ap);\n> > +       va_end(ap);\n> \n> Would it make sense to also\n> \n>     strbuf_complete(&buf, '\\n')\n> \n> here, as well, to be a bit more robust against lazy callers?\n\nI'd rather not make that assumption. It *may* be true that the current\nsole user wants the last line of the file to end in a newline. I try to\ndesign my code for maximum reusability, though. And who is to say whether\nmy next use case for the safe_append() function wants the semantics you\nsuggest, if it wants to append less than entire lines at a time, maybe?\nLet's not optimize prematurely, okay?\n\n> > +\n> > +       if (write_in_full(fd, buf.buf, buf.len) < 0) {\n> > +               rollback_lock_file(&lock);\n> > +               return error_errno(_(\"could not write to '%s'\"), filename);\n> \n> Reading lockfile.h & tempfile.c, I see that rollback_lock_file()\n> clobbers write_in_full()'s errno before error_errno() is called.\n\nTrue. Fixed.\n\nI also fixed the code from where I copy-edited this pattern (increasing\nthe patch series by yet another patch).\n\n> > +       }\n> > +       if (commit_lock_file(&lock) < 0) {\n> > +               rollback_lock_file(&lock);\n> > +               return error(_(\"failed to finalize '%s'\"), filename);\n> > +       }\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +static int do_reset(const char *name, int len)\n> > +{\n> > +       [...]\n> > +       strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> > +       if (get_oid(ref_name.buf, &oid) &&\n> > +           get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> > +               error(_(\"could not read '%s'\"), ref_name.buf);\n> \n> Checking my understanding: The two get_oid() calls allow the argument\n> to 'reset' to be a label created with the 'label' command or any other\n> way to name an object, right? If so, then I wonder if the error\n> invocation should instead be:\n> \n>     error(_(\"could not read '%.*s'\"), len, name);\n\nI would rather give the preferred form: refs/rewritten/<label>.\n\nThe main reason this code falls back to getting the OID of `<label>`\ndirectly is to support the `no-rebase-cousins` code: in that mode, topic\nbranches may be based on commits other than the one labeled `onto`, but\nthe original, unchanged one. In this case, we have no way of labeling the\nbase commit, and therefore use a unique abbreviation of that base commit's\nOID.\n\nBut this is really a very special use case, and the more common use case\nshould be the one using refs/rewritten/<label>.\n\nCiao,\nDscho\n"},{"id":"338997","messageId":"nycvar.QRO.7.76.6.1802102248590.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"cba8d5dc-f42d-8412-c471-edb5a860b3ae@talktalk.net","subject":"Re: [PATCH v2 02/10] sequencer: introduce new commands to reset therevision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-10T21:49:19Z","receivedAt":"2018-02-10T21:49:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Wed, 31 Jan 2018, Phillip Wood wrote:\n\n> On 31/01/18 13:21, Johannes Schindelin wrote:\n> > \n> > On Tue, 30 Jan 2018, Stefan Beller wrote:\n> > \n> >> On Mon, Jan 29, 2018 at 2:54 PM, Johannes Schindelin\n> >> <johannes.schindelin@gmx.de> wrote:\n> >>> @@ -116,6 +118,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n> >>>  static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n> >>>  static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n> >>>         \"rebase-merge/rewritten-pending\")\n> >>> +\n> >>> +/*\n> >>> + * The path of the file listing refs that need to be deleted after the rebase\n> >>> + * finishes. This is used by the `merge` command.\n> >>> + */\n> > \n> > Whoops. The comment \"This is used by the `merge` command`\" is completely\n> > wrong. Will fix.\n> > \n> >> So this file contains (label -> commit),\n> > \n> > Only `label`. No `commit`.\n> > \n> >> which is appended in do_label, it uses refs to store the commits in\n> >> refs/rewritten.  We do not have to worry about the contents of that file\n> >> getting too long, or label re-use, because the directory containing all\n> >> these helper files will be deleted upon successful rebase in\n> >> `sequencer_remove_state()`.\n> > \n> > Yes.\n> >\n> It might be a good idea to have 'git rebase --abort' delete the refs as\n> well as the file though\n\nThat makes sense. I made it so.\n\nCiao,\nDscho\n"},{"id":"338998","messageId":"nycvar.QRO.7.76.6.1802102357510.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"874lmqirma.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-10T23:06:28Z","receivedAt":"2018-02-10T23:06:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Fri, 9 Feb 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> [...]\n> \n> > With this patch, the goodness of the Git garden shears comes to `git\n> > rebase -i` itself. Passing the `--recreate-merges` option will generate\n> > a todo list that can be understood readily, and where it is obvious\n> > how to reorder commits. New branches can be introduced by inserting\n> > `label` commands and calling `merge - <label> <oneline>`. And once this\n> > mode has become stable and universally accepted, we can deprecate the\n> > design mistake that was `--preserve-merges`.\n> \n> This doesn't explain why you introduced this new --recreate-merges. Why\n> didn't you rather fix --preserve-merges to generate and use new todo\n> list format?\n\nBecause that would of course break existing users of --preserve-merges.\n\nSo why not --preserve-merges=v2? Because that would force me to maintain\n--preserve-merges forever. And I don't want to.\n\n> It doesn't seem likely that todo list created by one Git version is to\n> be ever used by another, right?\n\nNo. But by scripts based on `git rebase -p`.\n\n> Is there some hidden reason here? Some tools outside of Git that use old\n> todo list format, maybe?\n\nExactly.\n\nI did mention such a tool: the Git garden shears:\n\n\thttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nHave a look at it. It will inform the discussion.\n\n> Then, if new option indeed required, please look at the resulting manual:\n> \n> --recreate-merges::\n> \tRecreate merge commits instead of flattening the history by replaying\n> \tmerges. Merge conflict resolutions or manual amendments to merge\n> \tcommits are not preserved.\n> \n> -p::\n> --preserve-merges::\n> \tRecreate merge commits instead of flattening the history by replaying\n> \tcommits a merge commit introduces. Merge conflict resolutions or manual\n> \tamendments to merge commits are not preserved.\n\nAs I stated in the cover letter, there are more patches lined up after\nthis patch series.\n\nHave a look at https://github.com/git/git/pull/447, especially the latest\ncommit in there which is an early version of the deprecation I intend to\nbring about.\n\nAlso, please refrain from saying things like... \"Don't you think ...\"\n\nIf you don't like the wording, I wold much more appreciate it if a better\nalternative was suggested.\n\n> Don't you think more explanations are needed there in the manual on\n> why do we have 2 separate options with almost the same yet subtly\n> different description? Is this subtle difference even important? How?\n> \n> I also have trouble making sense of \"Recreate merge commits instead of\n> flattening the history by replaying merges.\" Is it \"<Recreate merge\n> commits by replaying merges> instead of <flattening the history>\" or is it\n> rather \"<Recreate merge commits> instead of <flattening the history by\n> replaying merges>?\n\nThe documentation of the --recreate-merges option is not meant to explain\nthe difference to --preserve-merges. It is meant to explain the difference\nto regular `git rebase -i`, which flattens the commit history into a\nsingle branch without merge commits (in fact, all merge commits are simply\nignored).\n\nAnd I would rather not start to describe the difference between\n--recreate-merges and --preserve-merges because I want to deprecate the\nlatter, and describing the difference as I get the sense is your wish\nwould simply mean more work because it would have to be added and then\nremoved again.\n\nIf you still think it would be a good idea to describe the difference\nbetween --recreate-merges and --preserve-merges, then please provide a\nsuggestion, preferably in the form of a patch, that adds appropriate\nparagraphs to *both* options' documentation, so that your proposal can be\ndiscussed properly.\n\nCiao,\nJohannes\n"},{"id":"338999","messageId":"cover.1518307771.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1517266437.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 00/12] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:09:58Z","receivedAt":"2018-02-11T00:10:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, I dreamt of an interactive rebase that would not\nflatten branch structure, but instead recreate the commit topology\nfaithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --recreate-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge -C 3456 D # C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --recreate-merges.\n\nChanges since v2:\n\n- fixed the incorrect comment for rebase_path_refs_to_delete.\n\n- we now error out properly if read_cache_unmerged() fails.\n\n- if there are unresolved merge conflicts, the `reset` command now errors out\n  (even if the current design should not allow for such a scenario to occur).\n\n- a diff hunk that was necessary to support `bud` was dropped from 2/10.\n\n- changed all `rollback_lock_file(); return error_errno(...);` patterns to\n  first show the errors (i.e. using the correct errno). This added 1/11.\n\n- The temporary refs are now also cleaned up upon `git rebase --abort`.\n\n- Reworked the entire patch series to support\n\n\tmerge -C <commit> <tip> # <oneline>\n\n  instead of the previous `merge <commit> <tip> <oneline>`.\n\n- Dropped the octopus part of the description of the `merge` command in\n  the usage at the bottom of the todo list, as it is subject to change.\n\n- The autosquash handling was not elegant, and cuddled into the same\n  commit as the post-rewrite changes. Now, the autosquash handling is a\n  lot more elegant, and a separate introductory patch (as it arguably\n  improves the current code on its own).\n\n\nJohannes Schindelin (11):\n  sequencer: avoid using errno clobbered by rollback_lock_file()\n  sequencer: make rearrange_squash() a bit more obvious\n  sequencer: introduce new commands to reset the revision\n  sequencer: introduce the `merge` command\n  sequencer: fast-forward merge commits, if possible\n  rebase-helper --make-script: introduce a flag to recreate merges\n  rebase: introduce the --recreate-merges option\n  sequencer: make refs generated by the `label` command worktree-local\n  sequencer: handle post-rewrite for merge commands\n  pull: accept --rebase=recreate to recreate the branch topology\n  rebase -i: introduce --recreate-merges=[no-]rebase-cousins\n\nStefan Beller (1):\n  git-rebase--interactive: clarify arguments\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   5 +-\n Documentation/git-rebase.txt           |  14 +-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |   2 +\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |  22 +-\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 736 ++++++++++++++++++++++++++++++++-\n sequencer.h                            |   7 +\n t/t3430-rebase-recreate-merges.sh      | 208 ++++++++++\n 13 files changed, 1021 insertions(+), 31 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\n\nbase-commit: 5be1f00a9a701532232f57958efab4be8c959a29\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v3\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v3\n\nInterdiff vs v2:\n diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n index 5e21e4cf269..e199fe1cca5 100644\n --- a/git-rebase--interactive.sh\n +++ b/git-rebase--interactive.sh\n @@ -164,10 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell\n  d, drop <commit> = remove commit\n  l, label <label> = label current HEAD with a name\n  t, reset <label> = reset HEAD to a label\n -m, merge <original-merge-commit> ( <label> | \\\"<label>...\\\" ) [<oneline>]\n +m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n  .       create a merge commit using the original merge commit's\n -.       message (or the oneline, if \"-\" is given). Use a quoted\n -.       list of commits to be merged for octopus merges.\n +.       message (or the oneline, if no original merge commit was\n +.       specified). Use -c <commit> to reword the commit message.\n  \n  These lines can be re-ordered; they are executed from top to bottom.\n  \" | git stripspace --comment-lines >>\"$todo\"\n diff --git a/sequencer.c b/sequencer.c\n index cd2f2ae5d53..c877432d7b4 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -123,7 +123,7 @@ static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n  \n  /*\n   * The path of the file listing refs that need to be deleted after the rebase\n - * finishes. This is used by the `merge` command.\n + * finishes. This is used by the `label` command to record the need for cleanup.\n   */\n  static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n  \n @@ -206,18 +206,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n  \n  int sequencer_remove_state(struct replay_opts *opts)\n  {\n -\tstruct strbuf dir = STRBUF_INIT;\n +\tstruct strbuf buf = STRBUF_INIT;\n  \tint i;\n  \n +\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n +\t\tchar *p = buf.buf;\n +\t\twhile (*p) {\n +\t\t\tchar *eol = strchr(p, '\\n');\n +\t\t\tif (eol)\n +\t\t\t\t*eol = '\\0';\n +\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n +\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n +\t\t\tif (!eol)\n +\t\t\t\tbreak;\n +\t\t\tp = eol + 1;\n +\t\t}\n +\t}\n +\n  \tfree(opts->gpg_sign);\n  \tfree(opts->strategy);\n  \tfor (i = 0; i < opts->xopts_nr; i++)\n  \t\tfree(opts->xopts[i]);\n  \tfree(opts->xopts);\n  \n -\tstrbuf_addstr(&dir, get_dir(opts));\n -\tremove_dir_recursively(&dir, 0);\n -\tstrbuf_release(&dir);\n +\tstrbuf_reset(&buf);\n +\tstrbuf_addstr(&buf, get_dir(opts));\n +\tremove_dir_recursively(&buf, 0);\n +\tstrbuf_release(&buf);\n  \n  \treturn 0;\n  }\n @@ -307,12 +322,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n  \tif (msg_fd < 0)\n  \t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n  \tif (write_in_full(msg_fd, buf, len) < 0) {\n +\t\terror_errno(_(\"could not write to '%s'\"), filename);\n  \t\trollback_lock_file(&msg_file);\n -\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n +\t\treturn -1;\n  \t}\n  \tif (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n +\t\terror_errno(_(\"could not write eol to '%s'\"), filename);\n  \t\trollback_lock_file(&msg_file);\n -\t\treturn error_errno(_(\"could not write eol to '%s'\"), filename);\n +\t\treturn -1;\n  \t}\n  \tif (commit_lock_file(&msg_file) < 0) {\n  \t\trollback_lock_file(&msg_file);\n @@ -781,6 +798,7 @@ enum todo_command {\n  \tTODO_LABEL,\n  \tTODO_RESET,\n  \tTODO_MERGE,\n +\tTODO_MERGE_AND_EDIT,\n  \t/* commands that do nothing but are counted for reporting progress */\n  \tTODO_NOOP,\n  \tTODO_DROP,\n @@ -802,6 +820,7 @@ static struct {\n  \t{ 'l', \"label\" },\n  \t{ 't', \"reset\" },\n  \t{ 'm', \"merge\" },\n +\t{ 0, \"merge\" }, /* MERGE_AND_EDIT */\n  \t{ 0,   \"noop\" },\n  \t{ 'd', \"drop\" },\n  \t{ 0,   NULL }\n @@ -1270,8 +1289,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n  \t\tif (skip_prefix(bol, todo_command_info[i].str, &bol)) {\n  \t\t\titem->command = i;\n  \t\t\tbreak;\n -\t\t} else if ((bol + 1 == eol || bol[1] == ' ') &&\n -\t\t\t   *bol == todo_command_info[i].c) {\n +\t\t} else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {\n  \t\t\tbol++;\n  \t\t\titem->command = i;\n  \t\t\tbreak;\n @@ -1305,21 +1323,30 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n  \t\treturn 0;\n  \t}\n  \n -\tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n -\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n -\titem->arg_len = (int)(eol - item->arg);\n -\n -\tif (item->command == TODO_MERGE && *bol == '-' &&\n -\t    bol + 1 == end_of_object_name) {\n -\t\titem->commit = NULL;\n -\t\treturn 0;\n +\tif (item->command == TODO_MERGE) {\n +\t\tif (skip_prefix(bol, \"-C\", &bol))\n +\t\t\tbol += strspn(bol, \" \\t\");\n +\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n +\t\t\tbol += strspn(bol, \" \\t\");\n +\t\t\titem->command = TODO_MERGE_AND_EDIT;\n +\t\t} else {\n +\t\t\titem->command = TODO_MERGE_AND_EDIT;\n +\t\t\titem->commit = NULL;\n +\t\t\titem->arg = bol;\n +\t\t\titem->arg_len = (int)(eol - bol);\n +\t\t\treturn 0;\n +\t\t}\n  \t}\n  \n +\tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n  \tsaved = *end_of_object_name;\n  \t*end_of_object_name = '\\0';\n  \tstatus = get_oid(bol, &commit_oid);\n  \t*end_of_object_name = saved;\n  \n +\titem->arg = end_of_object_name + strspn(end_of_object_name, \" \\t\");\n +\titem->arg_len = (int)(eol - item->arg);\n +\n  \tif (status < 0)\n  \t\treturn -1;\n  \n @@ -1609,16 +1636,17 @@ static int save_head(const char *head)\n  \n  \tfd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);\n  \tif (fd < 0) {\n +\t\terror_errno(_(\"could not lock HEAD\"));\n  \t\trollback_lock_file(&head_lock);\n -\t\treturn error_errno(_(\"could not lock HEAD\"));\n +\t\treturn -1;\n  \t}\n  \tstrbuf_addf(&buf, \"%s\\n\", head);\n  \twritten = write_in_full(fd, buf.buf, buf.len);\n  \tstrbuf_release(&buf);\n  \tif (written < 0) {\n +\t\terror_errno(_(\"could not write to '%s'\"), git_path_head_file());\n  \t\trollback_lock_file(&head_lock);\n -\t\treturn error_errno(_(\"could not write to '%s'\"),\n -\t\t\t\t   git_path_head_file());\n +\t\treturn -1;\n  \t}\n  \tif (commit_lock_file(&head_lock) < 0) {\n  \t\trollback_lock_file(&head_lock);\n @@ -1948,11 +1976,12 @@ static int safe_append(const char *filename, const char *fmt, ...)\n  {\n  \tva_list ap;\n  \tstruct lock_file lock = LOCK_INIT;\n -\tint fd = hold_lock_file_for_update(&lock, filename, 0);\n +\tint fd = hold_lock_file_for_update(&lock, filename,\n +\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n  \tstruct strbuf buf = STRBUF_INIT;\n  \n  \tif (fd < 0)\n -\t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n +\t\treturn -1;\n  \n  \tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n  \t\treturn error_errno(_(\"could not read '%s'\"), filename);\n @@ -1962,8 +1991,9 @@ static int safe_append(const char *filename, const char *fmt, ...)\n  \tva_end(ap);\n  \n  \tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n +\t\terror_errno(_(\"could not write to '%s'\"), filename);\n  \t\trollback_lock_file(&lock);\n -\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n +\t\treturn -1;\n  \t}\n  \tif (commit_lock_file(&lock) < 0) {\n  \t\trollback_lock_file(&lock);\n @@ -1982,6 +2012,9 @@ static int do_label(const char *name, int len)\n  \tint ret = 0;\n  \tstruct object_id head_oid;\n  \n +\tif (len == 1 && *name == '#')\n +\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n +\n  \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n  \tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n  \n @@ -2010,14 +2043,14 @@ static int do_label(const char *name, int len)\n  \treturn ret;\n  }\n  \n -static int do_reset(const char *name, int len)\n +static int do_reset(const char *name, int len, struct replay_opts *opts)\n  {\n  \tstruct strbuf ref_name = STRBUF_INIT;\n  \tstruct object_id oid;\n  \tstruct lock_file lock = LOCK_INIT;\n  \tstruct tree_desc desc;\n  \tstruct tree *tree;\n -\tstruct unpack_trees_options opts;\n +\tstruct unpack_trees_options unpack_tree_opts;\n  \tint ret = 0, i;\n  \n  \tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n @@ -2037,16 +2070,18 @@ static int do_reset(const char *name, int len)\n  \t\treturn -1;\n  \t}\n  \n -\tmemset(&opts, 0, sizeof(opts));\n -\topts.head_idx = 1;\n -\topts.src_index = &the_index;\n -\topts.dst_index = &the_index;\n -\topts.fn = oneway_merge;\n -\topts.merge = 1;\n -\topts.update = 1;\n -\topts.reset = 1;\n +\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n +\tunpack_tree_opts.head_idx = 1;\n +\tunpack_tree_opts.src_index = &the_index;\n +\tunpack_tree_opts.dst_index = &the_index;\n +\tunpack_tree_opts.fn = oneway_merge;\n +\tunpack_tree_opts.merge = 1;\n +\tunpack_tree_opts.update = 1;\n +\tunpack_tree_opts.reset = 1;\n +\n +\tif (read_cache_unmerged())\n +\t\treturn error_resolve_conflict(_(action_name(opts)));\n  \n -\tread_cache_unmerged();\n  \tif (!fill_tree_descriptor(&desc, &oid)) {\n  \t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n  \t\trollback_lock_file(&lock);\n @@ -2055,7 +2090,7 @@ static int do_reset(const char *name, int len)\n  \t\treturn -1;\n  \t}\n  \n -\tif (unpack_trees(1, &desc, &opts)) {\n +\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n  \t\trollback_lock_file(&lock);\n  \t\tfree((void *)desc.buffer);\n  \t\tstrbuf_release(&ref_name);\n @@ -2083,7 +2118,7 @@ static int do_reset(const char *name, int len)\n  }\n  \n  static int do_merge(struct commit *commit, const char *arg, int arg_len,\n -\t\t    struct replay_opts *opts)\n +\t\t    int run_commit_flags, struct replay_opts *opts)\n  {\n  \tint merge_arg_len;\n  \tstruct strbuf ref_name = STRBUF_INIT;\n @@ -2137,6 +2172,8 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\tstrbuf_reset(&buf);\n  \n  \t\tp += strspn(p, \" \\t\");\n +\t\tif (*p == '#' && isspace(p[1]))\n +\t\t\tp += 1 + strspn(p + 1, \" \\t\");\n  \t\tif (*p)\n  \t\t\tlen = strlen(p);\n  \t\telse {\n @@ -2221,7 +2258,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t}\n  \trollback_lock_file(&lock);\n  \n -\tret = run_git_commit(git_path_merge_msg(), opts, 0);\n +\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n  \tstrbuf_release(&ref_name);\n  \n  \treturn ret;\n @@ -2413,10 +2450,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t} else if (item->command == TODO_LABEL)\n  \t\t\tres = do_label(item->arg, item->arg_len);\n  \t\telse if (item->command == TODO_RESET)\n -\t\t\tres = do_reset(item->arg, item->arg_len);\n -\t\telse if (item->command == TODO_MERGE) {\n -\t\t\tres = do_merge(item->commit,\n -\t\t\t\t       item->arg, item->arg_len, opts);\n +\t\t\tres = do_reset(item->arg, item->arg_len, opts);\n +\t\telse if (item->command == TODO_MERGE ||\n +\t\t\t item->command == TODO_MERGE_AND_EDIT) {\n +\t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n +\t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n +\t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n  \t\t\tif (item->commit)\n  \t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n  \t\t\t\t\t\t    peek_command(todo_list, 1));\n @@ -2525,23 +2564,6 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t}\n  \t\tapply_autostash(opts);\n  \n -\t\tstrbuf_reset(&buf);\n -\t\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0)\n -\t\t    > 0) {\n -\t\t\tchar *p = buf.buf;\n -\t\t\twhile (*p) {\n -\t\t\t\tchar *eol = strchr(p, '\\n');\n -\t\t\t\tif (eol)\n -\t\t\t\t\t*eol = '\\0';\n -\t\t\t\tif (delete_ref(\"(rebase -i) cleanup\",\n -\t\t\t\t\t       p, NULL, 0) < 0)\n -\t\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n -\t\t\t\tif (!eol)\n -\t\t\t\t\tbreak;\n -\t\t\t\tp = eol + 1;\n -\t\t\t}\n -\t\t}\n -\n  \t\tfprintf(stderr, \"Successfully rebased and updated %s.\\n\",\n  \t\t\thead_ref.buf);\n  \n @@ -2867,11 +2889,14 @@ static const char *label_oid(struct object_id *oid, const char *label,\n  \t\t}\n  \t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n  \t\t    !get_oid_hex(label, &dummy)) ||\n +\t\t   (len == 1 && *label == '#') ||\n  \t\t   hashmap_get_from_hash(&state->labels,\n  \t\t\t\t\t strihash(label), label)) {\n  \t\t/*\n  \t\t * If the label already exists, or if the label is a valid full\n -\t\t * OID, we append a dash and a number to make it unique.\n +\t\t * OID, or the label is a '#' (which we use as a separator\n +\t\t * between merge heads and oneline), we append a dash and a\n +\t\t * number to make it unique.\n  \t\t */\n  \t\tstruct strbuf *buf = &state->buf;\n  \n @@ -2997,7 +3022,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\t\t\t*(char *)p1 = '-';\n  \n  \t\tstrbuf_reset(&buf);\n -\t\tstrbuf_addf(&buf, \"%s %s\",\n +\t\tstrbuf_addf(&buf, \"%s -C %s\",\n  \t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n  \n  \t\t/* label the tip of merged branch */\n @@ -3012,7 +3037,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \n  \t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n  \t\t}\n -\t\tstrbuf_addf(&buf, \" %s\", oneline.buf);\n +\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n  \n  \t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n  \t\toidcpy(&entry->entry.oid, &commit->object.oid);\n @@ -3262,9 +3287,14 @@ int transform_todos(unsigned flags)\n  \t\t\t\t\t  short_commit_name(item->commit) :\n  \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n  \n +\t\t\tif (item->command == TODO_MERGE)\n +\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n +\t\t\telse if (item->command == TODO_MERGE_AND_EDIT)\n +\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n +\n  \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n -\t\t} else if (item->command == TODO_MERGE)\n -\t\t\tstrbuf_addstr(&buf, \" -\");\n +\t\t}\n +\n  \t\t/* add all the rest */\n  \t\tif (!item->arg_len)\n  \t\t\tstrbuf_addch(&buf, '\\n');\n @@ -3567,8 +3597,7 @@ int rearrange_squash(void)\n  \t\tstruct subject2item_entry *entry;\n  \n  \t\tnext[i] = tail[i] = -1;\n -\t\tif (item->command >= TODO_EXEC &&\n -\t\t    (item->command != TODO_MERGE || !item->commit)) {\n +\t\tif (!item->commit || item->command == TODO_DROP) {\n  \t\t\tsubjects[i] = NULL;\n  \t\t\tcontinue;\n  \t\t}\n diff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\n index ab51b584ff9..9a59f12b670 100755\n --- a/t/t3430-rebase-recreate-merges.sh\n +++ b/t/t3430-rebase-recreate-merges.sh\n @@ -59,8 +59,8 @@ pick B\n  label second\n  \n  reset onto\n -merge H second\n -merge - onebranch Merge the topic branch 'onebranch'\n +merge -C H second\n +merge onebranch # Merge the topic branch 'onebranch'\n  EOF\n  \n  test_cmp_graph () {\n @@ -106,8 +106,8 @@ test_expect_success 'generate correct todo list' '\n  \n  \treset branch-point # C\n  \tpick 12bd07b D\n -\tmerge 2051b56 E E\n -\tmerge 233d48a H H\n +\tmerge -C 2051b56 E # E\n +\tmerge -C 233d48a H # H\n  \n  \tEOF\n  \n-- \n2.16.1.windows.1\n\n"},{"id":"339000","messageId":"67bb2eead7bb499b27436fb6a120cbfbb2de1262.1518307771.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 01/12] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:04Z","receivedAt":"2018-02-11T00:10:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"As pointed out in a review of the `--recreate-merges` patch series,\n`rollback_lock_file()` clobbers errno. Therefore, we have to report the\nerror message that uses errno before calling said function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 4d3f60594cb..114db3b2775 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -296,12 +296,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n \tif (msg_fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n \tif (write_in_full(msg_fd, buf, len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n+\t\terror_errno(_(\"could not write eol to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write eol to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&msg_file) < 0) {\n \t\trollback_lock_file(&msg_file);\n@@ -1584,16 +1586,17 @@ static int save_head(const char *head)\n \n \tfd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);\n \tif (fd < 0) {\n+\t\terror_errno(_(\"could not lock HEAD\"));\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not lock HEAD\"));\n+\t\treturn -1;\n \t}\n \tstrbuf_addf(&buf, \"%s\\n\", head);\n \twritten = write_in_full(fd, buf.buf, buf.len);\n \tstrbuf_release(&buf);\n \tif (written < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), git_path_head_file());\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t   git_path_head_file());\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&head_lock) < 0) {\n \t\trollback_lock_file(&head_lock);\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339001","messageId":"321a314584f61e5d5ba94bbe704716e0f9fc336d.1518307771.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 02/12] sequencer: make rearrange_squash() a bit more obvious","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:08Z","receivedAt":"2018-02-11T00:10:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"There are some commands that have to be skipped from rearranging by virtue\nof not handling any commits.\n\nHowever, the logic was not quite obvious: it skipped commands based on\ntheir position in the enum todo_command.\n\nInstead, let's make it explicit that we skip all commands that do not\nhandle any commit. With one exception: the `drop` command, because it,\nwell, drops the commit and is therefore not eligible to rearranging.\n\nNote: this is a bit academic at the moment because the only time we call\n`rearrange_squash()` is directly after generating the todo list, when we\nhave nothing but `pick` commands anyway.\n\nHowever, the upcoming `merge` command *will* want to be handled by that\nfunction, and it *can* handle commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 114db3b2775..764ad43388f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2890,7 +2890,7 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (!item->commit || item->command == TODO_DROP) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339002","messageId":"84bd8e667e2b5d9934854d390ae6ad6ce258987c.1518307771.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 03/12] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:13Z","receivedAt":"2018-02-11T00:10:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Stefan Beller <stefanbeller@gmail.com>\n\nUp to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we are about to introduce commands that take different\narguments, clarify each command by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex d47bd29593a..fcedece1860 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,13 +155,13 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339003","messageId":"6c1e131d2eb26534869c06b714cc5cc729004a41.1518307771.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 04/12] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:16Z","receivedAt":"2018-02-11T00:10:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming commits, we will teach the sequencer to recreate merges.\nThis will be done in a very different way from the unfortunate design of\n`git rebase --preserve-merges` (which does not allow for reordering\ncommits, or changing the branch topology).\n\nThe main idea is to introduce new todo list commands, to support\nlabeling the current revision with a given name, resetting the current\nrevision to a previous state, and  merging labeled revisions.\n\nThis idea was developed in Git for Windows' Git garden shears (that are\nused to maintain the \"thicket of branches\" on top of upstream Git), and\nthis patch is part of the effort to make it available to a wider\naudience, as well as to make the entire process more robust (by\nimplementing it in a safe and portable language rather than a Unix shell\nscript).\n\nThis commit implements the commands to label, and to reset to, given\nrevisions. The syntax is:\n\n\tlabel <name>\n\treset <name>\n\nInternally, the `label <name>` command creates the ref\n`refs/rewritten/<name>`. This makes it possible to work with the labeled\nrevisions interactively, or in a scripted fashion (e.g. via the todo\nlist command `exec`).\n\nThese temporary refs are removed upon sequencer_remove_state(), so that\neven a `git rebase --abort` cleans them up.\n\nWe disallow '#' as label because that character will be used as separator\nin the upcoming `merge` command.\n\nLater in this patch series, we will mark the `refs/rewritten/` refs as\nworktree-local, to allow for interactive rebases to be run in parallel in\nworktrees linked to the same repository.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   2 +\n sequencer.c                | 190 +++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 186 insertions(+), 6 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex fcedece1860..7e5281e74aa 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n+l, label <label> = label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 764ad43388f..8638086f667 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -21,6 +21,8 @@\n #include \"log-tree.h\"\n #include \"wt-status.h\"\n #include \"hashmap.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -116,6 +118,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `label` command to record the need for cleanup.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -195,18 +204,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n \n int sequencer_remove_state(struct replay_opts *opts)\n {\n-\tstruct strbuf dir = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i;\n \n+\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n+\t\tchar *p = buf.buf;\n+\t\twhile (*p) {\n+\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\tif (eol)\n+\t\t\t\t*eol = '\\0';\n+\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n+\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\tif (!eol)\n+\t\t\t\tbreak;\n+\t\t\tp = eol + 1;\n+\t\t}\n+\t}\n+\n \tfree(opts->gpg_sign);\n \tfree(opts->strategy);\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n \tfree(opts->xopts);\n \n-\tstrbuf_addstr(&dir, get_dir(opts));\n-\tremove_dir_recursively(&dir, 0);\n-\tstrbuf_release(&dir);\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, get_dir(opts));\n+\tremove_dir_recursively(&buf, 0);\n+\tstrbuf_release(&buf);\n \n \treturn 0;\n }\n@@ -769,6 +793,8 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -787,6 +813,8 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1281,7 +1309,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -1922,6 +1951,151 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn -1;\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n+\t\treturn error_errno(_(\"could not read '%s'\"), filename);\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tif (len == 1 && *name == '#')\n+\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t} else if (get_oid(\"HEAD\", &head_oid)) {\n+\t\terror(_(\"could not read HEAD\"));\n+\t\tret = -1;\n+\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n+\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t   ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static int do_reset(const char *name, int len, struct replay_opts *opts)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options unpack_tree_opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\t/* Determine the length of the label */\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n+\tunpack_tree_opts.head_idx = 1;\n+\tunpack_tree_opts.src_index = &the_index;\n+\tunpack_tree_opts.dst_index = &the_index;\n+\tunpack_tree_opts.fn = oneway_merge;\n+\tunpack_tree_opts.merge = 1;\n+\tunpack_tree_opts.update = 1;\n+\tunpack_tree_opts.reset = 1;\n+\n+\tif (read_cache_unmerged())\n+\t\treturn error_resolve_conflict(_(action_name(opts)));\n+\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret) {\n+\t\tstruct strbuf msg = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&msg, \"(rebase -i) reset '%.*s'\", len, name);\n+\t\tret = update_ref(msg.buf, \"HEAD\", &oid, NULL, 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n+\t\tstrbuf_release(&msg);\n+\t}\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2105,7 +2279,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n-\t\t} else if (!is_noop(item->command))\n+\t\t} else if (item->command == TODO_LABEL)\n+\t\t\tres = do_label(item->arg, item->arg_len);\n+\t\telse if (item->command == TODO_RESET)\n+\t\t\tres = do_reset(item->arg, item->arg_len, opts);\n+\t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339004","messageId":"852814d99f39281224c8f75668b18adae57864b9.1518307771.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 05/12] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:19Z","receivedAt":"2018-02-11T00:10:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label` and `reset` commands to label\ncommits and to reset to a labeled commits. This patch adds the `merge`\ncommand, with the following syntax:\n\n\tmerge [-C <commit>] <rev> # <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the merge commit that is about\nto be created.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list:\n\n\tlabel onto\n\n\t# Branch abc\n\treset onto\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\treset onto\n\tpick cafecafe And now for something completely different\n\tmerge -C baaabaaa abc # Merge the branch 'abc' into master\n\nTo edit the merge commit's message (a \"reword\" for merges, if you will),\nuse `-c` (lower-case) instead of `-C`; this convention was borrowed from\n`git commit` that also supports `-c` and `-C` with similar meanings.\n\nTo create *new* merges, i.e. without copying the commit message from an\nexisting commit, simply omit the `-C <commit>` parameter (which will\nopen an editor for the merge message):\n\n\tmerge abc\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in a separate\npatch series, support for merges using strategies other than the\nrecursive merge is left for the future.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   4 ++\n sequencer.c                | 158 +++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 162 insertions(+)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 7e5281e74aa..9d9d91f25e3 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -164,6 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n l, label <label> = label current HEAD with a name\n t, reset <label> = reset HEAD to a label\n+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n+.       create a merge commit using the original merge commit's\n+.       message (or the oneline, if no original merge commit was\n+.       specified). Use -c <commit> to reword the commit message.\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 8638086f667..e577c213494 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -795,6 +795,8 @@ enum todo_command {\n \tTODO_EXEC,\n \tTODO_LABEL,\n \tTODO_RESET,\n+\tTODO_MERGE,\n+\tTODO_MERGE_AND_EDIT,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -815,6 +817,8 @@ static struct {\n \t{ 'x', \"exec\" },\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n+\t{ 'm', \"merge\" },\n+\t{ 0, \"merge\" }, /* MERGE_AND_EDIT */\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1317,6 +1321,21 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn 0;\n \t}\n \n+\tif (item->command == TODO_MERGE) {\n+\t\tif (skip_prefix(bol, \"-C\", &bol))\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\t\titem->command = TODO_MERGE_AND_EDIT;\n+\t\t} else {\n+\t\t\titem->command = TODO_MERGE_AND_EDIT;\n+\t\t\titem->commit = NULL;\n+\t\t\titem->arg = bol;\n+\t\t\titem->arg_len = (int)(eol - bol);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n \tsaved = *end_of_object_name;\n \t*end_of_object_name = '\\0';\n@@ -2096,6 +2115,134 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n \treturn ret;\n }\n \n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    int run_commit_flags, struct replay_opts *opts)\n+{\n+\tint merge_arg_len;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *common, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint ret;\n+\tstatic struct lock_file lock;\n+\n+\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n+\t\tif (isspace(arg[merge_arg_len]))\n+\t\t\tbreak;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"cannot merge without a current revision\"));\n+\t}\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t     oid_to_hex(&commit->object.oid));\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tunuse_commit_buffer(commit, message);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tunuse_commit_buffer(commit, message);\n+\t} else {\n+\t\tconst char *p = arg + merge_arg_len;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tp += strspn(p, \" \\t\");\n+\t\tif (*p == '#' && isspace(p[1]))\n+\t\t\tp += 1 + strspn(p + 1, \" \\t\");\n+\t\tif (*p)\n+\t\t\tlen = strlen(p);\n+\t\telse {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tstrbuf_release(&buf);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\tif (!merge_commit) {\n+\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tcommon = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(common);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n+\t\t\t     merge_arg_len, arg);\n+\t}\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error(_(\"merge: Unable to write new index file\"));\n+\t}\n+\trollback_lock_file(&lock);\n+\n+\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2283,6 +2430,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_label(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n+\t\telse if (item->command == TODO_MERGE ||\n+\t\t\t item->command == TODO_MERGE_AND_EDIT)\n+\t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n+\t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n+\t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n \t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n@@ -2764,8 +2916,14 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  short_commit_name(item->commit) :\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n+\t\t\tif (item->command == TODO_MERGE)\n+\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n+\t\t\telse if (item->command == TODO_MERGE_AND_EDIT)\n+\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n+\n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n \t\t}\n+\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339005","messageId":"45cb7777b89da2e22a144496530be74846700040.1518307772.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 06/12] sequencer: fast-forward merge commits, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:22Z","receivedAt":"2018-02-11T00:10:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to recreate a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 21 ++++++++++++++++++++-\n 1 file changed, 20 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e577c213494..27d582479d1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2123,7 +2123,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *common, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint ret;\n+\tint can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \n \tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n@@ -2191,6 +2191,14 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\tstrbuf_release(&buf);\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the parent of the original merge commit,\n+\t * we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n \tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n \tif (!merge_commit) {\n@@ -2204,6 +2212,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\trollback_lock_file(&lock);\n \t\treturn -1;\n \t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn fast_forward_to(&commit->object.oid,\n+\t\t\t\t       &head_commit->object.oid, 0, opts);\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339006","messageId":"b0a2c33ffe316e61780ad89cfdc5d45390a0f939.1518307772.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 07/12] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:25Z","receivedAt":"2018-02-11T00:10:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --recreate-merges option. For a\ncommit topology like this (where the HEAD points to C):\n\n\t- A - B - C\n\t    \\   /\n\t      D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge -C 3456 D # C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch in this patch series.\n\nAs a special, hard-coded label, all merge-recreating todo lists start with\nthe command `label onto` so that we can later always refer to the revision\nonto which everything is rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 349 ++++++++++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 351 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex 7daee544b7b..a34ab5c0655 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -22,6 +22,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n+\t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -55,6 +56,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex 27d582479d1..7cd091a9fd6 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2808,6 +2810,341 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   (len == 1 && *label == '#') ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, or the label is a '#' (which we use as a separator\n+\t\t * between merge heads and oneline), we append a dash and a\n+\t\t * number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n+\t\t*cmd_label = abbr ? \"l\" : \"label\",\n+\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n+\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tif ((commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_original_commit_empty(commit))\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s -C %s\",\n+\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", cmd_label);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(to, \"onto\"))\n+\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n+\t\t\t\t\tcmd_reset, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\",\n+\t\t\t\t\tcmd_label, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -2818,11 +3155,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint recreate_merges = flags & TODO_LIST_RECREATE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_pick = 1;\n+\tif (recreate_merges)\n+\t\trevs.cherry_mark = 1;\n+\telse {\n+\t\trevs.max_parents = 1;\n+\t\trevs.cherry_pick = 1;\n+\t}\n \trevs.limited = 1;\n \trevs.reverse = 1;\n \trevs.right_only = 1;\n@@ -2846,6 +3188,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (recreate_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tstrbuf_reset(&buf);\n \t\tif (!keep_empty && is_original_commit_empty(commit))\ndiff --git a/sequencer.h b/sequencer.h\nindex 81f6d7d393f..11d1ac925ef 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -48,6 +48,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_RECREATE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.16.1.windows.1\n\n\n"},{"id":"339007","messageId":"f17e265d824a25dace1d6bf7c545e1061d746993.1518307772.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 08/12] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:27Z","receivedAt":"2018-02-11T00:10:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt to answer this was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto flatten the branch topology or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--recreate-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge <label>`. And once this mode will\nhave become stable and universally accepted, we can deprecate the design\nmistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |   9 +-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 ++\n t/t3430-rebase-recreate-merges.sh      | 146 +++++++++++++++++++++++++++++++++\n 5 files changed, 162 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 8a861c1e0d6..e9da7e26329 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -368,6 +368,12 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+--recreate-merges::\n+\tRecreate merge commits instead of flattening the history by replaying\n+\tmerges. Merge conflict resolutions or manual amendments to merge\n+\tcommits are not recreated automatically, but have to be recreated\n+\tmanually.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -770,7 +776,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+--recreate-merges for a more faithful representation.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3683c772c55..6893c3adabc 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2008,7 +2008,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--recreate-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 9d9d91f25e3..cfe3a537ac2 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -902,6 +902,7 @@ fi\n if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${recreate_merges:+--recreate-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex fd72a35c65b..d69bc7d0e0d 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+recreate-merges!   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -86,6 +87,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+recreate_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -262,6 +264,10 @@ do\n \t--keep-empty)\n \t\tkeep_empty=yes\n \t\t;;\n+\t--recreate-merges)\n+\t\trecreate_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nnew file mode 100755\nindex 00000000000..0073601a206\n--- /dev/null\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -0,0 +1,146 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2017 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --recreate-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+cat >script-from-scratch <<\\EOF\n+label onto\n+\n+# onebranch\n+pick G\n+pick D\n+label onebranch\n+\n+# second\n+reset onto\n+pick B\n+label second\n+\n+reset onto\n+merge -C H second\n+merge onebranch # Merge the topic branch 'onebranch'\n+EOF\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'create completely different structure' '\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\treset onto\n+\tpick d9df450 B\n+\tlabel E\n+\n+\treset onto\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point # C\n+\tpick 12bd07b D\n+\tmerge -C 2051b56 E # E\n+\tmerge -C 233d48a H # H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339008","messageId":"43ec5206b6c91279ad322cc2518629bd7769c0d1.1518307772.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 09/12] sequencer: make refs generated by the `label` command worktree-local","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:30Z","receivedAt":"2018-02-11T00:10:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                            |  3 ++-\n t/t3430-rebase-recreate-merges.sh | 14 ++++++++++++++\n 2 files changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 20ba82b4343..e8b84c189ff 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 0073601a206..1a3e43d66ff 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,4 +143,18 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339009","messageId":"90cad9f560f127528b2f1d790c7add43c3bb6543.1518307772.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 10/12] sequencer: handle post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:34Z","receivedAt":"2018-02-11T00:10:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --recreate-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits.\n\nThis patch implements the post-rewrite handling for the `merge` command\nwe just introduced. The other commands that were added recently (`label`\nand `reset`) do not create new commits, therefore post-rewrite do not\nneed to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c                       |  7 +++++--\n t/t3430-rebase-recreate-merges.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 30 insertions(+), 2 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 7cd091a9fd6..306ae014311 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2452,11 +2452,14 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n \t\telse if (item->command == TODO_MERGE ||\n-\t\t\t item->command == TODO_MERGE_AND_EDIT)\n+\t\t\t item->command == TODO_MERGE_AND_EDIT) {\n \t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n \t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n \t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n-\t\telse if (!is_noop(item->command))\n+\t\t\tif (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n+\t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 1a3e43d66ff..35a61ce90bb 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -157,4 +157,29 @@ test_expect_success 'refs/rewritten/* is worktree-local' '\n \ttest_cmp_rev HEAD \"$(cat wt/b)\"\n '\n \n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash --recreate-merges HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339010","messageId":"0001680fba5a24027678cfc8da13b96ecb76974d.1518307772.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 11/12] pull: accept --rebase=recreate to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:37Z","receivedAt":"2018-02-11T00:10:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `recreate` mode simply passes the\n`--recreate-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  5 ++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       |  2 ++\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 25 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0e25b2c92b3..da41ab246dc 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2607,6 +2611,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..b4f9f057ea9 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,16 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|recreate|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to recreate, rebase with the `--recreate-merges` option passed\n+to `git rebase` so that locally created merge commits will not be flattened.\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 511dbbe0f6e..e33c84e0345 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_RECREATE,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"recreate\", returns REBASE_RECREATE. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"recreate\"))\n+\t\treturn REBASE_RECREATE;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|recreate|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -798,7 +802,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_RECREATE)\n+\t\targv_array_push(&args, \"--recreate-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d95bf904c3b..b7d0f7ce596 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"recreate\"))\n+\t\t\t\tinfo->rebase = NORMAL_REBASE;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 6893c3adabc..6f98c96fee9 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2182,7 +2182,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true recreate preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.16.1.windows.1\n\n\n"},{"id":"339011","messageId":"f80c21efe578a4bae7fed105954b9f738483c994.1518307772.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 12/12] rebase -i: introduce --recreate-merges=[no-]rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-11T00:10:41Z","receivedAt":"2018-02-11T00:10:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This one is a bit tricky to explain, so let's try with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nTo illustrate what this new mode is all about, let's consider what\nhappens upon `git rebase -i --recreate-merges B`, in particular to\nthe commit `D`. So far, the new branch structure would be:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nThis is unintuitive behavior. Even worse, when recreating branch\nstructure, most use cases would appear to want cousins *not* to be\nrebased onto the new base commit. For example, Git for Windows (the\nheaviest user of the Git garden shears, which served as the blueprint\nfor --recreate-merges) frequently merges branches from `next` early, and\nthese branches certainly do *not* want to be rebased. In the example\nabove, the desired outcome would look like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and let's not rebase them by default, introducing the new\n\"rebase-cousins\" mode for use cases where they should be rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt      |  7 ++++++-\n builtin/rebase--helper.c          |  9 ++++++++-\n git-rebase--interactive.sh        |  1 +\n git-rebase.sh                     | 12 +++++++++++-\n sequencer.c                       |  4 ++++\n sequencer.h                       |  6 ++++++\n t/t3430-rebase-recreate-merges.sh | 23 +++++++++++++++++++++++\n 7 files changed, 59 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex e9da7e26329..0e6d020d924 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -368,11 +368,16 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n---recreate-merges::\n+--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tRecreate merge commits instead of flattening the history by replaying\n \tmerges. Merge conflict resolutions or manual amendments to merge\n \tcommits are not recreated automatically, but have to be recreated\n \tmanually.\n++\n+By default, or when `no-rebase-cousins` was specified, commits which do not\n+have `<upstream>` as direct ancestor keep their original branch point.\n+If the `rebase-cousins` mode is turned on, such commits are rebased onto\n+`<upstream>` (or `<onto>`, if specified).\n \n -p::\n --preserve-merges::\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex a34ab5c0655..cea99cb3235 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -23,6 +23,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"ff\", &opts.allow_ff, N_(\"allow fast-forward\")),\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n+\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,8 +59,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n+\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (rebase_cousins >= 0 && !recreate_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--recreate-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex cfe3a537ac2..e199fe1cca5 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -903,6 +903,7 @@ if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${recreate_merges:+--recreate-merges} \\\n+\t\t${rebase_cousins:+--rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex d69bc7d0e0d..58d778a2da0 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-recreate-merges!   try to recreate merges instead of skipping them\n+recreate-merges?   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -88,6 +88,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n recreate_merges=\n+rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -268,6 +269,15 @@ do\n \t\trecreate_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--recreate-merges=*)\n+\t\trecreate_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) rebase_cousins=t;;\n+\t\tno-rebase-cousins) rebase_cousins=;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex 306ae014311..c877432d7b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2931,6 +2931,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3106,6 +3107,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (!rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(to, \"onto\"))\n \t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\ndiff --git a/sequencer.h b/sequencer.h\nindex 11d1ac925ef..deebc6e3258 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -49,6 +49,12 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_RECREATE_MERGES (1U << 3)\n+/*\n+ * When recreating merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are *not* rebased onto the new base by default. If those\n+ * commits should be rebased onto the new base, this flag needs to be passed.\n+ */\n+#define TODO_LIST_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 35a61ce90bb..9a59f12b670 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,6 +143,29 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase cousins unless asked for' '\n+\twrite_script copy-editor.sh <<-\\EOF &&\n+\tcp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tEOF\n+\n+\ttest_config sequence.editor \\\"\"$PWD\"/copy-editor.sh\\\" &&\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges=rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.16.1.windows.1\n"},{"id":"339017","messageId":"CA+P7+xq3sQ78-iwrc-fNVT-Z7n3xi2mFUsFtPsnN61Xd+7Eahw@mail.gmail.com","threadId":"47635","inReplyTo":"ec5c7aa2-b36b-aca8-d82f-9d131ac83b41@kdbg.org","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-02-11T10:16:43Z","receivedAt":"2018-02-11T10:17:11Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Feb 8, 2018 at 11:13 PM, Johannes Sixt <j6t@kdbg.org> wrote:\n> Am 09.02.2018 um 07:11 schrieb Sergey Organov:\n>>\n>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>>>\n>>> Let me explain the scenario which comes up plenty of times in my work\n>>> with\n>>> Git for Windows. We have a thicket of some 70 branches on top of\n>>> git.git's\n>>> latest release. These branches often include fixup! and squash! commits\n>>> and even more complicated constructs that rebase cannot handle at all at\n>>> the moment, such as reorder-before! and reorder-after! (for commits that\n>>> really need to go into a different branch).\n>>\n>>\n>> I sympathize, but a solution that breaks even in simple cases can't be\n>> used reliably to solve more complex problems, sorry. Being so deep\n>> into your problems, I think you maybe just aren't seeing forest for the\n>> trees [1].\n>\n>\n> Hold your horses! Dscho has a point here. --preserve-merges --first-parent\n> works only as long as you don't tamper with the side branches. If you make\n> changes in the side branches during the same rebase operation, this\n> --first-parent mode would undo that change. (And, yes, its result would be\n> called an \"evil merge\", and that scary name _should_ frighten you!)\n>\n> -- Hannes\n\nThis is the reason I agree with Johannes, in regards to why\nrecreate-merges approach is correct.\n\nYes, an ideal system would be one which correctly, automatically\nre-creates the merge *as if* a human had re-merged the two newly\nre-created side branches, and preserves any changes in the result of\nthe merge, such as cases we call \"evil merges\" which includes\nnecessary changes to resolve conflicts properly.\n\nHowever, I would state that such a system, in order to cause the least\nsurprise to a user must be correct against arbitrary removal, reorder,\nand addition of new commits on both the main and topic side branches\nfor which we are re-creating the merges.\n\nThis is problematic, because something like how --preserve-merges\n--first-parent does not work under this case.\n\nAs a user of the tool, I may be biased because I already read and\nunderstand how recreate-merges is expected to work, but it makes sense\nto me that the re-creation of the merge merely re-does the merge and\nany modfications in the original would have to be carried over.\n\nI don't know what process we could use to essentially move the changes\nfrom the original merge into the new copy. What ever solution we have\nwould need to have a coherent user interface and be presentable in\nsome manner.\n\nOne way to think about the contents we're wanting to keep, rather than\nthe full tree result of the merge which we had before, what we\nactually want to keep in some sense is the resulting \"diff\" as shown\nby something like the condensed --combined output. This is obviously\nnot really a diff that we can apply.\n\nAnd even if we could apply it, since the merge is occurring, we can't\nexactly use 3-way merge conflict in order to actually apply the old\nchanges into the new merged setup? Could something like rerere logic\nwork here to track what was done and then re-apply it to the new merge\nwe create? And once we apply it, we need to be able to handle any\nconflicts that occur because of deleting, adding, or re-ordering\ncommits on the branches we're merging... so in some sense we could\nhave \"conflicts of conflicts\" which is a scenario that I don't yet\nhave a good handle on how this would be presented to the user.\n\nThanks,\nJake\n"},{"id":"339035","messageId":"874lmmerdu.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802102357510.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-12T04:58:05Z","receivedAt":"2018-02-12T04:58:13Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nThanks for explanations, and could you please answer this one:\n\n[...]\n\n>> I also have trouble making sense of \"Recreate merge commits instead of\n>> flattening the history by replaying merges.\" Is it \"<Recreate merge\n>> commits by replaying merges> instead of <flattening the history>\" or is it\n>> rather \"<Recreate merge commits> instead of <flattening the history by\n>> replaying merges>?\n\n-- Sergey\n"},{"id":"339036","messageId":"87zi4edbp3.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802102357510.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-12T05:22:16Z","receivedAt":"2018-02-12T05:22:27Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> Hi Sergey,\n>\n> On Fri, 9 Feb 2018, Sergey Organov wrote:\n>\n>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>> \n>> [...]\n>> \n>> > With this patch, the goodness of the Git garden shears comes to `git\n>> > rebase -i` itself. Passing the `--recreate-merges` option will generate\n>> > a todo list that can be understood readily, and where it is obvious\n>> > how to reorder commits. New branches can be introduced by inserting\n>> > `label` commands and calling `merge - <label> <oneline>`. And once this\n>> > mode has become stable and universally accepted, we can deprecate the\n>> > design mistake that was `--preserve-merges`.\n>> \n>> This doesn't explain why you introduced this new --recreate-merges. Why\n>> didn't you rather fix --preserve-merges to generate and use new todo\n>> list format?\n>\n> Because that would of course break existing users of\n> --preserve-merges.\n\nHow exactly? Doesn't \"--recreate-merges\" produce the same result as\n\"--preserve-merges\" if run non-interactively?\n\n> So why not --preserve-merges=v2? Because that would force me to maintain\n> --preserve-merges forever. And I don't want to.\n>\n>> It doesn't seem likely that todo list created by one Git version is to\n>> be ever used by another, right?\n>\n> No. But by scripts based on `git rebase -p`.\n>\n>> Is there some hidden reason here? Some tools outside of Git that use old\n>> todo list format, maybe?\n>\n> Exactly.\n>\n> I did mention such a tool: the Git garden shears:\n>\n> \thttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n>\n> Have a look at it. It will inform the discussion.\n\nI've searched for \"-p\" in the script, but didn't find positives for\neither \"-p\" or \"--preserve-merges\". How it would break if it doesn't use\nthem? What am I missing?\n\n>\n>> Then, if new option indeed required, please look at the resulting manual:\n>> \n>> --recreate-merges::\n>> \tRecreate merge commits instead of flattening the history by replaying\n>> \tmerges. Merge conflict resolutions or manual amendments to merge\n>> \tcommits are not preserved.\n>> \n>> -p::\n>> --preserve-merges::\n>> \tRecreate merge commits instead of flattening the history by replaying\n>> \tcommits a merge commit introduces. Merge conflict resolutions or manual\n>> \tamendments to merge commits are not preserved.\n>\n> As I stated in the cover letter, there are more patches lined up after\n> this patch series.\n\nGood, but I thought this one should better be self-consistent anyway.\nWhat if those that come later aren't included?\n\n>\n> Have a look at https://github.com/git/git/pull/447, especially the latest\n> commit in there which is an early version of the deprecation I intend to\n> bring about.\n\nYou shouldn't want a deprecation at all should you have re-used\n--preserve-merges in the first place, and I still don't see why you\nhaven't. \n\n>\n> Also, please refrain from saying things like... \"Don't you think ...\"\n>\n> If you don't like the wording, I wold much more appreciate it if a better\n> alternative was suggested.\n\nSorry, but how can I suggest one if I don't understand what you are\ndoing here in the first place? That's why I ask you.\n\n>\n>> Don't you think more explanations are needed there in the manual on\n>> why do we have 2 separate options with almost the same yet subtly\n>> different description? Is this subtle difference even important? How?\n>> \n>> I also have trouble making sense of \"Recreate merge commits instead of\n>> flattening the history by replaying merges.\" Is it \"<Recreate merge\n>> commits by replaying merges> instead of <flattening the history>\" or is it\n>> rather \"<Recreate merge commits> instead of <flattening the history by\n>> replaying merges>?\n>\n> The documentation of the --recreate-merges option is not meant to explain\n> the difference to --preserve-merges. It is meant to explain the difference\n> to regular `git rebase -i`, which flattens the commit history into a\n> single branch without merge commits (in fact, all merge commits are simply\n> ignored).\n\nYeah, that's obvious, but the point is that resulting manual is ended\nup being confusing.\n\n> And I would rather not start to describe the difference between\n> --recreate-merges and --preserve-merges because I want to deprecate the\n> latter, and describing the difference as I get the sense is your wish\n> would simply mean more work because it would have to be added and then\n> removed again.\n\nI suspect you actually didn't need those new option in the first place,\nand that's the core reason of these troubles.\n\n-- Sergey\n"},{"id":"339037","messageId":"87a7wed5e2.fsf@javad.com","threadId":"47635","inReplyTo":"ec5c7aa2-b36b-aca8-d82f-9d131ac83b41@kdbg.org","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-12T07:38:29Z","receivedAt":"2018-02-12T07:38:37Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n> Am 09.02.2018 um 07:11 schrieb Sergey Organov:\n>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>>> Let me explain the scenario which comes up plenty of times in my work with\n>>> Git for Windows. We have a thicket of some 70 branches on top of git.git's\n>>> latest release. These branches often include fixup! and squash! commits\n>>> and even more complicated constructs that rebase cannot handle at all at\n>>> the moment, such as reorder-before! and reorder-after! (for commits that\n>>> really need to go into a different branch).\n>>\n>> I sympathize, but a solution that breaks even in simple cases can't be\n>> used reliably to solve more complex problems, sorry. Being so deep\n>> into your problems, I think you maybe just aren't seeing forest for the\n>> trees [1].\n>\n> Hold your horses! Dscho has a point here. --preserve-merges\n> --first-parent works only as long as you don't tamper with the side\n> branches. If you make changes in the side branches during the same\n> rebase operation, this --first-parent mode would undo that change.\n\nHe has a point indeed, but it must not be used as an excuse to silently\ndamage user data, as if there are no other options!\n\nSimple --first-parent won't always fit, it's obvious. I used\n--first-parent patch as mere illustration of concept, it's rather\n\"rebase [-i] --keep-the-f*g-shape\" itself that should behave. There\nshould be no need for actual --first-parent that only fits\nno-manual-editing use-cases.\n\nLook at it as if it's a scale where --first-parent is on one side, and\n\"blind re-merge\" is on the other. The right answer(s) lie somewhere\nin-between, but I think they are much closer to --first-parent than they\nare to \"blind re-merge\".\n\n> (And, yes, its result would be called an \"evil merge\", and that scary\n> name _should_ frighten you!)\n\n(It won't always be \"evil merge\", and it still doesn't frighten even if\nit will, provided git stops making them more evil then they actually\ndeserve, and it isn't an excuse to silently distort user data anyway!)\n\n-- Sergey\n\n[1] The \"--first-parent\" here would rather keep that change from\npropagation to the main-line, not undo it, and sometimes it's even the\nright thing to do (\"-x ours\" for the original merge being one example).\nFrequently though it is needed on main-line indeed, and there should be\na way to tell git to propagate the change to the main-line, but even\nthen automatic blind unattended re-merge is wrong answer and I'm sure\ngit can be made to do better than that.\n"},{"id":"339062","messageId":"CAPig+cRc079RsC9i2S9PisE7pZjVfM8i7rUhbT-t6vupFZUaKQ@mail.gmail.com","threadId":"47635","inReplyTo":"852814d99f39281224c8f75668b18adae57864b9.1518307771.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 05/12] sequencer: introduce the `merge` command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-02-12T08:48:31Z","receivedAt":"2018-02-12T08:48:48Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Feb 10, 2018 at 7:10 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This patch is part of the effort to reimplement `--preserve-merges` with\n> a substantially improved design, a design that has been developed in the\n> Git for Windows project to maintain the dozens of Windows-specific patch\n> series on top of upstream Git.\n>\n> The previous patch implemented the `label` and `reset` commands to label\n> commits and to reset to a labeled commits. This patch adds the `merge`\n\ns/to a/to/\n\n> command, with the following syntax:\n>\n>         merge [-C <commit>] <rev> # <oneline>\n>\n> The <commit> parameter in this instance is the *original* merge commit,\n> whose author and message will be used for the merge commit that is about\n> to be created.\n>\n> The <rev> parameter refers to the (possibly rewritten) revision to\n> merge. Let's see an example of a todo list:\n>\n>         label onto\n>\n>         # Branch abc\n>         reset onto\n>         pick deadbeef Hello, world!\n>         label abc\n>\n>         reset onto\n>         pick cafecafe And now for something completely different\n>         merge -C baaabaaa abc # Merge the branch 'abc' into master\n>\n> To edit the merge commit's message (a \"reword\" for merges, if you will),\n> use `-c` (lower-case) instead of `-C`; this convention was borrowed from\n> `git commit` that also supports `-c` and `-C` with similar meanings.\n>\n> To create *new* merges, i.e. without copying the commit message from an\n> existing commit, simply omit the `-C <commit>` parameter (which will\n> open an editor for the merge message):\n>\n>         merge abc\n>\n> This comes in handy when splitting a branch into two or more branches.\n>\n> Note: this patch only adds support for recursive merges, to keep things\n> simple. Support for octopus merges will be added later in a separate\n> patch series, support for merges using strategies other than the\n> recursive merge is left for the future.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n"},{"id":"339095","messageId":"CAPig+cRVK3r2fSM77=L0zx77oBPyia_8ju79x6WJPT2U5o8e+A@mail.gmail.com","threadId":"47635","inReplyTo":"6c1e131d2eb26534869c06b714cc5cc729004a41.1518307771.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 04/12] sequencer: introduce new commands to reset the revision","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-02-12T19:26:46Z","receivedAt":"2018-02-12T19:26:59Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Feb 10, 2018 at 7:10 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> [...]\n> This commit implements the commands to label, and to reset to, given\n> revisions. The syntax is:\n>\n>         label <name>\n>         reset <name>\n> [...]\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/sequencer.c b/sequencer.c\n> @@ -1922,6 +1951,151 @@ static int do_exec(const char *command_line)\n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> +       [...]\n> +       if (write_in_full(fd, buf.buf, buf.len) < 0) {\n> +               error_errno(_(\"could not write to '%s'\"), filename);\n> +               rollback_lock_file(&lock);\n\nstrbuf_release(&buf);\n\n> +               return -1;\n> +       }\n> +       if (commit_lock_file(&lock) < 0) {\n> +               rollback_lock_file(&lock);\n\nstrbuf_release(&buf);\n\n> +               return error(_(\"failed to finalize '%s'\"), filename);\n> +       }\n> +\n\nstrbuf_release(&buf);\n\n> +       return 0;\n> +}\n> +\n> +static int do_reset(const char *name, int len, struct replay_opts *opts)\n> +{\n> +       [...]\n> +       unpack_tree_opts.reset = 1;\n> +\n> +       if (read_cache_unmerged())\n\nrollback_lock_file(&lock);\nstrbuf_release(&ref_name);\n\n> +               return error_resolve_conflict(_(action_name(opts)));\n> +\n> +       if (!fill_tree_descriptor(&desc, &oid)) {\n> +               error(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n> +               rollback_lock_file(&lock);\n> +               free((void *)desc.buffer);\n> +               strbuf_release(&ref_name);\n> +               return -1;\n> +       }\n"},{"id":"339100","messageId":"nycvar.QRO.7.76.6.1802122116200.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAPig+cRc079RsC9i2S9PisE7pZjVfM8i7rUhbT-t6vupFZUaKQ@mail.gmail.com","subject":"Re: [PATCH v3 05/12] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-12T20:17:17Z","receivedAt":"2018-02-12T20:17:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 12 Feb 2018, Eric Sunshine wrote:\n\n> On Sat, Feb 10, 2018 at 7:10 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > This patch is part of the effort to reimplement `--preserve-merges` with\n> > a substantially improved design, a design that has been developed in the\n> > Git for Windows project to maintain the dozens of Windows-specific patch\n> > series on top of upstream Git.\n> >\n> > The previous patch implemented the `label` and `reset` commands to label\n> > commits and to reset to a labeled commits. This patch adds the `merge`\n> \n> s/to a/to/\n\nFixed locally. Will be part of the next iteration, if one is necessary.\nOtherwise I will first ask Junio whether he can touch up the commit\nmessage before applying.\n\nCiao,\nDscho\n"},{"id":"339103","messageId":"nycvar.QRO.7.76.6.1802122118420.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"874lmmerdu.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-12T20:21:05Z","receivedAt":"2018-02-12T20:21:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Mon, 12 Feb 2018, Sergey Organov wrote:\n\n> Thanks for explanations, and could you please answer this one:\n> \n> [...]\n> \n> >> I also have trouble making sense of \"Recreate merge commits instead of\n> >> flattening the history by replaying merges.\" Is it \"<Recreate merge\n> >> commits by replaying merges> instead of <flattening the history>\" or is it\n> >> rather \"<Recreate merge commits> instead of <flattening the history by\n> >> replaying merges>?\n\nI thought I had answered that one.\n\nFlattening the history is what happens in regular rebase (i.e. without\n--recreate-merges and without --preserve-merges).\n\nThe idea to recreate merges is of course to *not* flatten the history.\n\nMaybe there should have been a comma after \"history\" to clarify what the\nsentence means.\n\nThe wording is poor either way, but you are also not a native speaker so\nwe have to rely on, say, Eric to help us out here.\n\nCiao,\nJohannes\n"},{"id":"339106","messageId":"nycvar.QRO.7.76.6.1802122121230.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87zi4edbp3.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-12T20:39:15Z","receivedAt":"2018-02-12T20:39:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Mon, 12 Feb 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >\n> > On Fri, 9 Feb 2018, Sergey Organov wrote:\n> >\n> >> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> >> \n> >> [...]\n> >> \n> >> > With this patch, the goodness of the Git garden shears comes to `git\n> >> > rebase -i` itself. Passing the `--recreate-merges` option will generate\n> >> > a todo list that can be understood readily, and where it is obvious\n> >> > how to reorder commits. New branches can be introduced by inserting\n> >> > `label` commands and calling `merge - <label> <oneline>`. And once this\n> >> > mode has become stable and universally accepted, we can deprecate the\n> >> > design mistake that was `--preserve-merges`.\n> >> \n> >> This doesn't explain why you introduced this new --recreate-merges. Why\n> >> didn't you rather fix --preserve-merges to generate and use new todo\n> >> list format?\n> >\n> > Because that would of course break existing users of\n> > --preserve-merges.\n> \n> How exactly?\n\nPower users of interactive rebase use scripting to augment Git's\nfunctionality. One particularly powerful trick is to override\nGIT_SEQUENCER_EDITOR with an invocation of such a script, to perform\nautomated edits. Such a script breaks when we change the format of the\ncontent to edit. If we change the format of the todo list generated in\n--preserve-merges mode, that is exactly what happens. We break existing\nusers.\n\nBTW it seems that you did not really read my previous reply carefully\nbecause I referenced such a use case: the Git garden shears. They do\noverride the sequencer editor, and while they do not exactly edit the todo\nlist (they simply through the generated one away), they generate a new\ntodo list and would break if that format changes. Of course, the shears do\nnot use the --preserve-merges mode, but from just reading about the way\nhow the Git garden shears work, it is quite obvious how similar users of\n--preserve-merges are likely to exist?\n\n> Doesn't \"--recreate-merges\" produce the same result as\n> \"--preserve-merges\" if run non-interactively?\n\nThe final result of a rebase where you do not edit the todo list? Should\nbe identical, indeed.\n\nBut that is the most boring, most uninteresting, and least important use\ncase. So we might just as well forget about it when we focus on keeping\nGit's usage stable.\n\n> > So why not --preserve-merges=v2? Because that would force me to\n> > maintain --preserve-merges forever. And I don't want to.\n> >\n> >> It doesn't seem likely that todo list created by one Git version is\n> >> to be ever used by another, right?\n> >\n> > No. But by scripts based on `git rebase -p`.\n> >\n> >> Is there some hidden reason here? Some tools outside of Git that use\n> >> old todo list format, maybe?\n> >\n> > Exactly.\n> >\n> > I did mention such a tool: the Git garden shears:\n> >\n> > \thttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n> >\n> > Have a look at it. It will inform the discussion.\n> \n> I've searched for \"-p\" in the script, but didn't find positives for\n> either \"-p\" or \"--preserve-merges\". How it would break if it doesn't use\n> them? What am I missing?\n\n*This* particular script does not use -p.\n\nBut it is not *this* particular script that I do not want to break! It is\n*all* scripts that use interactive rebase! Don't you also care about not\nbreaking existing users?\n\n> >> Then, if new option indeed required, please look at the resulting manual:\n> >> \n> >> --recreate-merges::\n> >> \tRecreate merge commits instead of flattening the history by replaying\n> >> \tmerges. Merge conflict resolutions or manual amendments to merge\n> >> \tcommits are not preserved.\n> >> \n> >> -p::\n> >> --preserve-merges::\n> >> \tRecreate merge commits instead of flattening the history by replaying\n> >> \tcommits a merge commit introduces. Merge conflict resolutions or manual\n> >> \tamendments to merge commits are not preserved.\n> >\n> > As I stated in the cover letter, there are more patches lined up after\n> > this patch series.\n> \n> Good, but I thought this one should better be self-consistent anyway.\n> What if those that come later aren't included?\n\nRight, let's just rip apart the partial progress because the latter\npatches might not make it in?\n\nI cannot work on that basis, and I also do not want to work on that basis.\n\nIf you do not like how the documentation is worded, fine, suggest a better\nalternative.\n\n> > Have a look at https://github.com/git/git/pull/447, especially the\n> > latest commit in there which is an early version of the deprecation I\n> > intend to bring about.\n> \n> You shouldn't want a deprecation at all should you have re-used\n> --preserve-merges in the first place, and I still don't see why you\n> haven't. \n\nKeep repeating it, and it won't become truer.\n\nIf you break formats, you break scripts. Git has *so* many users, there\nare very likely some who script *every* part of it.\n\nWe simply cannot do that.\n\nWhat we can is deprecate designs which we learned on the way were not only\nincomplete from the get-go, but bad overall and hard (or impossible) to\nfix. Like --preserve-merges.\n\nOr for that matter like the design you proposed, to use --first-parent for\n--recreate-merges. Or to use --first-parent for some --recreate-merges,\nsurprising users in very bad ways when it is not used (or when it is\nused). I get the impression that you still think it would be a good idea,\neven if it should be obvious that it is not.\n\n> > Also, please refrain from saying things like... \"Don't you think ...\"\n> >\n> > If you don't like the wording, I wold much more appreciate it if a better\n> > alternative was suggested.\n> \n> Sorry, but how can I suggest one if I don't understand what you are\n> doing here in the first place? That's why I ask you.\n\nThere are ways to put the person you ask on trial. And there are ways to\ngenuinely show interest and seek education.\n\nI am a really poor example how to communicate properly, of course, so\ndon't try to learn from me. I am trying myself to learn better ways to\nexpress what I mean clearly, and to express it in a direct yet kind\nmanner.\n\n> >> Don't you think more explanations are needed there in the manual on\n> >> why do we have 2 separate options with almost the same yet subtly\n> >> different description? Is this subtle difference even important? How?\n> >> \n> >> I also have trouble making sense of \"Recreate merge commits instead of\n> >> flattening the history by replaying merges.\" Is it \"<Recreate merge\n> >> commits by replaying merges> instead of <flattening the history>\" or is it\n> >> rather \"<Recreate merge commits> instead of <flattening the history by\n> >> replaying merges>?\n> >\n> > The documentation of the --recreate-merges option is not meant to explain\n> > the difference to --preserve-merges. It is meant to explain the difference\n> > to regular `git rebase -i`, which flattens the commit history into a\n> > single branch without merge commits (in fact, all merge commits are simply\n> > ignored).\n> \n> Yeah, that's obvious, but the point is that resulting manual is ended\n> up being confusing.\n\nAgain, just saying something is bad, is bad. Saying something leaves room\nfor improvement and then suggesting how to improve it, is good.\n\n> > And I would rather not start to describe the difference between\n> > --recreate-merges and --preserve-merges because I want to deprecate the\n> > latter, and describing the difference as I get the sense is your wish\n> > would simply mean more work because it would have to be added and then\n> > removed again.\n> \n> I suspect you actually didn't need those new option in the first place,\n> and that's the core reason of these troubles.\n\nAre you suspecting that I, myself, do not use --recreate-merges?\n\nIf so, please read the cover letter again, in particular the part where I\ndescribe how this entire series of patch series arose from the Git garden\nshears, which I invented myself to help with maintaining Git for Windows,\nand which I use for five years now. This should help disperse that\nsuspicion rather quickly: the intent of --recreate-merges is to allow me\nto simplify the shears by quite a bit, and maybe eventually even get rid\nof the script altogether (if I ever manage to convince myself that the\nconcept of a merging-rebase should be official enough to enter core Git).\n\nI am a heavy user of --recreate-merges, even if it does not really exist\nyet. I have five years of experience with it, which is the reason why I am\nso confident about its design, and why I can tell you a lot about typical\nuse cases and common pitfalls, and where the original design had to be\nadjusted.\n\nCiao,\nJohannes\n"},{"id":"339107","messageId":"nycvar.QRO.7.76.6.1802122145560.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAPig+cRVK3r2fSM77=L0zx77oBPyia_8ju79x6WJPT2U5o8e+A@mail.gmail.com","subject":"Re: [PATCH v3 04/12] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-12T20:46:56Z","receivedAt":"2018-02-12T20:47:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 12 Feb 2018, Eric Sunshine wrote:\n\n> On Sat, Feb 10, 2018 at 7:10 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > [...]\n> > This commit implements the commands to label, and to reset to, given\n> > revisions. The syntax is:\n> >\n> >         label <name>\n> >         reset <name>\n> > [...]\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/sequencer.c b/sequencer.c\n> > @@ -1922,6 +1951,151 @@ static int do_exec(const char *command_line)\n> > +static int safe_append(const char *filename, const char *fmt, ...)\n> > +{\n> > +       [...]\n> > +       if (write_in_full(fd, buf.buf, buf.len) < 0) {\n> > +               error_errno(_(\"could not write to '%s'\"), filename);\n> > +               rollback_lock_file(&lock);\n> \n> strbuf_release(&buf);\n> \n> > +               return -1;\n> > +       }\n> > +       if (commit_lock_file(&lock) < 0) {\n> > +               rollback_lock_file(&lock);\n> \n> strbuf_release(&buf);\n> \n> > +               return error(_(\"failed to finalize '%s'\"), filename);\n> > +       }\n> > +\n> \n> strbuf_release(&buf);\n> \n> > +       return 0;\n> > +}\n> > +\n> > +static int do_reset(const char *name, int len, struct replay_opts *opts)\n> > +{\n> > +       [...]\n> > +       unpack_tree_opts.reset = 1;\n> > +\n> > +       if (read_cache_unmerged())\n> \n> rollback_lock_file(&lock);\n> strbuf_release(&ref_name);\n\nThank you very much! I fixed these locally and force-pushed the\nrecreate-merges branch to https://github.com/dscho/git. These fixes will\nbe part of v4.\n\nCiao,\nDscho\n"},{"id":"339166","messageId":"CA+P7+xqKPbwt-KeYTKadM3bHDkU3m0WiezKeVu75TyftBSEYDA@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802122121230.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-02-13T04:39:00Z","receivedAt":"2018-02-13T04:39:27Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Mon, Feb 12, 2018 at 12:39 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Hi Sergey,\n>\n> On Mon, 12 Feb 2018, Sergey Organov wrote:\n>> > Have a look at https://github.com/git/git/pull/447, especially the\n>> > latest commit in there which is an early version of the deprecation I\n>> > intend to bring about.\n>>\n>> You shouldn't want a deprecation at all should you have re-used\n>> --preserve-merges in the first place, and I still don't see why you\n>> haven't.\n>\n> Keep repeating it, and it won't become truer.\n>\n> If you break formats, you break scripts. Git has *so* many users, there\n> are very likely some who script *every* part of it.\n>\n> We simply cannot do that.\n>\n> What we can is deprecate designs which we learned on the way were not only\n> incomplete from the get-go, but bad overall and hard (or impossible) to\n> fix. Like --preserve-merges.\n>\n> Or for that matter like the design you proposed, to use --first-parent for\n> --recreate-merges. Or to use --first-parent for some --recreate-merges,\n> surprising users in very bad ways when it is not used (or when it is\n> used). I get the impression that you still think it would be a good idea,\n> even if it should be obvious that it is not.\n\nIf we consider the addition of new todo list elements as \"user\nbreaking\", then yes this change would be user-script breaking.\n\nSince we did not originally spell out that todo-list items are subject\nto enhancement by addition of operations in the future, scripts are\nlikely not designed to allow addition of new elements.\n\nThus, adding recreate-merges, and deprecating preserve-merges, seems\nto me to be the correct action to take here.\n\nOne could argue that users should have expected new todo list elements\nto be added in the future and thus design their scripts to cope with\nsuch a thing. If you can convincingly argue this, then I don't\nnecessarily see it as a complete user breaking change to fix\npreserve-merges in order to allow it to handle re-ordering properly..\n\nI think I lean towards agreeing with Johannes, and that adding\nrecreate-merges and removing preserve-merges is the better solution.\n\nThanks,\nJake\n"},{"id":"339169","messageId":"87d1198k4e.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802122121230.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-13T06:43:45Z","receivedAt":"2018-02-13T06:43:55Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> Hi Sergey,\n>\n> On Mon, 12 Feb 2018, Sergey Organov wrote:\n>\n>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>> >\n>> > On Fri, 9 Feb 2018, Sergey Organov wrote:\n>> >\n>> >> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>> >> \n>> >> [...]\n>> >> \n>> >> > With this patch, the goodness of the Git garden shears comes to `git\n>> >> > rebase -i` itself. Passing the `--recreate-merges` option will generate\n>> >> > a todo list that can be understood readily, and where it is obvious\n>> >> > how to reorder commits. New branches can be introduced by inserting\n>> >> > `label` commands and calling `merge - <label> <oneline>`. And once this\n>> >> > mode has become stable and universally accepted, we can deprecate the\n>> >> > design mistake that was `--preserve-merges`.\n>> >> \n>> >> This doesn't explain why you introduced this new --recreate-merges. Why\n>> >> didn't you rather fix --preserve-merges to generate and use new todo\n>> >> list format?\n>> >\n>> > Because that would of course break existing users of\n>> > --preserve-merges.\n>> \n>> How exactly?\n>\n> Power users of interactive rebase use scripting to augment Git's\n> functionality. One particularly powerful trick is to override\n> GIT_SEQUENCER_EDITOR with an invocation of such a script, to perform\n> automated edits. Such a script breaks when we change the format of the\n> content to edit. If we change the format of the todo list generated in\n> --preserve-merges mode, that is exactly what happens. We break existing\n> users.\n\nI didn't say a word against \"--preserve-merges mode\", whatever it is,\nonly about re-using \"--preserve-merges\" command-line option to \"git\nrebase\", the git user interface. I'm sure you see the difference? Unless\nthere are out-of-git scripts that do use \"git rebase --preserve-merges\"\nand simultaneously do rely on the todo list format this exact command\ngenerates, there should be no breakage of existing users caused by\nchanging todo list format generated by  \"git rebase --preserve-merges\".\n\nOld broken \"--preserve-merges mode\" could be then kept in the\nimplementation for ages, unused by the new fixed \"git rebase\n--preserve-merge\", for the sake of compatibility.\n\n> BTW it seems that you did not really read my previous reply carefully\n> because I referenced such a use case: the Git garden shears.\n\nI thought I did. You confirm below that this script doesn't use \"git\nrebase --preserve-merges\" in the first place, nor will it break if \"git\nrebase --preserve-merges\" starts to generate new todo format, yet you\nexpected I'd readily see how it's relevant? No, I'm not that clever, nor\nam I a mind-reader.\n\n> They do override the sequencer editor, and while they do not exactly\n> edit the todo list (they simply through the generated one away), they\n> generate a new todo list and would break if that format changes. Of\n> course, the shears do not use the --preserve-merges mode,\n> but from just reading about the way how the Git garden shears work, it\n> is quite obvious how similar users of --preserve-merges are likely to\n> exist?\n\nMaybe, I dunno. If even \"garden shears\" won't break, then what will? Do\nyou know an example?\n\nAnyway, as it seems it's too late already for such a change, let me stop\nthis and assume there are indeed such scripts that will break and that\nit's indeed a good idea to introduce new option. Case closed. The manual\nshould still be fixed though, I think.\n\n>> Doesn't \"--recreate-merges\" produce the same result as\n>> \"--preserve-merges\" if run non-interactively?\n>\n> The final result of a rebase where you do not edit the todo list? Should\n> be identical, indeed.\n\nThat's good to hear.\n\n> But that is the most boring, most uninteresting, and least important use\n> case.\n\nFor you. Do you suddenly stop caring about compatibility?\n\n> So we might just as well forget about it when we focus on keeping\n> Git's usage stable.\n\nWhy? It's good it behaves the same, so --preserve-merges could indeed be\ndeprecated, as you apparently intend.\n\n>> > So why not --preserve-merges=v2? Because that would force me to\n>> > maintain --preserve-merges forever. And I don't want to.\n>> >\n>> >> It doesn't seem likely that todo list created by one Git version is\n>> >> to be ever used by another, right?\n>> >\n>> > No. But by scripts based on `git rebase -p`.\n>> >\n>> >> Is there some hidden reason here? Some tools outside of Git that use\n>> >> old todo list format, maybe?\n>> >\n>> > Exactly.\n>> >\n>> > I did mention such a tool: the Git garden shears:\n>> >\n>> > \thttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n>> >\n>> > Have a look at it. It will inform the discussion.\n>> \n>> I've searched for \"-p\" in the script, but didn't find positives for\n>> either \"-p\" or \"--preserve-merges\". How it would break if it doesn't use\n>> them? What am I missing?\n>\n> *This* particular script does not use -p.\n>\n> But it is not *this* particular script that I do not want to break!\n\nI thought that was an example of a tool that would break. Well, it won't\nbreak. Good.\n\n> It is *all* scripts that use interactive rebase!\n\nI'm really interested, and here I *do* ask for education. What are\nthose? As I now only ask this out of curiosity, and don't argue\n--recreate-merges anymore, are you finally willing to reveal the\ninformation?\n\n> Don't you also care about not breaking existing users?\n\nI do care. I just suspected they are very unlikely to exist, and I do\nwant to be educated in this matter indeed, as they could be rather\ninteresting.\n\n[Please notice violation of your own standard of not using \"Don't\nyou...\", not that I care myself.]\n\n>> >> Then, if new option indeed required, please look at the resulting manual:\n>> >> \n>> >> --recreate-merges::\n>> >> \tRecreate merge commits instead of flattening the history by replaying\n>> >> \tmerges. Merge conflict resolutions or manual amendments to merge\n>> >> \tcommits are not preserved.\n>> >> \n>> >> -p::\n>> >> --preserve-merges::\n>> >> \tRecreate merge commits instead of flattening the history by replaying\n>> >> \tcommits a merge commit introduces. Merge conflict resolutions or manual\n>> >> \tamendments to merge commits are not preserved.\n>> >\n>> > As I stated in the cover letter, there are more patches lined up after\n>> > this patch series.\n>> \n>> Good, but I thought this one should better be self-consistent anyway.\n>> What if those that come later aren't included?\n>\n> Right, let's just rip apart the partial progress because the latter\n> patches might not make it in?\n\nNo, let's fix it instead.\n\n>\n> I cannot work on that basis, and I also do not want to work on that basis.\n>\n> If you do not like how the documentation is worded, fine, suggest a better\n> alternative.\n\nI suggested to re-use --preserve-merges command-line option to \"git\nrebase\", unless there are actual users that would break. But as you\nbelieve that's wrong idea, then it could be something like this in the\nmanual:\n\n--recreate-merges::\n\tRecreate merge commits instead of flattening the history. Merge\n\tconflict resolutions or manual amendments to merge commits are\n\tnot preserved. \n\n-p::\n--preserve-merges::\n\tThis option is similar to --recreate-merges, but doesn't\n        support interactive mode properly. This option is deprecated,\n        use --recreate-merges instead.\n\n>\n>> > Have a look at https://github.com/git/git/pull/447, especially the\n>> > latest commit in there which is an early version of the deprecation I\n>> > intend to bring about.\n>> \n>> You shouldn't want a deprecation at all should you have re-used\n>> --preserve-merges in the first place, and I still don't see why you\n>> haven't. \n>\n> Keep repeating it, and it won't become truer.\n\nIt is just my point that I repeat, and you gave no evidence it is false,\nso I assume it's true, unless proved otherwise.\n\n[...]\n\n> Or for that matter like the design you proposed, to use --first-parent for\n> --recreate-merges. Or to use --first-parent for some --recreate-merges,\n> surprising users in very bad ways when it is not used (or when it is\n> used). I get the impression that you still think it would be a good idea,\n> even if it should be obvious that it is not.\n\nWhat you describe here is bad idea indeed, but it has little to do with\nwhat I actually have in mind and what you apparently don't want to even\ntry to understand.\n\n>> > Also, please refrain from saying things like... \"Don't you think ...\"\n>> >\n>> > If you don't like the wording, I wold much more appreciate it if a better\n>> > alternative was suggested.\n>> \n>> Sorry, but how can I suggest one if I don't understand what you are\n>> doing here in the first place? That's why I ask you.\n>\n> There are ways to put the person you ask on trial. And there are ways to\n> genuinely show interest and seek education.\n\nI didn't seek education, nor did I intend any trial. I asked for\nclarification of the patch to the manual page that you wrote in a way\nthat made resulting manual page confusing for me. Confusing manual is\noften indication of some additional problem(s) elsewhere, that's what\nI've learned for sure, from multiple occasions, so I did reveal my\ndoubts.\n\n> I am a really poor example how to communicate properly, of course, so\n> don't try to learn from me. I am trying myself to learn better ways to\n> express what I mean clearly, and to express it in a direct yet kind\n> manner.\n>\n>> >> Don't you think more explanations are needed there in the manual on\n>> >> why do we have 2 separate options with almost the same yet subtly\n>> >> different description? Is this subtle difference even important? How?\n>> >> \n>> >> I also have trouble making sense of \"Recreate merge commits instead of\n>> >> flattening the history by replaying merges.\" Is it \"<Recreate merge\n>> >> commits by replaying merges> instead of <flattening the history>\" or is it\n>> >> rather \"<Recreate merge commits> instead of <flattening the history by\n>> >> replaying merges>?\n>> >\n>> > The documentation of the --recreate-merges option is not meant to explain\n>> > the difference to --preserve-merges. It is meant to explain the difference\n>> > to regular `git rebase -i`, which flattens the commit history into a\n>> > single branch without merge commits (in fact, all merge commits are simply\n>> > ignored).\n>> \n>> Yeah, that's obvious, but the point is that resulting manual is ended\n>> up being confusing.\n>\n> Again, just saying something is bad, is bad. Saying something leaves room\n> for improvement and then suggesting how to improve it, is good.\n\nPlease see wording suggestion above.\n\n>> > And I would rather not start to describe the difference between\n>> > --recreate-merges and --preserve-merges because I want to deprecate the\n>> > latter, and describing the difference as I get the sense is your wish\n>> > would simply mean more work because it would have to be added and then\n>> > removed again.\n>> \n>> I suspect you actually didn't need those new option in the first place,\n>> and that's the core reason of these troubles.\n>\n> Are you suspecting that I, myself, do not use --recreate-merges?\n\nI suspect that if you've had rather changed --preserve-merges, you'd\nhappily use it and no --recreate-merges were ever necessary. You did\nwhat you did, and it seems to be too late to ask for changing it back,\nexactly due to heavy use of this new option.\n\n> If so, please read the cover letter again, in particular the part where I\n> describe how this entire series of patch series arose from the Git garden\n> shears, which I invented myself to help with maintaining Git for Windows,\n> and which I use for five years now. This should help disperse that\n> suspicion rather quickly: the intent of --recreate-merges is to allow me\n> to simplify the shears by quite a bit, and maybe eventually even get rid\n> of the script altogether (if I ever manage to convince myself that the\n> concept of a merging-rebase should be official enough to enter core Git).\n>\n> I am a heavy user of --recreate-merges, even if it does not really exist\n> yet. I have five years of experience with it, which is the reason why I am\n> so confident about its design, and why I can tell you a lot about typical\n> use cases and common pitfalls, and where the original design had to be\n> adjusted.\n\nI fail to see how anything of the above would change should\n--recreate-merges be still called --preserve-merges, but I do see why\nyou don't want that to happen now, so please only consider fixing of the\nmanual page.\n\n-- Sergey\n"},{"id":"339170","messageId":"87bmgt8k2k.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802122118420.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-13T06:44:51Z","receivedAt":"2018-02-13T06:44:58Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> Hi Sergey,\n>\n> On Mon, 12 Feb 2018, Sergey Organov wrote:\n>\n>> Thanks for explanations, and could you please answer this one:\n>> \n>> [...]\n>> \n>> >> I also have trouble making sense of \"Recreate merge commits instead of\n>> >> flattening the history by replaying merges.\" Is it \"<Recreate merge\n>> >> commits by replaying merges> instead of <flattening the history>\" or is it\n>> >> rather \"<Recreate merge commits> instead of <flattening the history by\n>> >> replaying merges>?\n>\n> I thought I had answered that one.\n\nNo, not really, but now you did, please see below.\n\n>\n> Flattening the history is what happens in regular rebase (i.e. without\n> --recreate-merges and without --preserve-merges).\n>\n> The idea to recreate merges is of course to *not* flatten the history.\n\nSure. Never supposed it is.\n\n> Maybe there should have been a comma after \"history\" to clarify what the\n> sentence means.\n\nThat's the actual answer to my question, but it in turn raises another\none: why did you change wording of --preserve-merges description for\nthis new option?\n\n> The wording is poor either way, but you are also not a native speaker so\n> we have to rely on, say, Eric to help us out here.\n\nLikely, but why didn't you keep original wording from --preserve-merges?\nDo you feel it's somehow poor either?\n\nAnyway, please also refer to wording suggestion in the another (lengthy)\nanswer in this thread.\n\n-- Sergey\n"},{"id":"339171","messageId":"8760718imo.fsf@javad.com","threadId":"47635","inReplyTo":"CA+P7+xqKPbwt-KeYTKadM3bHDkU3m0WiezKeVu75TyftBSEYDA@mail.gmail.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-13T07:15:59Z","receivedAt":"2018-02-13T07:16:06Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Jake,\n\nJacob Keller <jacob.keller@gmail.com> writes:\n\n> On Mon, Feb 12, 2018 at 12:39 PM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n>> Hi Sergey,\n>>\n>> On Mon, 12 Feb 2018, Sergey Organov wrote:\n>>> > Have a look at https://github.com/git/git/pull/447, especially the\n>>> > latest commit in there which is an early version of the deprecation I\n>>> > intend to bring about.\n>>>\n>>> You shouldn't want a deprecation at all should you have re-used\n>>> --preserve-merges in the first place, and I still don't see why you\n>>> haven't.\n>>\n>> Keep repeating it, and it won't become truer.\n>>\n>> If you break formats, you break scripts. Git has *so* many users, there\n>> are very likely some who script *every* part of it.\n>>\n>> We simply cannot do that.\n>>\n>> What we can is deprecate designs which we learned on the way were not only\n>> incomplete from the get-go, but bad overall and hard (or impossible) to\n>> fix. Like --preserve-merges.\n>>\n>> Or for that matter like the design you proposed, to use --first-parent for\n>> --recreate-merges. Or to use --first-parent for some --recreate-merges,\n>> surprising users in very bad ways when it is not used (or when it is\n>> used). I get the impression that you still think it would be a good idea,\n>> even if it should be obvious that it is not.\n>\n> If we consider the addition of new todo list elements as \"user\n> breaking\", then yes this change would be user-script breaking.\n\nIt _is_ user script breaking, provided such script exists. Has anybody\nactually seen one? Not that it's wrong to be extra-cautious about it,\njust curios. Note that to be actually affected, such a script must\ninvoke \"git rebase -p\" _command_ and then tweak its todo output to\nproduce outcome.\n\n> Since we did not originally spell out that todo-list items are subject\n> to enhancement by addition of operations in the future, scripts are\n> likely not designed to allow addition of new elements.\n\nOut of curiosity, are you going to spell it now, for the new todo\nformat?\n\n> Thus, adding recreate-merges, and deprecating preserve-merges, seems\n> to me to be the correct action to take here.\n\nYes, sure, provided there is actual breakage, or at least informed\nsuspicion there is one.\n\n> One could argue that users should have expected new todo list elements\n> to be added in the future and thus design their scripts to cope with\n> such a thing. If you can convincingly argue this, then I don't\n> necessarily see it as a complete user breaking change to fix\n> preserve-merges in order to allow it to handle re-ordering properly..\n\nI'd not argue this way myself. If there are out-of-git-tree non-human\nusers that accept and tweak todo _generated_ by current \"git rebase -p\"\n_command_, I also vote for a new option.\n\n> I think I lean towards agreeing with Johannes, and that adding\n> recreate-merges and removing preserve-merges is the better solution.\n\nOn these grounds it is, no objections.\n\n-- Sergey\n"},{"id":"339253","messageId":"CA+P7+xpg76nP8+KBrYWeYPcm8+0jS_MzL+B=LF6WPfBv44zrwA@mail.gmail.com","threadId":"47635","inReplyTo":"8760718imo.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-02-14T01:35:39Z","receivedAt":"2018-02-14T01:36:06Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Mon, Feb 12, 2018 at 11:15 PM, Sergey Organov <sorganov@gmail.com> wrote:\n> Hi Jake,\n>\n> Jacob Keller <jacob.keller@gmail.com> writes:\n>\n>> On Mon, Feb 12, 2018 at 12:39 PM, Johannes Schindelin\n>> <Johannes.Schindelin@gmx.de> wrote:\n>>> Hi Sergey,\n>>>\n>>> On Mon, 12 Feb 2018, Sergey Organov wrote:\n>>>> > Have a look at https://github.com/git/git/pull/447, especially the\n>>>> > latest commit in there which is an early version of the deprecation I\n>>>> > intend to bring about.\n>>>>\n>>>> You shouldn't want a deprecation at all should you have re-used\n>>>> --preserve-merges in the first place, and I still don't see why you\n>>>> haven't.\n>>>\n>>> Keep repeating it, and it won't become truer.\n>>>\n>>> If you break formats, you break scripts. Git has *so* many users, there\n>>> are very likely some who script *every* part of it.\n>>>\n>>> We simply cannot do that.\n>>>\n>>> What we can is deprecate designs which we learned on the way were not only\n>>> incomplete from the get-go, but bad overall and hard (or impossible) to\n>>> fix. Like --preserve-merges.\n>>>\n>>> Or for that matter like the design you proposed, to use --first-parent for\n>>> --recreate-merges. Or to use --first-parent for some --recreate-merges,\n>>> surprising users in very bad ways when it is not used (or when it is\n>>> used). I get the impression that you still think it would be a good idea,\n>>> even if it should be obvious that it is not.\n>>\n>> If we consider the addition of new todo list elements as \"user\n>> breaking\", then yes this change would be user-script breaking.\n>\n> It _is_ user script breaking, provided such script exists. Has anybody\n> actually seen one? Not that it's wrong to be extra-cautious about it,\n> just curios. Note that to be actually affected, such a script must\n> invoke \"git rebase -p\" _command_ and then tweak its todo output to\n> produce outcome.\n>\n>> Since we did not originally spell out that todo-list items are subject\n>> to enhancement by addition of operations in the future, scripts are\n>> likely not designed to allow addition of new elements.\n>\n> Out of curiosity, are you going to spell it now, for the new todo\n> format?\n>\n>> Thus, adding recreate-merges, and deprecating preserve-merges, seems\n>> to me to be the correct action to take here.\n>\n> Yes, sure, provided there is actual breakage, or at least informed\n> suspicion there is one.\n>\n>> One could argue that users should have expected new todo list elements\n>> to be added in the future and thus design their scripts to cope with\n>> such a thing. If you can convincingly argue this, then I don't\n>> necessarily see it as a complete user breaking change to fix\n>> preserve-merges in order to allow it to handle re-ordering properly..\n>\n> I'd not argue this way myself. If there are out-of-git-tree non-human\n> users that accept and tweak todo _generated_ by current \"git rebase -p\"\n> _command_, I also vote for a new option.\n>\n\nTo be fair, I have not seen anything that actually reads the todo list\nand tweaks it in such a manner. The closest example is the git garden\nshears script, which simply replaces the todo list.\n\nIt's certainly *possible* that such a script would exist though,\n\nThanks,\nJake\n\n>> I think I lean towards agreeing with Johannes, and that adding\n>> recreate-merges and removing preserve-merges is the better solution.\n>\n> On these grounds it is, no objections.\n>\n> -- Sergey\n"},{"id":"339261","messageId":"87vaf03z78.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802081317320.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-14T05:41:31Z","receivedAt":"2018-02-14T05:41:47Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n[...]\n> Just to give you one concrete example: when I recently rebased some\n> patches (no reording or dropping involved here!) and one of the picks\n> failed with merge conflicts, I realized that that particular commit\n> introduced incorrect formatting and fixed that right away (verifying that\n> no other commits introduced incorrect formatting, of course).\n>\n> With your new cute idea to magically cherry-pick -m1, this change would\n> have been magically dropped from the subsequent merge commits!\n\nYou put it as if the problem you describe is unsolvable short of getting\nback to your favorite blind re-merge. Do you really believe it?\n\nI thought it's obvious that I originally meant \"cherry-pick -m1\" to be\nan explanation facility, a proof of concept, not the final answer to all\nthe problems of history editing. It's a nice base for actually\napproaching these problems though, unlike blind re-merge currently being\nused, the latter having no potential.\n\nThe fact that bare naked \"cherry-pick -m1\" doesn't do what is often[1]\nrequired in such cases neither voids the general idea of reproducing\nmerge-the-result, nor does it make current re-merge approach less\nbroken.\n\n[1] Please take into consideration that it's _not always_ the case that\none needs a change made to a side-branch to actually propagate to the\nmain-line over the merge (think \"merge -x ours\", or something similar\nbut not that simple), and then it's rather the cute idea to blindly\nre-merge that will wreak havoc, as in a lot of other cases.\n\n-- Sergey\n"},{"id":"339424","messageId":"nycvar.QRO.7.76.6.1802150207170.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87bmgt8k2k.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-15T01:08:02Z","receivedAt":"2018-02-15T01:08:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 13 Feb 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > The wording is poor either way, but you are also not a native speaker so\n> > we have to rely on, say, Eric to help us out here.\n> \n> Likely, but why didn't you keep original wording from --preserve-merges?\n> Do you feel it's somehow poor either?\n\nYes, I felt it is poor, especially when --recreate-merges is present, that\nis indeed why I changed it.\n\nCiao,\nJohannes\n"},{"id":"339425","messageId":"nycvar.QRO.7.76.6.1802150209390.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xpg76nP8+KBrYWeYPcm8+0jS_MzL+B=LF6WPfBv44zrwA@mail.gmail.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-15T01:14:11Z","receivedAt":"2018-02-15T01:14:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Tue, 13 Feb 2018, Jacob Keller wrote:\n\n> On Mon, Feb 12, 2018 at 11:15 PM, Sergey Organov <sorganov@gmail.com> wrote:\n> >\n> > Jacob Keller <jacob.keller@gmail.com> writes:\n> >\n> >> On Mon, Feb 12, 2018 at 12:39 PM, Johannes Schindelin\n> >> <Johannes.Schindelin@gmx.de> wrote:\n> >>>\n> >>> On Mon, 12 Feb 2018, Sergey Organov wrote:\n> >>>> > Have a look at https://github.com/git/git/pull/447, especially the\n> >>>> > latest commit in there which is an early version of the deprecation I\n> >>>> > intend to bring about.\n> >>>>\n> >>>> You shouldn't want a deprecation at all should you have re-used\n> >>>> --preserve-merges in the first place, and I still don't see why you\n> >>>> haven't.\n> >>>\n> >>> Keep repeating it, and it won't become truer.\n> >>>\n> >>> If you break formats, you break scripts. Git has *so* many users, there\n> >>> are very likely some who script *every* part of it.\n> >>>\n> >>> We simply cannot do that.\n> >>>\n> >>> What we can is deprecate designs which we learned on the way were not only\n> >>> incomplete from the get-go, but bad overall and hard (or impossible) to\n> >>> fix. Like --preserve-merges.\n> >>>\n> >>> Or for that matter like the design you proposed, to use --first-parent for\n> >>> --recreate-merges. Or to use --first-parent for some --recreate-merges,\n> >>> surprising users in very bad ways when it is not used (or when it is\n> >>> used). I get the impression that you still think it would be a good idea,\n> >>> even if it should be obvious that it is not.\n> >>\n> >> If we consider the addition of new todo list elements as \"user\n> >> breaking\", then yes this change would be user-script breaking.\n> >\n> > It _is_ user script breaking, provided such script exists. Has anybody\n> > actually seen one? Not that it's wrong to be extra-cautious about it,\n> > just curios. Note that to be actually affected, such a script must\n> > invoke \"git rebase -p\" _command_ and then tweak its todo output to\n> > produce outcome.\n> >\n> >> Since we did not originally spell out that todo-list items are subject\n> >> to enhancement by addition of operations in the future, scripts are\n> >> likely not designed to allow addition of new elements.\n> >\n> > Out of curiosity, are you going to spell it now, for the new todo\n> > format?\n> >\n> >> Thus, adding recreate-merges, and deprecating preserve-merges, seems\n> >> to me to be the correct action to take here.\n> >\n> > Yes, sure, provided there is actual breakage, or at least informed\n> > suspicion there is one.\n> >\n> >> One could argue that users should have expected new todo list elements\n> >> to be added in the future and thus design their scripts to cope with\n> >> such a thing. If you can convincingly argue this, then I don't\n> >> necessarily see it as a complete user breaking change to fix\n> >> preserve-merges in order to allow it to handle re-ordering properly..\n> >\n> > I'd not argue this way myself. If there are out-of-git-tree non-human\n> > users that accept and tweak todo _generated_ by current \"git rebase -p\"\n> > _command_, I also vote for a new option.\n> >\n> \n> To be fair, I have not seen anything that actually reads the todo list\n> and tweaks it in such a manner. The closest example is the git garden\n> shears script, which simply replaces the todo list.\n> \n> It's certainly *possible* that such a script would exist though,\n\nWe actually know of such scripts.\n\nRemember how rewriting parts of rebase -i in C broke somebody's script\nbecause the todo list was not re-read after a successful `exec`?\n\nGuess three times why that script was broken? Precisely: it modified the\ntodo list!\n\nTo see the fix (and the explanation) in all its glory, just have a look at\n54fd3243dae (rebase -i: reread the todo list if `exec` touched it,\n2017-04-26).\n\nAnd even if we did not know about any user. What does that mean? Does it\nmean that there is no such user? Or does it not rather mean that our\nimagination is rather limited, but we *still* should practice safe\nsoftware development and use the totally appropriate vehicle of\ndeprecating, rather than replacing, functionality?\n\nObviously, the latter option is what I favor, that's why I suggested it in\nthe first place.\n\nCiao,\nDscho\n"},{"id":"339427","messageId":"nycvar.QRO.7.76.6.1802150214500.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87d1198k4e.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-15T01:40:56Z","receivedAt":"2018-02-15T01:41:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Tue, 13 Feb 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >\n> > On Mon, 12 Feb 2018, Sergey Organov wrote:\n> >\n> >> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >> >\n> >> > On Fri, 9 Feb 2018, Sergey Organov wrote:\n> >> >\n> >> >> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> >> >> \n> >> >> [...]\n> >> >> \n> >> >> > With this patch, the goodness of the Git garden shears comes to `git\n> >> >> > rebase -i` itself. Passing the `--recreate-merges` option will generate\n> >> >> > a todo list that can be understood readily, and where it is obvious\n> >> >> > how to reorder commits. New branches can be introduced by inserting\n> >> >> > `label` commands and calling `merge - <label> <oneline>`. And once this\n> >> >> > mode has become stable and universally accepted, we can deprecate the\n> >> >> > design mistake that was `--preserve-merges`.\n> >> >> \n> >> >> This doesn't explain why you introduced this new --recreate-merges. Why\n> >> >> didn't you rather fix --preserve-merges to generate and use new todo\n> >> >> list format?\n> >> >\n> >> > Because that would of course break existing users of\n> >> > --preserve-merges.\n> >> \n> >> How exactly?\n> >\n> > Power users of interactive rebase use scripting to augment Git's\n> > functionality. One particularly powerful trick is to override\n> > GIT_SEQUENCER_EDITOR with an invocation of such a script, to perform\n> > automated edits. Such a script breaks when we change the format of the\n> > content to edit. If we change the format of the todo list generated in\n> > --preserve-merges mode, that is exactly what happens. We break existing\n> > users.\n> \n> I didn't say a word against \"--preserve-merges mode\", whatever it is,\n> only about re-using \"--preserve-merges\" command-line option to \"git\n> rebase\", the git user interface.\n\n*I* said something against --preserve-merges. You did not even need to. I\nknow fully well its limitations.\n\nI also said something agains the suggestion to replace the functionality\nof a previously well-defined (although misdesigned) feature.\n\nI do not know how often I have to repeat that your suggestion would break\nbackwards-compatibility?\n\n> I'm sure you see the difference?\n\nYes, of course I do, and you do not even have to suggest otherwise by\nasking such a question.\n\nI already demonstrated plenty of times that I do understand what you wish\nfor, and that I see serious problems with it.\n\n> Unless there are out-of-git scripts that do use \"git rebase\n> --preserve-merges\" and simultaneously do rely on the todo list format\n> this exact command generates, there should be no breakage of existing\n> users caused by changing todo list format generated by  \"git rebase\n> --preserve-merges\".\n\nSo. Just because you cannot imagine that anybody uses rebase in such a\npowerful way means you are willing to break their setups?\n\nGit is used by millions of users. Many of them are power users. It would\nbe quite naive to assume that nobody uses rebase -p in a scripted manner\nthat modifies the todo list.\n\nChanging the behavior of --preserve-merges would be simply irresponsible,\nand that's why we won't do it.\n\nEven if that was not so, there is yet another really good reason not to\nreuse the name --preserve-merges: The name itself suggests that this mode\nis about preserving all merges in the specified commit range. That was its\noriginal intention, too, as I never designed it to be user with rebase -i.\nIf the todo list of rebase -p is not modified (preserving the entire\ncommit topology as well as possible), it works quite well.\n\nThe new mode is not so much about preserving, though. It is about\ninteractively modifying the todo list, to change the order of the commits,\neven to change the branch topology. That means that we do not necessarily\npreserve the merges. We recreate them. So you see, I did try to be careful\nabout the naming, too. I thought about this.\n\n> Old broken \"--preserve-merges mode\" could be then kept in the\n> implementation for ages, unused by the new fixed \"git rebase\n> --preserve-merge\", for the sake of compatibility.\n\nThis sentence contradicts itself. Either you keep the code unused, or you\nkeep it used for backwards-compatibility.\n\n> > BTW it seems that you did not really read my previous reply carefully\n> > because I referenced such a use case: the Git garden shears.\n> \n> I thought I did. You confirm below that this script doesn't use \"git\n> rebase --preserve-merges\" in the first place, nor will it break if \"git\n> rebase --preserve-merges\" starts to generate new todo format, yet you\n> expected I'd readily see how it's relevant? No, I'm not that clever, nor\n> am I a mind-reader.\n\nYou caught me. I am not a user of --preserve-merges. Not anymore.\n\nDoes that mean that by extension nobody is a user of that feature?\n\nCertainly not.\n\nAnd does my example of (ab-)using interactive rebase by scripting on top\nof it maybe suggest that others do the same? Maybe even with\n--preserve-merges? Most likely. Git is used by many, many users. It would\nbe foolish to make any assumption about how Git is used by others.\n\n> > They do override the sequencer editor, and while they do not exactly\n> > edit the todo list (they simply through the generated one away), they\n> > generate a new todo list and would break if that format changes. Of\n> > course, the shears do not use the --preserve-merges mode, but from\n> > just reading about the way how the Git garden shears work, it is quite\n> > obvious how similar users of --preserve-merges are likely to exist?\n> \n> Maybe, I dunno. If even \"garden shears\" won't break, then what will? Do\n> you know an example?\n\nYou are not seriously suggesting that we should assume that there is no\nsuch Git user, just because neither you nor I personally know such a user?\nSeriously?\n\n> Anyway, as it seems it's too late already for such a change, let me stop\n> this and assume there are indeed such scripts that will break and that\n> it's indeed a good idea to introduce new option. Case closed. The manual\n> should still be fixed though, I think.\n\nFinally I got through. Yes, we cannot break backwards-compatibility.\n\n> >> Doesn't \"--recreate-merges\" produce the same result as\n> >> \"--preserve-merges\" if run non-interactively?\n> >\n> > The final result of a rebase where you do not edit the todo list? Should\n> > be identical, indeed.\n> \n> That's good to hear.\n> \n> > But that is the most boring, most uninteresting, and least important use\n> > case.\n> \n> For you. Do you suddenly stop caring about compatibility?\n\nWhat does \"fun\" and \"interesting\" have to do with compatibility?\n\nYes, to me, this case is boring. And yes, I took pains to make it work\n(for compatibility).\n\nAnd yes, I did not stop after that. After the boring case, I still wanted\nto think things through, to come up with a design that would not be too\nlimited to be useful. With a design that is consistent.\n\nIf you find holes in the consistency or usability, please do call them\nout.\n\nBut please stop suggesting to break backwards-compatibility, or to\nintroduce features that are inconsistent and/or can produce \"surprising\"\nresults (as --first-parent would, and multiple contributors had to argue\nin concert, pointing out how it is not extensible to the general case, and\nis hence consistent).\n\n> > It is *all* scripts that use interactive rebase!\n> \n> I'm really interested, and here I *do* ask for education. What are\n> those? As I now only ask this out of curiosity, and don't argue\n> --recreate-merges anymore, are you finally willing to reveal the\n> information?\n\nIf you are interested, why don't you go about asking people for their\npower scripts.\n\nIn the context of this patch series, I am not interested in such a\ncollection. What I had to do was to convince myself that they could not\nexist, in which case I could just do away with backwards-compatibility. In\nthe alternative, the safe play is to go the deprecation route.\n\nA mere \"highly unlikely\" made up from thin air does not convince me,\nthough. So deprecation route it is.\n\n> > Don't you also care about not breaking existing users?\n> \n> I do care. I just suspected they are very unlikely to exist, and I do\n> want to be educated in this matter indeed, as they could be rather\n> interesting.\n\nOkay, \"very unlikely\". Not \"highly unlikely\". Still, it is an unconvincing\nargument that suffers very seriously from lack of any robust evidence.\n\n> [Please notice violation of your own standard of not using \"Don't\n> you...\", not that I care myself.]\n\nTrue. My apologies.\n\nAnd as the rest of the mail seems to reiterate the idea that the\n--recreate-merges code should override --preserve-merges (breaking\nbackwards-compatibility), despite my repeated efforts to educate you why\nthis would be a bad idea, I guess the best course of action to avoid\ntelling you \"Don't you ...\" is to just stop here.\n\nCiao,\nJohannes\n"},{"id":"339430","messageId":"87eflmzxjx.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802150207170.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-15T04:28:18Z","receivedAt":"2018-02-15T04:28:26Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi,\n>\n> On Tue, 13 Feb 2018, Sergey Organov wrote:\n>\n>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>> \n>> > The wording is poor either way, but you are also not a native speaker so\n>> > we have to rely on, say, Eric to help us out here.\n>> \n>> Likely, but why didn't you keep original wording from --preserve-merges?\n>> Do you feel it's somehow poor either?\n>\n> Yes, I felt it is poor, especially when --recreate-merges is present, that\n> is indeed why I changed it.\n\nSo, how about this (yeah, I noticed the option now got arguments, but\nplease, tweak this to the new implementation yourself):\n\n--recreate-merges::\n\tRecreate merge commits instead of flattening the history. Merge\n\tconflict resolutions or manual amendments to merge commits are\n\tnot preserved. \n\n-p::\n--preserve-merges::\n\t(deprecated) This option is similar to --recreate-merges. It has\n        no proper support for interactive mode and thus is deprecated.\n        Use '--recreate-merges' instead.\n\n\n-- Sergey\n"},{"id":"339431","messageId":"87a7wazx8k.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1802150209390.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-02-15T04:35:07Z","receivedAt":"2018-02-15T04:35:15Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\nJohannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n[...]\n\n>> > I'd not argue this way myself. If there are out-of-git-tree non-human\n>> > users that accept and tweak todo _generated_ by current \"git rebase -p\"\n>> > _command_, I also vote for a new option.\n>> >\n>> \n>> To be fair, I have not seen anything that actually reads the todo list\n>> and tweaks it in such a manner. The closest example is the git garden\n>> shears script, which simply replaces the todo list.\n>> \n>> It's certainly *possible* that such a script would exist though,\n>\n> We actually know of such scripts.\n\nPlease consider to explain this in the description of the change. I\nbelieve readers deserve an explanation of why you decided to invent new\noption instead of fixing the old one, even if it were only a suspicion,\nmore so if it is confidence.\n\n-- Sergey\n"},{"id":"339473","messageId":"nycvar.QRO.7.76.6.1802151748160.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87a7wazx8k.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-15T16:50:02Z","receivedAt":"2018-02-15T16:50:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 15 Feb 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> [...]\n> \n> >> > I'd not argue this way myself. If there are out-of-git-tree non-human\n> >> > users that accept and tweak todo _generated_ by current \"git rebase -p\"\n> >> > _command_, I also vote for a new option.\n> >> >\n> >> \n> >> To be fair, I have not seen anything that actually reads the todo list\n> >> and tweaks it in such a manner. The closest example is the git garden\n> >> shears script, which simply replaces the todo list.\n> >> \n> >> It's certainly *possible* that such a script would exist though,\n> >\n> > We actually know of such scripts.\n> \n> Please consider to explain this in the description of the change. I\n> believe readers deserve an explanation of why you decided to invent new\n> option instead of fixing the old one, even if it were only a suspicion,\n> more so if it is confidence.\n\nI considered.\n\nAnd since even the absence of this use case would *still* not be a\nconvincing case against keeping --preserve-merges backwards-compatible, I\nwill not mention it.\n\nJust saying that --preserve-merges is not changed, in order to keep\nbackwards-compatibility, is plenty enough.\n\nIt probably already convinced the Git maintainer, who is very careful\nabout backwards-compatibility, and rightfully so.\n\nCiao,\nJohannes\n"},{"id":"339474","messageId":"nycvar.QRO.7.76.6.1802151750250.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87eflmzxjx.fsf@javad.com","subject":"Re: [PATCH 5/8] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-15T16:51:58Z","receivedAt":"2018-02-15T16:52:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 15 Feb 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > On Tue, 13 Feb 2018, Sergey Organov wrote:\n> >\n> >> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >> \n> >> > The wording is poor either way, but you are also not a native speaker so\n> >> > we have to rely on, say, Eric to help us out here.\n> >> \n> >> Likely, but why didn't you keep original wording from --preserve-merges?\n> >> Do you feel it's somehow poor either?\n> >\n> > Yes, I felt it is poor, especially when --recreate-merges is present, that\n> > is indeed why I changed it.\n> \n> So, how about this (yeah, I noticed the option now got arguments, but\n> please, tweak this to the new implementation yourself):\n> \n> --recreate-merges::\n> \tRecreate merge commits instead of flattening the history. Merge\n> \tconflict resolutions or manual amendments to merge commits are\n> \tnot preserved. \n> \n> -p::\n> --preserve-merges::\n> \t(deprecated) This option is similar to --recreate-merges. It has\n>         no proper support for interactive mode and thus is deprecated.\n>         Use '--recreate-merges' instead.\n\nI still don't like either.\n\nI want something different there: descriptions that are a bit more\nself-contained, and only describe the differences to -i or\n--preserve-merges in a second paragraph.\n\nDon't worry about it, though, I don't think you or me are capable of a\ngood explanation. I will ask some native speakers I trust.\n\nCiao,\nJohannes\n"},{"id":"340018","messageId":"cover.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 00/12] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:35:37Z","receivedAt":"2018-02-23T12:36:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, I dreamt of an interactive rebase that would not\nflatten branch structure, but instead recreate the commit topology\nfaithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --recreate-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge -C 3456 D # C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --recreate-merges.\n\nChanges since v3:\n\n- fixed a grammar error in \"introduce the `merge` command\"'s commit message.\n\n- fixed a couple of resource leaks in safe_append() and do_reset(), pointed\n  out by Eric Sunshine.\n\n\nJohannes Schindelin (11):\n  sequencer: avoid using errno clobbered by rollback_lock_file()\n  sequencer: make rearrange_squash() a bit more obvious\n  sequencer: introduce new commands to reset the revision\n  sequencer: introduce the `merge` command\n  sequencer: fast-forward merge commits, if possible\n  rebase-helper --make-script: introduce a flag to recreate merges\n  rebase: introduce the --recreate-merges option\n  sequencer: make refs generated by the `label` command worktree-local\n  sequencer: handle post-rewrite for merge commands\n  pull: accept --rebase=recreate to recreate the branch topology\n  rebase -i: introduce --recreate-merges=[no-]rebase-cousins\n\nStefan Beller (1):\n  git-rebase--interactive: clarify arguments\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   5 +-\n Documentation/git-rebase.txt           |  14 +-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |   2 +\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |  22 +-\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 742 ++++++++++++++++++++++++++++++++-\n sequencer.h                            |   7 +\n t/t3430-rebase-recreate-merges.sh      | 208 +++++++++\n 13 files changed, 1027 insertions(+), 31 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\n\nbase-commit: e3a80781f5932f5fea12a49eb06f3ade4ed8945c\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v4\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v4\n\nInterdiff vs v3:\n diff --git a/Documentation/config.txt b/Documentation/config.txt\n index f57e9cf10ca..8c9adea0d0c 100644\n --- a/Documentation/config.txt\n +++ b/Documentation/config.txt\n @@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n  \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n  \tbranch-specific manner.\n  +\n +When recreate, also pass `--recreate-merges` along to 'git rebase'\n +so that locally committed merge commits will not be flattened\n +by running 'git pull'.\n ++\n  When preserve, also pass `--preserve-merges` along to 'git rebase'\n  so that locally committed merge commits will not be flattened\n  by running 'git pull'.\n @@ -2607,6 +2611,10 @@ pull.rebase::\n  \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n  \tper-branch basis.\n  +\n +When recreate, also pass `--recreate-merges` along to 'git rebase'\n +so that locally committed merge commits will not be flattened\n +by running 'git pull'.\n ++\n  When preserve, also pass `--preserve-merges` along to 'git rebase'\n  so that locally committed merge commits will not be flattened\n  by running 'git pull'.\n diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\n index ce05b7a5b13..b4f9f057ea9 100644\n --- a/Documentation/git-pull.txt\n +++ b/Documentation/git-pull.txt\n @@ -101,13 +101,16 @@ Options related to merging\n  include::merge-options.txt[]\n  \n  -r::\n ---rebase[=false|true|preserve|interactive]::\n +--rebase[=false|true|recreate|preserve|interactive]::\n  \tWhen true, rebase the current branch on top of the upstream\n  \tbranch after fetching. If there is a remote-tracking branch\n  \tcorresponding to the upstream branch and the upstream branch\n  \twas rebased since last fetched, the rebase uses that information\n  \tto avoid rebasing non-local changes.\n  +\n +When set to recreate, rebase with the `--recreate-merges` option passed\n +to `git rebase` so that locally created merge commits will not be flattened.\n ++\n  When set to preserve, rebase with the `--preserve-merges` option passed\n  to `git rebase` so that locally created merge commits will not be flattened.\n  +\n diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n index d713951b86a..c5a77599c47 100644\n --- a/Documentation/git-rebase.txt\n +++ b/Documentation/git-rebase.txt\n @@ -373,6 +373,17 @@ The commit list format can be changed by setting the configuration option\n  rebase.instructionFormat.  A customized instruction format will automatically\n  have the long commit hash prepended to the format.\n  \n +--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n +\tRecreate merge commits instead of flattening the history by replaying\n +\tmerges. Merge conflict resolutions or manual amendments to merge\n +\tcommits are not recreated automatically, but have to be recreated\n +\tmanually.\n ++\n +By default, or when `no-rebase-cousins` was specified, commits which do not\n +have `<upstream>` as direct ancestor keep their original branch point.\n +If the `rebase-cousins` mode is turned on, such commits are rebased onto\n +`<upstream>` (or `<onto>`, if specified).\n +\n  -p::\n  --preserve-merges::\n  \tRecreate merge commits instead of flattening the history by replaying\n @@ -775,7 +786,8 @@ BUGS\n  The todo list presented by `--preserve-merges --interactive` does not\n  represent the topology of the revision graph.  Editing commits and\n  rewording their commit messages should work fine, but attempts to\n -reorder commits tend to produce counterintuitive results.\n +reorder commits tend to produce counterintuitive results. Use\n +--recreate-merges for a more faithful representation.\n  \n  For example, an attempt to rearrange\n  ------------\n diff --git a/builtin/pull.c b/builtin/pull.c\n index 1876271af94..9da2cfa0bd3 100644\n --- a/builtin/pull.c\n +++ b/builtin/pull.c\n @@ -27,14 +27,16 @@ enum rebase_type {\n  \tREBASE_FALSE = 0,\n  \tREBASE_TRUE,\n  \tREBASE_PRESERVE,\n +\tREBASE_RECREATE,\n  \tREBASE_INTERACTIVE\n  };\n  \n  /**\n   * Parses the value of --rebase. If value is a false value, returns\n   * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n - * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n - * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n + * \"recreate\", returns REBASE_RECREATE. If value is \"preserve\", returns\n + * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n + * fatal is true, otherwise returns REBASE_INVALID.\n   */\n  static enum rebase_type parse_config_rebase(const char *key, const char *value,\n  \t\tint fatal)\n @@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n  \t\treturn REBASE_TRUE;\n  \telse if (!strcmp(value, \"preserve\"))\n  \t\treturn REBASE_PRESERVE;\n +\telse if (!strcmp(value, \"recreate\"))\n +\t\treturn REBASE_RECREATE;\n  \telse if (!strcmp(value, \"interactive\"))\n  \t\treturn REBASE_INTERACTIVE;\n  \n @@ -130,7 +134,7 @@ static struct option pull_options[] = {\n  \t/* Options passed to git-merge or git-rebase */\n  \tOPT_GROUP(N_(\"Options related to merging\")),\n  \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n -\t  \"false|true|preserve|interactive\",\n +\t  \"false|true|recreate|preserve|interactive\",\n  \t  N_(\"incorporate changes by rebasing rather than merging\"),\n  \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n  \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n @@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head,\n  \targv_push_verbosity(&args);\n  \n  \t/* Options passed to git-rebase */\n -\tif (opt_rebase == REBASE_PRESERVE)\n +\tif (opt_rebase == REBASE_RECREATE)\n +\t\targv_array_push(&args, \"--recreate-merges\");\n +\telse if (opt_rebase == REBASE_PRESERVE)\n  \t\targv_array_push(&args, \"--preserve-merges\");\n  \telse if (opt_rebase == REBASE_INTERACTIVE)\n  \t\targv_array_push(&args, \"--interactive\");\n diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\n index ad074705bb5..5d1f12de57b 100644\n --- a/builtin/rebase--helper.c\n +++ b/builtin/rebase--helper.c\n @@ -12,8 +12,8 @@ static const char * const builtin_rebase_helper_usage[] = {\n  int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  {\n  \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n -\tunsigned flags = 0, keep_empty = 0;\n -\tint abbreviate_commands = 0;\n +\tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n +\tint abbreviate_commands = 0, rebase_cousins = -1;\n  \tenum {\n  \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n  \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n @@ -24,6 +24,9 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n  \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n  \t\t\tN_(\"allow commits with empty messages\")),\n +\t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n +\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n +\t\t\t N_(\"keep original branch points of cousins\")),\n  \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n  \t\t\t\tCONTINUE),\n  \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n @@ -57,8 +60,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  \n  \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n  \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n +\tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n +\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n  \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n  \n +\tif (rebase_cousins >= 0 && !recreate_merges)\n +\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n +\t\t\t  \"--recreate-merges\"));\n +\n  \tif (command == CONTINUE && argc == 1)\n  \t\treturn !!sequencer_continue(&opts);\n  \tif (command == ABORT && argc == 1)\n diff --git a/builtin/remote.c b/builtin/remote.c\n index d95bf904c3b..b7d0f7ce596 100644\n --- a/builtin/remote.c\n +++ b/builtin/remote.c\n @@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n  \t\t\t\tinfo->rebase = v;\n  \t\t\telse if (!strcmp(value, \"preserve\"))\n  \t\t\t\tinfo->rebase = NORMAL_REBASE;\n +\t\t\telse if (!strcmp(value, \"recreate\"))\n +\t\t\t\tinfo->rebase = NORMAL_REBASE;\n  \t\t\telse if (!strcmp(value, \"interactive\"))\n  \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n  \t\t}\n diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n index 88813e91244..3d44cb6890c 100644\n --- a/contrib/completion/git-completion.bash\n +++ b/contrib/completion/git-completion.bash\n @@ -2008,7 +2008,7 @@ _git_rebase ()\n  \t--*)\n  \t\t__gitcomp \"\n  \t\t\t--onto --merge --strategy --interactive\n -\t\t\t--preserve-merges --stat --no-stat\n +\t\t\t--recreate-merges --preserve-merges --stat --no-stat\n  \t\t\t--committer-date-is-author-date --ignore-date\n  \t\t\t--ignore-whitespace --whitespace=\n  \t\t\t--autosquash --no-autosquash\n @@ -2182,7 +2182,7 @@ _git_config ()\n  \t\treturn\n  \t\t;;\n  \tbranch.*.rebase)\n -\t\t__gitcomp \"false true preserve interactive\"\n +\t\t__gitcomp \"false true recreate preserve interactive\"\n  \t\treturn\n  \t\t;;\n  \tremote.pushdefault)\n diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n index a2659fea982..679d79e0d17 100644\n --- a/git-rebase--interactive.sh\n +++ b/git-rebase--interactive.sh\n @@ -162,6 +162,12 @@ s, squash <commit> = use commit, but meld into previous commit\n  f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n  x, exec <commit> = run command (the rest of the line) using shell\n  d, drop <commit> = remove commit\n +l, label <label> = label current HEAD with a name\n +t, reset <label> = reset HEAD to a label\n +m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n +.       create a merge commit using the original merge commit's\n +.       message (or the oneline, if no original merge commit was\n +.       specified). Use -c <commit> to reword the commit message.\n  \n  These lines can be re-ordered; they are executed from top to bottom.\n  \" | git stripspace --comment-lines >>\"$todo\"\n @@ -900,6 +906,8 @@ fi\n  if test t != \"$preserve_merges\"\n  then\n  \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n +\t\t${recreate_merges:+--recreate-merges} \\\n +\t\t${rebase_cousins:+--rebase-cousins} \\\n  \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n  \tdie \"$(gettext \"Could not generate todo list\")\"\n  else\n diff --git a/git-rebase.sh b/git-rebase.sh\n index b353c33d417..9487e543bec 100755\n --- a/git-rebase.sh\n +++ b/git-rebase.sh\n @@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n  autostash          automatically stash/stash pop before and after\n  fork-point         use 'merge-base --fork-point' to refine upstream\n  onto=!             rebase onto given branch instead of upstream\n +recreate-merges?   try to recreate merges instead of skipping them\n  p,preserve-merges! try to recreate merges instead of ignoring them\n  s,strategy=!       use the given merge strategy\n  no-ff!             cherry-pick all commits, even if unchanged\n @@ -87,6 +88,8 @@ type=\n  state_dir=\n  # One of {'', continue, skip, abort}, as parsed from command line\n  action=\n +recreate_merges=\n +rebase_cousins=\n  preserve_merges=\n  autosquash=\n  keep_empty=\n @@ -267,6 +270,19 @@ do\n  \t--allow-empty-message)\n  \t\tallow_empty_message=--allow-empty-message\n  \t\t;;\n +\t--recreate-merges)\n +\t\trecreate_merges=t\n +\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n +\t\t;;\n +\t--recreate-merges=*)\n +\t\trecreate_merges=t\n +\t\tcase \"${1#*=}\" in\n +\t\trebase-cousins) rebase_cousins=t;;\n +\t\tno-rebase-cousins) rebase_cousins=;;\n +\t\t*) die \"Unknown mode: $1\";;\n +\t\tesac\n +\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n +\t\t;;\n  \t--preserve-merges)\n  \t\tpreserve_merges=t\n  \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n diff --git a/refs.c b/refs.c\n index 20ba82b4343..e8b84c189ff 100644\n --- a/refs.c\n +++ b/refs.c\n @@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n  static int is_per_worktree_ref(const char *refname)\n  {\n  \treturn !strcmp(refname, \"HEAD\") ||\n -\t\tstarts_with(refname, \"refs/bisect/\");\n +\t\tstarts_with(refname, \"refs/bisect/\") ||\n +\t\tstarts_with(refname, \"refs/rewritten/\");\n  }\n  \n  static int is_pseudoref_syntax(const char *refname)\n diff --git a/sequencer.c b/sequencer.c\n index cfa01d3bdd2..b2bf63029d4 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -23,6 +23,10 @@\n  #include \"hashmap.h\"\n  #include \"notes-utils.h\"\n  #include \"sigchain.h\"\n +#include \"unpack-trees.h\"\n +#include \"worktree.h\"\n +#include \"oidmap.h\"\n +#include \"oidset.h\"\n  \n  #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n  \n @@ -120,6 +124,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n  static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n  static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n  \t\"rebase-merge/rewritten-pending\")\n +\n +/*\n + * The path of the file listing refs that need to be deleted after the rebase\n + * finishes. This is used by the `label` command to record the need for cleanup.\n + */\n +static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n +\n  /*\n   * The following files are written by git-rebase just after parsing the\n   * command-line (and are only consumed, not modified, by the sequencer).\n @@ -244,18 +255,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n  \n  int sequencer_remove_state(struct replay_opts *opts)\n  {\n -\tstruct strbuf dir = STRBUF_INIT;\n +\tstruct strbuf buf = STRBUF_INIT;\n  \tint i;\n  \n +\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n +\t\tchar *p = buf.buf;\n +\t\twhile (*p) {\n +\t\t\tchar *eol = strchr(p, '\\n');\n +\t\t\tif (eol)\n +\t\t\t\t*eol = '\\0';\n +\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n +\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n +\t\t\tif (!eol)\n +\t\t\t\tbreak;\n +\t\t\tp = eol + 1;\n +\t\t}\n +\t}\n +\n  \tfree(opts->gpg_sign);\n  \tfree(opts->strategy);\n  \tfor (i = 0; i < opts->xopts_nr; i++)\n  \t\tfree(opts->xopts[i]);\n  \tfree(opts->xopts);\n  \n -\tstrbuf_addstr(&dir, get_dir(opts));\n -\tremove_dir_recursively(&dir, 0);\n -\tstrbuf_release(&dir);\n +\tstrbuf_reset(&buf);\n +\tstrbuf_addstr(&buf, get_dir(opts));\n +\tremove_dir_recursively(&buf, 0);\n +\tstrbuf_release(&buf);\n  \n  \treturn 0;\n  }\n @@ -1280,6 +1306,10 @@ enum todo_command {\n  \tTODO_SQUASH,\n  \t/* commands that do something else than handling a single commit */\n  \tTODO_EXEC,\n +\tTODO_LABEL,\n +\tTODO_RESET,\n +\tTODO_MERGE,\n +\tTODO_MERGE_AND_EDIT,\n  \t/* commands that do nothing but are counted for reporting progress */\n  \tTODO_NOOP,\n  \tTODO_DROP,\n @@ -1298,6 +1328,10 @@ static struct {\n  \t{ 'f', \"fixup\" },\n  \t{ 's', \"squash\" },\n  \t{ 'x', \"exec\" },\n +\t{ 'l', \"label\" },\n +\t{ 't', \"reset\" },\n +\t{ 'm', \"merge\" },\n +\t{ 0, \"merge\" }, /* MERGE_AND_EDIT */\n  \t{ 0,   \"noop\" },\n  \t{ 'd', \"drop\" },\n  \t{ 0,   NULL }\n @@ -1803,13 +1837,29 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n  \t\treturn error(_(\"missing arguments for %s\"),\n  \t\t\t     command_to_string(item->command));\n  \n -\tif (item->command == TODO_EXEC) {\n +\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n +\t    item->command == TODO_RESET) {\n  \t\titem->commit = NULL;\n  \t\titem->arg = bol;\n  \t\titem->arg_len = (int)(eol - bol);\n  \t\treturn 0;\n  \t}\n  \n +\tif (item->command == TODO_MERGE) {\n +\t\tif (skip_prefix(bol, \"-C\", &bol))\n +\t\t\tbol += strspn(bol, \" \\t\");\n +\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n +\t\t\tbol += strspn(bol, \" \\t\");\n +\t\t\titem->command = TODO_MERGE_AND_EDIT;\n +\t\t} else {\n +\t\t\titem->command = TODO_MERGE_AND_EDIT;\n +\t\t\titem->commit = NULL;\n +\t\t\titem->arg = bol;\n +\t\t\titem->arg_len = (int)(eol - bol);\n +\t\t\treturn 0;\n +\t\t}\n +\t}\n +\n  \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n  \tsaved = *end_of_object_name;\n  \t*end_of_object_name = '\\0';\n @@ -2444,6 +2494,304 @@ static int do_exec(const char *command_line)\n  \treturn status;\n  }\n  \n +static int safe_append(const char *filename, const char *fmt, ...)\n +{\n +\tva_list ap;\n +\tstruct lock_file lock = LOCK_INIT;\n +\tint fd = hold_lock_file_for_update(&lock, filename,\n +\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n +\tstruct strbuf buf = STRBUF_INIT;\n +\n +\tif (fd < 0)\n +\t\treturn -1;\n +\n +\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n +\t\treturn error_errno(_(\"could not read '%s'\"), filename);\n +\tstrbuf_complete(&buf, '\\n');\n +\tva_start(ap, fmt);\n +\tstrbuf_vaddf(&buf, fmt, ap);\n +\tva_end(ap);\n +\n +\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n +\t\terror_errno(_(\"could not write to '%s'\"), filename);\n +\t\tstrbuf_release(&buf);\n +\t\trollback_lock_file(&lock);\n +\t\treturn -1;\n +\t}\n +\tif (commit_lock_file(&lock) < 0) {\n +\t\tstrbuf_release(&buf);\n +\t\trollback_lock_file(&lock);\n +\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n +\t}\n +\n +\tstrbuf_release(&buf);\n +\treturn 0;\n +}\n +\n +static int do_label(const char *name, int len)\n +{\n +\tstruct ref_store *refs = get_main_ref_store();\n +\tstruct ref_transaction *transaction;\n +\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n +\tstruct strbuf msg = STRBUF_INIT;\n +\tint ret = 0;\n +\tstruct object_id head_oid;\n +\n +\tif (len == 1 && *name == '#')\n +\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n +\n +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n +\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n +\n +\ttransaction = ref_store_transaction_begin(refs, &err);\n +\tif (!transaction) {\n +\t\terror(\"%s\", err.buf);\n +\t\tret = -1;\n +\t} else if (get_oid(\"HEAD\", &head_oid)) {\n +\t\terror(_(\"could not read HEAD\"));\n +\t\tret = -1;\n +\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n +\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n +\t\t   ref_transaction_commit(transaction, &err)) {\n +\t\terror(\"%s\", err.buf);\n +\t\tret = -1;\n +\t}\n +\tref_transaction_free(transaction);\n +\tstrbuf_release(&err);\n +\tstrbuf_release(&msg);\n +\n +\tif (!ret)\n +\t\tret = safe_append(rebase_path_refs_to_delete(),\n +\t\t\t\t  \"%s\\n\", ref_name.buf);\n +\tstrbuf_release(&ref_name);\n +\n +\treturn ret;\n +}\n +\n +static int do_reset(const char *name, int len, struct replay_opts *opts)\n +{\n +\tstruct strbuf ref_name = STRBUF_INIT;\n +\tstruct object_id oid;\n +\tstruct lock_file lock = LOCK_INIT;\n +\tstruct tree_desc desc;\n +\tstruct tree *tree;\n +\tstruct unpack_trees_options unpack_tree_opts;\n +\tint ret = 0, i;\n +\n +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n +\t\treturn -1;\n +\n +\t/* Determine the length of the label */\n +\tfor (i = 0; i < len; i++)\n +\t\tif (isspace(name[i]))\n +\t\t\tlen = i;\n +\n +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n +\tif (get_oid(ref_name.buf, &oid) &&\n +\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n +\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n +\t\trollback_lock_file(&lock);\n +\t\tstrbuf_release(&ref_name);\n +\t\treturn -1;\n +\t}\n +\n +\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n +\tunpack_tree_opts.head_idx = 1;\n +\tunpack_tree_opts.src_index = &the_index;\n +\tunpack_tree_opts.dst_index = &the_index;\n +\tunpack_tree_opts.fn = oneway_merge;\n +\tunpack_tree_opts.merge = 1;\n +\tunpack_tree_opts.update = 1;\n +\tunpack_tree_opts.reset = 1;\n +\n +\tif (read_cache_unmerged()) {\n +\t\trollback_lock_file(&lock);\n +\t\tstrbuf_release(&ref_name);\n +\t\treturn error_resolve_conflict(_(action_name(opts)));\n +\t}\n +\n +\tif (!fill_tree_descriptor(&desc, &oid)) {\n +\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n +\t\trollback_lock_file(&lock);\n +\t\tfree((void *)desc.buffer);\n +\t\tstrbuf_release(&ref_name);\n +\t\treturn -1;\n +\t}\n +\n +\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n +\t\trollback_lock_file(&lock);\n +\t\tfree((void *)desc.buffer);\n +\t\tstrbuf_release(&ref_name);\n +\t\treturn -1;\n +\t}\n +\n +\ttree = parse_tree_indirect(&oid);\n +\tprime_cache_tree(&the_index, tree);\n +\n +\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n +\t\tret = error(_(\"could not write index\"));\n +\tfree((void *)desc.buffer);\n +\n +\tif (!ret) {\n +\t\tstruct strbuf msg = STRBUF_INIT;\n +\n +\t\tstrbuf_addf(&msg, \"(rebase -i) reset '%.*s'\", len, name);\n +\t\tret = update_ref(msg.buf, \"HEAD\", &oid, NULL, 0,\n +\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n +\t\tstrbuf_release(&msg);\n +\t}\n +\n +\tstrbuf_release(&ref_name);\n +\treturn ret;\n +}\n +\n +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n +\t\t    int run_commit_flags, struct replay_opts *opts)\n +{\n +\tint merge_arg_len;\n +\tstruct strbuf ref_name = STRBUF_INIT;\n +\tstruct commit *head_commit, *merge_commit, *i;\n +\tstruct commit_list *common, *j, *reversed = NULL;\n +\tstruct merge_options o;\n +\tint can_fast_forward, ret;\n +\tstatic struct lock_file lock;\n +\n +\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n +\t\tif (isspace(arg[merge_arg_len]))\n +\t\t\tbreak;\n +\n +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n +\t\treturn -1;\n +\n +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n +\tif (!head_commit) {\n +\t\trollback_lock_file(&lock);\n +\t\treturn error(_(\"cannot merge without a current revision\"));\n +\t}\n +\n +\tif (commit) {\n +\t\tconst char *message = get_commit_buffer(commit, NULL);\n +\t\tconst char *body;\n +\t\tint len;\n +\n +\t\tif (!message) {\n +\t\t\trollback_lock_file(&lock);\n +\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n +\t\t\t\t     oid_to_hex(&commit->object.oid));\n +\t\t}\n +\t\twrite_author_script(message);\n +\t\tfind_commit_subject(message, &body);\n +\t\tlen = strlen(body);\n +\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n +\t\t\terror_errno(_(\"could not write '%s'\"),\n +\t\t\t\t    git_path_merge_msg());\n +\t\t\tunuse_commit_buffer(commit, message);\n +\t\t\trollback_lock_file(&lock);\n +\t\t\treturn -1;\n +\t\t}\n +\t\tunuse_commit_buffer(commit, message);\n +\t} else {\n +\t\tconst char *p = arg + merge_arg_len;\n +\t\tstruct strbuf buf = STRBUF_INIT;\n +\t\tint len;\n +\n +\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n +\t\twrite_author_script(buf.buf);\n +\t\tstrbuf_reset(&buf);\n +\n +\t\tp += strspn(p, \" \\t\");\n +\t\tif (*p == '#' && isspace(p[1]))\n +\t\t\tp += 1 + strspn(p + 1, \" \\t\");\n +\t\tif (*p)\n +\t\t\tlen = strlen(p);\n +\t\telse {\n +\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n +\t\t\t\t    merge_arg_len, arg);\n +\t\t\tp = buf.buf;\n +\t\t\tlen = buf.len;\n +\t\t}\n +\n +\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n +\t\t\terror_errno(_(\"could not write '%s'\"),\n +\t\t\t\t    git_path_merge_msg());\n +\t\t\tstrbuf_release(&buf);\n +\t\t\trollback_lock_file(&lock);\n +\t\t\treturn -1;\n +\t\t}\n +\t\tstrbuf_release(&buf);\n +\t}\n +\n +\t/*\n +\t * If HEAD is not identical to the parent of the original merge commit,\n +\t * we cannot fast-forward.\n +\t */\n +\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n +\t\t!oidcmp(&commit->parents->item->object.oid,\n +\t\t\t&head_commit->object.oid);\n +\n +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n +\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n +\tif (!merge_commit) {\n +\t\t/* fall back to non-rewritten ref or commit */\n +\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n +\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n +\t}\n +\tif (!merge_commit) {\n +\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n +\t\tstrbuf_release(&ref_name);\n +\t\trollback_lock_file(&lock);\n +\t\treturn -1;\n +\t}\n +\n +\tif (can_fast_forward && commit->parents->next &&\n +\t    !commit->parents->next->next &&\n +\t    !oidcmp(&commit->parents->next->item->object.oid,\n +\t\t    &merge_commit->object.oid)) {\n +\t\tstrbuf_release(&ref_name);\n +\t\trollback_lock_file(&lock);\n +\t\treturn fast_forward_to(&commit->object.oid,\n +\t\t\t\t       &head_commit->object.oid, 0, opts);\n +\t}\n +\n +\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n +\t\t      git_path_merge_head(), 0);\n +\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n +\n +\tcommon = get_merge_bases(head_commit, merge_commit);\n +\tfor (j = common; j; j = j->next)\n +\t\tcommit_list_insert(j->item, &reversed);\n +\tfree_commit_list(common);\n +\n +\tread_cache();\n +\tinit_merge_options(&o);\n +\to.branch1 = \"HEAD\";\n +\to.branch2 = ref_name.buf;\n +\to.buffer_output = 2;\n +\n +\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n +\tif (ret <= 0)\n +\t\tfputs(o.obuf.buf, stdout);\n +\tstrbuf_release(&o.obuf);\n +\tif (ret < 0) {\n +\t\tstrbuf_release(&ref_name);\n +\t\trollback_lock_file(&lock);\n +\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n +\t\t\t     merge_arg_len, arg);\n +\t}\n +\n +\tif (active_cache_changed &&\n +\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n +\t\tstrbuf_release(&ref_name);\n +\t\treturn error(_(\"merge: Unable to write new index file\"));\n +\t}\n +\trollback_lock_file(&lock);\n +\n +\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n +\tstrbuf_release(&ref_name);\n +\n +\treturn ret;\n +}\n +\n  static int is_final_fixup(struct todo_list *todo_list)\n  {\n  \tint i = todo_list->current;\n @@ -2627,6 +2975,18 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\t\t/* `current` will be incremented below */\n  \t\t\t\ttodo_list->current = -1;\n  \t\t\t}\n +\t\t} else if (item->command == TODO_LABEL)\n +\t\t\tres = do_label(item->arg, item->arg_len);\n +\t\telse if (item->command == TODO_RESET)\n +\t\t\tres = do_reset(item->arg, item->arg_len, opts);\n +\t\telse if (item->command == TODO_MERGE ||\n +\t\t\t item->command == TODO_MERGE_AND_EDIT) {\n +\t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n +\t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n +\t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n +\t\t\tif (item->commit)\n +\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n +\t\t\t\t\t\t    peek_command(todo_list, 1));\n  \t\t} else if (!is_noop(item->command))\n  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n  \n @@ -2981,6 +3341,345 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n  \tstrbuf_release(&sob);\n  }\n  \n +struct labels_entry {\n +\tstruct hashmap_entry entry;\n +\tchar label[FLEX_ARRAY];\n +};\n +\n +static int labels_cmp(const void *fndata, const struct labels_entry *a,\n +\t\t      const struct labels_entry *b, const void *key)\n +{\n +\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n +}\n +\n +struct string_entry {\n +\tstruct oidmap_entry entry;\n +\tchar string[FLEX_ARRAY];\n +};\n +\n +struct label_state {\n +\tstruct oidmap commit2label;\n +\tstruct hashmap labels;\n +\tstruct strbuf buf;\n +};\n +\n +static const char *label_oid(struct object_id *oid, const char *label,\n +\t\t\t     struct label_state *state)\n +{\n +\tstruct labels_entry *labels_entry;\n +\tstruct string_entry *string_entry;\n +\tstruct object_id dummy;\n +\tsize_t len;\n +\tint i;\n +\n +\tstring_entry = oidmap_get(&state->commit2label, oid);\n +\tif (string_entry)\n +\t\treturn string_entry->string;\n +\n +\t/*\n +\t * For \"uninteresting\" commits, i.e. commits that are not to be\n +\t * rebased, and which can therefore not be labeled, we use a unique\n +\t * abbreviation of the commit name. This is slightly more complicated\n +\t * than calling find_unique_abbrev() because we also need to make\n +\t * sure that the abbreviation does not conflict with any other\n +\t * label.\n +\t *\n +\t * We disallow \"interesting\" commits to be labeled by a string that\n +\t * is a valid full-length hash, to ensure that we always can find an\n +\t * abbreviation for any uninteresting commit's names that does not\n +\t * clash with any other label.\n +\t */\n +\tif (!label) {\n +\t\tchar *p;\n +\n +\t\tstrbuf_reset(&state->buf);\n +\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n +\t\tlabel = p = state->buf.buf;\n +\n +\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n +\n +\t\t/*\n +\t\t * We may need to extend the abbreviated hash so that there is\n +\t\t * no conflicting label.\n +\t\t */\n +\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n +\t\t\tsize_t i = strlen(p) + 1;\n +\n +\t\t\toid_to_hex_r(p, oid);\n +\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n +\t\t\t\tchar save = p[i];\n +\t\t\t\tp[i] = '\\0';\n +\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n +\t\t\t\t\t\t\t   strihash(p), p))\n +\t\t\t\t\tbreak;\n +\t\t\t\tp[i] = save;\n +\t\t\t}\n +\t\t}\n +\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n +\t\t    !get_oid_hex(label, &dummy)) ||\n +\t\t   (len == 1 && *label == '#') ||\n +\t\t   hashmap_get_from_hash(&state->labels,\n +\t\t\t\t\t strihash(label), label)) {\n +\t\t/*\n +\t\t * If the label already exists, or if the label is a valid full\n +\t\t * OID, or the label is a '#' (which we use as a separator\n +\t\t * between merge heads and oneline), we append a dash and a\n +\t\t * number to make it unique.\n +\t\t */\n +\t\tstruct strbuf *buf = &state->buf;\n +\n +\t\tstrbuf_reset(buf);\n +\t\tstrbuf_add(buf, label, len);\n +\n +\t\tfor (i = 2; ; i++) {\n +\t\t\tstrbuf_setlen(buf, len);\n +\t\t\tstrbuf_addf(buf, \"-%d\", i);\n +\t\t\tif (!hashmap_get_from_hash(&state->labels,\n +\t\t\t\t\t\t   strihash(buf->buf),\n +\t\t\t\t\t\t   buf->buf))\n +\t\t\t\tbreak;\n +\t\t}\n +\n +\t\tlabel = buf->buf;\n +\t}\n +\n +\tFLEX_ALLOC_STR(labels_entry, label, label);\n +\thashmap_entry_init(labels_entry, strihash(label));\n +\thashmap_add(&state->labels, labels_entry);\n +\n +\tFLEX_ALLOC_STR(string_entry, string, label);\n +\toidcpy(&string_entry->entry.oid, oid);\n +\toidmap_put(&state->commit2label, string_entry);\n +\n +\treturn string_entry->string;\n +}\n +\n +static int make_script_with_merges(struct pretty_print_context *pp,\n +\t\t\t\t   struct rev_info *revs, FILE *out,\n +\t\t\t\t   unsigned flags)\n +{\n +\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n +\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n +\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n +\tstruct strbuf label = STRBUF_INIT;\n +\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n +\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n +\tstruct commit *commit;\n +\tstruct oidmap commit2todo = OIDMAP_INIT;\n +\tstruct string_entry *entry;\n +\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n +\t\tshown = OIDSET_INIT;\n +\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n +\n +\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n +\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n +\t\t*cmd_label = abbr ? \"l\" : \"label\",\n +\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n +\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n +\n +\toidmap_init(&commit2todo, 0);\n +\toidmap_init(&state.commit2label, 0);\n +\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n +\tstrbuf_init(&state.buf, 32);\n +\n +\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n +\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n +\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n +\t\toidcpy(&entry->entry.oid, oid);\n +\t\toidmap_put(&state.commit2label, entry);\n +\t}\n +\n +\t/*\n +\t * First phase:\n +\t * - get onelines for all commits\n +\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n +\t * - label all branch tips\n +\t */\n +\twhile ((commit = get_revision(revs))) {\n +\t\tstruct commit_list *to_merge;\n +\t\tint is_octopus;\n +\t\tconst char *p1, *p2;\n +\t\tstruct object_id *oid;\n +\n +\t\ttail = &commit_list_insert(commit, tail)->next;\n +\t\toidset_insert(&interesting, &commit->object.oid);\n +\n +\t\tif ((commit->object.flags & PATCHSAME))\n +\t\t\tcontinue;\n +\n +\t\tstrbuf_reset(&oneline);\n +\t\tpretty_print_commit(pp, commit, &oneline);\n +\n +\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n +\t\tif (!to_merge) {\n +\t\t\t/* non-merge commit: easy case */\n +\t\t\tstrbuf_reset(&buf);\n +\t\t\tif (!keep_empty && is_original_commit_empty(commit))\n +\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n +\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n +\t\t\t\t    oid_to_hex(&commit->object.oid),\n +\t\t\t\t    oneline.buf);\n +\n +\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n +\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n +\t\t\toidmap_put(&commit2todo, entry);\n +\n +\t\t\tcontinue;\n +\t\t}\n +\n +\t\tis_octopus = to_merge && to_merge->next;\n +\n +\t\tif (is_octopus)\n +\t\t\tBUG(\"Octopus merges not yet supported\");\n +\n +\t\t/* Create a label */\n +\t\tstrbuf_reset(&label);\n +\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n +\t\t    (p1 = strchr(p1, '\\'')) &&\n +\t\t    (p2 = strchr(++p1, '\\'')))\n +\t\t\tstrbuf_add(&label, p1, p2 - p1);\n +\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n +\t\t\t\t     &p1) &&\n +\t\t\t (p1 = strstr(p1, \" from \")))\n +\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n +\t\telse\n +\t\t\tstrbuf_addbuf(&label, &oneline);\n +\n +\t\tfor (p1 = label.buf; *p1; p1++)\n +\t\t\tif (isspace(*p1))\n +\t\t\t\t*(char *)p1 = '-';\n +\n +\t\tstrbuf_reset(&buf);\n +\t\tstrbuf_addf(&buf, \"%s -C %s\",\n +\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n +\n +\t\t/* label the tip of merged branch */\n +\t\toid = &to_merge->item->object.oid;\n +\t\tstrbuf_addch(&buf, ' ');\n +\n +\t\tif (!oidset_contains(&interesting, oid))\n +\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n +\t\telse {\n +\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n +\t\t\t\t\t\t\ttips_tail)->next;\n +\n +\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n +\t\t}\n +\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n +\n +\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n +\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n +\t\toidmap_put(&commit2todo, entry);\n +\t}\n +\n +\t/*\n +\t * Second phase:\n +\t * - label branch points\n +\t * - add HEAD to the branch tips\n +\t */\n +\tfor (iter = commits; iter; iter = iter->next) {\n +\t\tstruct commit_list *parent = iter->item->parents;\n +\t\tfor (; parent; parent = parent->next) {\n +\t\t\tstruct object_id *oid = &parent->item->object.oid;\n +\t\t\tif (!oidset_contains(&interesting, oid))\n +\t\t\t\tcontinue;\n +\t\t\tif (!oidset_contains(&child_seen, oid))\n +\t\t\t\toidset_insert(&child_seen, oid);\n +\t\t\telse\n +\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n +\t\t}\n +\n +\t\t/* Add HEAD as implict \"tip of branch\" */\n +\t\tif (!iter->next)\n +\t\t\ttips_tail = &commit_list_insert(iter->item,\n +\t\t\t\t\t\t\ttips_tail)->next;\n +\t}\n +\n +\t/*\n +\t * Third phase: output the todo list. This is a bit tricky, as we\n +\t * want to avoid jumping back and forth between revisions. To\n +\t * accomplish that goal, we walk backwards from the branch tips,\n +\t * gathering commits not yet shown, reversing the list on the fly,\n +\t * then outputting that list (labeling revisions as needed).\n +\t */\n +\tfprintf(out, \"%s onto\\n\", cmd_label);\n +\tfor (iter = tips; iter; iter = iter->next) {\n +\t\tstruct commit_list *list = NULL, *iter2;\n +\n +\t\tcommit = iter->item;\n +\t\tif (oidset_contains(&shown, &commit->object.oid))\n +\t\t\tcontinue;\n +\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n +\n +\t\tif (entry)\n +\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n +\t\telse\n +\t\t\tfprintf(out, \"\\n\");\n +\n +\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n +\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n +\t\t\tcommit_list_insert(commit, &list);\n +\t\t\tif (!commit->parents) {\n +\t\t\t\tcommit = NULL;\n +\t\t\t\tbreak;\n +\t\t\t}\n +\t\t\tcommit = commit->parents->item;\n +\t\t}\n +\n +\t\tif (!commit)\n +\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n +\t\telse {\n +\t\t\tconst char *to = NULL;\n +\n +\t\t\tentry = oidmap_get(&state.commit2label,\n +\t\t\t\t\t   &commit->object.oid);\n +\t\t\tif (entry)\n +\t\t\t\tto = entry->string;\n +\t\t\telse if (!rebase_cousins)\n +\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n +\t\t\t\t\t       &state);\n +\n +\t\t\tif (!to || !strcmp(to, \"onto\"))\n +\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n +\t\t\telse {\n +\t\t\t\tstrbuf_reset(&oneline);\n +\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n +\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n +\t\t\t\t\tcmd_reset, to, oneline.buf);\n +\t\t\t}\n +\t\t}\n +\n +\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n +\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n +\t\t\tentry = oidmap_get(&commit2todo, oid);\n +\t\t\t/* only show if not already upstream */\n +\t\t\tif (entry)\n +\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n +\t\t\tentry = oidmap_get(&state.commit2label, oid);\n +\t\t\tif (entry)\n +\t\t\t\tfprintf(out, \"%s %s\\n\",\n +\t\t\t\t\tcmd_label, entry->string);\n +\t\t\toidset_insert(&shown, oid);\n +\t\t}\n +\n +\t\tfree_commit_list(list);\n +\t}\n +\n +\tfree_commit_list(commits);\n +\tfree_commit_list(tips);\n +\n +\tstrbuf_release(&label);\n +\tstrbuf_release(&oneline);\n +\tstrbuf_release(&buf);\n +\n +\toidmap_free(&commit2todo, 1);\n +\toidmap_free(&state.commit2label, 1);\n +\thashmap_free(&state.labels, 1);\n +\tstrbuf_release(&state.buf);\n +\n +\treturn 0;\n +}\n +\n  int sequencer_make_script(FILE *out, int argc, const char **argv,\n  \t\t\t  unsigned flags)\n  {\n @@ -2991,11 +3690,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n  \tstruct commit *commit;\n  \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n  \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n +\tint recreate_merges = flags & TODO_LIST_RECREATE_MERGES;\n  \n  \tinit_revisions(&revs, NULL);\n  \trevs.verbose_header = 1;\n -\trevs.max_parents = 1;\n -\trevs.cherry_pick = 1;\n +\tif (recreate_merges)\n +\t\trevs.cherry_mark = 1;\n +\telse {\n +\t\trevs.max_parents = 1;\n +\t\trevs.cherry_pick = 1;\n +\t}\n  \trevs.limited = 1;\n  \trevs.reverse = 1;\n  \trevs.right_only = 1;\n @@ -3019,6 +3723,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n  \tif (prepare_revision_walk(&revs) < 0)\n  \t\treturn error(_(\"make_script: error preparing revisions\"));\n  \n +\tif (recreate_merges)\n +\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n +\n  \twhile ((commit = get_revision(&revs))) {\n  \t\tstrbuf_reset(&buf);\n  \t\tif (!keep_empty && is_original_commit_empty(commit))\n @@ -3108,8 +3815,14 @@ int transform_todos(unsigned flags)\n  \t\t\t\t\t  short_commit_name(item->commit) :\n  \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n  \n +\t\t\tif (item->command == TODO_MERGE)\n +\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n +\t\t\telse if (item->command == TODO_MERGE_AND_EDIT)\n +\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n +\n  \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n  \t\t}\n +\n  \t\t/* add all the rest */\n  \t\tif (!item->arg_len)\n  \t\t\tstrbuf_addch(&buf, '\\n');\n diff --git a/sequencer.h b/sequencer.h\n index e45b178dfc4..739dd0fa92b 100644\n --- a/sequencer.h\n +++ b/sequencer.h\n @@ -59,6 +59,13 @@ int sequencer_remove_state(struct replay_opts *opts);\n  #define TODO_LIST_KEEP_EMPTY (1U << 0)\n  #define TODO_LIST_SHORTEN_IDS (1U << 1)\n  #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n +#define TODO_LIST_RECREATE_MERGES (1U << 3)\n +/*\n + * When recreating merges, commits that do have the base commit as ancestor\n + * (\"cousins\") are *not* rebased onto the new base by default. If those\n + * commits should be rebased onto the new base, this flag needs to be passed.\n + */\n +#define TODO_LIST_REBASE_COUSINS (1U << 4)\n  int sequencer_make_script(FILE *out, int argc, const char **argv,\n  \t\t\t  unsigned flags);\n  \n diff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\n new file mode 100755\n index 00000000000..9a59f12b670\n --- /dev/null\n +++ b/t/t3430-rebase-recreate-merges.sh\n @@ -0,0 +1,208 @@\n +#!/bin/sh\n +#\n +# Copyright (c) 2017 Johannes E. Schindelin\n +#\n +\n +test_description='git rebase -i --recreate-merges\n +\n +This test runs git rebase \"interactively\", retaining the branch structure by\n +recreating merge commits.\n +\n +Initial setup:\n +\n +    -- B --                   (first)\n +   /       \\\n + A - C - D - E - H            (master)\n +       \\       /\n +         F - G                (second)\n +'\n +. ./test-lib.sh\n +. \"$TEST_DIRECTORY\"/lib-rebase.sh\n +\n +test_expect_success 'setup' '\n +\twrite_script replace-editor.sh <<-\\EOF &&\n +\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n +\tcp script-from-scratch \"$1\"\n +\tEOF\n +\n +\ttest_commit A &&\n +\tgit checkout -b first &&\n +\ttest_commit B &&\n +\tgit checkout master &&\n +\ttest_commit C &&\n +\ttest_commit D &&\n +\tgit merge --no-commit B &&\n +\ttest_tick &&\n +\tgit commit -m E &&\n +\tgit tag -m E E &&\n +\tgit checkout -b second C &&\n +\ttest_commit F &&\n +\ttest_commit G &&\n +\tgit checkout master &&\n +\tgit merge --no-commit G &&\n +\ttest_tick &&\n +\tgit commit -m H &&\n +\tgit tag -m H H\n +'\n +\n +cat >script-from-scratch <<\\EOF\n +label onto\n +\n +# onebranch\n +pick G\n +pick D\n +label onebranch\n +\n +# second\n +reset onto\n +pick B\n +label second\n +\n +reset onto\n +merge -C H second\n +merge onebranch # Merge the topic branch 'onebranch'\n +EOF\n +\n +test_cmp_graph () {\n +\tcat >expect &&\n +\tgit log --graph --boundary --format=%s \"$@\" >output &&\n +\tsed \"s/ *$//\" <output >output.trimmed &&\n +\ttest_cmp expect output.trimmed\n +}\n +\n +test_expect_success 'create completely different structure' '\n +\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n +\ttest_tick &&\n +\tgit rebase -i --recreate-merges A &&\n +\ttest_cmp_graph <<-\\EOF\n +\t*   Merge the topic branch '\\''onebranch'\\''\n +\t|\\\n +\t| * D\n +\t| * G\n +\t* |   H\n +\t|\\ \\\n +\t| |/\n +\t|/|\n +\t| * B\n +\t|/\n +\t* A\n +\tEOF\n +'\n +\n +test_expect_success 'generate correct todo list' '\n +\tcat >expect <<-\\EOF &&\n +\tlabel onto\n +\n +\treset onto\n +\tpick d9df450 B\n +\tlabel E\n +\n +\treset onto\n +\tpick 5dee784 C\n +\tlabel branch-point\n +\tpick ca2c861 F\n +\tpick 088b00a G\n +\tlabel H\n +\n +\treset branch-point # C\n +\tpick 12bd07b D\n +\tmerge -C 2051b56 E # E\n +\tmerge -C 233d48a H # H\n +\n +\tEOF\n +\n +\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n +\ttest_cmp expect output\n +'\n +\n +test_expect_success 'with a branch tip that was cherry-picked already' '\n +\tgit checkout -b already-upstream master &&\n +\tbase=\"$(git rev-parse --verify HEAD)\" &&\n +\n +\ttest_commit A1 &&\n +\ttest_commit A2 &&\n +\tgit reset --hard $base &&\n +\ttest_commit B1 &&\n +\ttest_tick &&\n +\tgit merge -m \"Merge branch A\" A2 &&\n +\n +\tgit checkout -b upstream-with-a2 $base &&\n +\ttest_tick &&\n +\tgit cherry-pick A2 &&\n +\n +\tgit checkout already-upstream &&\n +\ttest_tick &&\n +\tgit rebase -i --recreate-merges upstream-with-a2 &&\n +\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n +\t*   Merge branch A\n +\t|\\\n +\t| * A1\n +\t* | B1\n +\t|/\n +\to A2\n +\tEOF\n +'\n +\n +test_expect_success 'do not rebase cousins unless asked for' '\n +\twrite_script copy-editor.sh <<-\\EOF &&\n +\tcp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n +\tEOF\n +\n +\ttest_config sequence.editor \\\"\"$PWD\"/copy-editor.sh\\\" &&\n +\tgit checkout -b cousins master &&\n +\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n +\ttest_tick &&\n +\tgit rebase -i --recreate-merges HEAD^ &&\n +\ttest_cmp_rev HEAD $before &&\n +\ttest_tick &&\n +\tgit rebase -i --recreate-merges=rebase-cousins HEAD^ &&\n +\ttest_cmp_graph HEAD^.. <<-\\EOF\n +\t*   Merge the topic branch '\\''onebranch'\\''\n +\t|\\\n +\t| * D\n +\t| * G\n +\t|/\n +\to H\n +\tEOF\n +'\n +\n +test_expect_success 'refs/rewritten/* is worktree-local' '\n +\tgit worktree add wt &&\n +\tcat >wt/script-from-scratch <<-\\EOF &&\n +\tlabel xyz\n +\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n +\texec git rev-parse --verify refs/rewritten/xyz >b\n +\tEOF\n +\n +\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n +\tgit -C wt rebase -i HEAD &&\n +\ttest_must_be_empty wt/a &&\n +\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n +'\n +\n +test_expect_success 'post-rewrite hook and fixups work for merges' '\n +\tgit checkout -b post-rewrite &&\n +\ttest_commit same1 &&\n +\tgit reset --hard HEAD^ &&\n +\ttest_commit same2 &&\n +\tgit merge -m \"to fix up\" same1 &&\n +\techo same old same old >same2.t &&\n +\ttest_tick &&\n +\tgit commit --fixup HEAD same2.t &&\n +\tfixup=\"$(git rev-parse HEAD)\" &&\n +\n +\tmkdir -p .git/hooks &&\n +\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n +\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n +\n +\ttest_tick &&\n +\tgit rebase -i --autosquash --recreate-merges HEAD^^^ &&\n +\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n +\t\t$fixup^^2 HEAD^2 \\\n +\t\t$fixup^^ HEAD^ \\\n +\t\t$fixup^ HEAD \\\n +\t\t$fixup HEAD) &&\n +\ttest_cmp expect actual\n +'\n +\n +test_done\n-- \n2.16.1.windows.4\n\n"},{"id":"340019","messageId":"8ec3a73dfdc76c503d50e34e5fc8b8a3d5ea7dd8.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 01/12] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:35:56Z","receivedAt":"2018-02-23T12:36:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"As pointed out in a review of the `--recreate-merges` patch series,\n`rollback_lock_file()` clobbers errno. Therefore, we have to report the\nerror message that uses errno before calling said function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e9baaf59bd9..5aa3dc3c95c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -345,12 +345,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n \tif (msg_fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n \tif (write_in_full(msg_fd, buf, len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n+\t\terror_errno(_(\"could not write eol to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write eol to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&msg_file) < 0) {\n \t\trollback_lock_file(&msg_file);\n@@ -2106,16 +2108,17 @@ static int save_head(const char *head)\n \n \tfd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);\n \tif (fd < 0) {\n+\t\terror_errno(_(\"could not lock HEAD\"));\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not lock HEAD\"));\n+\t\treturn -1;\n \t}\n \tstrbuf_addf(&buf, \"%s\\n\", head);\n \twritten = write_in_full(fd, buf.buf, buf.len);\n \tstrbuf_release(&buf);\n \tif (written < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), git_path_head_file());\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t   git_path_head_file());\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&head_lock) < 0) {\n \t\trollback_lock_file(&head_lock);\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340020","messageId":"a7e01df062d64a2b89c99a4de4a772cfb0c84017.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 02/12] sequencer: make rearrange_squash() a bit more obvious","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:36:19Z","receivedAt":"2018-02-23T12:36:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"There are some commands that have to be skipped from rearranging by virtue\nof not handling any commits.\n\nHowever, the logic was not quite obvious: it skipped commands based on\ntheir position in the enum todo_command.\n\nInstead, let's make it explicit that we skip all commands that do not\nhandle any commit. With one exception: the `drop` command, because it,\nwell, drops the commit and is therefore not eligible to rearranging.\n\nNote: this is a bit academic at the moment because the only time we call\n`rearrange_squash()` is directly after generating the todo list, when we\nhave nothing but `pick` commands anyway.\n\nHowever, the upcoming `merge` command *will* want to be handled by that\nfunction, and it *can* handle commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 5aa3dc3c95c..cfa01d3bdd2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3412,7 +3412,7 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (!item->commit || item->command == TODO_DROP) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340021","messageId":"29ffc4990e9282ba5a34eef7a64af42c1b9e1525.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 04/12] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:37:01Z","receivedAt":"2018-02-23T12:37:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming commits, we will teach the sequencer to recreate merges.\nThis will be done in a very different way from the unfortunate design of\n`git rebase --preserve-merges` (which does not allow for reordering\ncommits, or changing the branch topology).\n\nThe main idea is to introduce new todo list commands, to support\nlabeling the current revision with a given name, resetting the current\nrevision to a previous state, and  merging labeled revisions.\n\nThis idea was developed in Git for Windows' Git garden shears (that are\nused to maintain the \"thicket of branches\" on top of upstream Git), and\nthis patch is part of the effort to make it available to a wider\naudience, as well as to make the entire process more robust (by\nimplementing it in a safe and portable language rather than a Unix shell\nscript).\n\nThis commit implements the commands to label, and to reset to, given\nrevisions. The syntax is:\n\n\tlabel <name>\n\treset <name>\n\nInternally, the `label <name>` command creates the ref\n`refs/rewritten/<name>`. This makes it possible to work with the labeled\nrevisions interactively, or in a scripted fashion (e.g. via the todo\nlist command `exec`).\n\nThese temporary refs are removed upon sequencer_remove_state(), so that\neven a `git rebase --abort` cleans them up.\n\nWe disallow '#' as label because that character will be used as separator\nin the upcoming `merge` command.\n\nLater in this patch series, we will mark the `refs/rewritten/` refs as\nworktree-local, to allow for interactive rebases to be run in parallel in\nworktrees linked to the same repository.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   2 +\n sequencer.c                | 196 +++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 192 insertions(+), 6 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex a2659fea982..501f09b28c4 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n+l, label <label> = label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex cfa01d3bdd2..e25522ecdf1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"notes-utils.h\"\n #include \"sigchain.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `label` command to record the need for cleanup.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -244,18 +253,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n \n int sequencer_remove_state(struct replay_opts *opts)\n {\n-\tstruct strbuf dir = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i;\n \n+\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n+\t\tchar *p = buf.buf;\n+\t\twhile (*p) {\n+\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\tif (eol)\n+\t\t\t\t*eol = '\\0';\n+\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n+\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\tif (!eol)\n+\t\t\t\tbreak;\n+\t\t\tp = eol + 1;\n+\t\t}\n+\t}\n+\n \tfree(opts->gpg_sign);\n \tfree(opts->strategy);\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n \tfree(opts->xopts);\n \n-\tstrbuf_addstr(&dir, get_dir(opts));\n-\tremove_dir_recursively(&dir, 0);\n-\tstrbuf_release(&dir);\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, get_dir(opts));\n+\tremove_dir_recursively(&buf, 0);\n+\tstrbuf_release(&buf);\n \n \treturn 0;\n }\n@@ -1280,6 +1304,8 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1298,6 +1324,8 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1803,7 +1831,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -2444,6 +2473,157 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn -1;\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n+\t\treturn error_errno(_(\"could not read '%s'\"), filename);\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tif (len == 1 && *name == '#')\n+\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t} else if (get_oid(\"HEAD\", &head_oid)) {\n+\t\terror(_(\"could not read HEAD\"));\n+\t\tret = -1;\n+\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n+\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t   ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static int do_reset(const char *name, int len, struct replay_opts *opts)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options unpack_tree_opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\t/* Determine the length of the label */\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n+\tunpack_tree_opts.head_idx = 1;\n+\tunpack_tree_opts.src_index = &the_index;\n+\tunpack_tree_opts.dst_index = &the_index;\n+\tunpack_tree_opts.fn = oneway_merge;\n+\tunpack_tree_opts.merge = 1;\n+\tunpack_tree_opts.update = 1;\n+\tunpack_tree_opts.reset = 1;\n+\n+\tif (read_cache_unmerged()) {\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error_resolve_conflict(_(action_name(opts)));\n+\t}\n+\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret) {\n+\t\tstruct strbuf msg = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&msg, \"(rebase -i) reset '%.*s'\", len, name);\n+\t\tret = update_ref(msg.buf, \"HEAD\", &oid, NULL, 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n+\t\tstrbuf_release(&msg);\n+\t}\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2627,7 +2807,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n-\t\t} else if (!is_noop(item->command))\n+\t\t} else if (item->command == TODO_LABEL)\n+\t\t\tres = do_label(item->arg, item->arg_len);\n+\t\telse if (item->command == TODO_RESET)\n+\t\t\tres = do_reset(item->arg, item->arg_len, opts);\n+\t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340022","messageId":"157d36ec8954e33ddc5670548b66ca58025d81ad.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 03/12] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:36:40Z","receivedAt":"2018-02-23T12:37:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Stefan Beller <stefanbeller@gmail.com>\n\nUp to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we are about to introduce commands that take different\narguments, clarify each command by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 81c5b428757..a2659fea982 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,13 +155,13 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340023","messageId":"63a97e5d4c34b921574fe1ccff02747591c7f615.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 05/12] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:37:22Z","receivedAt":"2018-02-23T12:37:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label` and `reset` commands to label\ncommits and to reset to labeled commits. This patch adds the `merge`\ncommand, with the following syntax:\n\n\tmerge [-C <commit>] <rev> # <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the merge commit that is about\nto be created.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list:\n\n\tlabel onto\n\n\t# Branch abc\n\treset onto\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\treset onto\n\tpick cafecafe And now for something completely different\n\tmerge -C baaabaaa abc # Merge the branch 'abc' into master\n\nTo edit the merge commit's message (a \"reword\" for merges, if you will),\nuse `-c` (lower-case) instead of `-C`; this convention was borrowed from\n`git commit` that also supports `-c` and `-C` with similar meanings.\n\nTo create *new* merges, i.e. without copying the commit message from an\nexisting commit, simply omit the `-C <commit>` parameter (which will\nopen an editor for the merge message):\n\n\tmerge abc\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in a separate\npatch series, support for merges using strategies other than the\nrecursive merge is left for the future.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   4 ++\n sequencer.c                | 158 +++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 162 insertions(+)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 501f09b28c4..2d8bbe20b74 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -164,6 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n l, label <label> = label current HEAD with a name\n t, reset <label> = reset HEAD to a label\n+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n+.       create a merge commit using the original merge commit's\n+.       message (or the oneline, if no original merge commit was\n+.       specified). Use -c <commit> to reword the commit message.\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex e25522ecdf1..64dbd1d3e2e 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1306,6 +1306,8 @@ enum todo_command {\n \tTODO_EXEC,\n \tTODO_LABEL,\n \tTODO_RESET,\n+\tTODO_MERGE,\n+\tTODO_MERGE_AND_EDIT,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1326,6 +1328,8 @@ static struct {\n \t{ 'x', \"exec\" },\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n+\t{ 'm', \"merge\" },\n+\t{ 0, \"merge\" }, /* MERGE_AND_EDIT */\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1839,6 +1843,21 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn 0;\n \t}\n \n+\tif (item->command == TODO_MERGE) {\n+\t\tif (skip_prefix(bol, \"-C\", &bol))\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\t\titem->command = TODO_MERGE_AND_EDIT;\n+\t\t} else {\n+\t\t\titem->command = TODO_MERGE_AND_EDIT;\n+\t\t\titem->commit = NULL;\n+\t\t\titem->arg = bol;\n+\t\t\titem->arg_len = (int)(eol - bol);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n \tsaved = *end_of_object_name;\n \t*end_of_object_name = '\\0';\n@@ -2624,6 +2643,134 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n \treturn ret;\n }\n \n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    int run_commit_flags, struct replay_opts *opts)\n+{\n+\tint merge_arg_len;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *common, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint ret;\n+\tstatic struct lock_file lock;\n+\n+\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n+\t\tif (isspace(arg[merge_arg_len]))\n+\t\t\tbreak;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"cannot merge without a current revision\"));\n+\t}\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t     oid_to_hex(&commit->object.oid));\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tunuse_commit_buffer(commit, message);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tunuse_commit_buffer(commit, message);\n+\t} else {\n+\t\tconst char *p = arg + merge_arg_len;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tp += strspn(p, \" \\t\");\n+\t\tif (*p == '#' && isspace(p[1]))\n+\t\t\tp += 1 + strspn(p + 1, \" \\t\");\n+\t\tif (*p)\n+\t\t\tlen = strlen(p);\n+\t\telse {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tstrbuf_release(&buf);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\tif (!merge_commit) {\n+\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tcommon = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(common);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n+\t\t\t     merge_arg_len, arg);\n+\t}\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error(_(\"merge: Unable to write new index file\"));\n+\t}\n+\trollback_lock_file(&lock);\n+\n+\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2811,6 +2958,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_label(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n+\t\telse if (item->command == TODO_MERGE ||\n+\t\t\t item->command == TODO_MERGE_AND_EDIT)\n+\t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n+\t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n+\t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n \t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n@@ -3292,8 +3444,14 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  short_commit_name(item->commit) :\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n+\t\t\tif (item->command == TODO_MERGE)\n+\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n+\t\t\telse if (item->command == TODO_MERGE_AND_EDIT)\n+\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n+\n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n \t\t}\n+\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340024","messageId":"6f05185df94d07db847b4bdad0297af31b798bfd.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 06/12] sequencer: fast-forward merge commits, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:37:42Z","receivedAt":"2018-02-23T12:38:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to recreate a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 21 ++++++++++++++++++++-\n 1 file changed, 20 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 64dbd1d3e2e..361ec98f764 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2651,7 +2651,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *common, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint ret;\n+\tint can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \n \tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n@@ -2719,6 +2719,14 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\tstrbuf_release(&buf);\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the parent of the original merge commit,\n+\t * we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n \tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n \tif (!merge_commit) {\n@@ -2732,6 +2740,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\trollback_lock_file(&lock);\n \t\treturn -1;\n \t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn fast_forward_to(&commit->object.oid,\n+\t\t\t\t       &head_commit->object.oid, 0, opts);\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340025","messageId":"4f901883659bcd31fc0a2839caf9c957646c8797.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 07/12] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:38:01Z","receivedAt":"2018-02-23T12:38:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --recreate-merges option. For a\ncommit topology like this (where the HEAD points to C):\n\n\t- A - B - C\n\t    \\   /\n\t      D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge -C 3456 D # C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch in this patch series.\n\nAs a special, hard-coded label, all merge-recreating todo lists start with\nthe command `label onto` so that we can later always refer to the revision\nonto which everything is rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 349 ++++++++++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 351 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex ad074705bb5..a5b07c43c96 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -24,6 +24,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n+\t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,6 +58,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex 361ec98f764..01bafe2fe47 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -25,6 +25,8 @@\n #include \"sigchain.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -3336,6 +3338,341 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   (len == 1 && *label == '#') ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, or the label is a '#' (which we use as a separator\n+\t\t * between merge heads and oneline), we append a dash and a\n+\t\t * number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n+\t\t*cmd_label = abbr ? \"l\" : \"label\",\n+\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n+\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tif ((commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_original_commit_empty(commit))\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s -C %s\",\n+\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", cmd_label);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(to, \"onto\"))\n+\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n+\t\t\t\t\tcmd_reset, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\",\n+\t\t\t\t\tcmd_label, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -3346,11 +3683,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint recreate_merges = flags & TODO_LIST_RECREATE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_pick = 1;\n+\tif (recreate_merges)\n+\t\trevs.cherry_mark = 1;\n+\telse {\n+\t\trevs.max_parents = 1;\n+\t\trevs.cherry_pick = 1;\n+\t}\n \trevs.limited = 1;\n \trevs.reverse = 1;\n \trevs.right_only = 1;\n@@ -3374,6 +3716,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (recreate_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tstrbuf_reset(&buf);\n \t\tif (!keep_empty && is_original_commit_empty(commit))\ndiff --git a/sequencer.h b/sequencer.h\nindex e45b178dfc4..7c7c67d623c 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -59,6 +59,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_RECREATE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.16.1.windows.4\n\n\n"},{"id":"340026","messageId":"31098ae586dcd2c5b08e3f7db1a99d1ccef64ed8.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 08/12] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:38:24Z","receivedAt":"2018-02-23T12:38:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt to answer this was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto flatten the branch topology or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--recreate-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge <label>`. And once this mode will\nhave become stable and universally accepted, we can deprecate the design\nmistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |   9 +-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 ++\n t/t3430-rebase-recreate-merges.sh      | 146 +++++++++++++++++++++++++++++++++\n 5 files changed, 162 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex d713951b86a..5e056c8ab6b 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -373,6 +373,12 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+--recreate-merges::\n+\tRecreate merge commits instead of flattening the history by replaying\n+\tmerges. Merge conflict resolutions or manual amendments to merge\n+\tcommits are not recreated automatically, but have to be recreated\n+\tmanually.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -775,7 +781,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+--recreate-merges for a more faithful representation.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 88813e91244..38bba3835c6 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2008,7 +2008,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--recreate-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 2d8bbe20b74..f5c8db2fdf8 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -906,6 +906,7 @@ fi\n if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${recreate_merges:+--recreate-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex b353c33d417..528fa0073ac 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+recreate-merges!   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -87,6 +88,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+recreate_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -267,6 +269,10 @@ do\n \t--allow-empty-message)\n \t\tallow_empty_message=--allow-empty-message\n \t\t;;\n+\t--recreate-merges)\n+\t\trecreate_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nnew file mode 100755\nindex 00000000000..0073601a206\n--- /dev/null\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -0,0 +1,146 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2017 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --recreate-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+cat >script-from-scratch <<\\EOF\n+label onto\n+\n+# onebranch\n+pick G\n+pick D\n+label onebranch\n+\n+# second\n+reset onto\n+pick B\n+label second\n+\n+reset onto\n+merge -C H second\n+merge onebranch # Merge the topic branch 'onebranch'\n+EOF\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'create completely different structure' '\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\treset onto\n+\tpick d9df450 B\n+\tlabel E\n+\n+\treset onto\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point # C\n+\tpick 12bd07b D\n+\tmerge -C 2051b56 E # E\n+\tmerge -C 233d48a H # H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340027","messageId":"620954c3c964cdaa43b41b598b3c508cd6be9049.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 09/12] sequencer: make refs generated by the `label` command worktree-local","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:38:44Z","receivedAt":"2018-02-23T12:39:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                            |  3 ++-\n t/t3430-rebase-recreate-merges.sh | 14 ++++++++++++++\n 2 files changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 20ba82b4343..e8b84c189ff 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 0073601a206..1a3e43d66ff 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,4 +143,18 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340028","messageId":"8ecddf181eeaa9c6bfc9ff573562f9bd9d3d32a5.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 10/12] sequencer: handle post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:39:07Z","receivedAt":"2018-02-23T12:39:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --recreate-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits.\n\nThis patch implements the post-rewrite handling for the `merge` command\nwe just introduced. The other commands that were added recently (`label`\nand `reset`) do not create new commits, therefore post-rewrite do not\nneed to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c                       |  7 +++++--\n t/t3430-rebase-recreate-merges.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 30 insertions(+), 2 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 01bafe2fe47..85ce37cb99f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2980,11 +2980,14 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n \t\telse if (item->command == TODO_MERGE ||\n-\t\t\t item->command == TODO_MERGE_AND_EDIT)\n+\t\t\t item->command == TODO_MERGE_AND_EDIT) {\n \t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n \t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n \t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n-\t\telse if (!is_noop(item->command))\n+\t\t\tif (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n+\t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 1a3e43d66ff..35a61ce90bb 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -157,4 +157,29 @@ test_expect_success 'refs/rewritten/* is worktree-local' '\n \ttest_cmp_rev HEAD \"$(cat wt/b)\"\n '\n \n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash --recreate-merges HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340029","messageId":"f475b92b9ae8ef946512841cc539f7881372b63d.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 11/12] pull: accept --rebase=recreate to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:39:26Z","receivedAt":"2018-02-23T12:39:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `recreate` mode simply passes the\n`--recreate-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  5 ++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       |  2 ++\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 25 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex f57e9cf10ca..8c9adea0d0c 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2607,6 +2611,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..b4f9f057ea9 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,16 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|recreate|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to recreate, rebase with the `--recreate-merges` option passed\n+to `git rebase` so that locally created merge commits will not be flattened.\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 1876271af94..9da2cfa0bd3 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_RECREATE,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"recreate\", returns REBASE_RECREATE. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"recreate\"))\n+\t\treturn REBASE_RECREATE;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|recreate|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_RECREATE)\n+\t\targv_array_push(&args, \"--recreate-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d95bf904c3b..b7d0f7ce596 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"recreate\"))\n+\t\t\t\tinfo->rebase = NORMAL_REBASE;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 38bba3835c6..3d44cb6890c 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2182,7 +2182,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true recreate preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340030","messageId":"528364b9aef14669fc2521e8cc5ec27bb5c06dcd.1519389319.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 12/12] rebase -i: introduce --recreate-merges=[no-]rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-23T12:39:54Z","receivedAt":"2018-02-23T12:40:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This one is a bit tricky to explain, so let's try with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nTo illustrate what this new mode is all about, let's consider what\nhappens upon `git rebase -i --recreate-merges B`, in particular to\nthe commit `D`. So far, the new branch structure would be:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nThis is unintuitive behavior. Even worse, when recreating branch\nstructure, most use cases would appear to want cousins *not* to be\nrebased onto the new base commit. For example, Git for Windows (the\nheaviest user of the Git garden shears, which served as the blueprint\nfor --recreate-merges) frequently merges branches from `next` early, and\nthese branches certainly do *not* want to be rebased. In the example\nabove, the desired outcome would look like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and let's not rebase them by default, introducing the new\n\"rebase-cousins\" mode for use cases where they should be rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt      |  7 ++++++-\n builtin/rebase--helper.c          |  9 ++++++++-\n git-rebase--interactive.sh        |  1 +\n git-rebase.sh                     | 12 +++++++++++-\n sequencer.c                       |  4 ++++\n sequencer.h                       |  6 ++++++\n t/t3430-rebase-recreate-merges.sh | 23 +++++++++++++++++++++++\n 7 files changed, 59 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 5e056c8ab6b..c5a77599c47 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -373,11 +373,16 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n---recreate-merges::\n+--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tRecreate merge commits instead of flattening the history by replaying\n \tmerges. Merge conflict resolutions or manual amendments to merge\n \tcommits are not recreated automatically, but have to be recreated\n \tmanually.\n++\n+By default, or when `no-rebase-cousins` was specified, commits which do not\n+have `<upstream>` as direct ancestor keep their original branch point.\n+If the `rebase-cousins` mode is turned on, such commits are rebased onto\n+`<upstream>` (or `<onto>`, if specified).\n \n -p::\n --preserve-merges::\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex a5b07c43c96..5d1f12de57b 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -25,6 +25,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n+\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -59,8 +61,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n+\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (rebase_cousins >= 0 && !recreate_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--recreate-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex f5c8db2fdf8..679d79e0d17 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -907,6 +907,7 @@ if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${recreate_merges:+--recreate-merges} \\\n+\t\t${rebase_cousins:+--rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex 528fa0073ac..9487e543bec 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-recreate-merges!   try to recreate merges instead of skipping them\n+recreate-merges?   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -89,6 +89,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n recreate_merges=\n+rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -273,6 +274,15 @@ do\n \t\trecreate_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--recreate-merges=*)\n+\t\trecreate_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) rebase_cousins=t;;\n+\t\tno-rebase-cousins) rebase_cousins=;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex 85ce37cb99f..b2bf63029d4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3459,6 +3459,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3634,6 +3635,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (!rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(to, \"onto\"))\n \t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\ndiff --git a/sequencer.h b/sequencer.h\nindex 7c7c67d623c..739dd0fa92b 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -60,6 +60,12 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_RECREATE_MERGES (1U << 3)\n+/*\n+ * When recreating merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are *not* rebased onto the new base by default. If those\n+ * commits should be rebased onto the new base, this flag needs to be passed.\n+ */\n+#define TODO_LIST_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 35a61ce90bb..9a59f12b670 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,6 +143,29 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase cousins unless asked for' '\n+\twrite_script copy-editor.sh <<-\\EOF &&\n+\tcp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tEOF\n+\n+\ttest_config sequence.editor \\\"\"$PWD\"/copy-editor.sh\\\" &&\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges=rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.16.1.windows.4\n"},{"id":"340197","messageId":"CA+P7+xpr1Ho4+xi4QhADP6sWRMP49yqvVRkcHkCY2QUZWkUz5Q@mail.gmail.com","threadId":"47635","inReplyTo":"cover.1519389319.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 00/12] rebase -i: offer to recreate merge commits","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-02-25T10:54:08Z","receivedAt":"2018-02-25T10:54:36Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Fri, Feb 23, 2018 at 4:35 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> Changes since v3:\n>\n> - fixed a grammar error in \"introduce the `merge` command\"'s commit message.\n>\n> - fixed a couple of resource leaks in safe_append() and do_reset(), pointed\n>   out by Eric Sunshine.\n>\n\nThe interdiff seems incorrect for such a small list of changes, it\nappears like large sections of code added by this series appear in the\ninterdiff without subtractions from the previous versions? Is all that\ncode new to v3? If not, I'd suspect you accidentally diffed between\nthe wrong points.\n\nThanks,\nJake\n"},{"id":"340326","messageId":"nycvar.QRO.7.76.6.1802262148560.56@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xpr1Ho4+xi4QhADP6sWRMP49yqvVRkcHkCY2QUZWkUz5Q@mail.gmail.com","subject":"Re: [PATCH v4 00/12] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T20:49:20Z","receivedAt":"2018-02-26T20:49:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Sun, 25 Feb 2018, Jacob Keller wrote:\n\n> On Fri, Feb 23, 2018 at 4:35 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > Changes since v3:\n> >\n> > - fixed a grammar error in \"introduce the `merge` command\"'s commit message.\n> >\n> > - fixed a couple of resource leaks in safe_append() and do_reset(), pointed\n> >   out by Eric Sunshine.\n> >\n> \n> The interdiff seems incorrect for such a small list of changes, it\n> appears like large sections of code added by this series appear in the\n> interdiff without subtractions from the previous versions? Is all that\n> code new to v3? If not, I'd suspect you accidentally diffed between\n> the wrong points.\n\nIndeed, it seems that I messed this iteration up rather well. Will redo.\n\nCiao,\nDscho\n"},{"id":"340330","messageId":"cover.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1518307771.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 00/12] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:11Z","receivedAt":"2018-02-26T21:29:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, I dreamt of an interactive rebase that would not\nflatten branch structure, but instead recreate the commit topology\nfaithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --recreate-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge -C 3456 D # C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --recreate-merges.\n\nChanges since v3:\n\n- (sorry for the broken iteration v4)\n\n- fixed a grammar error in \"introduce the `merge` command\"'s commit message.\n\n- fixed a couple of resource leaks in safe_append() and do_reset(), pointed\n  out by Eric Sunshine.\n\n\nJohannes Schindelin (11):\n  sequencer: avoid using errno clobbered by rollback_lock_file()\n  sequencer: make rearrange_squash() a bit more obvious\n  sequencer: introduce new commands to reset the revision\n  sequencer: introduce the `merge` command\n  sequencer: fast-forward merge commits, if possible\n  rebase-helper --make-script: introduce a flag to recreate merges\n  rebase: introduce the --recreate-merges option\n  sequencer: make refs generated by the `label` command worktree-local\n  sequencer: handle post-rewrite for merge commands\n  pull: accept --rebase=recreate to recreate the branch topology\n  rebase -i: introduce --recreate-merges=[no-]rebase-cousins\n\nStefan Beller (1):\n  git-rebase--interactive: clarify arguments\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   5 +-\n Documentation/git-rebase.txt           |  14 +-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |   2 +\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |  22 +-\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 742 ++++++++++++++++++++++++++++++++-\n sequencer.h                            |   7 +\n t/t3430-rebase-recreate-merges.sh      | 208 +++++++++\n 13 files changed, 1027 insertions(+), 31 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\n\nbase-commit: e3a80781f5932f5fea12a49eb06f3ade4ed8945c\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v5\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v5\n\nInterdiff vs v4:\n diff --git a/sequencer.c b/sequencer.c\n index 63ae71a7512..b2bf63029d4 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -2514,14 +2514,17 @@ static int safe_append(const char *filename, const char *fmt, ...)\n  \n  \tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n  \t\terror_errno(_(\"could not write to '%s'\"), filename);\n +\t\tstrbuf_release(&buf);\n  \t\trollback_lock_file(&lock);\n  \t\treturn -1;\n  \t}\n  \tif (commit_lock_file(&lock) < 0) {\n +\t\tstrbuf_release(&buf);\n  \t\trollback_lock_file(&lock);\n  \t\treturn error(_(\"failed to finalize '%s'\"), filename);\n  \t}\n  \n +\tstrbuf_release(&buf);\n  \treturn 0;\n  }\n  \n @@ -2601,8 +2604,11 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n  \tunpack_tree_opts.update = 1;\n  \tunpack_tree_opts.reset = 1;\n  \n -\tif (read_cache_unmerged())\n +\tif (read_cache_unmerged()) {\n +\t\trollback_lock_file(&lock);\n +\t\tstrbuf_release(&ref_name);\n  \t\treturn error_resolve_conflict(_(action_name(opts)));\n +\t}\n  \n  \tif (!fill_tree_descriptor(&desc, &oid)) {\n  \t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n-- \n2.16.1.windows.4\n\n"},{"id":"340331","messageId":"8ec3a73dfdc76c503d50e34e5fc8b8a3d5ea7dd8.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 01/12] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:16Z","receivedAt":"2018-02-26T21:29:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"As pointed out in a review of the `--recreate-merges` patch series,\n`rollback_lock_file()` clobbers errno. Therefore, we have to report the\nerror message that uses errno before calling said function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e9baaf59bd9..5aa3dc3c95c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -345,12 +345,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n \tif (msg_fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n \tif (write_in_full(msg_fd, buf, len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n+\t\terror_errno(_(\"could not write eol to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write eol to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&msg_file) < 0) {\n \t\trollback_lock_file(&msg_file);\n@@ -2106,16 +2108,17 @@ static int save_head(const char *head)\n \n \tfd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);\n \tif (fd < 0) {\n+\t\terror_errno(_(\"could not lock HEAD\"));\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not lock HEAD\"));\n+\t\treturn -1;\n \t}\n \tstrbuf_addf(&buf, \"%s\\n\", head);\n \twritten = write_in_full(fd, buf.buf, buf.len);\n \tstrbuf_release(&buf);\n \tif (written < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), git_path_head_file());\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t   git_path_head_file());\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&head_lock) < 0) {\n \t\trollback_lock_file(&head_lock);\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340332","messageId":"157d36ec8954e33ddc5670548b66ca58025d81ad.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 03/12] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:23Z","receivedAt":"2018-02-26T21:29:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Stefan Beller <stefanbeller@gmail.com>\n\nUp to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we are about to introduce commands that take different\narguments, clarify each command by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 81c5b428757..a2659fea982 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,13 +155,13 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340333","messageId":"29ffc4990e9282ba5a34eef7a64af42c1b9e1525.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 04/12] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:29Z","receivedAt":"2018-02-26T21:29:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming commits, we will teach the sequencer to recreate merges.\nThis will be done in a very different way from the unfortunate design of\n`git rebase --preserve-merges` (which does not allow for reordering\ncommits, or changing the branch topology).\n\nThe main idea is to introduce new todo list commands, to support\nlabeling the current revision with a given name, resetting the current\nrevision to a previous state, and  merging labeled revisions.\n\nThis idea was developed in Git for Windows' Git garden shears (that are\nused to maintain the \"thicket of branches\" on top of upstream Git), and\nthis patch is part of the effort to make it available to a wider\naudience, as well as to make the entire process more robust (by\nimplementing it in a safe and portable language rather than a Unix shell\nscript).\n\nThis commit implements the commands to label, and to reset to, given\nrevisions. The syntax is:\n\n\tlabel <name>\n\treset <name>\n\nInternally, the `label <name>` command creates the ref\n`refs/rewritten/<name>`. This makes it possible to work with the labeled\nrevisions interactively, or in a scripted fashion (e.g. via the todo\nlist command `exec`).\n\nThese temporary refs are removed upon sequencer_remove_state(), so that\neven a `git rebase --abort` cleans them up.\n\nWe disallow '#' as label because that character will be used as separator\nin the upcoming `merge` command.\n\nLater in this patch series, we will mark the `refs/rewritten/` refs as\nworktree-local, to allow for interactive rebases to be run in parallel in\nworktrees linked to the same repository.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   2 +\n sequencer.c                | 196 +++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 192 insertions(+), 6 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex a2659fea982..501f09b28c4 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n+l, label <label> = label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex cfa01d3bdd2..e25522ecdf1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"notes-utils.h\"\n #include \"sigchain.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `label` command to record the need for cleanup.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -244,18 +253,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n \n int sequencer_remove_state(struct replay_opts *opts)\n {\n-\tstruct strbuf dir = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i;\n \n+\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n+\t\tchar *p = buf.buf;\n+\t\twhile (*p) {\n+\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\tif (eol)\n+\t\t\t\t*eol = '\\0';\n+\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n+\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\tif (!eol)\n+\t\t\t\tbreak;\n+\t\t\tp = eol + 1;\n+\t\t}\n+\t}\n+\n \tfree(opts->gpg_sign);\n \tfree(opts->strategy);\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n \tfree(opts->xopts);\n \n-\tstrbuf_addstr(&dir, get_dir(opts));\n-\tremove_dir_recursively(&dir, 0);\n-\tstrbuf_release(&dir);\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, get_dir(opts));\n+\tremove_dir_recursively(&buf, 0);\n+\tstrbuf_release(&buf);\n \n \treturn 0;\n }\n@@ -1280,6 +1304,8 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1298,6 +1324,8 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1803,7 +1831,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -2444,6 +2473,157 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn -1;\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n+\t\treturn error_errno(_(\"could not read '%s'\"), filename);\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tif (len == 1 && *name == '#')\n+\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t} else if (get_oid(\"HEAD\", &head_oid)) {\n+\t\terror(_(\"could not read HEAD\"));\n+\t\tret = -1;\n+\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n+\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t   ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static int do_reset(const char *name, int len, struct replay_opts *opts)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options unpack_tree_opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\t/* Determine the length of the label */\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n+\tunpack_tree_opts.head_idx = 1;\n+\tunpack_tree_opts.src_index = &the_index;\n+\tunpack_tree_opts.dst_index = &the_index;\n+\tunpack_tree_opts.fn = oneway_merge;\n+\tunpack_tree_opts.merge = 1;\n+\tunpack_tree_opts.update = 1;\n+\tunpack_tree_opts.reset = 1;\n+\n+\tif (read_cache_unmerged()) {\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error_resolve_conflict(_(action_name(opts)));\n+\t}\n+\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret) {\n+\t\tstruct strbuf msg = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&msg, \"(rebase -i) reset '%.*s'\", len, name);\n+\t\tret = update_ref(msg.buf, \"HEAD\", &oid, NULL, 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n+\t\tstrbuf_release(&msg);\n+\t}\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2627,7 +2807,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n-\t\t} else if (!is_noop(item->command))\n+\t\t} else if (item->command == TODO_LABEL)\n+\t\t\tres = do_label(item->arg, item->arg_len);\n+\t\telse if (item->command == TODO_RESET)\n+\t\t\tres = do_reset(item->arg, item->arg_len, opts);\n+\t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340334","messageId":"63a97e5d4c34b921574fe1ccff02747591c7f615.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 05/12] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:33Z","receivedAt":"2018-02-26T21:29:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label` and `reset` commands to label\ncommits and to reset to labeled commits. This patch adds the `merge`\ncommand, with the following syntax:\n\n\tmerge [-C <commit>] <rev> # <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the merge commit that is about\nto be created.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list:\n\n\tlabel onto\n\n\t# Branch abc\n\treset onto\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\treset onto\n\tpick cafecafe And now for something completely different\n\tmerge -C baaabaaa abc # Merge the branch 'abc' into master\n\nTo edit the merge commit's message (a \"reword\" for merges, if you will),\nuse `-c` (lower-case) instead of `-C`; this convention was borrowed from\n`git commit` that also supports `-c` and `-C` with similar meanings.\n\nTo create *new* merges, i.e. without copying the commit message from an\nexisting commit, simply omit the `-C <commit>` parameter (which will\nopen an editor for the merge message):\n\n\tmerge abc\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in a separate\npatch series, support for merges using strategies other than the\nrecursive merge is left for the future.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   4 ++\n sequencer.c                | 158 +++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 162 insertions(+)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 501f09b28c4..2d8bbe20b74 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -164,6 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n l, label <label> = label current HEAD with a name\n t, reset <label> = reset HEAD to a label\n+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n+.       create a merge commit using the original merge commit's\n+.       message (or the oneline, if no original merge commit was\n+.       specified). Use -c <commit> to reword the commit message.\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex e25522ecdf1..64dbd1d3e2e 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1306,6 +1306,8 @@ enum todo_command {\n \tTODO_EXEC,\n \tTODO_LABEL,\n \tTODO_RESET,\n+\tTODO_MERGE,\n+\tTODO_MERGE_AND_EDIT,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1326,6 +1328,8 @@ static struct {\n \t{ 'x', \"exec\" },\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n+\t{ 'm', \"merge\" },\n+\t{ 0, \"merge\" }, /* MERGE_AND_EDIT */\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1839,6 +1843,21 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn 0;\n \t}\n \n+\tif (item->command == TODO_MERGE) {\n+\t\tif (skip_prefix(bol, \"-C\", &bol))\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\t\titem->command = TODO_MERGE_AND_EDIT;\n+\t\t} else {\n+\t\t\titem->command = TODO_MERGE_AND_EDIT;\n+\t\t\titem->commit = NULL;\n+\t\t\titem->arg = bol;\n+\t\t\titem->arg_len = (int)(eol - bol);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n \tsaved = *end_of_object_name;\n \t*end_of_object_name = '\\0';\n@@ -2624,6 +2643,134 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n \treturn ret;\n }\n \n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    int run_commit_flags, struct replay_opts *opts)\n+{\n+\tint merge_arg_len;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *common, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint ret;\n+\tstatic struct lock_file lock;\n+\n+\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n+\t\tif (isspace(arg[merge_arg_len]))\n+\t\t\tbreak;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"cannot merge without a current revision\"));\n+\t}\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t     oid_to_hex(&commit->object.oid));\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tunuse_commit_buffer(commit, message);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tunuse_commit_buffer(commit, message);\n+\t} else {\n+\t\tconst char *p = arg + merge_arg_len;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tp += strspn(p, \" \\t\");\n+\t\tif (*p == '#' && isspace(p[1]))\n+\t\t\tp += 1 + strspn(p + 1, \" \\t\");\n+\t\tif (*p)\n+\t\t\tlen = strlen(p);\n+\t\telse {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tstrbuf_release(&buf);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\tif (!merge_commit) {\n+\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tcommon = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(common);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n+\t\t\t     merge_arg_len, arg);\n+\t}\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error(_(\"merge: Unable to write new index file\"));\n+\t}\n+\trollback_lock_file(&lock);\n+\n+\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2811,6 +2958,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_label(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n+\t\telse if (item->command == TODO_MERGE ||\n+\t\t\t item->command == TODO_MERGE_AND_EDIT)\n+\t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n+\t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n+\t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n \t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n@@ -3292,8 +3444,14 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  short_commit_name(item->commit) :\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n+\t\t\tif (item->command == TODO_MERGE)\n+\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n+\t\t\telse if (item->command == TODO_MERGE_AND_EDIT)\n+\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n+\n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n \t\t}\n+\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340335","messageId":"4f901883659bcd31fc0a2839caf9c957646c8797.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 07/12] rebase-helper --make-script: introduce a flag to recreate merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:39Z","receivedAt":"2018-02-26T21:29:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --recreate-merges option. For a\ncommit topology like this (where the HEAD points to C):\n\n\t- A - B - C\n\t    \\   /\n\t      D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge -C 3456 D # C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch in this patch series.\n\nAs a special, hard-coded label, all merge-recreating todo lists start with\nthe command `label onto` so that we can later always refer to the revision\nonto which everything is rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 349 ++++++++++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 351 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex ad074705bb5..a5b07c43c96 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -24,6 +24,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n+\t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,6 +58,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex 361ec98f764..01bafe2fe47 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -25,6 +25,8 @@\n #include \"sigchain.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -3336,6 +3338,341 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   (len == 1 && *label == '#') ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, or the label is a '#' (which we use as a separator\n+\t\t * between merge heads and oneline), we append a dash and a\n+\t\t * number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n+\t\t*cmd_label = abbr ? \"l\" : \"label\",\n+\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n+\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tif ((commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_original_commit_empty(commit))\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s -C %s\",\n+\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", cmd_label);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(to, \"onto\"))\n+\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n+\t\t\t\t\tcmd_reset, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\",\n+\t\t\t\t\tcmd_label, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -3346,11 +3683,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint recreate_merges = flags & TODO_LIST_RECREATE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_pick = 1;\n+\tif (recreate_merges)\n+\t\trevs.cherry_mark = 1;\n+\telse {\n+\t\trevs.max_parents = 1;\n+\t\trevs.cherry_pick = 1;\n+\t}\n \trevs.limited = 1;\n \trevs.reverse = 1;\n \trevs.right_only = 1;\n@@ -3374,6 +3716,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (recreate_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tstrbuf_reset(&buf);\n \t\tif (!keep_empty && is_original_commit_empty(commit))\ndiff --git a/sequencer.h b/sequencer.h\nindex e45b178dfc4..7c7c67d623c 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -59,6 +59,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_RECREATE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.16.1.windows.4\n\n\n"},{"id":"340336","messageId":"6f05185df94d07db847b4bdad0297af31b798bfd.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 06/12] sequencer: fast-forward merge commits, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:36Z","receivedAt":"2018-02-26T21:29:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to recreate a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 21 ++++++++++++++++++++-\n 1 file changed, 20 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 64dbd1d3e2e..361ec98f764 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2651,7 +2651,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *common, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint ret;\n+\tint can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \n \tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n@@ -2719,6 +2719,14 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\tstrbuf_release(&buf);\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the parent of the original merge commit,\n+\t * we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n \tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n \tif (!merge_commit) {\n@@ -2732,6 +2740,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\trollback_lock_file(&lock);\n \t\treturn -1;\n \t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn fast_forward_to(&commit->object.oid,\n+\t\t\t\t       &head_commit->object.oid, 0, opts);\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340337","messageId":"31098ae586dcd2c5b08e3f7db1a99d1ccef64ed8.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 08/12] rebase: introduce the --recreate-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:43Z","receivedAt":"2018-02-26T21:30:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt to answer this was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto flatten the branch topology or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--recreate-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge <label>`. And once this mode will\nhave become stable and universally accepted, we can deprecate the design\nmistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |   9 +-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 ++\n t/t3430-rebase-recreate-merges.sh      | 146 +++++++++++++++++++++++++++++++++\n 5 files changed, 162 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-recreate-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex d713951b86a..5e056c8ab6b 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -373,6 +373,12 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+--recreate-merges::\n+\tRecreate merge commits instead of flattening the history by replaying\n+\tmerges. Merge conflict resolutions or manual amendments to merge\n+\tcommits are not recreated automatically, but have to be recreated\n+\tmanually.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -775,7 +781,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+--recreate-merges for a more faithful representation.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 88813e91244..38bba3835c6 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2008,7 +2008,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--recreate-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 2d8bbe20b74..f5c8db2fdf8 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -906,6 +906,7 @@ fi\n if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${recreate_merges:+--recreate-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex b353c33d417..528fa0073ac 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+recreate-merges!   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -87,6 +88,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+recreate_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -267,6 +269,10 @@ do\n \t--allow-empty-message)\n \t\tallow_empty_message=--allow-empty-message\n \t\t;;\n+\t--recreate-merges)\n+\t\trecreate_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nnew file mode 100755\nindex 00000000000..0073601a206\n--- /dev/null\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -0,0 +1,146 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2017 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --recreate-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+cat >script-from-scratch <<\\EOF\n+label onto\n+\n+# onebranch\n+pick G\n+pick D\n+label onebranch\n+\n+# second\n+reset onto\n+pick B\n+label second\n+\n+reset onto\n+merge -C H second\n+merge onebranch # Merge the topic branch 'onebranch'\n+EOF\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'create completely different structure' '\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\treset onto\n+\tpick d9df450 B\n+\tlabel E\n+\n+\treset onto\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point # C\n+\tpick 12bd07b D\n+\tmerge -C 2051b56 E # E\n+\tmerge -C 233d48a H # H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340338","messageId":"620954c3c964cdaa43b41b598b3c508cd6be9049.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 09/12] sequencer: make refs generated by the `label` command worktree-local","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:47Z","receivedAt":"2018-02-26T21:30:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                            |  3 ++-\n t/t3430-rebase-recreate-merges.sh | 14 ++++++++++++++\n 2 files changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 20ba82b4343..e8b84c189ff 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 0073601a206..1a3e43d66ff 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,4 +143,18 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340339","messageId":"8ecddf181eeaa9c6bfc9ff573562f9bd9d3d32a5.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 10/12] sequencer: handle post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:50Z","receivedAt":"2018-02-26T21:30:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --recreate-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits.\n\nThis patch implements the post-rewrite handling for the `merge` command\nwe just introduced. The other commands that were added recently (`label`\nand `reset`) do not create new commits, therefore post-rewrite do not\nneed to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c                       |  7 +++++--\n t/t3430-rebase-recreate-merges.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 30 insertions(+), 2 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 01bafe2fe47..85ce37cb99f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2980,11 +2980,14 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n \t\telse if (item->command == TODO_MERGE ||\n-\t\t\t item->command == TODO_MERGE_AND_EDIT)\n+\t\t\t item->command == TODO_MERGE_AND_EDIT) {\n \t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n \t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n \t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n-\t\telse if (!is_noop(item->command))\n+\t\t\tif (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n+\t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 1a3e43d66ff..35a61ce90bb 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -157,4 +157,29 @@ test_expect_success 'refs/rewritten/* is worktree-local' '\n \ttest_cmp_rev HEAD \"$(cat wt/b)\"\n '\n \n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash --recreate-merges HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340340","messageId":"f475b92b9ae8ef946512841cc539f7881372b63d.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 11/12] pull: accept --rebase=recreate to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:53Z","receivedAt":"2018-02-26T21:30:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `recreate` mode simply passes the\n`--recreate-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  5 ++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       |  2 ++\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 25 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex f57e9cf10ca..8c9adea0d0c 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2607,6 +2611,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When recreate, also pass `--recreate-merges` along to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..b4f9f057ea9 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,16 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|recreate|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to recreate, rebase with the `--recreate-merges` option passed\n+to `git rebase` so that locally created merge commits will not be flattened.\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 1876271af94..9da2cfa0bd3 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_RECREATE,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"recreate\", returns REBASE_RECREATE. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"recreate\"))\n+\t\treturn REBASE_RECREATE;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|recreate|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_RECREATE)\n+\t\targv_array_push(&args, \"--recreate-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d95bf904c3b..b7d0f7ce596 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"recreate\"))\n+\t\t\t\tinfo->rebase = NORMAL_REBASE;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 38bba3835c6..3d44cb6890c 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2182,7 +2182,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true recreate preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340341","messageId":"528364b9aef14669fc2521e8cc5ec27bb5c06dcd.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 12/12] rebase -i: introduce --recreate-merges=[no-]rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:58Z","receivedAt":"2018-02-26T21:30:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This one is a bit tricky to explain, so let's try with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nTo illustrate what this new mode is all about, let's consider what\nhappens upon `git rebase -i --recreate-merges B`, in particular to\nthe commit `D`. So far, the new branch structure would be:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nThis is unintuitive behavior. Even worse, when recreating branch\nstructure, most use cases would appear to want cousins *not* to be\nrebased onto the new base commit. For example, Git for Windows (the\nheaviest user of the Git garden shears, which served as the blueprint\nfor --recreate-merges) frequently merges branches from `next` early, and\nthese branches certainly do *not* want to be rebased. In the example\nabove, the desired outcome would look like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and let's not rebase them by default, introducing the new\n\"rebase-cousins\" mode for use cases where they should be rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt      |  7 ++++++-\n builtin/rebase--helper.c          |  9 ++++++++-\n git-rebase--interactive.sh        |  1 +\n git-rebase.sh                     | 12 +++++++++++-\n sequencer.c                       |  4 ++++\n sequencer.h                       |  6 ++++++\n t/t3430-rebase-recreate-merges.sh | 23 +++++++++++++++++++++++\n 7 files changed, 59 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 5e056c8ab6b..c5a77599c47 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -373,11 +373,16 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n---recreate-merges::\n+--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tRecreate merge commits instead of flattening the history by replaying\n \tmerges. Merge conflict resolutions or manual amendments to merge\n \tcommits are not recreated automatically, but have to be recreated\n \tmanually.\n++\n+By default, or when `no-rebase-cousins` was specified, commits which do not\n+have `<upstream>` as direct ancestor keep their original branch point.\n+If the `rebase-cousins` mode is turned on, such commits are rebased onto\n+`<upstream>` (or `<onto>`, if specified).\n \n -p::\n --preserve-merges::\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex a5b07c43c96..5d1f12de57b 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -25,6 +25,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n+\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -59,8 +61,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n+\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (rebase_cousins >= 0 && !recreate_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--recreate-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex f5c8db2fdf8..679d79e0d17 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -907,6 +907,7 @@ if test t != \"$preserve_merges\"\n then\n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${recreate_merges:+--recreate-merges} \\\n+\t\t${rebase_cousins:+--rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n else\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex 528fa0073ac..9487e543bec 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-recreate-merges!   try to recreate merges instead of skipping them\n+recreate-merges?   try to recreate merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -89,6 +89,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n recreate_merges=\n+rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -273,6 +274,15 @@ do\n \t\trecreate_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--recreate-merges=*)\n+\t\trecreate_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) rebase_cousins=t;;\n+\t\tno-rebase-cousins) rebase_cousins=;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex 85ce37cb99f..b2bf63029d4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3459,6 +3459,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3634,6 +3635,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (!rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(to, \"onto\"))\n \t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\ndiff --git a/sequencer.h b/sequencer.h\nindex 7c7c67d623c..739dd0fa92b 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -60,6 +60,12 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_RECREATE_MERGES (1U << 3)\n+/*\n+ * When recreating merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are *not* rebased onto the new base by default. If those\n+ * commits should be rebased onto the new base, this flag needs to be passed.\n+ */\n+#define TODO_LIST_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh\nindex 35a61ce90bb..9a59f12b670 100755\n--- a/t/t3430-rebase-recreate-merges.sh\n+++ b/t/t3430-rebase-recreate-merges.sh\n@@ -143,6 +143,29 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase cousins unless asked for' '\n+\twrite_script copy-editor.sh <<-\\EOF &&\n+\tcp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tEOF\n+\n+\ttest_config sequence.editor \\\"\"$PWD\"/copy-editor.sh\\\" &&\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase -i --recreate-merges=rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.16.1.windows.4\n"},{"id":"340342","messageId":"a7e01df062d64a2b89c99a4de4a772cfb0c84017.1519680483.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 02/12] sequencer: make rearrange_squash() a bit more obvious","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-02-26T21:29:20Z","receivedAt":"2018-02-26T21:30:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"There are some commands that have to be skipped from rearranging by virtue\nof not handling any commits.\n\nHowever, the logic was not quite obvious: it skipped commands based on\ntheir position in the enum todo_command.\n\nInstead, let's make it explicit that we skip all commands that do not\nhandle any commit. With one exception: the `drop` command, because it,\nwell, drops the commit and is therefore not eligible to rearranging.\n\nNote: this is a bit academic at the moment because the only time we call\n`rearrange_squash()` is directly after generating the todo list, when we\nhave nothing but `pick` commands anyway.\n\nHowever, the upcoming `merge` command *will* want to be handled by that\nfunction, and it *can* handle commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 5aa3dc3c95c..cfa01d3bdd2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3412,7 +3412,7 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (!item->commit || item->command == TODO_DROP) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\n-- \n2.16.1.windows.4\n\n\n"},{"id":"340459","messageId":"CAN0heSpCiHxGDnWyafTQU4+++73JGRW9w0Bq1nd2LcqgMqOnzA@mail.gmail.com","threadId":"47635","inReplyTo":"8ec3a73dfdc76c503d50e34e5fc8b8a3d5ea7dd8.1519680483.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 01/12] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-02-27T21:33:17Z","receivedAt":"2018-02-27T21:33:23Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 26 February 2018 at 22:29, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> As pointed out in a review of the `--recreate-merges` patch series,\n> `rollback_lock_file()` clobbers errno. Therefore, we have to report the\n> error message that uses errno before calling said function.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  sequencer.c | 13 ++++++++-----\n>  1 file changed, 8 insertions(+), 5 deletions(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index e9baaf59bd9..5aa3dc3c95c 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -345,12 +345,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n>         if (msg_fd < 0)\n>                 return error_errno(_(\"could not lock '%s'\"), filename);\n>         if (write_in_full(msg_fd, buf, len) < 0) {\n> +               error_errno(_(\"could not write to '%s'\"), filename);\n>                 rollback_lock_file(&msg_file);\n> -               return error_errno(_(\"could not write to '%s'\"), filename);\n> +               return -1;\n>         }\n>         if (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n> +               error_errno(_(\"could not write eol to '%s'\"), filename);\n>                 rollback_lock_file(&msg_file);\n> -               return error_errno(_(\"could not write eol to '%s'\"), filename);\n> +               return -1;\n>         }\n>         if (commit_lock_file(&msg_file) < 0) {\n>                 rollback_lock_file(&msg_file);\n> @@ -2106,16 +2108,17 @@ static int save_head(const char *head)\n>\n>         fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);\n>         if (fd < 0) {\n> +               error_errno(_(\"could not lock HEAD\"));\n>                 rollback_lock_file(&head_lock);\n> -               return error_errno(_(\"could not lock HEAD\"));\n> +               return -1;\n>         }\n\nI just noticed this when test-merging my series of lockfile-fixes to pu.\nThis `rollback_lock_file()` is not needed, since failure to take the\nlock leaves it unlocked. If one wants to roll back the lock \"for\nclarity\" or \"just to be safe\", then the same should arguably be done in\n`write_message()`, just barely visible at the top of this diff.\n\nPerhaps not worth a reroll. The conflict resolution between this and my\npatch would be to take my hunk.\n\nhttps://public-inbox.org/git/cover.1519763396.git.martin.agren@gmail.com/T/#t\n\nMartin\n"},{"id":"340834","messageId":"nycvar.QRO.7.76.6.1803022133320.56@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAN0heSpCiHxGDnWyafTQU4+++73JGRW9w0Bq1nd2LcqgMqOnzA@mail.gmail.com","subject":"Re: [PATCH v5 01/12] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-03-02T20:33:46Z","receivedAt":"2018-03-02T20:34:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Martin,\n\nOn Tue, 27 Feb 2018, Martin Ågren wrote:\n\n> On 26 February 2018 at 22:29, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > As pointed out in a review of the `--recreate-merges` patch series,\n> > `rollback_lock_file()` clobbers errno. Therefore, we have to report the\n> > error message that uses errno before calling said function.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  sequencer.c | 13 ++++++++-----\n> >  1 file changed, 8 insertions(+), 5 deletions(-)\n> >\n> > diff --git a/sequencer.c b/sequencer.c\n> > index e9baaf59bd9..5aa3dc3c95c 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -345,12 +345,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n> >         if (msg_fd < 0)\n> >                 return error_errno(_(\"could not lock '%s'\"), filename);\n> >         if (write_in_full(msg_fd, buf, len) < 0) {\n> > +               error_errno(_(\"could not write to '%s'\"), filename);\n> >                 rollback_lock_file(&msg_file);\n> > -               return error_errno(_(\"could not write to '%s'\"), filename);\n> > +               return -1;\n> >         }\n> >         if (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n> > +               error_errno(_(\"could not write eol to '%s'\"), filename);\n> >                 rollback_lock_file(&msg_file);\n> > -               return error_errno(_(\"could not write eol to '%s'\"), filename);\n> > +               return -1;\n> >         }\n> >         if (commit_lock_file(&msg_file) < 0) {\n> >                 rollback_lock_file(&msg_file);\n> > @@ -2106,16 +2108,17 @@ static int save_head(const char *head)\n> >\n> >         fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);\n> >         if (fd < 0) {\n> > +               error_errno(_(\"could not lock HEAD\"));\n> >                 rollback_lock_file(&head_lock);\n> > -               return error_errno(_(\"could not lock HEAD\"));\n> > +               return -1;\n> >         }\n> \n> I just noticed this when test-merging my series of lockfile-fixes to pu.\n> This `rollback_lock_file()` is not needed, since failure to take the\n> lock leaves it unlocked. If one wants to roll back the lock \"for\n> clarity\" or \"just to be safe\", then the same should arguably be done in\n> `write_message()`, just barely visible at the top of this diff.\n> \n> Perhaps not worth a reroll. The conflict resolution between this and my\n> patch would be to take my hunk.\n> \n> https://public-inbox.org/git/cover.1519763396.git.martin.agren@gmail.com/T/#t\n\nThank you for working on this!\nDscho"},{"id":"341095","messageId":"bc9f82fb-fd18-ee45-36a4-921a1381b32e@gmail.com","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 00/12] rebase -i: offer to recreate merge commits","fromName":"Igor Djordjevic","fromEmail":"igor.d.djordjevic@gmail.com","sentAt":"2018-03-06T04:02:28Z","receivedAt":"2018-03-06T04:02:52Z","isPatch":true,"sender":{"key":"igor.d.djordjevic@gmail.com","avatar":null},"body":"Hi Johannes,\n\nOn 26/02/2018 22:29, Johannes Schindelin wrote:\n> \n> Once upon a time, I dreamt of an interactive rebase that would not\n> flatten branch structure, but instead recreate the commit topology\n> faithfully.\n> \n> My original attempt was --preserve-merges, but that design was so\n> limited that I did not even enable it in interactive mode.\n> \n> Subsequently, it *was* enabled in interactive mode, with the predictable\n> consequences: as the --preserve-merges design does not allow for\n> specifying the parents of merge commits explicitly, all the new commits'\n> parents are defined *implicitly* by the previous commit history, and\n> hence it is *not possible to even reorder commits*.\n> \n> This design flaw cannot be fixed. Not without a complete re-design, at\n> least. This patch series offers such a re-design.\n> \n> Think of --recreate-merges as \"--preserve-merges done right\".\n\nFirst of all, thanks for this wonderful improvement to existing `git \nrebase` functionality, I`m really excited to have this in the mainline! :)\n\nBut in the light of \"--preserve-merges done right\", I would like to \nhear your opinion on a topic that might be considered more or less \nimportant, and thus tackled in a few different ways... :$\n\nRebasing amended merges :( Even though documentation is quite clear \nabout merge conflicts and manual amendments not recreated \nautomatically, this might be considered quite an issue (a bug, even), \nas even in case of non-interactive rebase, amended content will be \ndropped - and even worse, it all happens silently, without alerting \nthe user (for whom we presume to know what he`s doing, I guess).\n\nNow, might be this is considered the least interesting use case, in \ncomparison to all the power of more serious interactive rebases, but \nI would argue it could be the one most used by less adventurous users \nthat would simply like to stay up-to-date with upstream, rebasing their \ncurrent work on top of it (think `git pull --rebase=recreate`, even).\n\nAs it currently is, and that was the case with `--preserve-merges`, \ntoo, this will cause them to silently lose their work (amended merge \ncontent). And while documentation is clear about it, these might be \nless knowledgeable users, too, and thus potentially be the ones we \nshould (try to) protect even more, if possible.\n\nNow, in the light of that other, ongoing \"merge rebasing\" topic[1], \nit seems we really might be able to do much better, actually \n_rebasing_ merges (and keeping manual conflict resolutions/amendments), \ninstead of _recreating_ them (and silently loosing content), and doing \nso reliably (or stopping for possible user inspection, but not silently \ndoing the wrong thing, even if documented).\n\nThis concerns non-interactive rebase the most, but I have ideas on \nmaking it aligned with interactive one, too, where user could \nactually decide whether to rebase or (re)create the merge (rebase \nbecoming the default, intuitively aligned with non-interactive rebase).\n\nBut before elaborating, I would like to hear your opinion on whether \nyou find it worth to pursue that goal here, before `--recreate-merges` \nhits the mainstream, or it might be just fine as a possible later\nimprovement, too (if accepted, that is).\n\nMy main concern, and why I raised the question inside this topic in \nthe first place, is default behavior. With `--recreate-merges` just \nbeing introduced, we have no backwards compatibility to think about, \nbeing a unique chance to make default behave as needed (not to say \n\"correct\", even), and might be really ticking one more of \n\"--preserve-merges done right\" boxes, and could be a pretty important \none, too.\n\nBut once this becomes widely available, I guess it will be hard to \nimprove (fix?) this merge rebasing silent content losing behavior \n(even if we would acknowledge it as a bug), without introducing \nadditional options - and without a possibility to make possibly \n\"right\" behavior a default one, thus further complicating user \nexperience.\n\nSo, I wanted to hear your stance on this :(\n\nKnowing how much this means to you, it is really not my wish to drag \nthis topic further, and if you find it that we`re good here as it is, \nI wouldn`t have any objections - I guess later new `--rebase-merges` \noption is a possibility, too, might be a wrapper around \n`--recreate-merges`, but with actual merge rebasing being a default \n(where merge recreation would still be possible, too)...\n\nOtherwise, if you have any interest in it now, I can further elaborate \nwhat I`m thinking about, where it might help improve both user \nexperience and rebase possibilities, for what might not be too much \nextra work... hopefully :P\n\nWhatever ends up being your response, I`m really grateful for your \nwork on this matter so far, and thank you for everything you did.\n\np.s. lol, now that I said it, and after writing all this, I might \nactually even like the idea of (later) having `--rebase-merges` \nalongside `--recreate-merges`, too, each one clearly communicating \nits default mode of operation - rebase merges vs. recreate merges... \nas one might rightfully expect ;) Eh :P\n\nRegards, Buga\n\n[1] https://public-inbox.org/git/87y3jtqdyg.fsf@javad.com/\n"},{"id":"341202","messageId":"nycvar.QRO.7.76.6.1803071445510.20700@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"bc9f82fb-fd18-ee45-36a4-921a1381b32e@gmail.com","subject":"Re: [PATCH v5 00/12] rebase -i: offer to recreate merge commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-03-07T13:50:00Z","receivedAt":"2018-03-07T13:50:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Buga,\n\nOn Tue, 6 Mar 2018, Igor Djordjevic wrote:\n\n> [...]\n>\n> But before elaborating, I would like to hear your opinion on whether you\n> find it worth to pursue that goal here, before `--recreate-merges` hits\n> the mainstream, or it might be just fine as a possible later\n> improvement, too (if accepted, that is).\n\nAs I suggested in another sub-thread, I think the best way forward is to\nuse your idea to make the 'rebase original merge commits' strategy\nexplicit.\n\nThat would not actually hold up the current --recreate-merges patch\nseries, but would mean to provide an add-on patch series to add support\nfor `merge -R` and then use that from the generated todo list.\n\nFor implementation detail reasons, it may actually make sense to integrate\nthose patches into the --recreate-merges patch series, though. Should not\nbe hard (except during GitMerge).\n\n> p.s. lol, now that I said it, and after writing all this, I might \n> actually even like the idea of (later) having `--rebase-merges` \n> alongside `--recreate-merges`, too, each one clearly communicating \n> its default mode of operation - rebase merges vs. recreate merges... \n> as one might rightfully expect ;) Eh :P\n\nHehe...\n\nCiao,\nDscho\n"},{"id":"344311","messageId":"cover.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1519680483.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:23Z","receivedAt":"2018-04-10T12:29:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, I dreamt of an interactive rebase that would not\nflatten branch structure, but instead recreate the commit topology\nfaithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --rebase-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge -C 3456 D # C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --rebase-merges. And then one\nto allow for rebasing merge commits in a smarter way (this one will need\na bit more work, though, as it can result in very complicated, nested\nmerge conflicts *very* easily).\n\nChanges since v5 (sorry, this one is big, and so is the interdiff):\n\n- rebased to `master`, resolving conflicts with `ws/rebase-p` and\n  `pw/rebase-keep-empty-fixes` (these changes are not reflected in the\n  interdiff because I still did not find a good way to represent such\n  fixups).\n\n- just like `git merge` refuses to merge ancestors of HEAD, so does now\n  the todo command `merge`.\n\n- `git remote -v`'s output now differs when pulling with --rebase-merges\n  vs pulling with --interactive.\n\n- the `merge` command now also gives rerere a chance (just like `pick`\n  already does).\n\n- simplified test for rebase-cousins (no need to run --rebase-merges\n  interactively, so there is no need to override the editor either).\n\n- fixed `safe_append()` to roll back the lock file even when *reading*\n  failed.\n\n- used `reflog_message()` in `do_reset()` rather than duplicating the\n  logic.\n\n- reworded misleading commit message talking about fast-forwarding merge\n  commits, when we just fast-forward `merge` commands *to* those merge commits\n  whenever possible.\n\n- removed duplicate `if (can_fast_forward)` clause.\n\n- stopped promising support for octopus merges in --make-script in this patch\n  series (it will be added in a later patch series).\n\n- fixed grammar error in the message of the commit adding support for\n  post-rewrite hooks to handle commits processed via the `merge` command.\n\n- folded TODO_MERGE_AND_EDIT into TODO_MERGE by using a new `flags`\n  field.\n\n- the code of `do_merge()` has been made more obvious by using a variable\n  `oneline_offset` instead of the non-descriptive `p`.\n\n- renamed the option to `rebase-merges`, in preparation for doing it\n  smarter using Phillip Wood's strategy (this will be contributed in a\n  follow-up patch series after two others that add support for octopus\n  merges and for handling --root via the sequencer).\n\n- included Phillip Wood's test for --keep-empty with the new mode (and folded\n  in a fix into the code of the `merge` command).\n\n- added -r as shortcut for --rebase-merges\n\n- added an entire section about \"REBASING MERGES\" to git-rebase's man page.\n\n\nJohannes Schindelin (13):\n  sequencer: avoid using errno clobbered by rollback_lock_file()\n  sequencer: make rearrange_squash() a bit more obvious\n  sequencer: introduce new commands to reset the revision\n  sequencer: introduce the `merge` command\n  sequencer: fast-forward `merge` commands, if possible\n  rebase-helper --make-script: introduce a flag to rebase merges\n  rebase: introduce the --rebase-merges option\n  sequencer: make refs generated by the `label` command worktree-local\n  sequencer: handle post-rewrite for merge commands\n  rebase --rebase-merges: avoid \"empty merges\"\n  pull: accept --rebase=merges to recreate the branch topology\n  rebase -i: introduce --rebase-merges=[no-]rebase-cousins\n  rebase -i --rebase-merges: add a section to the man page\n\nPhillip Wood (1):\n  rebase --rebase-merges: add test for --keep-empty\n\nStefan Beller (1):\n  git-rebase--interactive: clarify arguments\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   5 +-\n Documentation/git-rebase.txt           | 140 ++++-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |  18 +-\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |  22 +-\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 775 ++++++++++++++++++++++++-\n sequencer.h                            |   7 +\n t/t3421-rebase-topology-linear.sh      |   1 +\n t/t3430-rebase-merges.sh               | 211 +++++++\n 14 files changed, 1203 insertions(+), 34 deletions(-)\n create mode 100755 t/t3430-rebase-merges.sh\n\n\nbase-commit: 0b0cc9f86731f894cff8dd25299a9b38c254569e\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v6\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v6\n\nInterdiff vs v5:\n diff --git a/Documentation/config.txt b/Documentation/config.txt\n index 85dc3a0c429..45916ea8104 100644\n --- a/Documentation/config.txt\n +++ b/Documentation/config.txt\n @@ -1058,7 +1058,7 @@ branch.<name>.rebase::\n  \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n  \tbranch-specific manner.\n  +\n -When recreate, also pass `--recreate-merges` along to 'git rebase'\n +When `merges`, pass the `--rebase-merges` option to 'git rebase'\n  so that locally committed merge commits will not be flattened\n  by running 'git pull'.\n  +\n @@ -2620,7 +2620,7 @@ pull.rebase::\n  \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n  \tper-branch basis.\n  +\n -When recreate, also pass `--recreate-merges` along to 'git rebase'\n +When `merges`, pass the `--rebase-merges` option to 'git rebase'\n  so that locally committed merge commits will not be flattened\n  by running 'git pull'.\n  +\n diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\n index b4f9f057ea9..6f76d815dd3 100644\n --- a/Documentation/git-pull.txt\n +++ b/Documentation/git-pull.txt\n @@ -101,15 +101,15 @@ Options related to merging\n  include::merge-options.txt[]\n  \n  -r::\n ---rebase[=false|true|recreate|preserve|interactive]::\n +--rebase[=false|true|merges|preserve|interactive]::\n  \tWhen true, rebase the current branch on top of the upstream\n  \tbranch after fetching. If there is a remote-tracking branch\n  \tcorresponding to the upstream branch and the upstream branch\n  \twas rebased since last fetched, the rebase uses that information\n  \tto avoid rebasing non-local changes.\n  +\n -When set to recreate, rebase with the `--recreate-merges` option passed\n -to `git rebase` so that locally created merge commits will not be flattened.\n +When set to `merges`, rebase using `git rebase --rebase-merges` so that\n +locally created merge commits will not be flattened.\n  +\n  When set to preserve, rebase with the `--preserve-merges` option passed\n  to `git rebase` so that locally created merge commits will not be flattened.\n diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n index 2b85416f969..be946de2efb 100644\n --- a/Documentation/git-rebase.txt\n +++ b/Documentation/git-rebase.txt\n @@ -378,16 +378,19 @@ The commit list format can be changed by setting the configuration option\n  rebase.instructionFormat.  A customized instruction format will automatically\n  have the long commit hash prepended to the format.\n  \n ---recreate-merges[=(rebase-cousins|no-rebase-cousins)]::\n -\tRecreate merge commits instead of flattening the history by replaying\n +-r::\n +--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n +\tRebase merge commits instead of flattening the history by replaying\n  \tmerges. Merge conflict resolutions or manual amendments to merge\n -\tcommits are not recreated automatically, but have to be recreated\n +\tcommits are not rebased automatically, but have to be applied\n  \tmanually.\n  +\n  By default, or when `no-rebase-cousins` was specified, commits which do not\n -have `<upstream>` as direct ancestor keep their original branch point.\n +have `<upstream>` as direct ancestor will keep their original branch point.\n  If the `rebase-cousins` mode is turned on, such commits are rebased onto\n  `<upstream>` (or `<onto>`, if specified).\n ++\n +See also REBASING MERGES below.\n  \n  -p::\n  --preserve-merges::\n @@ -786,13 +789,136 @@ The ripple effect of a \"hard case\" recovery is especially bad:\n  'everyone' downstream from 'topic' will now have to perform a \"hard\n  case\" recovery too!\n  \n +REBASING MERGES\n +-----------------\n +\n +The interactive rebase command was originally designed to handle\n +individual patch series. As such, it makes sense to exclude merge\n +commits from the todo list, as the developer may have merged the\n +current `master` while working on the branch, only to eventually\n +rebase all the commits onto `master` (skipping the merge commits).\n +\n +However, there are legitimate reasons why a developer may want to\n +recreate merge commits: to keep the branch structure (or \"commit\n +topology\") when working on multiple, inter-related branches.\n +\n +In the following example, the developer works on a topic branch that\n +refactors the way buttons are defined, and on another topic branch\n +that uses that refactoring to implement a \"Report a bug\" button. The\n +output of `git log --graph --format=%s -5` may look like this:\n +\n +------------\n +*   Merge branch 'report-a-bug'\n +|\\\n +| * Add the feedback button\n +* | Merge branch 'refactor-button'\n +|\\ \\\n +| |/\n +| * Use the Button class for all buttons\n +| * Extract a generic Button class from the DownloadButton one\n +------------\n +\n +The developer might want to rebase those commits to a newer `master`\n +while keeping the branch topology, for example when the first topic\n +branch is expected to be integrated into `master` much earlier than the\n +second one, say, to resolve merge conflicts with changes to the\n +DownloadButton class that made it into `master`.\n +\n +This rebase can be performed using the `--rebase-merges` option.\n +It will generate a todo list looking like this:\n +\n +------------\n +label onto\n +\n +# Branch: refactor-button\n +reset onto\n +pick 123456 Extract a generic Button class from the DownloadButton one\n +pick 654321 Use the Button class for all buttons\n +label refactor-button\n +\n +# Branch: report-a-bug\n +reset refactor-button # Use the Button class for all buttons\n +pick abcdef Add the feedback button\n +label report-a-bug\n +\n +reset onto\n +merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n +merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n +------------\n +\n +In contrast to a regular interactive rebase, there are `label`, `reset` and\n +`merge` commands in addition to `pick` ones.\n +\n +The `label` command puts a label to whatever will be the current\n +revision when that command is executed. Internally, these labels are\n +worktree-local refs that will be deleted when the rebase finishes or\n +when it is aborted. That way, rebase operations in multiple worktrees\n +linked to the same repository do not interfere with one another.\n +\n +The `reset` command is essentially a `git reset --hard` to the specified\n +revision (typically a previously-labeled one).\n +\n +The `merge` command will merge the specified revision into whatever is\n +HEAD at that time. With `-C <original-commit>`, the commit message of\n +the specified merge commit will be used. When the `-C` is changed to\n +a lower-case `-c`, the message will be opened in an editor after a\n +successful merge so that the user can edit the message.\n +\n +At this time, the `merge` command will *always* use the `recursive`\n +merge strategy, with no way to choose a different one. To work around\n +this, an `exec` command can be used to call `git merge` explicitly,\n +using the fact that the labels are worktree-local refs (the ref\n +`refs/rewritten/onto` would correspond to the label `onto`).\n +\n +Note: the first command (`reset onto`) labels the revision onto which\n +the commits are rebased; The name `onto` is just a convention, as a nod\n +to the `--onto` option.\n +\n +It is also possible to introduce completely new merge commits from scratch\n +by adding a command of the form `merge <merge-head>`. This form will\n +generate a tentative commit message and always open an editor to let the\n +user edit it. This can be useful e.g. when a topic branch turns out to\n +address more than a single concern and wants to be split into two or\n +even more topic branches. Consider this todo list:\n +\n +------------\n +pick 192837 Switch from GNU Makefiles to CMake\n +pick 5a6c7e Document the switch to CMake\n +pick 918273 Fix detection of OpenSSL in CMake\n +pick afbecd http: add support for TLS v1.3\n +pick fdbaec Fix detection of cURL in CMake on Windows\n +------------\n +\n +The one commit in this list that is not related to CMake may very well\n +have been motivated by working on fixing all those bugs introduced by\n +switching to CMake, but it addresses a different concern. To split this\n +branch into two topic branches, the todo list could be edited like this:\n +\n +------------\n +label onto\n +\n +pick afbecd http: add support for TLS v1.3\n +label tlsv1.3\n +\n +reset onto\n +pick 192837 Switch from GNU Makefiles to CMake\n +pick 918273 Fix detection of OpenSSL in CMake\n +pick fdbaec Fix detection of cURL in CMake on Windows\n +pick 5a6c7e Document the switch to CMake\n +label cmake\n +\n +reset onto\n +merge tlsv1.3\n +merge cmake\n +------------\n +\n  BUGS\n  ----\n  The todo list presented by `--preserve-merges --interactive` does not\n  represent the topology of the revision graph.  Editing commits and\n  rewording their commit messages should work fine, but attempts to\n  reorder commits tend to produce counterintuitive results. Use\n ---recreate-merges for a more faithful representation.\n +--rebase-merges for a more faithful representation.\n  \n  For example, an attempt to rearrange\n  ------------\n diff --git a/builtin/pull.c b/builtin/pull.c\n index 3d1cc60eed6..70b44146ce4 100644\n --- a/builtin/pull.c\n +++ b/builtin/pull.c\n @@ -27,14 +27,14 @@ enum rebase_type {\n  \tREBASE_FALSE = 0,\n  \tREBASE_TRUE,\n  \tREBASE_PRESERVE,\n -\tREBASE_RECREATE,\n +\tREBASE_MERGES,\n  \tREBASE_INTERACTIVE\n  };\n  \n  /**\n   * Parses the value of --rebase. If value is a false value, returns\n   * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n - * \"recreate\", returns REBASE_RECREATE. If value is \"preserve\", returns\n + * \"merges\", returns REBASE_MERGES. If value is \"preserve\", returns\n   * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n   * fatal is true, otherwise returns REBASE_INVALID.\n   */\n @@ -49,8 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n  \t\treturn REBASE_TRUE;\n  \telse if (!strcmp(value, \"preserve\"))\n  \t\treturn REBASE_PRESERVE;\n -\telse if (!strcmp(value, \"recreate\"))\n -\t\treturn REBASE_RECREATE;\n +\telse if (!strcmp(value, \"merges\"))\n +\t\treturn REBASE_MERGES;\n  \telse if (!strcmp(value, \"interactive\"))\n  \t\treturn REBASE_INTERACTIVE;\n  \n @@ -134,7 +134,7 @@ static struct option pull_options[] = {\n  \t/* Options passed to git-merge or git-rebase */\n  \tOPT_GROUP(N_(\"Options related to merging\")),\n  \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n -\t  \"false|true|recreate|preserve|interactive\",\n +\t  \"false|true|merges|preserve|interactive\",\n  \t  N_(\"incorporate changes by rebasing rather than merging\"),\n  \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n  \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n @@ -804,8 +804,8 @@ static int run_rebase(const struct object_id *curr_head,\n  \targv_push_verbosity(&args);\n  \n  \t/* Options passed to git-rebase */\n -\tif (opt_rebase == REBASE_RECREATE)\n -\t\targv_array_push(&args, \"--recreate-merges\");\n +\tif (opt_rebase == REBASE_MERGES)\n +\t\targv_array_push(&args, \"--rebase-merges\");\n  \telse if (opt_rebase == REBASE_PRESERVE)\n  \t\targv_array_push(&args, \"--preserve-merges\");\n  \telse if (opt_rebase == REBASE_INTERACTIVE)\n diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\n index 5d1f12de57b..f7c2a5fdc81 100644\n --- a/builtin/rebase--helper.c\n +++ b/builtin/rebase--helper.c\n @@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n  int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  {\n  \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n -\tunsigned flags = 0, keep_empty = 0, recreate_merges = 0;\n +\tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n  \tint abbreviate_commands = 0, rebase_cousins = -1;\n  \tenum {\n  \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n @@ -24,7 +24,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n  \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n  \t\t\tN_(\"allow commits with empty messages\")),\n -\t\tOPT_BOOL(0, \"recreate-merges\", &recreate_merges, N_(\"recreate merge commits\")),\n +\t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n  \t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n  \t\t\t N_(\"keep original branch points of cousins\")),\n  \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n @@ -60,13 +60,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n  \n  \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n  \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n -\tflags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;\n +\tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n  \tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n  \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n  \n -\tif (rebase_cousins >= 0 && !recreate_merges)\n +\tif (rebase_cousins >= 0 && !rebase_merges)\n  \t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n -\t\t\t  \"--recreate-merges\"));\n +\t\t\t  \"--rebase-merges\"));\n  \n  \tif (command == CONTINUE && argc == 1)\n  \t\treturn !!sequencer_continue(&opts);\n diff --git a/builtin/remote.c b/builtin/remote.c\n index 210890c8a8e..45c9219e07a 100644\n --- a/builtin/remote.c\n +++ b/builtin/remote.c\n @@ -245,7 +245,9 @@ static int add(int argc, const char **argv)\n  struct branch_info {\n  \tchar *remote_name;\n  \tstruct string_list merge;\n -\tenum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;\n +\tenum {\n +\t\tNO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES\n +\t} rebase;\n  };\n  \n  static struct string_list branch_list = STRING_LIST_INIT_NODUP;\n @@ -306,8 +308,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n  \t\t\t\tinfo->rebase = v;\n  \t\t\telse if (!strcmp(value, \"preserve\"))\n  \t\t\t\tinfo->rebase = NORMAL_REBASE;\n -\t\t\telse if (!strcmp(value, \"recreate\"))\n -\t\t\t\tinfo->rebase = NORMAL_REBASE;\n +\t\t\telse if (!strcmp(value, \"merges\"))\n +\t\t\t\tinfo->rebase = REBASE_MERGES;\n  \t\t\telse if (!strcmp(value, \"interactive\"))\n  \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n  \t\t}\n @@ -965,9 +967,15 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)\n  \n  \tprintf(\"    %-*s \", show_info->width, item->string);\n  \tif (branch_info->rebase) {\n -\t\tprintf_ln(branch_info->rebase == INTERACTIVE_REBASE\n -\t\t\t  ? _(\"rebases interactively onto remote %s\")\n -\t\t\t  : _(\"rebases onto remote %s\"), merge->items[0].string);\n +\t\tconst char *msg;\n +\t\tif (branch_info->rebase == INTERACTIVE_REBASE)\n +\t\t\tmsg = _(\"rebases interactively onto remote %s\");\n +\t\telse if (branch_info->rebase == REBASE_MERGES)\n +\t\t\tmsg = _(\"rebases interactively (with merges) onto \"\n +\t\t\t\t\"remote %s\");\n +\t\telse\n +\t\t\tmsg = _(\"rebases onto remote %s\");\n +\t\tprintf_ln(msg, merge->items[0].string);\n  \t\treturn 0;\n  \t} else if (show_info->any_rebase) {\n  \t\tprintf_ln(_(\" merges with remote %s\"), merge->items[0].string);\n diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n index 7d2e7062919..6af65155c59 100644\n --- a/contrib/completion/git-completion.bash\n +++ b/contrib/completion/git-completion.bash\n @@ -1949,7 +1949,7 @@ _git_rebase ()\n  \t--*)\n  \t\t__gitcomp \"\n  \t\t\t--onto --merge --strategy --interactive\n -\t\t\t--recreate-merges --preserve-merges --stat --no-stat\n +\t\t\t--rebase-merges --preserve-merges --stat --no-stat\n  \t\t\t--committer-date-is-author-date --ignore-date\n  \t\t\t--ignore-whitespace --whitespace=\n  \t\t\t--autosquash --no-autosquash\n @@ -2120,7 +2120,7 @@ _git_config ()\n  \t\treturn\n  \t\t;;\n  \tbranch.*.rebase)\n -\t\t__gitcomp \"false true recreate preserve interactive\"\n +\t\t__gitcomp \"false true merges preserve interactive\"\n  \t\treturn\n  \t\t;;\n  \tremote.pushdefault)\n diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n index 4c21faaccb1..b4ad130e8b1 100644\n --- a/git-rebase--interactive.sh\n +++ b/git-rebase--interactive.sh\n @@ -970,7 +970,7 @@ git_rebase__interactive () {\n  \tinit_revisions_and_shortrevisions\n  \n  \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n -\t\t${recreate_merges:+--recreate-merges} \\\n +\t\t${rebase_merges:+--rebase-merges} \\\n  \t\t${rebase_cousins:+--rebase-cousins} \\\n  \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n  \tdie \"$(gettext \"Could not generate todo list\")\"\n diff --git a/git-rebase.sh b/git-rebase.sh\n index dd39dfb1112..157705d2a72 100755\n --- a/git-rebase.sh\n +++ b/git-rebase.sh\n @@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n  autostash          automatically stash/stash pop before and after\n  fork-point         use 'merge-base --fork-point' to refine upstream\n  onto=!             rebase onto given branch instead of upstream\n -recreate-merges?   try to recreate merges instead of skipping them\n +r,rebase-merges?   try to rebase merges instead of skipping them\n  p,preserve-merges! try to recreate merges instead of ignoring them\n  s,strategy=!       use the given merge strategy\n  no-ff!             cherry-pick all commits, even if unchanged\n @@ -89,7 +89,7 @@ type=\n  state_dir=\n  # One of {'', continue, skip, abort}, as parsed from command line\n  action=\n -recreate_merges=\n +rebase_merges=\n  rebase_cousins=\n  preserve_merges=\n  autosquash=\n @@ -273,12 +273,12 @@ do\n  \t--allow-empty-message)\n  \t\tallow_empty_message=--allow-empty-message\n  \t\t;;\n -\t--recreate-merges)\n -\t\trecreate_merges=t\n +\t--rebase-merges)\n +\t\trebase_merges=t\n  \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n  \t\t;;\n -\t--recreate-merges=*)\n -\t\trecreate_merges=t\n +\t--rebase-merges=*)\n +\t\trebase_merges=t\n  \t\tcase \"${1#*=}\" in\n  \t\trebase-cousins) rebase_cousins=t;;\n  \t\tno-rebase-cousins) rebase_cousins=;;\n diff --git a/sequencer.c b/sequencer.c\n index 0b6aaced9a5..809df1ce484 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -1308,7 +1308,6 @@ enum todo_command {\n  \tTODO_LABEL,\n  \tTODO_RESET,\n  \tTODO_MERGE,\n -\tTODO_MERGE_AND_EDIT,\n  \t/* commands that do nothing but are counted for reporting progress */\n  \tTODO_NOOP,\n  \tTODO_DROP,\n @@ -1330,7 +1329,6 @@ static struct {\n  \t{ 'l', \"label\" },\n  \t{ 't', \"reset\" },\n  \t{ 'm', \"merge\" },\n -\t{ 0, \"merge\" }, /* MERGE_AND_EDIT */\n  \t{ 0,   \"noop\" },\n  \t{ 'd', \"drop\" },\n  \t{ 0,   NULL }\n @@ -1758,9 +1756,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)\n  \treturn 0;\n  }\n  \n +enum todo_item_flags {\n +\tTODO_EDIT_MERGE_MSG = 1\n +};\n +\n  struct todo_item {\n  \tenum todo_command command;\n  \tstruct commit *commit;\n +\tunsigned int flags;\n  \tconst char *arg;\n  \tint arg_len;\n  \tsize_t offset_in_buf;\n @@ -1795,6 +1798,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n  \tchar *end_of_object_name;\n  \tint i, saved, status, padding;\n  \n +\titem->flags = 0;\n +\n  \t/* left-trim */\n  \tbol += strspn(bol, \" \\t\");\n  \n @@ -1849,9 +1854,9 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n  \t\t\tbol += strspn(bol, \" \\t\");\n  \t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n  \t\t\tbol += strspn(bol, \" \\t\");\n -\t\t\titem->command = TODO_MERGE_AND_EDIT;\n +\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n  \t\t} else {\n -\t\t\titem->command = TODO_MERGE_AND_EDIT;\n +\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n  \t\t\titem->commit = NULL;\n  \t\t\titem->arg = bol;\n  \t\t\titem->arg_len = (int)(eol - bol);\n @@ -2511,8 +2516,11 @@ static int safe_append(const char *filename, const char *fmt, ...)\n  \tif (fd < 0)\n  \t\treturn -1;\n  \n -\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT)\n -\t\treturn error_errno(_(\"could not read '%s'\"), filename);\n +\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n +\t\terror_errno(_(\"could not read '%s'\"), filename);\n +\t\trollback_lock_file(&lock);\n +\t\treturn -1;\n +\t}\n  \tstrbuf_complete(&buf, '\\n');\n  \tva_start(ap, fmt);\n  \tstrbuf_vaddf(&buf, fmt, ap);\n @@ -2574,6 +2582,9 @@ static int do_label(const char *name, int len)\n  \treturn ret;\n  }\n  \n +static const char *reflog_message(struct replay_opts *opts,\n +\tconst char *sub_action, const char *fmt, ...);\n +\n  static int do_reset(const char *name, int len, struct replay_opts *opts)\n  {\n  \tstruct strbuf ref_name = STRBUF_INIT;\n @@ -2638,33 +2649,50 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n  \t\tret = error(_(\"could not write index\"));\n  \tfree((void *)desc.buffer);\n  \n -\tif (!ret) {\n -\t\tstruct strbuf msg = STRBUF_INIT;\n -\n -\t\tstrbuf_addf(&msg, \"(rebase -i) reset '%.*s'\", len, name);\n -\t\tret = update_ref(msg.buf, \"HEAD\", &oid, NULL, 0,\n -\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n -\t\tstrbuf_release(&msg);\n -\t}\n +\tif (!ret)\n +\t\tret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n +\t\t\t\t\t\tlen, name), \"HEAD\", &oid,\n +\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n  \n  \tstrbuf_release(&ref_name);\n  \treturn ret;\n  }\n  \n  static int do_merge(struct commit *commit, const char *arg, int arg_len,\n -\t\t    int run_commit_flags, struct replay_opts *opts)\n +\t\t    int flags, struct replay_opts *opts)\n  {\n -\tint merge_arg_len;\n +\tint run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n +\t\tEDIT_MSG | VERIFY_MSG : 0;\n  \tstruct strbuf ref_name = STRBUF_INIT;\n  \tstruct commit *head_commit, *merge_commit, *i;\n -\tstruct commit_list *common, *j, *reversed = NULL;\n +\tstruct commit_list *bases, *j, *reversed = NULL;\n  \tstruct merge_options o;\n -\tint can_fast_forward, ret;\n +\tint merge_arg_len, oneline_offset, can_fast_forward, ret;\n  \tstatic struct lock_file lock;\n +\tconst char *p;\n  \n -\tfor (merge_arg_len = 0; merge_arg_len < arg_len; merge_arg_len++)\n -\t\tif (isspace(arg[merge_arg_len]))\n -\t\t\tbreak;\n +\toneline_offset = arg_len;\n +\tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n +\tp = arg + merge_arg_len;\n +\tp += strspn(p, \" \\t\\n\");\n +\tif (*p == '#' && (!p[1] || isspace(p[1]))) {\n +\t\tp += 1 + strspn(p + 1, \" \\t\\n\");\n +\t\toneline_offset = p - arg;\n +\t} else if (p - arg < arg_len)\n +\t\tBUG(\"octopus merges are not supported yet: '%s'\", p);\n +\n +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n +\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n +\tif (!merge_commit) {\n +\t\t/* fall back to non-rewritten ref or commit */\n +\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n +\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n +\t}\n +\tif (!merge_commit) {\n +\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n +\t\tstrbuf_release(&ref_name);\n +\t\treturn -1;\n +\t}\n  \n  \tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n  \t\treturn -1;\n @@ -2697,7 +2725,6 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\t}\n  \t\tunuse_commit_buffer(commit, message);\n  \t} else {\n -\t\tconst char *p = arg + merge_arg_len;\n  \t\tstruct strbuf buf = STRBUF_INIT;\n  \t\tint len;\n  \n @@ -2705,12 +2732,10 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\twrite_author_script(buf.buf);\n  \t\tstrbuf_reset(&buf);\n  \n -\t\tp += strspn(p, \" \\t\");\n -\t\tif (*p == '#' && isspace(p[1]))\n -\t\t\tp += 1 + strspn(p + 1, \" \\t\");\n -\t\tif (*p)\n -\t\t\tlen = strlen(p);\n -\t\telse {\n +\t\tif (oneline_offset < arg_len) {\n +\t\t\tp = arg + oneline_offset;\n +\t\t\tlen = arg_len - oneline_offset;\n +\t\t} else {\n  \t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n  \t\t\t\t    merge_arg_len, arg);\n  \t\t\tp = buf.buf;\n @@ -2728,25 +2753,24 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t}\n  \n  \t/*\n -\t * If HEAD is not identical to the parent of the original merge commit,\n -\t * we cannot fast-forward.\n +\t * If HEAD is not identical to the first parent of the original merge\n +\t * commit, we cannot fast-forward.\n  \t */\n  \tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n  \t\t!oidcmp(&commit->parents->item->object.oid,\n  \t\t\t&head_commit->object.oid);\n  \n -\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n -\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n -\tif (!merge_commit) {\n -\t\t/* fall back to non-rewritten ref or commit */\n -\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n -\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n -\t}\n -\tif (!merge_commit) {\n -\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n -\t\tstrbuf_release(&ref_name);\n -\t\trollback_lock_file(&lock);\n -\t\treturn -1;\n +\t/*\n +\t * If the merge head is different from the original one, we cannot\n +\t * fast-forward.\n +\t */\n +\tif (can_fast_forward) {\n +\t\tstruct commit_list *second_parent = commit->parents->next;\n +\n +\t\tif (second_parent && !second_parent->next &&\n +\t\t    oidcmp(&merge_commit->object.oid,\n +\t\t\t   &second_parent->item->object.oid))\n +\t\t\tcan_fast_forward = 0;\n  \t}\n  \n  \tif (can_fast_forward && commit->parents->next &&\n @@ -2763,10 +2787,18 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\t      git_path_merge_head(), 0);\n  \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n  \n -\tcommon = get_merge_bases(head_commit, merge_commit);\n -\tfor (j = common; j; j = j->next)\n +\tbases = get_merge_bases(head_commit, merge_commit);\n +\tif (bases && !oidcmp(&merge_commit->object.oid,\n +\t\t\t     &bases->item->object.oid)) {\n +\t\tstrbuf_release(&ref_name);\n +\t\trollback_lock_file(&lock);\n +\t\t/* skip merging an ancestor of HEAD */\n +\t\treturn 0;\n +\t}\n +\n +\tfor (j = bases; j; j = j->next)\n  \t\tcommit_list_insert(j->item, &reversed);\n -\tfree_commit_list(common);\n +\tfree_commit_list(bases);\n  \n  \tread_cache();\n  \tinit_merge_options(&o);\n @@ -2775,6 +2807,8 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \to.buffer_output = 2;\n  \n  \tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n +\tif (!ret)\n +\t\trerere(opts->allow_rerere_auto);\n  \tif (ret <= 0)\n  \t\tfputs(o.obuf.buf, stdout);\n  \tstrbuf_release(&o.obuf);\n @@ -2986,11 +3020,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\tres = do_label(item->arg, item->arg_len);\n  \t\telse if (item->command == TODO_RESET)\n  \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n -\t\telse if (item->command == TODO_MERGE ||\n -\t\t\t item->command == TODO_MERGE_AND_EDIT) {\n +\t\telse if (item->command == TODO_MERGE) {\n  \t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n -\t\t\t\t       item->command == TODO_MERGE_AND_EDIT ?\n -\t\t\t\t       EDIT_MSG | VERIFY_MSG : 0, opts);\n +\t\t\t\t       item->flags, opts);\n  \t\t\tif (item->commit)\n  \t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n  \t\t\t\t\t\t    peek_command(todo_list, 1));\n @@ -3404,7 +3436,7 @@ static const char *label_oid(struct object_id *oid, const char *label,\n  \t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n  \t\tlabel = p = state->buf.buf;\n  \n -\t\tfind_unique_abbrev_r(p, oid->hash, default_abbrev);\n +\t\tfind_unique_abbrev_r(p, oid, default_abbrev);\n  \n  \t\t/*\n  \t\t * We may need to extend the abbreviated hash so that there is\n @@ -3508,11 +3540,13 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\tint is_octopus;\n  \t\tconst char *p1, *p2;\n  \t\tstruct object_id *oid;\n +\t\tint is_empty;\n  \n  \t\ttail = &commit_list_insert(commit, tail)->next;\n  \t\toidset_insert(&interesting, &commit->object.oid);\n  \n -\t\tif ((commit->object.flags & PATCHSAME))\n +\t\tis_empty = is_original_commit_empty(commit);\n +\t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n  \t\t\tcontinue;\n  \n  \t\tstrbuf_reset(&oneline);\n @@ -3522,7 +3556,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n  \t\tif (!to_merge) {\n  \t\t\t/* non-merge commit: easy case */\n  \t\t\tstrbuf_reset(&buf);\n -\t\t\tif (!keep_empty && is_original_commit_empty(commit))\n +\t\t\tif (!keep_empty && is_empty)\n  \t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n  \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n  \t\t\t\t    oid_to_hex(&commit->object.oid),\n @@ -3698,11 +3732,11 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n  \tstruct commit *commit;\n  \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n  \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n -\tint recreate_merges = flags & TODO_LIST_RECREATE_MERGES;\n +\tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n  \n  \tinit_revisions(&revs, NULL);\n  \trevs.verbose_header = 1;\n -\tif (recreate_merges)\n +\tif (rebase_merges)\n  \t\trevs.cherry_mark = 1;\n  \telse {\n  \t\trevs.max_parents = 1;\n @@ -3731,7 +3765,7 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n  \tif (prepare_revision_walk(&revs) < 0)\n  \t\treturn error(_(\"make_script: error preparing revisions\"));\n  \n -\tif (recreate_merges)\n +\tif (rebase_merges)\n  \t\treturn make_script_with_merges(&pp, &revs, out, flags);\n  \n  \twhile ((commit = get_revision(&revs))) {\n @@ -3823,10 +3857,12 @@ int transform_todos(unsigned flags)\n  \t\t\t\t\t  short_commit_name(item->commit) :\n  \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n  \n -\t\t\tif (item->command == TODO_MERGE)\n -\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n -\t\t\telse if (item->command == TODO_MERGE_AND_EDIT)\n -\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n +\t\t\tif (item->command == TODO_MERGE) {\n +\t\t\t\tif (item->flags & TODO_EDIT_MERGE_MSG)\n +\t\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n +\t\t\t\telse\n +\t\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n +\t\t\t}\n  \n  \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n  \t\t}\n diff --git a/sequencer.h b/sequencer.h\n index 739dd0fa92b..d9570d92b11 100644\n --- a/sequencer.h\n +++ b/sequencer.h\n @@ -59,9 +59,9 @@ int sequencer_remove_state(struct replay_opts *opts);\n  #define TODO_LIST_KEEP_EMPTY (1U << 0)\n  #define TODO_LIST_SHORTEN_IDS (1U << 1)\n  #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n -#define TODO_LIST_RECREATE_MERGES (1U << 3)\n +#define TODO_LIST_REBASE_MERGES (1U << 3)\n  /*\n - * When recreating merges, commits that do have the base commit as ancestor\n + * When rebasing merges, commits that do have the base commit as ancestor\n   * (\"cousins\") are *not* rebased onto the new base by default. If those\n   * commits should be rebased onto the new base, this flag needs to be passed.\n   */\n diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\n index 68fe2003ef5..fbae5dab7e2 100755\n --- a/t/t3421-rebase-topology-linear.sh\n +++ b/t/t3421-rebase-topology-linear.sh\n @@ -217,6 +217,7 @@ test_run_rebase success ''\n  test_run_rebase failure -m\n  test_run_rebase failure -i\n  test_run_rebase failure -p\n +test_run_rebase success --rebase-merges\n  \n  #       m\n  #      /\n diff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-merges.sh\n similarity index 87%\n rename from t/t3430-rebase-recreate-merges.sh\n rename to t/t3430-rebase-merges.sh\n index 9a59f12b670..ee006810573 100755\n --- a/t/t3430-rebase-recreate-merges.sh\n +++ b/t/t3430-rebase-merges.sh\n @@ -1,9 +1,9 @@\n  #!/bin/sh\n  #\n -# Copyright (c) 2017 Johannes E. Schindelin\n +# Copyright (c) 2018 Johannes E. Schindelin\n  #\n  \n -test_description='git rebase -i --recreate-merges\n +test_description='git rebase -i --rebase-merges\n  \n  This test runs git rebase \"interactively\", retaining the branch structure by\n  recreating merge commits.\n @@ -19,6 +19,13 @@ Initial setup:\n  . ./test-lib.sh\n  . \"$TEST_DIRECTORY\"/lib-rebase.sh\n  \n +test_cmp_graph () {\n +\tcat >expect &&\n +\tgit log --graph --boundary --format=%s \"$@\" >output &&\n +\tsed \"s/ *$//\" <output >output.trimmed &&\n +\ttest_cmp expect output.trimmed\n +}\n +\n  test_expect_success 'setup' '\n  \twrite_script replace-editor.sh <<-\\EOF &&\n  \tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n @@ -63,17 +70,10 @@ merge -C H second\n  merge onebranch # Merge the topic branch 'onebranch'\n  EOF\n  \n -test_cmp_graph () {\n -\tcat >expect &&\n -\tgit log --graph --boundary --format=%s \"$@\" >output &&\n -\tsed \"s/ *$//\" <output >output.trimmed &&\n -\ttest_cmp expect output.trimmed\n -}\n -\n  test_expect_success 'create completely different structure' '\n  \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n  \ttest_tick &&\n -\tgit rebase -i --recreate-merges A &&\n +\tgit rebase -i -r A &&\n  \ttest_cmp_graph <<-\\EOF\n  \t*   Merge the topic branch '\\''onebranch'\\''\n  \t|\\\n @@ -132,7 +132,7 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n  \n  \tgit checkout already-upstream &&\n  \ttest_tick &&\n -\tgit rebase -i --recreate-merges upstream-with-a2 &&\n +\tgit rebase -i -r upstream-with-a2 &&\n  \ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n  \t*   Merge branch A\n  \t|\\\n @@ -144,18 +144,13 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n  '\n  \n  test_expect_success 'do not rebase cousins unless asked for' '\n -\twrite_script copy-editor.sh <<-\\EOF &&\n -\tcp \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n -\tEOF\n -\n -\ttest_config sequence.editor \\\"\"$PWD\"/copy-editor.sh\\\" &&\n  \tgit checkout -b cousins master &&\n  \tbefore=\"$(git rev-parse --verify HEAD)\" &&\n  \ttest_tick &&\n -\tgit rebase -i --recreate-merges HEAD^ &&\n +\tgit rebase -r HEAD^ &&\n  \ttest_cmp_rev HEAD $before &&\n  \ttest_tick &&\n -\tgit rebase -i --recreate-merges=rebase-cousins HEAD^ &&\n +\tgit rebase --rebase-merges=rebase-cousins HEAD^ &&\n  \ttest_cmp_graph HEAD^.. <<-\\EOF\n  \t*   Merge the topic branch '\\''onebranch'\\''\n  \t|\\\n @@ -196,7 +191,7 @@ test_expect_success 'post-rewrite hook and fixups work for merges' '\n  \techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n  \n  \ttest_tick &&\n -\tgit rebase -i --autosquash --recreate-merges HEAD^^^ &&\n +\tgit rebase -i --autosquash -r HEAD^^^ &&\n  \tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n  \t\t$fixup^^2 HEAD^2 \\\n  \t\t$fixup^^ HEAD^ \\\n @@ -205,4 +200,12 @@ test_expect_success 'post-rewrite hook and fixups work for merges' '\n  \ttest_cmp expect actual\n  '\n  \n +test_expect_success 'refuse to merge ancestors of HEAD' '\n +\techo \"merge HEAD^\" >script-from-scratch &&\n +\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n +\tbefore=\"$(git rev-parse HEAD)\" &&\n +\tgit rebase -i HEAD &&\n +\ttest_cmp_rev HEAD $before\n +'\n +\n  test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n"},{"id":"344312","messageId":"824260f7d71ab2fcc29e4f067092e8d1acbfd33a.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 02/15] sequencer: make rearrange_squash() a bit more obvious","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:35Z","receivedAt":"2018-04-10T12:29:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"There are some commands that have to be skipped from rearranging by virtue\nof not handling any commits.\n\nHowever, the logic was not quite obvious: it skipped commands based on\ntheir position in the enum todo_command.\n\nInstead, let's make it explicit that we skip all commands that do not\nhandle any commit. With one exception: the `drop` command, because it,\nwell, drops the commit and is therefore not eligible to rearranging.\n\nNote: this is a bit academic at the moment because the only time we call\n`rearrange_squash()` is directly after generating the todo list, when we\nhave nothing but `pick` commands anyway.\n\nHowever, the upcoming `merge` command *will* want to be handled by that\nfunction, and it *can* handle commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 096e6d241e0..1ee70d843c1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3393,7 +3393,7 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (!item->commit || item->command == TODO_DROP) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344313","messageId":"ed60b263766205c0de5460885c44a7398b569e78.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 01/15] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:31Z","receivedAt":"2018-04-10T12:29:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"As pointed out in a review of the `--rebase-merges` patch series,\n`rollback_lock_file()` clobbers errno. Therefore, we have to report the\nerror message that uses errno before calling said function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 10 ++++++----\n 1 file changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 667f35ebdff..096e6d241e0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -345,12 +345,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n \tif (msg_fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n \tif (write_in_full(msg_fd, buf, len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n+\t\terror_errno(_(\"could not write eol to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write eol to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&msg_file) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), filename);\n@@ -2119,9 +2121,9 @@ static int save_head(const char *head)\n \twritten = write_in_full(fd, buf.buf, buf.len);\n \tstrbuf_release(&buf);\n \tif (written < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), git_path_head_file());\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t   git_path_head_file());\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&head_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), git_path_head_file());\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344314","messageId":"087d6dc07f3d90911631d18c32e95219403bf423.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 03/15] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:38Z","receivedAt":"2018-04-10T12:29:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Stefan Beller <stefanbeller@gmail.com>\n\nUp to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we are about to introduce commands that take different\narguments, clarify each command by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 50323fc2735..e1b865f43f2 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,13 +155,13 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344315","messageId":"fb797f32b7385317b9dc18524e3863ba627f6c98.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 04/15] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:41Z","receivedAt":"2018-04-10T12:29:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming commits, we will teach the sequencer to rebase merges.\nThis will be done in a very different way from the unfortunate design of\n`git rebase --preserve-merges` (which does not allow for reordering\ncommits, or changing the branch topology).\n\nThe main idea is to introduce new todo list commands, to support\nlabeling the current revision with a given name, resetting the current\nrevision to a previous state, and  merging labeled revisions.\n\nThis idea was developed in Git for Windows' Git garden shears (that are\nused to maintain Git for Windows' \"thicket of branches\" on top of\nupstream Git), and this patch is part of the effort to make it available\nto a wider audience, as well as to make the entire process more robust\n(by implementing it in a safe and portable language rather than a Unix\nshell script).\n\nThis commit implements the commands to label, and to reset to, given\nrevisions. The syntax is:\n\n\tlabel <name>\n\treset <name>\n\nInternally, the `label <name>` command creates the ref\n`refs/rewritten/<name>`. This makes it possible to work with the labeled\nrevisions interactively, or in a scripted fashion (e.g. via the todo\nlist command `exec`).\n\nThese temporary refs are removed upon sequencer_remove_state(), so that\neven a `git rebase --abort` cleans them up.\n\nWe disallow '#' as label because that character will be used as separator\nin the upcoming `merge` command.\n\nLater in this patch series, we will mark the `refs/rewritten/` refs as\nworktree-local, to allow for interactive rebases to be run in parallel in\nworktrees linked to the same repository.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   2 +\n sequencer.c                | 198 +++++++++++++++++++++++++++++++++++--\n 2 files changed, 194 insertions(+), 6 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex e1b865f43f2..e8d3a7d7588 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n+l, label <label> = label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 1ee70d843c1..c63d47f5e09 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"notes-utils.h\"\n #include \"sigchain.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `label` command to record the need for cleanup.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -244,18 +253,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n \n int sequencer_remove_state(struct replay_opts *opts)\n {\n-\tstruct strbuf dir = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i;\n \n+\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n+\t\tchar *p = buf.buf;\n+\t\twhile (*p) {\n+\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\tif (eol)\n+\t\t\t\t*eol = '\\0';\n+\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n+\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\tif (!eol)\n+\t\t\t\tbreak;\n+\t\t\tp = eol + 1;\n+\t\t}\n+\t}\n+\n \tfree(opts->gpg_sign);\n \tfree(opts->strategy);\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n \tfree(opts->xopts);\n \n-\tstrbuf_addstr(&dir, get_dir(opts));\n-\tremove_dir_recursively(&dir, 0);\n-\tstrbuf_release(&dir);\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, get_dir(opts));\n+\tremove_dir_recursively(&buf, 0);\n+\tstrbuf_release(&buf);\n \n \treturn 0;\n }\n@@ -1279,6 +1303,8 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1297,6 +1323,8 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1802,7 +1830,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -2450,6 +2479,159 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn -1;\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n+\t\terror_errno(_(\"could not read '%s'\"), filename);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tif (len == 1 && *name == '#')\n+\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t} else if (get_oid(\"HEAD\", &head_oid)) {\n+\t\terror(_(\"could not read HEAD\"));\n+\t\tret = -1;\n+\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n+\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t   ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static const char *reflog_message(struct replay_opts *opts,\n+\tconst char *sub_action, const char *fmt, ...);\n+\n+static int do_reset(const char *name, int len, struct replay_opts *opts)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options unpack_tree_opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\t/* Determine the length of the label */\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n+\tunpack_tree_opts.head_idx = 1;\n+\tunpack_tree_opts.src_index = &the_index;\n+\tunpack_tree_opts.dst_index = &the_index;\n+\tunpack_tree_opts.fn = oneway_merge;\n+\tunpack_tree_opts.merge = 1;\n+\tunpack_tree_opts.update = 1;\n+\tunpack_tree_opts.reset = 1;\n+\n+\tif (read_cache_unmerged()) {\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error_resolve_conflict(_(action_name(opts)));\n+\t}\n+\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret)\n+\t\tret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n+\t\t\t\t\t\tlen, name), \"HEAD\", &oid,\n+\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2634,7 +2816,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n-\t\t} else if (!is_noop(item->command))\n+\t\t} else if (item->command == TODO_LABEL)\n+\t\t\tres = do_label(item->arg, item->arg_len);\n+\t\telse if (item->command == TODO_RESET)\n+\t\t\tres = do_reset(item->arg, item->arg_len, opts);\n+\t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344316","messageId":"8a5ae61f603df8d8cb19268e1b8470f0a4993445.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 05/15] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:48Z","receivedAt":"2018-04-10T12:30:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label` and `reset` commands to label\ncommits and to reset to labeled commits. This patch adds the `merge`\ncommand, with the following syntax:\n\n\tmerge [-C <commit>] <rev> # <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the merge commit that is about\nto be created.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list:\n\n\tlabel onto\n\n\t# Branch abc\n\treset onto\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\treset onto\n\tpick cafecafe And now for something completely different\n\tmerge -C baaabaaa abc # Merge the branch 'abc' into master\n\nTo edit the merge commit's message (a \"reword\" for merges, if you will),\nuse `-c` (lower-case) instead of `-C`; this convention was borrowed from\n`git commit` that also supports `-c` and `-C` with similar meanings.\n\nTo create *new* merges, i.e. without copying the commit message from an\nexisting commit, simply omit the `-C <commit>` parameter (which will\nopen an editor for the merge message):\n\n\tmerge abc\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in a separate\npatch series, support for merges using strategies other than the\nrecursive merge is left for the future.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   4 +\n sequencer.c                | 170 +++++++++++++++++++++++++++++++++++++\n 2 files changed, 174 insertions(+)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex e8d3a7d7588..ccd5254d1c9 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -164,6 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n l, label <label> = label current HEAD with a name\n t, reset <label> = reset HEAD to a label\n+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n+.       create a merge commit using the original merge commit's\n+.       message (or the oneline, if no original merge commit was\n+.       specified). Use -c <commit> to reword the commit message.\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex c63d47f5e09..1b5f1441102 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1305,6 +1305,7 @@ enum todo_command {\n \tTODO_EXEC,\n \tTODO_LABEL,\n \tTODO_RESET,\n+\tTODO_MERGE,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1325,6 +1326,7 @@ static struct {\n \t{ 'x', \"exec\" },\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n+\t{ 'm', \"merge\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1752,9 +1754,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)\n \treturn 0;\n }\n \n+enum todo_item_flags {\n+\tTODO_EDIT_MERGE_MSG = 1\n+};\n+\n struct todo_item {\n \tenum todo_command command;\n \tstruct commit *commit;\n+\tunsigned int flags;\n \tconst char *arg;\n \tint arg_len;\n \tsize_t offset_in_buf;\n@@ -1789,6 +1796,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \tchar *end_of_object_name;\n \tint i, saved, status, padding;\n \n+\titem->flags = 0;\n+\n \t/* left-trim */\n \tbol += strspn(bol, \" \\t\");\n \n@@ -1838,6 +1847,21 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn 0;\n \t}\n \n+\tif (item->command == TODO_MERGE) {\n+\t\tif (skip_prefix(bol, \"-C\", &bol))\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n+\t\t} else {\n+\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n+\t\t\titem->commit = NULL;\n+\t\t\titem->arg = bol;\n+\t\t\titem->arg_len = (int)(eol - bol);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n \tsaved = *end_of_object_name;\n \t*end_of_object_name = '\\0';\n@@ -2632,6 +2656,141 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n \treturn ret;\n }\n \n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    int flags, struct replay_opts *opts)\n+{\n+\tint run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n+\t\tEDIT_MSG | VERIFY_MSG : 0;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *bases, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint merge_arg_len, oneline_offset, ret;\n+\tstatic struct lock_file lock;\n+\tconst char *p;\n+\n+\toneline_offset = arg_len;\n+\tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n+\tp = arg + merge_arg_len;\n+\tp += strspn(p, \" \\t\\n\");\n+\tif (*p == '#' && (!p[1] || isspace(p[1]))) {\n+\t\tp += 1 + strspn(p + 1, \" \\t\\n\");\n+\t\toneline_offset = p - arg;\n+\t} else if (p - arg < arg_len)\n+\t\tBUG(\"octopus merges are not supported yet: '%s'\", p);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\tif (!merge_commit) {\n+\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"cannot merge without a current revision\"));\n+\t}\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t     oid_to_hex(&commit->object.oid));\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tunuse_commit_buffer(commit, message);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tunuse_commit_buffer(commit, message);\n+\t} else {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tif (oneline_offset < arg_len) {\n+\t\t\tp = arg + oneline_offset;\n+\t\t\tlen = arg_len - oneline_offset;\n+\t\t} else {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tstrbuf_release(&buf);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tbases = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = bases; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(bases);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (!ret)\n+\t\trerere(opts->allow_rerere_auto);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n+\t\t\t     merge_arg_len, arg);\n+\t}\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error(_(\"merge: Unable to write new index file\"));\n+\t}\n+\trollback_lock_file(&lock);\n+\n+\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2820,6 +2979,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_label(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n+\t\telse if (item->command == TODO_MERGE)\n+\t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n+\t\t\t\t       item->flags, opts);\n \t\telse if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n@@ -3302,8 +3464,16 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  short_commit_name(item->commit) :\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n+\t\t\tif (item->command == TODO_MERGE) {\n+\t\t\t\tif (item->flags & TODO_EDIT_MERGE_MSG)\n+\t\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n+\t\t\t}\n+\n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n \t\t}\n+\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344317","messageId":"4e0d0ea09c7b6173fd52b71ebc41753691aa0a66.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 06/15] sequencer: fast-forward `merge` commands, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:52Z","receivedAt":"2018-04-10T12:30:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to rebase a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 33 ++++++++++++++++++++++++++++++++-\n 1 file changed, 32 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1b5f1441102..fb362fafaa7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2665,7 +2665,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint merge_arg_len, oneline_offset, ret;\n+\tint merge_arg_len, oneline_offset, can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \tconst char *p;\n \n@@ -2750,6 +2750,37 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\tstrbuf_release(&buf);\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the first parent of the original merge\n+\t * commit, we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n+\t/*\n+\t * If the merge head is different from the original one, we cannot\n+\t * fast-forward.\n+\t */\n+\tif (can_fast_forward) {\n+\t\tstruct commit_list *second_parent = commit->parents->next;\n+\n+\t\tif (second_parent && !second_parent->next &&\n+\t\t    oidcmp(&merge_commit->object.oid,\n+\t\t\t   &second_parent->item->object.oid))\n+\t\t\tcan_fast_forward = 0;\n+\t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\treturn fast_forward_to(&commit->object.oid,\n+\t\t\t\t       &head_commit->object.oid, 0, opts);\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344318","messageId":"4ccfcf33e1c72ced6d7c7fa43df5e19c9bdafa17.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 07/15] rebase-helper --make-script: introduce a flag to rebase merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:55Z","receivedAt":"2018-04-10T12:30:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --rebase-merges option. For a\ncommit topology like this (where the HEAD points to C):\n\n\t- A - B - C\n\t    \\   /\n\t      D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge -C 3456 D # C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch series.\n\nAs a special, hard-coded label, all merge-rebasing todo lists start with\nthe command `label onto` so that we can later always refer to the revision\nonto which everything is rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 351 ++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 353 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex ad074705bb5..781782e7272 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -24,6 +24,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n+\t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,6 +58,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex fb362fafaa7..422c71db975 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -25,6 +25,8 @@\n #include \"sigchain.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -3368,6 +3370,343 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   (len == 1 && *label == '#') ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, or the label is a '#' (which we use as a separator\n+\t\t * between merge heads and oneline), we append a dash and a\n+\t\t * number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n+\t\t*cmd_label = abbr ? \"l\" : \"label\",\n+\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n+\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\t\tint is_empty;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tis_empty = is_original_commit_empty(commit);\n+\t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_empty)\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s -C %s\",\n+\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", cmd_label);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(to, \"onto\"))\n+\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n+\t\t\t\t\tcmd_reset, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\",\n+\t\t\t\t\tcmd_label, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -3378,11 +3717,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_pick = 1;\n+\tif (rebase_merges)\n+\t\trevs.cherry_mark = 1;\n+\telse {\n+\t\trevs.max_parents = 1;\n+\t\trevs.cherry_pick = 1;\n+\t}\n \trevs.limited = 1;\n \trevs.reverse = 1;\n \trevs.right_only = 1;\n@@ -3406,6 +3750,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (rebase_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tstrbuf_reset(&buf);\n \t\tif (!keep_empty && is_original_commit_empty(commit))\ndiff --git a/sequencer.h b/sequencer.h\nindex e45b178dfc4..6bc4da17243 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -59,6 +59,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_REBASE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344319","messageId":"43bd4bf3b179ad0bc593eb92bd031a1a88ab15b5.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 08/15] rebase: introduce the --rebase-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:29:59Z","receivedAt":"2018-04-10T12:30:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt to answer this was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto flatten the branch topology or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--rebase-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge <label>`. And once this mode will\nhave become stable and universally accepted, we can deprecate the design\nmistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |  10 +-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 +\n t/t3430-rebase-merges.sh               | 146 +++++++++++++++++++++++++\n 5 files changed, 163 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 3277ca14327..936c5619b42 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -378,6 +378,13 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+-r::\n+--rebase-merges::\n+\tRebase merge commits instead of flattening the history by replaying\n+\tmerges. Merge conflict resolutions or manual amendments to merge\n+\tcommits are not rebased automatically, but have to be applied\n+\tmanually.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -780,7 +787,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+--rebase-merges for a more faithful representation.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex a7570739454..d4c0a995c39 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1949,7 +1949,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--rebase-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex ccd5254d1c9..7a3daf3e40c 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -970,6 +970,7 @@ git_rebase__interactive () {\n \tinit_revisions_and_shortrevisions\n \n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${rebase_merges:+--rebase-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n \ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex fb64ee1fe42..a64460fd25a 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+r,rebase-merges!   try to rebase merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -88,6 +89,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+rebase_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -270,6 +272,10 @@ do\n \t--allow-empty-message)\n \t\tallow_empty_message=--allow-empty-message\n \t\t;;\n+\t--rebase-merges)\n+\t\trebase_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nnew file mode 100755\nindex 00000000000..1965005778b\n--- /dev/null\n+++ b/t/t3430-rebase-merges.sh\n@@ -0,0 +1,146 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2018 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --rebase-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+cat >script-from-scratch <<\\EOF\n+label onto\n+\n+# onebranch\n+pick G\n+pick D\n+label onebranch\n+\n+# second\n+reset onto\n+pick B\n+label second\n+\n+reset onto\n+merge -C H second\n+merge onebranch # Merge the topic branch 'onebranch'\n+EOF\n+\n+test_expect_success 'create completely different structure' '\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i -r A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\treset onto\n+\tpick d9df450 B\n+\tlabel E\n+\n+\treset onto\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point # C\n+\tpick 12bd07b D\n+\tmerge -C 2051b56 E # E\n+\tmerge -C 233d48a H # H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i -r upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344320","messageId":"4e12058ce815a4b6c780c0c907f819861459be1d.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 09/15] rebase --rebase-merges: add test for --keep-empty","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:30:03Z","receivedAt":"2018-04-10T12:30:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf there are empty commits on the left hand side of $upstream...HEAD\nthen the empty commits on the right hand side that we want to keep are\nbeing pruned.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n t/t3421-rebase-topology-linear.sh | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 68fe2003ef5..fbae5dab7e2 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -217,6 +217,7 @@ test_run_rebase success ''\n test_run_rebase failure -m\n test_run_rebase failure -i\n test_run_rebase failure -p\n+test_run_rebase success --rebase-merges\n \n #       m\n #      /\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344321","messageId":"b384ed94c4d26a97e748be7cefa6d2c138bcbfb3.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 10/15] sequencer: make refs generated by the `label` command worktree-local","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:30:07Z","receivedAt":"2018-04-10T12:30:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                   |  3 ++-\n t/t3430-rebase-merges.sh | 14 ++++++++++++++\n 2 files changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 8b7a77fe5ee..f61ec58d1df 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 1965005778b..95f99d965c1 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -143,4 +143,18 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344322","messageId":"83ed7db2611c092eec775b05537eaa1826b2e664.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 12/15] rebase --rebase-merges: avoid \"empty merges\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:30:16Z","receivedAt":"2018-04-10T12:30:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The `git merge` command does not allow merging commits that are already\nreachable from HEAD: `git merge HEAD^`, for example, will report that we\nare already up to date and not change a thing.\n\nIn an interactive rebase, such a merge could occur previously, e.g. when\ncompeting (or slightly modified) versions of a patch series were applied\nupstream, and the user had to `git rebase --skip` all of the local\ncommits, and the topic branch becomes \"empty\" as a consequence.\n\nLet's teach the todo command `merge` to behave the same as `git merge`.\n\nSeeing as it requires some low-level trickery to create such merges with\nGit's commands in the first place, we do not even have to bother to\nintroduce an option to force `merge` to create such merge commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c              | 8 ++++++++\n t/t3430-rebase-merges.sh | 8 ++++++++\n 2 files changed, 16 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 878ff449fe8..60bad5708c6 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2788,6 +2788,14 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n \n \tbases = get_merge_bases(head_commit, merge_commit);\n+\tif (bases && !oidcmp(&merge_commit->object.oid,\n+\t\t\t     &bases->item->object.oid)) {\n+\t\tstrbuf_release(&ref_name);\n+\t\trollback_lock_file(&lock);\n+\t\t/* skip merging an ancestor of HEAD */\n+\t\treturn 0;\n+\t}\n+\n \tfor (j = bases; j; j = j->next)\n \t\tcommit_list_insert(j->item, &reversed);\n \tfree_commit_list(bases);\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 392c1136973..63faf7c2246 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -182,4 +182,12 @@ test_expect_success 'post-rewrite hook and fixups work for merges' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'refuse to merge ancestors of HEAD' '\n+\techo \"merge HEAD^\" >script-from-scratch &&\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tbefore=\"$(git rev-parse HEAD)\" &&\n+\tgit rebase -i HEAD &&\n+\ttest_cmp_rev HEAD $before\n+'\n+\n test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344323","messageId":"e975f86e5598923e98ab4ddbdba1d3e344f2dc89.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 13/15] pull: accept --rebase=merges to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:30:22Z","receivedAt":"2018-04-10T12:30:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `merges` mode simply passes the\n`--rebase-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  5 ++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       | 18 ++++++++++++++----\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 37 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 4e0cff87f62..45916ea8104 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When `merges`, pass the `--rebase-merges` option to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2616,6 +2620,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When `merges`, pass the `--rebase-merges` option to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..6f76d815dd3 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,16 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|merges|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to `merges`, rebase using `git rebase --rebase-merges` so that\n+locally created merge commits will not be flattened.\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e32d6cd5b4c..70b44146ce4 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_MERGES,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"merges\", returns REBASE_MERGES. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"merges\"))\n+\t\treturn REBASE_MERGES;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|merges|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_MERGES)\n+\t\targv_array_push(&args, \"--rebase-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 805ffc05cdb..45c9219e07a 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -245,7 +245,9 @@ static int add(int argc, const char **argv)\n struct branch_info {\n \tchar *remote_name;\n \tstruct string_list merge;\n-\tenum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;\n+\tenum {\n+\t\tNO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES\n+\t} rebase;\n };\n \n static struct string_list branch_list = STRING_LIST_INIT_NODUP;\n@@ -306,6 +308,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"merges\"))\n+\t\t\t\tinfo->rebase = REBASE_MERGES;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\n@@ -963,9 +967,15 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)\n \n \tprintf(\"    %-*s \", show_info->width, item->string);\n \tif (branch_info->rebase) {\n-\t\tprintf_ln(branch_info->rebase == INTERACTIVE_REBASE\n-\t\t\t  ? _(\"rebases interactively onto remote %s\")\n-\t\t\t  : _(\"rebases onto remote %s\"), merge->items[0].string);\n+\t\tconst char *msg;\n+\t\tif (branch_info->rebase == INTERACTIVE_REBASE)\n+\t\t\tmsg = _(\"rebases interactively onto remote %s\");\n+\t\telse if (branch_info->rebase == REBASE_MERGES)\n+\t\t\tmsg = _(\"rebases interactively (with merges) onto \"\n+\t\t\t\t\"remote %s\");\n+\t\telse\n+\t\t\tmsg = _(\"rebases onto remote %s\");\n+\t\tprintf_ln(msg, merge->items[0].string);\n \t\treturn 0;\n \t} else if (show_info->any_rebase) {\n \t\tprintf_ln(_(\" merges with remote %s\"), merge->items[0].string);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex d4c0a995c39..6af65155c59 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2120,7 +2120,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true merges preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344324","messageId":"349021284f5b3b9c57a49f88221cef1eb4643020.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 14/15] rebase -i: introduce --rebase-merges=[no-]rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:30:26Z","receivedAt":"2018-04-10T12:30:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This one is a bit tricky to explain, so let's try with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nTo illustrate what this new mode is all about, let's consider what\nhappens upon `git rebase -i --rebase-merges B`, in particular to\nthe commit `D`. So far, the new branch structure would be:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nThis is unintuitive behavior. Even worse, when recreating branch\nstructure, most use cases would appear to want cousins *not* to be\nrebased onto the new base commit. For example, Git for Windows (the\nheaviest user of the Git garden shears, which served as the blueprint\nfor --rebase-merges) frequently merges branches from `next` early, and\nthese branches certainly do *not* want to be rebased. In the example\nabove, the desired outcome would look like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and let's not rebase them by default, introducing the new\n\"rebase-cousins\" mode for use cases where they should be rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt |  7 ++++++-\n builtin/rebase--helper.c     |  9 ++++++++-\n git-rebase--interactive.sh   |  1 +\n git-rebase.sh                | 12 +++++++++++-\n sequencer.c                  |  4 ++++\n sequencer.h                  |  6 ++++++\n t/t3430-rebase-merges.sh     | 18 ++++++++++++++++++\n 7 files changed, 54 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 936c5619b42..8feadf6e663 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -379,11 +379,16 @@ rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n -r::\n---rebase-merges::\n+--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tRebase merge commits instead of flattening the history by replaying\n \tmerges. Merge conflict resolutions or manual amendments to merge\n \tcommits are not rebased automatically, but have to be applied\n \tmanually.\n++\n+By default, or when `no-rebase-cousins` was specified, commits which do not\n+have `<upstream>` as direct ancestor will keep their original branch point.\n+If the `rebase-cousins` mode is turned on, such commits are rebased onto\n+`<upstream>` (or `<onto>`, if specified).\n \n -p::\n --preserve-merges::\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex 781782e7272..f7c2a5fdc81 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -25,6 +25,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n+\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -59,8 +61,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n+\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (rebase_cousins >= 0 && !rebase_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--rebase-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 7a3daf3e40c..b4ad130e8b1 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -971,6 +971,7 @@ git_rebase__interactive () {\n \n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${rebase_merges:+--rebase-merges} \\\n+\t\t${rebase_cousins:+--rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n \ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex a64460fd25a..157705d2a72 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-r,rebase-merges!   try to rebase merges instead of skipping them\n+r,rebase-merges?   try to rebase merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -90,6 +90,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n rebase_merges=\n+rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -276,6 +277,15 @@ do\n \t\trebase_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--rebase-merges=*)\n+\t\trebase_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) rebase_cousins=t;;\n+\t\tno-rebase-cousins) rebase_cousins=;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex 60bad5708c6..809df1ce484 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3499,6 +3499,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3676,6 +3677,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (!rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(to, \"onto\"))\n \t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\ndiff --git a/sequencer.h b/sequencer.h\nindex 6bc4da17243..d9570d92b11 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -60,6 +60,12 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\n+/*\n+ * When rebasing merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are *not* rebased onto the new base by default. If those\n+ * commits should be rebased onto the new base, this flag needs to be passed.\n+ */\n+#define TODO_LIST_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 63faf7c2246..ee006810573 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -143,6 +143,24 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase cousins unless asked for' '\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -r HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase --rebase-merges=rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344325","messageId":"a162afa761e3000eb60169fce7a50938888f80b4.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:30:30Z","receivedAt":"2018-04-10T12:30:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The --rebase-merges mode is probably not half as intuitive to use as\nits inventor hopes, so let's document it some.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt | 125 +++++++++++++++++++++++++++++++++++\n 1 file changed, 125 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 8feadf6e663..be946de2efb 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -389,6 +389,8 @@ By default, or when `no-rebase-cousins` was specified, commits which do not\n have `<upstream>` as direct ancestor will keep their original branch point.\n If the `rebase-cousins` mode is turned on, such commits are rebased onto\n `<upstream>` (or `<onto>`, if specified).\n++\n+See also REBASING MERGES below.\n \n -p::\n --preserve-merges::\n@@ -787,6 +789,129 @@ The ripple effect of a \"hard case\" recovery is especially bad:\n 'everyone' downstream from 'topic' will now have to perform a \"hard\n case\" recovery too!\n \n+REBASING MERGES\n+-----------------\n+\n+The interactive rebase command was originally designed to handle\n+individual patch series. As such, it makes sense to exclude merge\n+commits from the todo list, as the developer may have merged the\n+current `master` while working on the branch, only to eventually\n+rebase all the commits onto `master` (skipping the merge commits).\n+\n+However, there are legitimate reasons why a developer may want to\n+recreate merge commits: to keep the branch structure (or \"commit\n+topology\") when working on multiple, inter-related branches.\n+\n+In the following example, the developer works on a topic branch that\n+refactors the way buttons are defined, and on another topic branch\n+that uses that refactoring to implement a \"Report a bug\" button. The\n+output of `git log --graph --format=%s -5` may look like this:\n+\n+------------\n+*   Merge branch 'report-a-bug'\n+|\\\n+| * Add the feedback button\n+* | Merge branch 'refactor-button'\n+|\\ \\\n+| |/\n+| * Use the Button class for all buttons\n+| * Extract a generic Button class from the DownloadButton one\n+------------\n+\n+The developer might want to rebase those commits to a newer `master`\n+while keeping the branch topology, for example when the first topic\n+branch is expected to be integrated into `master` much earlier than the\n+second one, say, to resolve merge conflicts with changes to the\n+DownloadButton class that made it into `master`.\n+\n+This rebase can be performed using the `--rebase-merges` option.\n+It will generate a todo list looking like this:\n+\n+------------\n+label onto\n+\n+# Branch: refactor-button\n+reset onto\n+pick 123456 Extract a generic Button class from the DownloadButton one\n+pick 654321 Use the Button class for all buttons\n+label refactor-button\n+\n+# Branch: report-a-bug\n+reset refactor-button # Use the Button class for all buttons\n+pick abcdef Add the feedback button\n+label report-a-bug\n+\n+reset onto\n+merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n+merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n+------------\n+\n+In contrast to a regular interactive rebase, there are `label`, `reset` and\n+`merge` commands in addition to `pick` ones.\n+\n+The `label` command puts a label to whatever will be the current\n+revision when that command is executed. Internally, these labels are\n+worktree-local refs that will be deleted when the rebase finishes or\n+when it is aborted. That way, rebase operations in multiple worktrees\n+linked to the same repository do not interfere with one another.\n+\n+The `reset` command is essentially a `git reset --hard` to the specified\n+revision (typically a previously-labeled one).\n+\n+The `merge` command will merge the specified revision into whatever is\n+HEAD at that time. With `-C <original-commit>`, the commit message of\n+the specified merge commit will be used. When the `-C` is changed to\n+a lower-case `-c`, the message will be opened in an editor after a\n+successful merge so that the user can edit the message.\n+\n+At this time, the `merge` command will *always* use the `recursive`\n+merge strategy, with no way to choose a different one. To work around\n+this, an `exec` command can be used to call `git merge` explicitly,\n+using the fact that the labels are worktree-local refs (the ref\n+`refs/rewritten/onto` would correspond to the label `onto`).\n+\n+Note: the first command (`reset onto`) labels the revision onto which\n+the commits are rebased; The name `onto` is just a convention, as a nod\n+to the `--onto` option.\n+\n+It is also possible to introduce completely new merge commits from scratch\n+by adding a command of the form `merge <merge-head>`. This form will\n+generate a tentative commit message and always open an editor to let the\n+user edit it. This can be useful e.g. when a topic branch turns out to\n+address more than a single concern and wants to be split into two or\n+even more topic branches. Consider this todo list:\n+\n+------------\n+pick 192837 Switch from GNU Makefiles to CMake\n+pick 5a6c7e Document the switch to CMake\n+pick 918273 Fix detection of OpenSSL in CMake\n+pick afbecd http: add support for TLS v1.3\n+pick fdbaec Fix detection of cURL in CMake on Windows\n+------------\n+\n+The one commit in this list that is not related to CMake may very well\n+have been motivated by working on fixing all those bugs introduced by\n+switching to CMake, but it addresses a different concern. To split this\n+branch into two topic branches, the todo list could be edited like this:\n+\n+------------\n+label onto\n+\n+pick afbecd http: add support for TLS v1.3\n+label tlsv1.3\n+\n+reset onto\n+pick 192837 Switch from GNU Makefiles to CMake\n+pick 918273 Fix detection of OpenSSL in CMake\n+pick fdbaec Fix detection of cURL in CMake on Windows\n+pick 5a6c7e Document the switch to CMake\n+label cmake\n+\n+reset onto\n+merge tlsv1.3\n+merge cmake\n+------------\n+\n BUGS\n ----\n The todo list presented by `--preserve-merges --interactive` does not\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n"},{"id":"344326","messageId":"3316ce1ee802d0bded8c47e3ad2abd00ea63956c.1523362469.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 11/15] sequencer: handle post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T12:30:11Z","receivedAt":"2018-04-10T12:30:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --rebase-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits.\n\nThis patch implements the post-rewrite handling for the `merge` command\nwe just introduced. The other commands that were added recently (`label`\nand `reset`) do not create new commits, therefore post-rewrite hooks do\nnot need to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c              |  7 +++++--\n t/t3430-rebase-merges.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 30 insertions(+), 2 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 422c71db975..878ff449fe8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3012,10 +3012,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_label(item->arg, item->arg_len);\n \t\telse if (item->command == TODO_RESET)\n \t\t\tres = do_reset(item->arg, item->arg_len, opts);\n-\t\telse if (item->command == TODO_MERGE)\n+\t\telse if (item->command == TODO_MERGE) {\n \t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n \t\t\t\t       item->flags, opts);\n-\t\telse if (!is_noop(item->command))\n+\t\t\tif (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n+\t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\ttodo_list->current++;\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 95f99d965c1..392c1136973 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -157,4 +157,29 @@ test_expect_success 'refs/rewritten/* is worktree-local' '\n \ttest_cmp_rev HEAD \"$(cat wt/b)\"\n '\n \n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash -r HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"344349","messageId":"87tvsj6rn2.fsf@javad.com","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-10T14:52:01Z","receivedAt":"2018-04-10T14:52:09Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Once upon a time, I dreamt of an interactive rebase that would not\n> flatten branch structure, but instead recreate the commit topology\n> faithfully.\n\n[...]\n\n> Think of --rebase-merges as \"--preserve-merges done right\".\n\nBoth option names seem to miss the primary point of the mode of\noperation that you've formulated in the first sentence. I suggest to\nrather call the new option in accordance to your description, say,\n--no-flatten, --keep-topology, or --preserve-shape.\n\nBesides, this way the option name will only specify one thing: _what_ it\nis about, leaving out the _how_ part, that could vary and could then be\nspecified as option value or as another companion option(s), that is\nusually considered to be an indication of a good design.\n\n-- Sergey\n"},{"id":"344371","messageId":"CAN0heSrSvKiRGxSS0jq9Hsxkc7NSuZkt+gLD7DhXRjkfNJd_hw@mail.gmail.com","threadId":"47635","inReplyTo":"a162afa761e3000eb60169fce7a50938888f80b4.1523362469.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-04-10T18:49:04Z","receivedAt":"2018-04-10T18:49:10Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 10 April 2018 at 14:30, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> The --rebase-merges mode is probably not half as intuitive to use as\n> its inventor hopes, so let's document it some.\n\nI quite like this documentation. Well-structured and well-paced.\nAlready after the first reading, I believe I understand how to use this.\n\n> +The `label` command puts a label to whatever will be the current\n> +revision when that command is executed. Internally, these labels are\n> +worktree-local refs that will be deleted when the rebase finishes or\n> +when it is aborted. That way, rebase operations in multiple worktrees\n> +linked to the same repository do not interfere with one another.\n\nIn the above paragraph, you say \"internally\".\n\n> +At this time, the `merge` command will *always* use the `recursive`\n> +merge strategy, with no way to choose a different one. To work around\n> +this, an `exec` command can be used to call `git merge` explicitly,\n> +using the fact that the labels are worktree-local refs (the ref\n> +`refs/rewritten/onto` would correspond to the label `onto`).\n\nThis sort of encourages use of that \"internal\" detail, which made me a\nlittle bit surprised at first. But if we can't come up with a reason why\nwe would want to change the \"refs/rewritten/<label>\"-concept later (I\ncan't) and if we think the gain this paragraph gives is significant (it\nbasically gives access to `git merge` in its entirety), then providing\nthis hint might be the correct thing to do.\n\n> +Note: the first command (`reset onto`) labels the revision onto which\n> +the commits are rebased; The name `onto` is just a convention, as a nod\n> +to the `--onto` option.\n\ns/reset onto/label onto/\n\nMartin\n"},{"id":"344412","messageId":"nycvar.QRO.7.76.6.1804102348190.56@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAN0heSrSvKiRGxSS0jq9Hsxkc7NSuZkt+gLD7DhXRjkfNJd_hw@mail.gmail.com","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T21:56:08Z","receivedAt":"2018-04-10T21:56:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Martin,\n\nOn Tue, 10 Apr 2018, Martin Ågren wrote:\n\n> On 10 April 2018 at 14:30, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > The --rebase-merges mode is probably not half as intuitive to use as\n> > its inventor hopes, so let's document it some.\n> \n> I quite like this documentation. Well-structured and well-paced.\n> Already after the first reading, I believe I understand how to use this.\n\nThanks!\n\n> > +The `label` command puts a label to whatever will be the current\n> > +revision when that command is executed. Internally, these labels are\n> > +worktree-local refs that will be deleted when the rebase finishes or\n> > +when it is aborted. That way, rebase operations in multiple worktrees\n> > +linked to the same repository do not interfere with one another.\n> \n> In the above paragraph, you say \"internally\".\n\nI guess that I should reword this to say \"These labels are created as\nworktree-local refs (`refs/rewritten/<label>`) that will be ...\"\n\nI'll do that, thanks for the sanity check!\n\n> > +At this time, the `merge` command will *always* use the `recursive`\n> > +merge strategy, with no way to choose a different one. To work around\n> > +this, an `exec` command can be used to call `git merge` explicitly,\n> > +using the fact that the labels are worktree-local refs (the ref\n> > +`refs/rewritten/onto` would correspond to the label `onto`).\n> \n> This sort of encourages use of that \"internal\" detail, which made me a\n> little bit surprised at first. But if we can't come up with a reason why\n> we would want to change the \"refs/rewritten/<label>\"-concept later (I\n> can't) and if we think the gain this paragraph gives is significant (it\n> basically gives access to `git merge` in its entirety), then providing\n> this hint might be the correct thing to do.\n\nYou are right. I made it sound as if this was an implementation detail\nthat you should not rely on, when I wanted to say that this is how it is\nimplemented and you are free to use it in your scripts.\n\n> > +Note: the first command (`reset onto`) labels the revision onto which\n> > +the commits are rebased; The name `onto` is just a convention, as a nod\n> > +to the `--onto` option.\n> \n> s/reset onto/label onto/\n\nD'oh!\n\nThanks, fixed. Current state is in `sequencer-shears` in\nhttps://github.com/dscho/git (I will update the `recreate-merges` branch,\nwhich needs to keep its name so that my scripts will connect the mail\nthreads for the patch submissions, once I called `git rebase -kir @{u}`).\n\nCiao,\nDscho"},{"id":"344415","messageId":"nycvar.QRO.7.76.6.1804110009080.56@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87tvsj6rn2.fsf@javad.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-10T22:11:39Z","receivedAt":"2018-04-10T22:12:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Tue, 10 Apr 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Once upon a time, I dreamt of an interactive rebase that would not\n> > flatten branch structure, but instead recreate the commit topology\n> > faithfully.\n> \n> [...]\n> \n> > Think of --rebase-merges as \"--preserve-merges done right\".\n> \n> Both option names seem to miss the primary point of the mode of\n> operation that you've formulated in the first sentence. I suggest to\n> rather call the new option in accordance to your description, say,\n> --no-flatten, --keep-topology, or --preserve-shape.\n\nA very quick A/B test shows that neither --no-flatten nor --keep-topology\nand certainly not --preserve-shape conveys to Git users what those options\nare supposed to do.\n\nBut --rebase-merges did convey the purpose of my patch series. So there.\n\nCiao,\nJohannes\n"},{"id":"344439","messageId":"871sfm737e.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804110009080.56@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-11T04:54:29Z","receivedAt":"2018-04-11T04:54:38Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Sergey,\n>\n> On Tue, 10 Apr 2018, Sergey Organov wrote:\n>\n>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>> \n>> > Once upon a time, I dreamt of an interactive rebase that would not\n>> > flatten branch structure, but instead recreate the commit topology\n>> > faithfully.\n>> \n>> [...]\n>> \n>> > Think of --rebase-merges as \"--preserve-merges done right\".\n>> \n>> Both option names seem to miss the primary point of the mode of\n>> operation that you've formulated in the first sentence. I suggest to\n>> rather call the new option in accordance to your description, say,\n>> --no-flatten, --keep-topology, or --preserve-shape.\n>\n> A very quick A/B test shows that neither --no-flatten nor --keep-topology\n> and certainly not --preserve-shape conveys to Git users what those options\n> are supposed to do.\n\nIn fact, my preference would be --[no-]flatten, exactly because the\ndefault mode of rebase operation flattens the history, and thus what I'm\ntalking about is:\n\ngit rebase --no-flatten\n\nvs \n\ngit rebase --rebase-merges\n\nI honestly fail to see how the latter conveys the purpose of the option\nbetter than the former, sorry. To tell the truth, the latter also looks\nplain ugly to me.\n\n> But --rebase-merges did convey the purpose of my patch series. So\n> there.\n\n- Except that your primary description of the series (that I find pretty\nsolid) doesn't mention _merges_ at all and still conveys the purpose?\n\n- Except that this patch series _don't_ actually _rebase_ merges?\nYeah, I remember a follow-up is to be expected, but anyway.\n\nI'm still unconvinced.\n\n-- Sergey\n"},{"id":"344441","messageId":"878t9u5ksw.fsf@javad.com","threadId":"47635","inReplyTo":"fb797f32b7385317b9dc18524e3863ba627f6c98.1523362469.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 04/15] sequencer: introduce new commands to reset the revision","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-11T06:17:19Z","receivedAt":"2018-04-11T06:17:27Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n[...]\n\n> We disallow '#' as label because that character will be used as separator\n> in the upcoming `merge` command.\n\nPlease consider to use # not only in `merge` and `reset`, but in the rest\nof the commands as well, to unify this new syntax. I.e., right now it\nseems to be:\n\npick  abcd A commit message\nmerge beaf # B commit message\n\nI suggest to turn it to:\n\npick  abcd # A commit message\nmerge beaf # B commit message\n\nSo that the # is finally universally the start of comment.\n\nWhile we are at this, I couldn't find any even semi-formal syntax\ndescription of the entire todo list. Is there one already? Are you going\nto provide one?\n\n-- Sergey\n"},{"id":"344453","messageId":"nycvar.QRO.7.76.6.1804111324350.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"871sfm737e.fsf@javad.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-11T11:28:51Z","receivedAt":"2018-04-11T11:29:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Wed, 11 Apr 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > On Tue, 10 Apr 2018, Sergey Organov wrote:\n> >\n> >> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> >> \n> >> > Once upon a time, I dreamt of an interactive rebase that would not\n> >> > flatten branch structure, but instead recreate the commit topology\n> >> > faithfully.\n> >> \n> >> [...]\n> >> \n> >> > Think of --rebase-merges as \"--preserve-merges done right\".\n> >> \n> >> Both option names seem to miss the primary point of the mode of\n> >> operation that you've formulated in the first sentence. I suggest to\n> >> rather call the new option in accordance to your description, say,\n> >> --no-flatten, --keep-topology, or --preserve-shape.\n> >\n> > A very quick A/B test shows that neither --no-flatten nor --keep-topology\n> > and certainly not --preserve-shape conveys to Git users what those options\n> > are supposed to do.\n> \n> In fact, my preference would be --[no-]flatten, exactly because the\n> default mode of rebase operation flattens the history, and thus what I'm\n> talking about is:\n> \n> git rebase --no-flatten\n\nAnd this is the option out of the four that fared *worst* in the A/B\ntesting. Not even experts in Git internals were able to figure out what\nthe heck you are talking about.\n\nNow, you can beat that dead horse until it is pulp. Your choice. I'd\nrather go on to more interesting things, because as far as I am concerned,\nthe naming issue has been settled, with you being the only person in\ndisfavor of --rebase-merges.\n\nWhat you *could* do is finally take your RFC to the test. Run it with the\nconcrete example I showed you in\nhttps://public-inbox.org/git/nycvar.QRO.7.76.6.1803261405170.77@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz/\n\nIt is high time that you demonstrated on this concrete case study how your\nproposed solution performs. And then tally that up with Phillip's\nstrategy.\n\nCiao,\nJohannes\n"},{"id":"344454","messageId":"nycvar.QRO.7.76.6.1804111329270.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"878t9u5ksw.fsf@javad.com","subject":"Re: [PATCH v6 04/15] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-11T11:36:48Z","receivedAt":"2018-04-11T11:37:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Wed, 11 Apr 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> [...]\n> \n> > We disallow '#' as label because that character will be used as\n> > separator in the upcoming `merge` command.\n> \n> Please consider to use # not only in `merge` and `reset`, but in the\n> rest of the commands as well, to unify this new syntax. I.e., right now\n> it seems to be:\n> \n> pick  abcd A commit message\n> merge beaf # B commit message\n> \n> I suggest to turn it to:\n> \n> pick  abcd # A commit message\n> merge beaf # B commit message\n\nFirst of all, that alignment of pick's and merge's first arguments? That\ndoes not exist. If you want aligned arguments, you have to use the\nrebase.abbreviateCommands feature.\n\nSecond: this change would break backwards-compatibility. For almost eleven\nyears, we generated `pick abcdef0123 A commit message`. Even if there are\nno scripts that rely on this form, power users have gotten used to it, and\nI can tell you from experience how unsettling even minor visual changes\nare in everyday operations.\n\nIn short: no, we cannot do that. Just like your proposal to conflate the\n`merge` and `pick` commands for some perception of consistency: The user\nexperience is more important than individual persons' sense of elegance\n(that might not even be shared with the majority).\n\nCiao,\nJohannes\n"},{"id":"344458","messageId":"87h8oh51jm.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804111324350.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-11T13:13:17Z","receivedAt":"2018-04-11T13:13:26Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Sergey,\n>\n> On Wed, 11 Apr 2018, Sergey Organov wrote:\n>\n>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>>\n>> > On Tue, 10 Apr 2018, Sergey Organov wrote:\n>> >\n>> >> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>> >>\n>> >> > Once upon a time, I dreamt of an interactive rebase that would not\n>> >> > flatten branch structure, but instead recreate the commit topology\n>> >> > faithfully.\n>> >>\n>> >> [...]\n>> >>\n>> >> > Think of --rebase-merges as \"--preserve-merges done right\".\n>> >>\n>> >> Both option names seem to miss the primary point of the mode of\n>> >> operation that you've formulated in the first sentence. I suggest to\n>> >> rather call the new option in accordance to your description, say,\n>> >> --no-flatten, --keep-topology, or --preserve-shape.\n>> >\n>> > A very quick A/B test shows that neither --no-flatten nor --keep-topology\n>> > and certainly not --preserve-shape conveys to Git users what those options\n>> > are supposed to do.\n>>\n>> In fact, my preference would be --[no-]flatten, exactly because the\n>> default mode of rebase operation flattens the history, and thus what I'm\n>> talking about is:\n>>\n>> git rebase --no-flatten\n>\n> And this is the option out of the four that fared *worst* in the A/B\n> testing. Not even experts in Git internals were able to figure out what\n> the heck you are talking about.\n\nIt was you who introduced the \"flatten\" term, not me. I took it from\nyour descriptions.\n\nSo they are able to make sense of your own:\n\n>>> Once upon a time, I dreamt of an interactive rebase that would not\n>>> flatten branch structure, but instead recreate the commit topology\n>>> faithfully.\n\nYet they can't get:\n\n--no-flatten::\n\tInstead of flattening branch structure, recreate the commit\n\ttopology faithfully\n\nAre you kidding?\n\nWell, suppose for a moment that nobody could even guess what \"flatten\"\nmeans indeed. Then are you willing to remove the \"flatten\" from both the\ndescription of our patch series and from the proposed patch to the Git\nmanual:\n\n-r::\n--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n\tRebase merge commits instead of _flattening_ the history by replaying\n\tmerges.\n\n???\n\n>\n> Now, you can beat that dead horse until it is pulp. Your choice. I'd\n> rather go on to more interesting things, because as far as I am concerned,\n> the naming issue has been settled, with you being the only person in\n> disfavor of --rebase-merges.\n\nIt was rather --recreate-merges just a few weeks ago, and I've seen\nnobody actually commented either in favor or against the\n--rebase-merges.\n\ngit rebase --rebase-merges\n\n_is_ plain simple ugly.\n\n>\n> What you *could* do is finally take your RFC to the test. Run it with the\n> concrete example I showed you in\n> https://public-inbox.org/git/nycvar.QRO.7.76.6.1803261405170.77@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz/\n>\n> It is high time that you demonstrated on this concrete case study how your\n> proposed solution performs. And then tally that up with Phillip's\n> strategy.\n\nWhat you could do is to stop shifting the subject of discussion.\n\nThe RFC v2 and Phillip's strategy are essentially the same, as has been\nalready shown multiple times, both theoretically and by testing. Ask\nBugga for details.\n\nOne way or another, this doesn't make\n\ngit rebase --rebase-merges\n\neven a bit less ugly.\n\n-- Sergey\n"},{"id":"344461","messageId":"26c21a44-92b3-80f4-5211-034c64f7568c@talktalk.net","threadId":"47635","inReplyTo":"a162afa761e3000eb60169fce7a50938888f80b4.1523362469.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-11T15:35:52Z","receivedAt":"2018-04-11T15:36:02Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 10/04/18 13:30, Johannes Schindelin wrote:\n\nFirstly let me say that I think expanding the documentation and having \nan example is an excellent idea.\n\n> +\n> +------------\n> +label onto\n> +\n> +# Branch: refactor-button\n> +reset onto\n> +pick 123456 Extract a generic Button class from the DownloadButton one\n> +pick 654321 Use the Button class for all buttons\n> +label refactor-button\n> +\n> +# Branch: report-a-bug\n> +reset refactor-button # Use the Button class for all buttons\n> +pick abcdef Add the feedback button\n> +label report-a-bug\n> +\n> +reset onto\n> +merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n> +merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n> +------------\n> +\n> +In contrast to a regular interactive rebase, there are `label`, `reset` and\n> +`merge` commands in addition to `pick` ones.\n> +\n> +The `label` command puts a label to whatever will be the current\n\ns/puts a label to/associates a label with/ would be clearer I think. \nMaybe s/whatever will be the current revision/the current HEAD/ an well?\n\n> +revision when that command is executed. Internally, these labels are\n> +worktree-local refs that will be deleted when the rebase finishes or\n> +when it is aborted.\n\nI agree they should be deleted when the rebase is aborted but I cannot \nsee any changes to git-rebase.sh to make that happen. I think they \nshould also be deleted by 'rebase --quit'.\n\n> That way, rebase operations in multiple worktrees\n> +linked to the same repository do not interfere with one another.\n> +\n> +The `reset` command is essentially a `git reset --hard` to the specified\n> +revision (typically a previously-labeled one).\n\ns/labeled/labelled/\n\nI think it would be worthwhile to point out that unlike the other \ncommands this will not preserve untracked files. Maybe something like\n\"Note that unlike the `pick` or `merge` commands or initial checkout \nwhen the rebase starts the `reset` command will overwrite any untracked \nfiles.\"\n\n\nBest Wishes\n\nPhillip\n"},{"id":"344464","messageId":"87zi293ew4.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804111329270.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 04/15] sequencer: introduce new commands to reset the revision","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-11T16:07:55Z","receivedAt":"2018-04-11T16:08:03Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Sergey,\n>\n> On Wed, 11 Apr 2018, Sergey Organov wrote:\n>\n>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>> \n>> [...]\n>> \n>> > We disallow '#' as label because that character will be used as\n>> > separator in the upcoming `merge` command.\n>> \n>> Please consider to use # not only in `merge` and `reset`, but in the\n>> rest of the commands as well, to unify this new syntax. I.e., right now\n>> it seems to be:\n>> \n>> pick  abcd A commit message\n>> merge beaf # B commit message\n>> \n>> I suggest to turn it to:\n>> \n>> pick  abcd # A commit message\n>> merge beaf # B commit message\n>\n> First of all, that alignment of pick's and merge's first arguments?\n\nAs if it has anything to do with the topic of the issue!\n\nJust a nice look. Let it be:\n\npick abcd # A commit message\nmerge beaf # B commit message\n\nif it's that essential indeed.\n\n> That does not exist. If you want aligned arguments, you have to use the\n> rebase.abbreviateCommands feature.\n\nIt's changing the subject.\n\n> Second: this change would break backwards-compatibility. For almost eleven\n> years, we generated `pick abcdef0123 A commit message`.\n\nI thought we already agreed that you have no backward compatibility\nissues with this new feature, as it's a new feature, complete re-design,\nas you put it yourself:\n\n\"This design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\"\n\nAt least could you please answer plain yes/no to this simple question: is\nthis feature a complete re-design or not? yes/no, please!\n\n> Even if there are no scripts that rely on this form, power users have\n> gotten used to it, and I can tell you from experience how unsettling\n> even minor visual changes are in everyday operations.\n> In short: no, we cannot do that.\n\nYou can do that, provided it's complete re-design indeed. You don't wish\nto, but you can. Nothing will break and things will be at least a little\nbit cleaner.\n\nEach directive having its own dedicated syntax... gosh! No luck getting\nsyntax description, I'm afraid.\n\n> Just like your proposal to conflate the `merge` and `pick` commands\n\nThere was never such proposal. The proposal was not to introduce new\n`merge` command when there is already `pick` that could simply be\nextended to pick any commit, whatever number of parents it happens to\nhave.\n\nBut provided you decline to even put a # before the commit message...\nthat proposal is simply a pie in the sky.\n\n> for some perception of consistency: The user experience is more\n> important than individual persons' sense of elegance (that might not\n> even be shared with the majority).\n\nIt's about consistency indeed. Consistent handling of commits is\nessential. Consistency is one of the things that bring positive user\nexperience. You disagree?\n\nBesides, it was bad user experience that forced you to re-design, isn't\nit? I'm afraid you miss good opportunity to fix some of your former\nmistakes and you make some new. As the discussion goes, it seems you'd\nnever admit it, the design is set in stone, and my attempts are in fact\npointless.\n\nOverall, I hereby withdraw all my pending suggestions to improve this\npatch series.\n\n-- Sergey\n"},{"id":"344472","messageId":"CAPig+cRfQgz2tCab2d9g-XznHORW0FJvDKS4fe85JkLPxm2HMQ@mail.gmail.com","threadId":"47635","inReplyTo":"26c21a44-92b3-80f4-5211-034c64f7568c@talktalk.net","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-04-11T19:10:17Z","receivedAt":"2018-04-11T19:10:44Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Apr 11, 2018 at 11:35 AM, Phillip Wood\n<phillip.wood@talktalk.net> wrote:\n> On 10/04/18 13:30, Johannes Schindelin wrote:\n>> +The `reset` command is essentially a `git reset --hard` to the specified\n>> +revision (typically a previously-labeled one).\n>\n> s/labeled/labelled/\n\nAmerican vs. British English spelling.\n\nCodingGuidelines and SubmittingPatches talk about this. Junio\nsummarizes the issue well in [1]. The TL;DR is to lean toward the\nAmerican English spelling.\n\n[1]: https://public-inbox.org/git/xmqq4m9gpebm.fsf@gitster.mtv.corp.google.com/\n"},{"id":"344487","messageId":"nycvar.QRO.7.76.6.1804112233470.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87h8oh51jm.fsf@javad.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-11T20:40:51Z","receivedAt":"2018-04-11T20:41:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Wed, 11 Apr 2018, Sergey Organov wrote:\n\n> The RFC v2 and Phillip's strategy are essentially the same, as has been\n> already shown multiple times, both theoretically and by testing.\n\nNo, they are not.\n\nI am really tired of repeating myself here, as I have demonstrated it at\nlength, at least half a dozen times, that they are *not* in practice the\nsame.\n\nIf you had played through the example as I suggested, you would actually\nsee where the differences are, and that your proposal is simply\nimpractical.\n\nAnd you would see that Phillip's strategy is better, but I get the\nimpression that you want to avoid that insight at all cost.\n\nCiao,\nJohannes\n\n\n"},{"id":"344509","messageId":"CA+P7+xq8KJKJ0d1unW3btUhKVjOZM3EdTSKdDs96pn5J3WwSKA@mail.gmail.com","threadId":"47635","inReplyTo":"87h8oh51jm.fsf@javad.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-04-11T23:52:33Z","receivedAt":"2018-04-11T23:52:59Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Wed, Apr 11, 2018 at 6:13 AM, Sergey Organov <sorganov@gmail.com> wrote:\n> It was rather --recreate-merges just a few weeks ago, and I've seen\n> nobody actually commented either in favor or against the\n> --rebase-merges.\n>\n> git rebase --rebase-merges\n>\n\nI'm going to jump in here and say that *I* prefer --rebase-merges, as\nit clearly mentions merge commits (which is the thing that changes).\n\nI hadn't mentioned this before, because it was a suggestion that\nsomeone else made and it seemed that Johannes liked it, so I didn't\nthink further discussion was worthwhile.\n\nThanks,\nJake\n"},{"id":"344529","messageId":"87sh810ym4.fsf@javad.com","threadId":"47635","inReplyTo":"CA+P7+xq8KJKJ0d1unW3btUhKVjOZM3EdTSKdDs96pn5J3WwSKA@mail.gmail.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-12T05:42:27Z","receivedAt":"2018-04-12T05:42:35Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Jacob,\n\nJacob Keller <jacob.keller@gmail.com> writes:\n> On Wed, Apr 11, 2018 at 6:13 AM, Sergey Organov <sorganov@gmail.com> wrote:\n>> It was rather --recreate-merges just a few weeks ago, and I've seen\n>> nobody actually commented either in favor or against the\n>> --rebase-merges.\n>>\n>> git rebase --rebase-merges\n>>\n>\n> I'm going to jump in here and say that *I* prefer --rebase-merges, as\n> it clearly mentions merge commits (which is the thing that changes).\n\nOK, thanks, it's fair and the first argument in favor of --rebase-merges\nI see.\n\nI don't get why this detail matters so much it should be reflected in\nthe option name, and if it is what matters most, why the patch series\nare not headed:\n\n<twisted quote>\nrebase -i: offer to rebase merge commits.\n\nOnce upon a time, I dreamt of an interactive rebase that would not\ndrop merge commits, but instead rebase them.\n</twisted quote>\n\n> I hadn't mentioned this before, because it was a suggestion that\n> someone else made and it seemed that Johannes liked it, so I didn't\n> think further discussion was worthwhile.\n\nSo you guys seem to be winning 2:1, or even 3:1, counting the guy who\nmade the suggestion. Except it was Buga's suggestion [1], and I believe\nI was able to convince him that something like --no-flatten would be\nbetter [2]:\n\n<quote>\n> I hope he'd be pleased to be able to say --no-flatten=remerge and get\n> back his current mode of operation, that he obviously has a good use\n> for.\n\nMakes sense, I like it, thanks for elaborating. [ Especially that you \nused \"(no) flatten\" phrasing, where original `--preserve-merges` \ndocumentation says it`s used \"not to flatten the history\", nice touch\n;) ]\n</quote>\n\nSo I assume it's 2:2 by now, with the author of original suggestion on\nmy side.\n\nI still find\n\ngit rebase --rebase-merges\n\nboth being ugly and missing the point.\n\nWhen I look at it with a fresh eye, the questions that immediately rise\nare: \"What the hell else could 'git _rebase_' do with (merge) commits\nbut _rebase_ them? Why do I even need to specify this option? Should I\nalso specify --rebase-non-merges to rebase the rest of commits?\"\n\nWell, if it was called something like --[no-]keep-merges, it'd make more\nsense as it'd be obvious that alternative is to drop merges (notice how\nthe old --preserve-merges does match this criteria). However, it'd still\nmiss to reflect the generic intent of the patch series, -- to preserve\nhistory shape as much as possible, -- now citing author's head message\nnon-twisted: \n\n<quote>\nrebase -i: offer to recreate commit topology\n\nOnce upon a time, I dreamt of an interactive rebase that would not\nflatten branch structure, but instead recreate the commit topology\nfaithfully.\n</quote>\n\n-- Sergey\n\n[1] https://public-inbox.org/git/bc9f82fb-fd18-ee45-36a4-921a1381b32e@gmail.com/\n[2] https://public-inbox.org/git/a3d40dca-f508-5853-89bc-1f9ab393416b@gmail.com/\n"},{"id":"344532","messageId":"87in8w2585.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804112233470.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-12T08:34:18Z","receivedAt":"2018-04-12T08:34:29Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> Hi Sergey,\n>\n> On Wed, 11 Apr 2018, Sergey Organov wrote:\n>\n>> The RFC v2 and Phillip's strategy are essentially the same, as has been\n>> already shown multiple times, both theoretically and by testing.\n>\n> No, they are not.\n\nIt's off-topic here. If you _really_ want to discuss it further, you are\nstill welcome to come back to where you ran away from and continue:\n\nhttps://public-inbox.org/git/87po3oddl1.fsf@javad.com/\n\nAbrupt change of the topic of discussion indicates your intention to\ntake attention off the apparent ugliness of \n\ngit rebase --rebase-merges\n\nI also get it as an indication that there are no more arguments in favor\nof --rebase-merges on your side, at least for now.\n\n-- Sergey\n"},{"id":"344534","messageId":"nycvar.QRO.7.76.6.1804121059110.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAPig+cRfQgz2tCab2d9g-XznHORW0FJvDKS4fe85JkLPxm2HMQ@mail.gmail.com","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-12T09:00:23Z","receivedAt":"2018-04-12T09:01:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric & Phillip,\n\nOn Wed, 11 Apr 2018, Eric Sunshine wrote:\n\n> On Wed, Apr 11, 2018 at 11:35 AM, Phillip Wood\n> <phillip.wood@talktalk.net> wrote:\n> > On 10/04/18 13:30, Johannes Schindelin wrote:\n> >> +The `reset` command is essentially a `git reset --hard` to the specified\n> >> +revision (typically a previously-labeled one).\n> >\n> > s/labeled/labelled/\n> \n> American vs. British English spelling.\n> \n> CodingGuidelines and SubmittingPatches talk about this. Junio\n> summarizes the issue well in [1]. The TL;DR is to lean toward the\n> American English spelling.\n> \n> [1]: https://public-inbox.org/git/xmqq4m9gpebm.fsf@gitster.mtv.corp.google.com/\n\nThanks, I meant to look that up because I was not sure, and now I do not\nhave to ;-)\n\nNo worries, Phillip, I will keep spelling your name with two `l`s. :0)\n\nCiao,\nDscho\n"},{"id":"344538","messageId":"nycvar.QRO.7.76.6.1804121102260.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"26c21a44-92b3-80f4-5211-034c64f7568c@talktalk.net","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-12T09:30:29Z","receivedAt":"2018-04-12T09:31:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Wed, 11 Apr 2018, Phillip Wood wrote:\n\n> On 10/04/18 13:30, Johannes Schindelin wrote:\n> \n> Firstly let me say that I think expanding the documentation and having an\n> example is an excellent idea.\n\nThanks! At first, I meant to leave this for others to contribute, but I\nthink it makes sense for me to describe it, as I do have a little bit of\nexperience with rebasing merges.\n\n> > +\n> > +------------\n> > +label onto\n> > +\n> > +# Branch: refactor-button\n> > +reset onto\n> > +pick 123456 Extract a generic Button class from the DownloadButton one\n> > +pick 654321 Use the Button class for all buttons\n> > +label refactor-button\n> > +\n> > +# Branch: report-a-bug\n> > +reset refactor-button # Use the Button class for all buttons\n> > +pick abcdef Add the feedback button\n> > +label report-a-bug\n> > +\n> > +reset onto\n> > +merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n> > +merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n> > +------------\n> > +\n> > +In contrast to a regular interactive rebase, there are `label`, `reset` and\n> > +`merge` commands in addition to `pick` ones.\n> > +\n> > +The `label` command puts a label to whatever will be the current\n> \n> s/puts a label to/associates a label with/ would be clearer I think. Maybe\n> s/whatever will be the current revision/the current HEAD/ an well?\n\nThanks, I incorporated both changes here.\n\n> > +revision when that command is executed. Internally, these labels are\n> > +worktree-local refs that will be deleted when the rebase finishes or\n> > +when it is aborted.\n> \n> I agree they should be deleted when the rebase is aborted but I cannot see any\n> changes to git-rebase.sh to make that happen. I think they should also be\n> deleted by 'rebase --quit'.\n\nOh right! For some reason I thought I already hooked up rebase--helper\n--abort when rebase was called with --abort or quit, but I had not managed\nyet. I think I will leave this for later, or for GSoC, or something.\n\nIn the meantime, I'll just drop the \"or when it is aborted.\".\n\n> > That way, rebase operations in multiple worktrees\n> > +linked to the same repository do not interfere with one another.\n> > +\n> > +The `reset` command is essentially a `git reset --hard` to the specified\n> > +revision (typically a previously-labeled one).\n> \n> s/labeled/labelled/\n\nAs Eric pointed out, I am using 'murricane spelling here (or is it\nspeling? Ya never know these days).\n\n> I think it would be worthwhile to point out that unlike the other commands\n> this will not preserve untracked files. Maybe something like\n> \"Note that unlike the `pick` or `merge` commands or initial checkout when the\n> rebase starts the `reset` command will overwrite any untracked files.\"\n\nYou know what? You just pointed out a bug in my thinking. Previously, I\nthought that this is impossible, that you cannot overwrite untracked files\nbecause we labeled this revision previously, so the only new files to\nwrite by `reset` were tracked files previous. But that forgets `exec` and\n`reset` with unlabeled revisions (e.g. for cousins).\n\nSo I changed the `reset` command to refuse overwriting untracked files...\n\nThank you for improving this patch series!\nDscho\n"},{"id":"344540","messageId":"877epczmof.fsf@javad.com","threadId":"47635","inReplyTo":"349021284f5b3b9c57a49f88221cef1eb4643020.1523362469.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 14/15] rebase -i: introduce --rebase-merges=[no-]rebase-cousins","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-12T11:30:56Z","receivedAt":"2018-04-12T11:31:08Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n[...]\n\n> ++\n> +By default, or when `no-rebase-cousins` was specified, commits which do not\n> +have `<upstream>` as direct ancestor will keep their original branch point.\n\n<upstream>\n\nsans quotes, <...> are used without them throughout the manual page.\n\n> +If the `rebase-cousins` mode is turned on, such commits are rebased onto\n> +`<upstream>` (or `<onto>`, if specified).\n\n<upstream> (or <newbase>, if --onto is specified).\n\nsans quotes, and there is no <onto> defined.\n\n-- Sergey\n"},{"id":"344545","messageId":"87o9ioy73t.fsf@javad.com","threadId":"47635","inReplyTo":"a162afa761e3000eb60169fce7a50938888f80b4.1523362469.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-12T11:52:38Z","receivedAt":"2018-04-12T11:52:49Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> +------------\n> +*   Merge branch 'report-a-bug'\n> +|\\\n> +| * Add the feedback button\n> +* | Merge branch 'refactor-button'\n> +|\\ \\\n> +| |/\n> +| * Use the Button class for all buttons\n> +| * Extract a generic Button class from the DownloadButton one\n> +------------\n\nConsider to put SHA1s into the diagram, as they are then used in\nexplanaitions. Hopefully I got them right here:\n\n------------\n*   6f5e4d Merge branch 'report-a-bug'\n|\\\n| * abcdef Add the feedback button\n* | a1b2c3 Merge branch 'refactor-button'\n|\\ \\\n| |/\n| * 654321 Use the Button class for all buttons\n| * 123456 Extract a generic Button class from the DownloadButton one\n------------\n\nOriginal explanation, just for reference, unchanged:\n\n> +------------\n> +label onto\n> +\n> +# Branch: refactor-button\n> +reset onto\n> +pick 123456 Extract a generic Button class from the DownloadButton one\n> +pick 654321 Use the Button class for all buttons\n> +label refactor-button\n> +\n> +# Branch: report-a-bug\n> +reset refactor-button # Use the Button class for all buttons\n> +pick abcdef Add the feedback button\n> +label report-a-bug\n> +\n> +reset onto\n> +merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n> +merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n> +------------\n\n-- Sergey\n"},{"id":"344548","messageId":"nycvar.QRO.7.76.6.1804121411580.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"87in8w2585.fsf@javad.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-12T12:31:00Z","receivedAt":"2018-04-12T12:31:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Sergey,\n\nOn Thu, 12 Apr 2018, Sergey Organov wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> > On Wed, 11 Apr 2018, Sergey Organov wrote:\n> >\n> >> The RFC v2 and Phillip's strategy are essentially the same, as has been\n> >> already shown multiple times, both theoretically and by testing.\n> >\n> > No, they are not.\n> \n> It's off-topic here.\n\nWell, you directed the discussion there. So there.\n\n> If you _really_ want to discuss it further [...]\n\nI am always interested in a constructive discussion toward the goal of\nmaking Git better, to improve its user experience, to give users more\npowerful options, and to make things easier to use.\n\nI'll let you know when I detect a change in this discussion in that vague\ndirection.\n\n> Abrupt change of the topic of discussion indicates your intention to\n> take attention off the apparent ugliness of \n> \n> git rebase --rebase-merges\n\nIf you want to discuss ugly things in Git, that is really an abrupt\ndiversion, but I would not fault you: there is plenty of that in Git.\n\nAs to `git rebase --rebase-merges`? I do not actually find that really\nugly. I find that it says what I want it to say. And after how many people\nagreed, I find it rather pointless and time-wasting to discuss this\nfurther. Naming is hard, and you seem to have a knack for coming up with\nnames that are really terrible. That is why I stopped discussing this with\nyou.\n\n> I also get it as an indication that there are no more arguments in favor\n> of --rebase-merges on your side, at least for now.\n\nYou seem to misinterpret your own arguments against --rebase-merges to be\nanywhere in the realm of convincing. They are not.\n\nDid I say \"flatten history\" to you in this discussion? Sure I did. We also\ntalked about Darcs. About the theory of patches. About the inner workings\nof recursive merges. About commit graphs. And topologies. And we threw\naround many terms that we know people understand who are deep into the\ninner workings of merges and cherry-picks.\n\nDoes this mean that we should expose all the terms we used in this\ntechnical discussion to the user interface?\n\nNo, it does not. We should not absolutely not do that.\n\nSo it is not at all a convincing argument to say \"but you said XYZ\". *In\nthis mail thread*. Which is necessarily full of technical lingo.\n\nAlso, I am still waiting for something tangible from your side. Something\nnon-theoretic. Something practical. Something like taking that FAKE_INIT\nexample at heart, studying it, deducing from it what weaknesses we cannot\ntolerate in strategies to \"cherry-pick merge commits\" or \"forward-port\nmerges\" or \"re-apply amendments in merge commits\" or whatever you want to\ncall it.\n\nYour suggestions so far are heavily biased by your own preferences, based\nin theoretical musings, not in practical examples. I do not see any focus\non the Git user base at large. \"What? They don't know what a topology is?\"\nis a question I could see you asking.\n\nThere has been a lot of talk in this mail thread, and the only actual\noutcome I see is my own work, and Buga's tireless efforts to test\napproaches for their practicality. There is zilch concrete testing from\nyour side. No implementation of anything. No demonstration what kinds of\nmerge conflicts are produced, how often they would have to be resolved by\nthe user. None.\n\nThe important thing to keep in mind is that all my efforts here are spent\nin order to come up with a feature in Git that empowers users. And I want\nthis feature to be as usable as possible. And I want it to use as simple\nlanguage and option names as possible. That is what I will keep focusing\non, like it or not.\n\nCiao,\nJohannes\n"},{"id":"344551","messageId":"CA+P7+xq8LOUZxVd832cOQFi4V=2YvydxtT6_y4NLKvFkki4QdA@mail.gmail.com","threadId":"47635","inReplyTo":"87sh810ym4.fsf@javad.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-04-12T17:03:02Z","receivedAt":"2018-04-12T17:03:31Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Wed, Apr 11, 2018 at 10:42 PM, Sergey Organov <sorganov@gmail.com> wrote:\n> Hi Jacob,\n>\n> Jacob Keller <jacob.keller@gmail.com> writes:\n>> On Wed, Apr 11, 2018 at 6:13 AM, Sergey Organov <sorganov@gmail.com> wrote:\n>>> It was rather --recreate-merges just a few weeks ago, and I've seen\n>>> nobody actually commented either in favor or against the\n>>> --rebase-merges.\n>>>\n>>> git rebase --rebase-merges\n>>>\n>>\n>> I'm going to jump in here and say that *I* prefer --rebase-merges, as\n>> it clearly mentions merge commits (which is the thing that changes).\n>\n> OK, thanks, it's fair and the first argument in favor of --rebase-merges\n> I see.\n>\n\nI'd be ok with \"--keep-merges\" also. I don't like the idea of\n\"flatten\" as it, to me, means that anyone who wants to understand the\noption without prior knowledge must immediately read the man page or\nthey will be confused. Something like \"--rebase-merges\" at least my\ncoworkers got it instantly. The same could be said for \"--keep-merges\"\ntoo, but so far no one I asked said the immediately understood\n\"--no-flatten\".\n\nThanks,\nJake\n"},{"id":"344553","messageId":"CA+P7+xrYe8tcRdk0_DZum8yR0=WLXgQRj4kkiQv_LBBcGoD=rA@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804121102260.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-04-12T18:29:18Z","receivedAt":"2018-04-12T18:29:44Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Apr 12, 2018 at 2:30 AM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>> I think it would be worthwhile to point out that unlike the other commands\n>> this will not preserve untracked files. Maybe something like\n>> \"Note that unlike the `pick` or `merge` commands or initial checkout when the\n>> rebase starts the `reset` command will overwrite any untracked files.\"\n>\n> You know what? You just pointed out a bug in my thinking. Previously, I\n> thought that this is impossible, that you cannot overwrite untracked files\n> because we labeled this revision previously, so the only new files to\n> write by `reset` were tracked files previous. But that forgets `exec` and\n> `reset` with unlabeled revisions (e.g. for cousins).\n>\n> So I changed the `reset` command to refuse overwriting untracked files...\n>\n> Thank you for improving this patch series!\n> Dscho\n\nGood catch! This could possibly have bitten someone badly.\n"},{"id":"344565","messageId":"nycvar.QRO.7.76.6.1804130002090.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xq8LOUZxVd832cOQFi4V=2YvydxtT6_y4NLKvFkki4QdA@mail.gmail.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-12T22:02:14Z","receivedAt":"2018-04-12T22:02:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Thu, 12 Apr 2018, Jacob Keller wrote:\n\n> On Wed, Apr 11, 2018 at 10:42 PM, Sergey Organov <sorganov@gmail.com> wrote:\n> >\n> > Jacob Keller <jacob.keller@gmail.com> writes:\n> >> On Wed, Apr 11, 2018 at 6:13 AM, Sergey Organov <sorganov@gmail.com> wrote:\n> >>> It was rather --recreate-merges just a few weeks ago, and I've seen\n> >>> nobody actually commented either in favor or against the\n> >>> --rebase-merges.\n> >>>\n> >>> git rebase --rebase-merges\n> >>\n> >> I'm going to jump in here and say that *I* prefer --rebase-merges, as\n> >> it clearly mentions merge commits (which is the thing that changes).\n> >\n> > OK, thanks, it's fair and the first argument in favor of\n> > --rebase-merges I see.\n> \n> I'd be ok with \"--keep-merges\" also.\n\nMy main argument against --keep-merges is that there is no good short\noption for it: -k and -m are already taken. And I really like my `git\nrebase -kir` now...\n\nA minor argument in favor of `--rebase-merges` vs `--keep-merges` is that\nwe do not really keep the merge commits, we rewrite them. In the version\nas per this here patch series, we really create recursive merges from\nscratch.\n\nIn the later patch series on which I am working, we use a variation of\nPhillip's strategy which can be construed as a generalization of the\ncherry-pick to include merges: for a cherry-pick, we perform a 3-way merge\nbetween the commit and HEAD, with the commit's parent commit as merge\nbase. With Phillip's strategy, we perform a 3-way merge between the merge\ncommit and HEAD (i.e. the rebased first parent), with the merge commit's\nfirst parent as merge base, followed by a 3-way merge with the rebased\n2nd parent (with the original 2nd parent as merge base), etc\n\nHowever. This strategy, while it performed well in my initial tests (and\nin Buga's initial tests, too), *does* involve more than one 3-way merge,\nand therefore it risks something very, very nasty: *nested* merge\nconflicts.\n\nNow, I did see nested merge conflicts in the past, very rarely, but that\ncan happen, when two developers criss-cross merge each others' `master`\nbranch and are really happy to perform invasive changes that our merge\ndoes not deal well with, such as indentation changes.\n\nWhen rebasing a merge conflict, however, such nested conflicts can happen\nrelatively easily. Not rare at all.\n\nI found out about this by doing what I keep preaching in this thred:\ntheory is often very nice *right* until the point where it hits reality,\nand then frequently turns really useless, real quickly. Theoretical\nmusings can therefore be an utter waste of time, unless accompanied by\nconcrete examples.\n\nTo start, I built on the example for an \"evil merge\" that I gave already\nin the very beginning of this insanely chatty thread: if one branch\nchanges the signature of a function, and a second branch adds a caller to\nthat function, then by necessity a merge between those two branches has to\nchange the caller to accommodate the signature change. Otherwise it would\nend up in a broken state.\n\nIn my `sequencer-shears` branch at https://github.com/dscho/git, I added\nthis as a test case, where I start out with a main.c containing a single\nfunction called core(). I then create one branch where this function is\nrenamed to hi(), and another branch where the function caller() is added\nthat calls core(). Then I merge both, amending the merge commit so that\ncaller() now calls hi(). So this is the main.c after merging:\n\n\tint hi(void) {\n\t\tprintf(\"Hello, world!\\n\");\n\t}\n\t/* caller */\n\tvoid caller(void) {\n\t\thi();\n\t}\n\nTo create the kind of problems that are all too common in my daily work\n(seemingly every time some stable patch in Git for Windows gets\nupstreamed, it changes into an incompatible version, causing merge\nconflicts, and sometimes not only that... but I digress...), I then added\nan \"upstream\" where some maintainer decided that core() is better called\ngreeting(), and also that a placeholder function for an event loop should\nbe added. So in upstream, main.c looks like this:\n\n\tint greeting(void) {\n\t\tprintf(\"Hello, world!\\n\");\n\t}\n\t/* main event loop */\n\tvoid event_loop(void) {\n\t\t/* TODO: place holder for now */\n\t}\n\nKeep in mind: while this is a minimal example of disagreeing changes that\nmay look unrealistic, in practice this is the exact type of problem I am\ndealing with on a daily basis, in Git for Windows as well as in GVFS Git\n(which adds a thicket of branches on top of Git for Windows) and with the\nMSYS2 runtime (where Git for Windows stacks patches on top of MSYs2, which\nin turn maintains their set of patches on top of the Cygwin runtime), and\nwith BusyBox, and probably other projects I forgot spontaneously. This\nmakes me convinced that this is the exact type of problem that will\nchallenge whatever --rebase-merges has to deal with, or better put: what\nthe user of --rebase-merges will have to deal with.\n\n(If I got a penny for every merge conflict I resolved, where test cases\nwere appended to files in t/, I'd probably be rich by now. Likewise, the\n`const char *` -> `struct object_oid *` conflicts have gotten to a point\nwhere I can resolve them while chatting to somebody.)\n\nNow, rebasing the original patches above (renaming core() to hi(), and\nadding caller()) will obviously conflict with those upstream patches\n(renaming core() to greeting(), and adding event_loop()). That cannot be\navoided. In the example above, I decided to override upstream's decision\nby insisting on the name hi(), and resolving the other merge conflict by\nadding *both* event_loop() and caller().\n\nThe big trick, now, is to avoid forcing the user to resolve the same\nconflicts *again* when the merge commit is rebased. The better we can help\nthe user here, the more powerful will this mode be.\n\nBut here, Phillip's strategy (as implemented by yours truly) runs this\nproblem:\n\n\tint hi(void) {\n\t\tprintf(\"Hello, world!\\n\");\n\t}\n\t<<<<<<< intermediate merge\n\t<<<<<<< HEAD\n\t/* main event loop */\n\tvoid event_loop(void) {\n\t\t/* TODO: place holder for now */\n\t=======\n\t=======\n\t}\n\t>>>>>>> <HASH>... merge head #1\n\t/* caller */\n\tvoid caller(void) {\n\t\thi();\n\t>>>>>>> <HASH>... original merge\n\t}\n\nNow, no matter who I ask, everybody so far agreed with me that this looks\nbad. Like, really bad. There are two merge conflicts, obviously, but it is\nnot even clear which conflict markers belong together!\n\nIt gets a little better when I take a page out of recursive merge's\nplaybook, which uses different marker sizes for nested merge conflicts\n(which I of course implemented and pushed to `sequencer-shears`, currently\nstill in an unpolished state):\n\n\tint hi(void) {\n\t\tprintf(\"Hello, world!\\n\");\n\t}\n\t<<<<<<< intermediate merge\n\t<<<<<<<< HEAD\n\t/* main event loop */\n\tvoid event_loop(void) {\n\t\t/* TODO: place holder for now */\n\t========\n\t=======\n\t}\n\t>>>>>>> <HASH>... merge head #1\n\t/* caller */\n\tvoid caller(void) {\n\t\thi();\n\t>>>>>>>> <HASH>... original merge\n\t}\n\nAt least now we understand which conflict markers belong together. But I\nstill needed to inspect the intermediate states to understand what is\ngoing on:\n\nAfter the first 3-way merge (the one between the original merge commit and\nHEAD), we have the conflict markers around event_loop() and caller(),\nbecause they had been added into the same spot.\n\nThe second 3-way merge would also want to add the event_loop(), but not\ncaller(), so ideally it should see that event_loop() is already there and\nnot add any conflict markers. But that is not the case: event_loop() was\nadded *with conflict markers*.\n\nSo those conflict markers in the first 3-way merge *cause* the conflicts\nin the second 3-way merge!\n\nAnd indeed, if we merge the other way round (original merge with 2nd\nparent, then with 1st parent), the result looks much better:\n\n\tint hi(void) {\n\t\tprintf(\"Hello, world!\\n\");\n\t}\n\t/* main event loop */\n\tvoid event_loop(void) {\n\t\t/* TODO: place holder for now */\n\t}\n\t<<<<<<<< HEAD\n\t========\n\t/* caller */\n\tvoid caller(void) {\n\t\thi();\n\t}\n\t>>>>>>>> <HASH>... intermediate merge\n\nSo: the order of the 3-way merges does matter.\n\nI did implement this, too, in the `sequencer_shears` branch: if the first\n3-way merge causes conflicts, attempt the second one, and if that one is\nclean, try merging that merge result into HEAD (forgetting about the first\nattempted 3-way merge).\n\nThat is still unsatisfying, though, as it is easy to come up with a\nmain2.c in the above example that requires the *opposite* merge order to\navoid nested conflicts.\n\nThe only way out I can see is to implement some sort of \"W merge\" or\n\"chandelier merge\" that can perform an N-way merge between one revision\nand N-1 other revisions (each of the N-1 bringing its own merge base). I\ncall them \"W\" or \"chandelier\" because such a merge can be visualized by\nthe original merge commit being the center of a chandelier, and each arm\nrepresenting one of the N-1 merge heads with their own merge bases.\n\nSimilar to the 3-way merge we have implemented in xdiff/xmerge.c, this\n\"chandelier merge\" would then generate the two diffs between merge base\nand both merge heads, except not only one time, but N-1 times. It would\nthen iterate through all hunks ordered by file name and line range. Any\nhunk without conflicting changes would be applied as-is, and the remaining\nones be turned into conflicts (handling those chandelier arms first where\nboth diffs' hunks look identical).\n\nHave I missed any simpler alternative?\n\nCiao,\nJohannes\n"},{"id":"344566","messageId":"CA+P7+xrGMtbtquHohnWbfq1Fy6Smj=LAXnqM=_wXOLvHMU+F2A@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804130002090.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-04-12T22:14:50Z","receivedAt":"2018-04-12T22:15:18Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Apr 12, 2018 at 3:02 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Hi Jake,\n>\n> On Thu, 12 Apr 2018, Jacob Keller wrote:\n>\n>> On Wed, Apr 11, 2018 at 10:42 PM, Sergey Organov <sorganov@gmail.com> wrote:\n>> >\n>> > Jacob Keller <jacob.keller@gmail.com> writes:\n>> >> On Wed, Apr 11, 2018 at 6:13 AM, Sergey Organov <sorganov@gmail.com> wrote:\n>> >>> It was rather --recreate-merges just a few weeks ago, and I've seen\n>> >>> nobody actually commented either in favor or against the\n>> >>> --rebase-merges.\n>> >>>\n>> >>> git rebase --rebase-merges\n>> >>\n>> >> I'm going to jump in here and say that *I* prefer --rebase-merges, as\n>> >> it clearly mentions merge commits (which is the thing that changes).\n>> >\n>> > OK, thanks, it's fair and the first argument in favor of\n>> > --rebase-merges I see.\n>>\n>> I'd be ok with \"--keep-merges\" also.\n>\n> My main argument against --keep-merges is that there is no good short\n> option for it: -k and -m are already taken. And I really like my `git\n> rebase -kir` now...\n>\n\nRight, that's unfortunate.\n\n> A minor argument in favor of `--rebase-merges` vs `--keep-merges` is that\n> we do not really keep the merge commits, we rewrite them. In the version\n> as per this here patch series, we really create recursive merges from\n> scratch.\n\nI also don't have a strong opinion in regards to --keep vs --rebase..\n\n>\n> In the later patch series on which I am working, we use a variation of\n> Phillip's strategy which can be construed as a generalization of the\n> cherry-pick to include merges: for a cherry-pick, we perform a 3-way merge\n> between the commit and HEAD, with the commit's parent commit as merge\n> base. With Phillip's strategy, we perform a 3-way merge between the merge\n> commit and HEAD (i.e. the rebased first parent), with the merge commit's\n> first parent as merge base, followed by a 3-way merge with the rebased\n> 2nd parent (with the original 2nd parent as merge base), etc\n>\n> However. This strategy, while it performed well in my initial tests (and\n> in Buga's initial tests, too), *does* involve more than one 3-way merge,\n> and therefore it risks something very, very nasty: *nested* merge\n> conflicts.\n\nYea, it could. Finding an elegant solution around this would be ideal!\n(By elegant, I mean a solution which produces merge conflicts that\nusers can resolve relatively easily).\n\n>\n> Now, I did see nested merge conflicts in the past, very rarely, but that\n> can happen, when two developers criss-cross merge each others' `master`\n> branch and are really happy to perform invasive changes that our merge\n> does not deal well with, such as indentation changes.\n>\n> When rebasing a merge conflict, however, such nested conflicts can happen\n> relatively easily. Not rare at all.\n\nRight. This would be true regardless of what strategy we choose, I think.\n\n>\n> I found out about this by doing what I keep preaching in this thred:\n> theory is often very nice *right* until the point where it hits reality,\n> and then frequently turns really useless, real quickly. Theoretical\n> musings can therefore be an utter waste of time, unless accompanied by\n> concrete examples.\n\nAgreed.\n\n>\n> To start, I built on the example for an \"evil merge\" that I gave already\n> in the very beginning of this insanely chatty thread: if one branch\n> changes the signature of a function, and a second branch adds a caller to\n> that function, then by necessity a merge between those two branches has to\n> change the caller to accommodate the signature change. Otherwise it would\n> end up in a broken state.\n>\n> In my `sequencer-shears` branch at https://github.com/dscho/git, I added\n> this as a test case, where I start out with a main.c containing a single\n> function called core(). I then create one branch where this function is\n> renamed to hi(), and another branch where the function caller() is added\n> that calls core(). Then I merge both, amending the merge commit so that\n> caller() now calls hi(). So this is the main.c after merging:\n>\n>         int hi(void) {\n>                 printf(\"Hello, world!\\n\");\n>         }\n>         /* caller */\n>         void caller(void) {\n>                 hi();\n>         }\n>\n> To create the kind of problems that are all too common in my daily work\n> (seemingly every time some stable patch in Git for Windows gets\n> upstreamed, it changes into an incompatible version, causing merge\n> conflicts, and sometimes not only that... but I digress...), I then added\n> an \"upstream\" where some maintainer decided that core() is better called\n> greeting(), and also that a placeholder function for an event loop should\n> be added. So in upstream, main.c looks like this:\n>\n>         int greeting(void) {\n>                 printf(\"Hello, world!\\n\");\n>         }\n>         /* main event loop */\n>         void event_loop(void) {\n>                 /* TODO: place holder for now */\n>         }\n>\n> Keep in mind: while this is a minimal example of disagreeing changes that\n> may look unrealistic, in practice this is the exact type of problem I am\n> dealing with on a daily basis, in Git for Windows as well as in GVFS Git\n> (which adds a thicket of branches on top of Git for Windows) and with the\n> MSYS2 runtime (where Git for Windows stacks patches on top of MSYs2, which\n> in turn maintains their set of patches on top of the Cygwin runtime), and\n> with BusyBox, and probably other projects I forgot spontaneously. This\n> makes me convinced that this is the exact type of problem that will\n> challenge whatever --rebase-merges has to deal with, or better put: what\n> the user of --rebase-merges will have to deal with.\n>\n> (If I got a penny for every merge conflict I resolved, where test cases\n> were appended to files in t/, I'd probably be rich by now. Likewise, the\n> `const char *` -> `struct object_oid *` conflicts have gotten to a point\n> where I can resolve them while chatting to somebody.)\n>\n> Now, rebasing the original patches above (renaming core() to hi(), and\n> adding caller()) will obviously conflict with those upstream patches\n> (renaming core() to greeting(), and adding event_loop()). That cannot be\n> avoided. In the example above, I decided to override upstream's decision\n> by insisting on the name hi(), and resolving the other merge conflict by\n> adding *both* event_loop() and caller().\n>\n> The big trick, now, is to avoid forcing the user to resolve the same\n> conflicts *again* when the merge commit is rebased. The better we can help\n> the user here, the more powerful will this mode be.\n>\n> But here, Phillip's strategy (as implemented by yours truly) runs this\n> problem:\n>\n>         int hi(void) {\n>                 printf(\"Hello, world!\\n\");\n>         }\n>         <<<<<<< intermediate merge\n>         <<<<<<< HEAD\n>         /* main event loop */\n>         void event_loop(void) {\n>                 /* TODO: place holder for now */\n>         =======\n>         =======\n>         }\n>         >>>>>>> <HASH>... merge head #1\n>         /* caller */\n>         void caller(void) {\n>                 hi();\n>         >>>>>>> <HASH>... original merge\n>         }\n>\n> Now, no matter who I ask, everybody so far agreed with me that this looks\n> bad. Like, really bad. There are two merge conflicts, obviously, but it is\n> not even clear which conflict markers belong together!\n>\n> It gets a little better when I take a page out of recursive merge's\n> playbook, which uses different marker sizes for nested merge conflicts\n> (which I of course implemented and pushed to `sequencer-shears`, currently\n> still in an unpolished state):\n>\n>         int hi(void) {\n>                 printf(\"Hello, world!\\n\");\n>         }\n>         <<<<<<< intermediate merge\n>         <<<<<<<< HEAD\n>         /* main event loop */\n>         void event_loop(void) {\n>                 /* TODO: place holder for now */\n>         ========\n>         =======\n>         }\n>         >>>>>>> <HASH>... merge head #1\n>         /* caller */\n>         void caller(void) {\n>                 hi();\n>         >>>>>>>> <HASH>... original merge\n>         }\n>\n> At least now we understand which conflict markers belong together. But I\n> still needed to inspect the intermediate states to understand what is\n> going on:\n>\n> After the first 3-way merge (the one between the original merge commit and\n> HEAD), we have the conflict markers around event_loop() and caller(),\n> because they had been added into the same spot.\n>\n> The second 3-way merge would also want to add the event_loop(), but not\n> caller(), so ideally it should see that event_loop() is already there and\n> not add any conflict markers. But that is not the case: event_loop() was\n> added *with conflict markers*.\n>\n> So those conflict markers in the first 3-way merge *cause* the conflicts\n> in the second 3-way merge!\n>\n> And indeed, if we merge the other way round (original merge with 2nd\n> parent, then with 1st parent), the result looks much better:\n>\n>         int hi(void) {\n>                 printf(\"Hello, world!\\n\");\n>         }\n>         /* main event loop */\n>         void event_loop(void) {\n>                 /* TODO: place holder for now */\n>         }\n>         <<<<<<<< HEAD\n>         ========\n>         /* caller */\n>         void caller(void) {\n>                 hi();\n>         }\n>         >>>>>>>> <HASH>... intermediate merge\n>\n> So: the order of the 3-way merges does matter.\n>\n> I did implement this, too, in the `sequencer_shears` branch: if the first\n> 3-way merge causes conflicts, attempt the second one, and if that one is\n> clean, try merging that merge result into HEAD (forgetting about the first\n> attempted 3-way merge).\n>\n> That is still unsatisfying, though, as it is easy to come up with a\n> main2.c in the above example that requires the *opposite* merge order to\n> avoid nested conflicts.\n\nI agree, this solution won't work reliably because we can show\nexamples which fail the opposite way.\n\n>\n> The only way out I can see is to implement some sort of \"W merge\" or\n> \"chandelier merge\" that can perform an N-way merge between one revision\n> and N-1 other revisions (each of the N-1 bringing its own merge base). I\n> call them \"W\" or \"chandelier\" because such a merge can be visualized by\n> the original merge commit being the center of a chandelier, and each arm\n> representing one of the N-1 merge heads with their own merge bases.\n>\n\nI think this approach sounds reasonable.\n\n> Similar to the 3-way merge we have implemented in xdiff/xmerge.c, this\n> \"chandelier merge\" would then generate the two diffs between merge base\n> and both merge heads, except not only one time, but N-1 times. It would\n> then iterate through all hunks ordered by file name and line range. Any\n> hunk without conflicting changes would be applied as-is, and the remaining\n> ones be turned into conflicts (handling those chandelier arms first where\n> both diffs' hunks look identical).\n>\n> Have I missed any simpler alternative?\n\nI *think* this would work well if I understand it, but it's difficult\nto process without examples.\n\n>\n> Ciao,\n> Johannes\n"},{"id":"344597","messageId":"2bec489c-521e-0277-3a15-00b39126e652@talktalk.net","threadId":"47635","inReplyTo":"fb797f32b7385317b9dc18524e3863ba627f6c98.1523362469.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 04/15] sequencer: introduce new commands to reset the revision","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-13T10:03:37Z","receivedAt":"2018-04-13T10:03:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 10/04/18 13:29, Johannes Schindelin wrote:\n> In the upcoming commits, we will teach the sequencer to rebase merges.\n> This will be done in a very different way from the unfortunate design of\n> `git rebase --preserve-merges` (which does not allow for reordering\n> commits, or changing the branch topology).\n> \n> The main idea is to introduce new todo list commands, to support\n> labeling the current revision with a given name, resetting the current\n> revision to a previous state, and  merging labeled revisions.\n> \n> This idea was developed in Git for Windows' Git garden shears (that are\n> used to maintain Git for Windows' \"thicket of branches\" on top of\n> upstream Git), and this patch is part of the effort to make it available\n> to a wider audience, as well as to make the entire process more robust\n> (by implementing it in a safe and portable language rather than a Unix\n> shell script).\n> \n> This commit implements the commands to label, and to reset to, given\n> revisions. The syntax is:\n> \n> \tlabel <name>\n> \treset <name>\n> \n> Internally, the `label <name>` command creates the ref\n> `refs/rewritten/<name>`. This makes it possible to work with the labeled\n> revisions interactively, or in a scripted fashion (e.g. via the todo\n> list command `exec`).\n> \n> These temporary refs are removed upon sequencer_remove_state(), so that\n> even a `git rebase --abort` cleans them up.\n> \n> We disallow '#' as label because that character will be used as separator\n> in the upcoming `merge` command.\n> \n> Later in this patch series, we will mark the `refs/rewritten/` refs as\n> worktree-local, to allow for interactive rebases to be run in parallel in\n> worktrees linked to the same repository.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIf a label or reset command fails it is likely to be due to a\ntypo. Rescheduling the command would make it easier for the user to fix\nthe problem as they can just run 'git rebase --edit-todo'. It also\nensures that the problem has actually been fixed when the rebase\ncontinues. I think you could do it like this\n\n--->8---\nFrom: Phillip Wood <phillip.wood@dunelm.org.uk>\nSubject: [PATCH] fixup! sequencer: introduce new commands to reset the revision\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 809df1ce48..e1b9be7327 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3029,6 +3029,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n+\t\tif (res < 0 && (item->command == TODO_LABEL ||\n+\t\t\t\titem->command == TODO_RESET)) {\n+\t\t\t/* Reschedule */\n+\t\t\ttodo_list->current--;\n+\t\t\tsave_todo(todo_list, opts);\n+\t\t\treturn res;\n+\t\t}\n \t\ttodo_list->current++;\n \t\tif (res)\n \t\t\treturn res;\n-- \n2.17.0\n"},{"id":"344598","messageId":"55216d0b-cb37-6514-7f80-91f4617c105d@talktalk.net","threadId":"47635","inReplyTo":"8a5ae61f603df8d8cb19268e1b8470f0a4993445.1523362469.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 05/15] sequencer: introduce the `merge` command","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-13T10:12:29Z","receivedAt":"2018-04-13T10:12:37Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 10/04/18 13:29, Johannes Schindelin wrote:\n> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n> +\t\t    int flags, struct replay_opts *opts)\n> +{\n> +\tint run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n> +\t\tEDIT_MSG | VERIFY_MSG : 0;\n> +\tstruct strbuf ref_name = STRBUF_INIT;\n> +\tstruct commit *head_commit, *merge_commit, *i;\n> +\tstruct commit_list *bases, *j, *reversed = NULL;\n> +\tstruct merge_options o;\n> +\tint merge_arg_len, oneline_offset, ret;\n> +\tstatic struct lock_file lock;\n> +\tconst char *p;\n> +\n> +\toneline_offset = arg_len;\n> +\tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n> +\tp = arg + merge_arg_len;\n> +\tp += strspn(p, \" \\t\\n\");\n> +\tif (*p == '#' && (!p[1] || isspace(p[1]))) {\n> +\t\tp += 1 + strspn(p + 1, \" \\t\\n\");\n> +\t\toneline_offset = p - arg;\n> +\t} else if (p - arg < arg_len)\n> +\t\tBUG(\"octopus merges are not supported yet: '%s'\", p);\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n> +\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> +\tif (!merge_commit) {\n> +\t\t/* fall back to non-rewritten ref or commit */\n> +\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n> +\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> +\t}\n> +\tif (!merge_commit) {\n> +\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> +\t\treturn -1;\n> +\n> +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n> +\tif (!head_commit) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error(_(\"cannot merge without a current revision\"));\n> +\t}\n> +\n> +\tif (commit) {\n> +\t\tconst char *message = get_commit_buffer(commit, NULL);\n> +\t\tconst char *body;\n> +\t\tint len;\n> +\n> +\t\tif (!message) {\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n> +\t\t\t\t     oid_to_hex(&commit->object.oid));\n> +\t\t}\n> +\t\twrite_author_script(message);\n> +\t\tfind_commit_subject(message, &body);\n> +\t\tlen = strlen(body);\n> +\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n> +\t\t\terror_errno(_(\"could not write '%s'\"),\n> +\t\t\t\t    git_path_merge_msg());\n> +\t\t\tunuse_commit_buffer(commit, message);\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tunuse_commit_buffer(commit, message);\n> +\t} else {\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tint len;\n> +\n> +\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n> +\t\twrite_author_script(buf.buf);\n> +\t\tstrbuf_reset(&buf);\n> +\n> +\t\tif (oneline_offset < arg_len) {\n> +\t\t\tp = arg + oneline_offset;\n> +\t\t\tlen = arg_len - oneline_offset;\n> +\t\t} else {\n> +\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n> +\t\t\t\t    merge_arg_len, arg);\n> +\t\t\tp = buf.buf;\n> +\t\t\tlen = buf.len;\n> +\t\t}\n> +\n> +\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n> +\t\t\terror_errno(_(\"could not write '%s'\"),\n> +\t\t\t\t    git_path_merge_msg());\n> +\t\t\tstrbuf_release(&buf);\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tstrbuf_release(&buf);\n> +\t}\n> +\n> +\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n> +\t\t      git_path_merge_head(), 0);\n> +\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n> +\n> +\tbases = get_merge_bases(head_commit, merge_commit);\n> +\tfor (j = bases; j; j = j->next)\n> +\t\tcommit_list_insert(j->item, &reversed);\n> +\tfree_commit_list(bases);\n> +\n> +\tread_cache();\n> +\tinit_merge_options(&o);\n> +\to.branch1 = \"HEAD\";\n> +\to.branch2 = ref_name.buf;\n> +\to.buffer_output = 2;\n> +\n> +\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n> +\tif (!ret)\n> +\t\trerere(opts->allow_rerere_auto);\n> +\tif (ret <= 0)\n> +\t\tfputs(o.obuf.buf, stdout);\n> +\tstrbuf_release(&o.obuf);\n> +\tif (ret < 0) {\n> +\t\tstrbuf_release(&ref_name);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n> +\t\t\t     merge_arg_len, arg);\n> +\t}\n\nIf there are conflicts then ret == 0 rather than -1\n\n> +\n> +\tif (active_cache_changed &&\n> +\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn error(_(\"merge: Unable to write new index file\"));\n> +\t}\n> +\trollback_lock_file(&lock);\n> +\n> +\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n\nIf there were conflicts this will try and run git commit with unmerged\ncache entries\n\n> +\tstrbuf_release(&ref_name);\n> +\n> +\treturn ret;\n> +}\n> +\n\nIf the merge fails with an error rather than conflicts then I think it\nshould be rescheduled as we do for picks that fail with an error. The\npatch below does that and also adjusts the logic following the merge so\nthat it does not call 'git commit' when there are conflicts. I think we\nmay want to say something about fixing the conflicts and running\n'git rebase --continue' as we do for conflicts when picking.\n\nBest Wishes\n\nPhillip\n\n--->8---\nFrom: Phillip Wood <phillip.wood@dunelm.org.uk>\nSubject: [PATCH] fixup! sequencer: introduce the `merge` command\n\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e1b9be7327..511b7fddca 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2807,27 +2807,26 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \to.buffer_output = 2;\n \n \tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n-\tif (!ret)\n-\t\trerere(opts->allow_rerere_auto);\n+\tstrbuf_release(&ref_name);\n \tif (ret <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n \tif (ret < 0) {\n-\t\tstrbuf_release(&ref_name);\n \t\trollback_lock_file(&lock);\n-\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n-\t\t\t     merge_arg_len, arg);\n+\t\treturn ret;\n \t}\n \n \tif (active_cache_changed &&\n-\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n-\t\tstrbuf_release(&ref_name);\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK))\n \t\treturn error(_(\"merge: Unable to write new index file\"));\n-\t}\n \trollback_lock_file(&lock);\n+\tif (!ret) {\n+\t\trerere(opts->allow_rerere_auto);\n+\t\terror(_(\"conflicts while merging '%.*s'\"), merge_arg_len, arg);\n+\t\treturn 1;\n+\t}\n \n \tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n-\tstrbuf_release(&ref_name);\n \n \treturn ret;\n }\n@@ -3030,7 +3029,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n \t\tif (res < 0 && (item->command == TODO_LABEL ||\n-\t\t\t\titem->command == TODO_RESET)) {\n+\t\t\t\titem->command == TODO_RESET ||\n+\t\t\t\titem->command == TODO_MERGE)) {\n \t\t\t/* Reschedule */\n \t\t\ttodo_list->current--;\n \t\t\tsave_todo(todo_list, opts);\n-- \n2.17.0\n"},{"id":"344603","messageId":"nycvar.QRO.7.76.6.1804131243010.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xrGMtbtquHohnWbfq1Fy6Smj=LAXnqM=_wXOLvHMU+F2A@mail.gmail.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-13T12:08:26Z","receivedAt":"2018-04-13T12:09:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Thu, 12 Apr 2018, Jacob Keller wrote:\n\n> On Thu, Apr 12, 2018 at 3:02 PM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> \n> > [... talking about nested merge conflicts ...]\n> >\n> > The only way out I can see is to implement some sort of \"W merge\" or\n> > \"chandelier merge\" that can perform an N-way merge between one revision\n> > and N-1 other revisions (each of the N-1 bringing its own merge base). I\n> > call them \"W\" or \"chandelier\" because such a merge can be visualized by\n> > the original merge commit being the center of a chandelier, and each arm\n> > representing one of the N-1 merge heads with their own merge bases.\n> >\n> \n> I think this approach sounds reasonable.\n\n... and it would incidentally also offer a saner way to do octopus merges\n(so far, an octopus merge that causes merge conflicts causes... huge\npains, as it usually stops in the middle of everything, without a UI to\nhelp with concluding the merge).\n\n> > Similar to the 3-way merge we have implemented in xdiff/xmerge.c, this\n> > \"chandelier merge\" would then generate the two diffs between merge base\n> > and both merge heads, except not only one time, but N-1 times. It would\n> > then iterate through all hunks ordered by file name and line range. Any\n> > hunk without conflicting changes would be applied as-is, and the remaining\n> > ones be turned into conflicts (handling those chandelier arms first where\n> > both diffs' hunks look identical).\n> >\n> > Have I missed any simpler alternative?\n> \n> I *think* this would work well if I understand it, but it's difficult\n> to process without examples.\n\nWell, I am fairly certain about the implementation details (it's been a\nwhile since I contributed xdiff/xmerge.c, and if you ever want to hear the\nhorrible story how I wrote the initial version in a stopped train in the\nmiddle of the night, just buy me a beer or three, my memory is fresh on\nthe \"simultaneous walking\" of the diff hunks).\n\nSo it goes somewhat like this. You have two diffs, and for the matter of\nthe discussion, let's just look at the hunk headers (with 0 context lines,\ni.e. -U0):\n\ndiff base..HEAD\n@@ -10,1 +10,2 @@\n@@ -40,2 +41,0 @@\n\ndiff base..branch\n@@ -8,4 +8,3 @@\n\nSo on one side of the merge, we changed line 10 (e.g. wrapping a long\nline), and we removed lines 40 and 41.\n\nIn the branch we want to merge, lines 8--11 were edited (removing one\nline).\n\nThe 3-way merge as implemented in xdiff/xmerge.c handles only one file,\nand first uses the diff machinery to figure out the hunk headers of both\ndiffs, then iterates through both diffs. This is the `while (xscr1 &&\nxscr2)` loop in `xdl_do_merge()`, and the \"scr\" stands for \"script\" as in\n\"edit script\". In other words, `xscr1` refers to the current hunk in the\nfirst diff, and `xscr2` to the one in the second diff.\n\nInside the loop, we look whether they overlap. If not, the one with the\nsmaller line numbers is \"applied\" and we iterate to the next hunk after\nthat.\n\nIf the hunks overlap, we have a look at the respective post images to see\nwhether both sides of the merge modified that part identically; if they\ndon't, we create a conflict (and later, we will try to reduce the conflict\nby trimming identially-changed lines at both ends of the line range).\n\nLather, rinse & repeat.\n\nNow, what I have in mind is that we will have not only two diffs' hunks to\nlook through, but (N-1)*2 (note that if N == 2, it is the exact same thing\nas before).\n\nAgain, at each iteration, we look for the next hunk among all available\nones, then determine whether it overlaps with any other hunk. If it does\nnot, we apply it. If it does, we first look whether all overlapping hunks\nagree on the post image and if they do: apply the change, otherwise create\na conflict.\n\nHow to present such conflicts to the user, though?\n\nThe worst case, I think, would be N diverging changes with N-1 agreeing on\na large part of the post image and the remaining post image being\ncompletely different. Imagine, for example, that the original merge\ncontains a long function hi() that was renamed to greeting() in HEAD, but\nreplaced by a completely different implementation in the rebased\nbranch-to-merge. In such a case, this nested conflict would be most\nintuitive, methinks:\n\n\t<<< intermediate merge\n\t<<<< HEAD\n\tgreeting()\n\t====\n\thi()\n\t>>>> original merge\n\t... /* original function body */\n\t===\n\thi()\n\t... /* complete rewrite */\n\t>>> branch\n\nBut now that I look at it, it is still hard to parse. *Is* there any good\nway to present this conflict?\n\nAnd then there is the problem that our index really is only prepared for\n*three* stages, but we would need N*2-1.\n\nSo maybe I am overthinking this and we should stick with the\nimplementation I have right now (try to merge HEAD and the original merge\nfirst, then merge the rebased 2nd parent if there are no conflicts,\notherwise try the other way round), and simply come up with a *very good*\nmessage to the unfortunate user who encounters this situation?\n\nI am thinking about something along these lines:\n\n\tThere were conflicts merging the original merge\n\t\tdeadbee (Merge 'side-branch')\n\twith its rebased first parent\n\t\tb1ab1ab (Rename 'core()' to 'hi()')\n\tand its rebased second parent\n\t\tceeceec (Call core() in the event loop)\n\n\tThe intermediate merge(s) are available as\n\t\tabcdef6 (intermediate merge)\n\nMaybe that is good enough? Then the user could always try to glean which\namendments in the original merge (if any) were responsible for the\nconflicts, and maybe even try to recreate the merge and then apply the\namendments manually... or something else...\n\nI could even imagine that we could come up with more clever fall-back\nstrategies, such as recreating the original merge with a regular\nmerge_trees() to see whether it resulted in the same tree, i.e. find out\nwhether there *were* amendments, and in that case simply recreate a new\nmerge from scratch.\n\nAt some point, though, I should stop spending so much time on something\nthat may not even happen all that much in practice, I guess... ;-)\n\nCiao,\nDscho\n"},{"id":"344611","messageId":"de682d44-a8e7-6fdd-f7bc-e06fea2685ab@talktalk.net","threadId":"47635","inReplyTo":"CAPig+cRfQgz2tCab2d9g-XznHORW0FJvDKS4fe85JkLPxm2HMQ@mail.gmail.com","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-13T15:21:56Z","receivedAt":"2018-04-13T15:22:06Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 11/04/18 20:10, Eric Sunshine wrote:\n> On Wed, Apr 11, 2018 at 11:35 AM, Phillip Wood\n> <phillip.wood@talktalk.net> wrote:\n>> On 10/04/18 13:30, Johannes Schindelin wrote:\n>>> +The `reset` command is essentially a `git reset --hard` to the specified\n>>> +revision (typically a previously-labeled one).\n>>\n>> s/labeled/labelled/\n> \n> American vs. British English spelling.\n\nAh, I'd forgotten that the American version only had one 'l'\n\nThanks\n\nPhillip\n\n> CodingGuidelines and SubmittingPatches talk about this. Junio\n> summarizes the issue well in [1]. The TL;DR is to lean toward the\n> American English spelling.\n> \n> [1]: https://public-inbox.org/git/xmqq4m9gpebm.fsf@gitster.mtv.corp.google.com/\n> \n\n"},{"id":"344612","messageId":"5c968cfe-b897-e308-d41a-9948529b796f@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804121102260.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 15/15] rebase -i --rebase-merges: add a section to the man page","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-13T15:27:18Z","receivedAt":"2018-04-13T15:27:28Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 12/04/18 10:30, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Wed, 11 Apr 2018, Phillip Wood wrote:\n> \n>> On 10/04/18 13:30, Johannes Schindelin wrote:\n>>\n>> Firstly let me say that I think expanding the documentation and having an\n>> example is an excellent idea.\n> \n> Thanks! At first, I meant to leave this for others to contribute, but I\n> think it makes sense for me to describe it, as I do have a little bit of\n> experience with rebasing merges.\n> \n>>> +\n>>> +------------\n>>> +label onto\n>>> +\n>>> +# Branch: refactor-button\n>>> +reset onto\n>>> +pick 123456 Extract a generic Button class from the DownloadButton one\n>>> +pick 654321 Use the Button class for all buttons\n>>> +label refactor-button\n>>> +\n>>> +# Branch: report-a-bug\n>>> +reset refactor-button # Use the Button class for all buttons\n>>> +pick abcdef Add the feedback button\n>>> +label report-a-bug\n>>> +\n>>> +reset onto\n>>> +merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n>>> +merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n>>> +------------\n>>> +\n>>> +In contrast to a regular interactive rebase, there are `label`, `reset` and\n>>> +`merge` commands in addition to `pick` ones.\n>>> +\n>>> +The `label` command puts a label to whatever will be the current\n>>\n>> s/puts a label to/associates a label with/ would be clearer I think. Maybe\n>> s/whatever will be the current revision/the current HEAD/ an well?\n> \n> Thanks, I incorporated both changes here.\n> \n>>> +revision when that command is executed. Internally, these labels are\n>>> +worktree-local refs that will be deleted when the rebase finishes or\n>>> +when it is aborted.\n>>\n>> I agree they should be deleted when the rebase is aborted but I cannot see any\n>> changes to git-rebase.sh to make that happen. I think they should also be\n>> deleted by 'rebase --quit'.\n> \n> Oh right! For some reason I thought I already hooked up rebase--helper\n> --abort when rebase was called with --abort or quit, but I had not managed\n> yet. I think I will leave this for later, or for GSoC, or something.\n> \n> In the meantime, I'll just drop the \"or when it is aborted.\".\n> \n>>> That way, rebase operations in multiple worktrees\n>>> +linked to the same repository do not interfere with one another.\n>>> +\n>>> +The `reset` command is essentially a `git reset --hard` to the specified\n>>> +revision (typically a previously-labeled one).\n>>\n>> s/labeled/labelled/\n> \n> As Eric pointed out, I am using 'murricane spelling here (or is it\n> speling? Ya never know these days).\n\n:-)\n\n>> I think it would be worthwhile to point out that unlike the other commands\n>> this will not preserve untracked files. Maybe something like\n>> \"Note that unlike the `pick` or `merge` commands or initial checkout when the\n>> rebase starts the `reset` command will overwrite any untracked files.\"\n> \n> You know what? You just pointed out a bug in my thinking. Previously, I\n> thought that this is impossible, that you cannot overwrite untracked files\n> because we labeled this revision previously, so the only new files to\n> write by `reset` were tracked files previous. But that forgets `exec` and\n> `reset` with unlabeled revisions (e.g. for cousins).\n> \n> So I changed the `reset` command to refuse overwriting untracked files...\n\nThat sounds like the safest plan\n\nThanks\n\nPhillip\n> \n> Thank you for improving this patch series!\n> Dscho\n> \n\n"},{"id":"344614","messageId":"5d522cc9-3922-9f24-3a1e-0c121c47ca96@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804130002090.65@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-13T15:43:42Z","receivedAt":"2018-04-13T15:43:50Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 12/04/18 23:02, Johannes Schindelin wrote:\n> Hi Jake,\n> \n> On Thu, 12 Apr 2018, Jacob Keller wrote:\n> \n>> On Wed, Apr 11, 2018 at 10:42 PM, Sergey Organov <sorganov@gmail.com> wrote:\n>>>\n>>> Jacob Keller <jacob.keller@gmail.com> writes:\n>>>> On Wed, Apr 11, 2018 at 6:13 AM, Sergey Organov <sorganov@gmail.com> wrote:\n>>>>> It was rather --recreate-merges just a few weeks ago, and I've seen\n>>>>> nobody actually commented either in favor or against the\n>>>>> --rebase-merges.\n>>>>>\n>>>>> git rebase --rebase-merges\n>>>>\n>>>> I'm going to jump in here and say that *I* prefer --rebase-merges, as\n>>>> it clearly mentions merge commits (which is the thing that changes).\n>>>\n>>> OK, thanks, it's fair and the first argument in favor of\n>>> --rebase-merges I see.\n>>\n>> I'd be ok with \"--keep-merges\" also.\n> \n> My main argument against --keep-merges is that there is no good short\n> option for it: -k and -m are already taken. And I really like my `git\n> rebase -kir` now...\n> \n> A minor argument in favor of `--rebase-merges` vs `--keep-merges` is that\n> we do not really keep the merge commits, we rewrite them. In the version\n> as per this here patch series, we really create recursive merges from\n> scratch.\n> \n> In the later patch series on which I am working, we use a variation of\n> Phillip's strategy which can be construed as a generalization of the\n> cherry-pick to include merges: for a cherry-pick, we perform a 3-way merge\n> between the commit and HEAD, with the commit's parent commit as merge\n> base. With Phillip's strategy, we perform a 3-way merge between the merge\n> commit and HEAD (i.e. the rebased first parent), with the merge commit's\n> first parent as merge base, followed by a 3-way merge with the rebased\n> 2nd parent (with the original 2nd parent as merge base), etc\n> \n> However. This strategy, while it performed well in my initial tests (and\n> in Buga's initial tests, too), *does* involve more than one 3-way merge,\n> and therefore it risks something very, very nasty: *nested* merge\n> conflicts.\n> \n> Now, I did see nested merge conflicts in the past, very rarely, but that\n> can happen, when two developers criss-cross merge each others' `master`\n> branch and are really happy to perform invasive changes that our merge\n> does not deal well with, such as indentation changes.\n> \n> When rebasing a merge conflict, however, such nested conflicts can happen\n> relatively easily. Not rare at all.\n> \n> I found out about this by doing what I keep preaching in this thred:\n> theory is often very nice *right* until the point where it hits reality,\n> and then frequently turns really useless, real quickly. Theoretical\n> musings can therefore be an utter waste of time, unless accompanied by\n> concrete examples.\n\nExactly (that's one reason I've been keeping a low profile on this \nthread since my initial suggestion - I haven't had time to test out any \nexamples). Thanks for taking the time to test out the theory\n\n> To start, I built on the example for an \"evil merge\" that I gave already\n> in the very beginning of this insanely chatty thread: if one branch\n> changes the signature of a function, and a second branch adds a caller to\n> that function, then by necessity a merge between those two branches has to\n> change the caller to accommodate the signature change. Otherwise it would\n> end up in a broken state.\n> \n> In my `sequencer-shears` branch at https://github.com/dscho/git, I added\n> this as a test case, where I start out with a main.c containing a single\n> function called core(). I then create one branch where this function is\n> renamed to hi(), and another branch where the function caller() is added\n> that calls core(). Then I merge both, amending the merge commit so that\n> caller() now calls hi(). So this is the main.c after merging:\n> \n> \tint hi(void) {\n> \t\tprintf(\"Hello, world!\\n\");\n> \t}\n> \t/* caller */\n> \tvoid caller(void) {\n> \t\thi();\n> \t}\n> \n> To create the kind of problems that are all too common in my daily work\n> (seemingly every time some stable patch in Git for Windows gets\n> upstreamed, it changes into an incompatible version, causing merge\n> conflicts, and sometimes not only that... but I digress...), I then added\n> an \"upstream\" where some maintainer decided that core() is better called\n> greeting(), and also that a placeholder function for an event loop should\n> be added. So in upstream, main.c looks like this:\n> \n> \tint greeting(void) {\n> \t\tprintf(\"Hello, world!\\n\");\n> \t}\n> \t/* main event loop */\n> \tvoid event_loop(void) {\n> \t\t/* TODO: place holder for now */\n> \t}\n> \n> Keep in mind: while this is a minimal example of disagreeing changes that\n> may look unrealistic, in practice this is the exact type of problem I am\n> dealing with on a daily basis, in Git for Windows as well as in GVFS Git\n> (which adds a thicket of branches on top of Git for Windows) and with the\n> MSYS2 runtime (where Git for Windows stacks patches on top of MSYs2, which\n> in turn maintains their set of patches on top of the Cygwin runtime), and\n> with BusyBox, and probably other projects I forgot spontaneously. This\n> makes me convinced that this is the exact type of problem that will\n> challenge whatever --rebase-merges has to deal with, or better put: what\n> the user of --rebase-merges will have to deal with.\n> \n> (If I got a penny for every merge conflict I resolved, where test cases\n> were appended to files in t/, I'd probably be rich by now. Likewise, the\n> `const char *` -> `struct object_oid *` conflicts have gotten to a point\n> where I can resolve them while chatting to somebody.)\n> \n> Now, rebasing the original patches above (renaming core() to hi(), and\n> adding caller()) will obviously conflict with those upstream patches\n> (renaming core() to greeting(), and adding event_loop()). That cannot be\n> avoided. In the example above, I decided to override upstream's decision\n> by insisting on the name hi(), and resolving the other merge conflict by\n> adding *both* event_loop() and caller().\n> \n> The big trick, now, is to avoid forcing the user to resolve the same\n> conflicts *again* when the merge commit is rebased. The better we can help\n> the user here, the more powerful will this mode be.\n> \n> But here, Phillip's strategy (as implemented by yours truly) runs this\n> problem:\n> \n> \tint hi(void) {\n> \t\tprintf(\"Hello, world!\\n\");\n> \t}\n> \t<<<<<<< intermediate merge\n> \t<<<<<<< HEAD\n> \t/* main event loop */\n> \tvoid event_loop(void) {\n> \t\t/* TODO: place holder for now */\n> \t=======\n> \t=======\n> \t}\n> \t>>>>>>> <HASH>... merge head #1\n> \t/* caller */\n> \tvoid caller(void) {\n> \t\thi();\n> \t>>>>>>> <HASH>... original merge\n> \t}\n> \n> Now, no matter who I ask, everybody so far agreed with me that this looks\n> bad. Like, really bad. There are two merge conflicts, obviously, but it is\n> not even clear which conflict markers belong together!\n> \n> It gets a little better when I take a page out of recursive merge's\n> playbook, which uses different marker sizes for nested merge conflicts\n> (which I of course implemented and pushed to `sequencer-shears`, currently\n> still in an unpolished state):\n> \n> \tint hi(void) {\n> \t\tprintf(\"Hello, world!\\n\");\n> \t}\n> \t<<<<<<< intermediate merge\n> \t<<<<<<<< HEAD\n> \t/* main event loop */\n> \tvoid event_loop(void) {\n> \t\t/* TODO: place holder for now */\n> \t========\n> \t=======\n> \t}\n> \t>>>>>>> <HASH>... merge head #1\n> \t/* caller */\n> \tvoid caller(void) {\n> \t\thi();\n> \t>>>>>>>> <HASH>... original merge\n> \t}\n> \n> At least now we understand which conflict markers belong together. But I\n> still needed to inspect the intermediate states to understand what is\n> going on:\n> \n> After the first 3-way merge (the one between the original merge commit and\n> HEAD), we have the conflict markers around event_loop() and caller(),\n> because they had been added into the same spot.\n> \n> The second 3-way merge would also want to add the event_loop(), but not\n> caller(), so ideally it should see that event_loop() is already there and\n> not add any conflict markers. But that is not the case: event_loop() was\n> added *with conflict markers*.\n> \n> So those conflict markers in the first 3-way merge *cause* the conflicts\n> in the second 3-way merge!\n> \n> And indeed, if we merge the other way round (original merge with 2nd\n> parent, then with 1st parent), the result looks much better:\n> \n> \tint hi(void) {\n> \t\tprintf(\"Hello, world!\\n\");\n> \t}\n> \t/* main event loop */\n> \tvoid event_loop(void) {\n> \t\t/* TODO: place holder for now */\n> \t}\n> \t<<<<<<<< HEAD\n> \t========\n> \t/* caller */\n> \tvoid caller(void) {\n> \t\thi();\n> \t}\n> \t>>>>>>>> <HASH>... intermediate merge\n> \n> So: the order of the 3-way merges does matter.\n> \n> I did implement this, too, in the `sequencer_shears` branch: if the first\n> 3-way merge causes conflicts, attempt the second one, and if that one is\n> clean, try merging that merge result into HEAD (forgetting about the first\n> attempted 3-way merge).\n> \n> That is still unsatisfying, though, as it is easy to come up with a\n> main2.c in the above example that requires the *opposite* merge order to\n> avoid nested conflicts.\n> \n> The only way out I can see is to implement some sort of \"W merge\" or\n> \"chandelier merge\" that can perform an N-way merge between one revision\n> and N-1 other revisions (each of the N-1 bringing its own merge base). I\n> call them \"W\" or \"chandelier\" because such a merge can be visualized by\n> the original merge commit being the center of a chandelier, and each arm\n> representing one of the N-1 merge heads with their own merge bases.\n> \n> Similar to the 3-way merge we have implemented in xdiff/xmerge.c, this\n> \"chandelier merge\" would then generate the two diffs between merge base\n> and both merge heads, except not only one time, but N-1 times. It would\n> then iterate through all hunks ordered by file name and line range. Any\n> hunk without conflicting changes would be applied as-is, and the remaining\n> ones be turned into conflicts (handling those chandelier arms first where\n> both diffs' hunks look identical).\n> \n> Have I missed any simpler alternative?\n\nThose conflicts certainly look intimidating (and the ones in your later \nreply with the N way merge example still look quite complicated). One \noption would be just to stop and have the user resolve the conflicts \nafter each conflicting 3-way merge rather than at the end of all the \nmerges. There are some downsides: there would need to be a way to \nexplain to the user that this is an intermediate step (and what that \nstep was); the code would have to do some book keeping to know where it \nhad got to; and it would stop and prompt the user to resolve conflicts \nmore often which could be annoying but hopefully they'd be clearer to \nresolve because they weren't nested.\n\nBest Wishes\n\nPhillip\n\n> \n> Ciao,\n> Johannes\n> \n\n"},{"id":"344623","messageId":"85232331-97a0-7989-e095-854ed962c7a0@talktalk.net","threadId":"47635","inReplyTo":"55216d0b-cb37-6514-7f80-91f4617c105d@talktalk.net","subject":"Re: [PATCH v6 05/15] sequencer: introduce the `merge` command","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-13T17:57:03Z","receivedAt":"2018-04-13T17:57:13Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 13/04/18 11:12, Phillip Wood wrote:\n> On 10/04/18 13:29, Johannes Schindelin wrote:\n>> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n>> +\t\t    int flags, struct replay_opts *opts)\n>> +{\n>> +\tint run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n>> +\t\tEDIT_MSG | VERIFY_MSG : 0;\n>> +\tstruct strbuf ref_name = STRBUF_INIT;\n>> +\tstruct commit *head_commit, *merge_commit, *i;\n>> +\tstruct commit_list *bases, *j, *reversed = NULL;\n>> +\tstruct merge_options o;\n>> +\tint merge_arg_len, oneline_offset, ret;\n>> +\tstatic struct lock_file lock;\n>> +\tconst char *p;\n>> +\n>> +\toneline_offset = arg_len;\n>> +\tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n>> +\tp = arg + merge_arg_len;\n>> +\tp += strspn(p, \" \\t\\n\");\n>> +\tif (*p == '#' && (!p[1] || isspace(p[1]))) {\n>> +\t\tp += 1 + strspn(p + 1, \" \\t\\n\");\n>> +\t\toneline_offset = p - arg;\n>> +\t} else if (p - arg < arg_len)\n>> +\t\tBUG(\"octopus merges are not supported yet: '%s'\", p);\n>> +\n>> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n>> +\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n>> +\tif (!merge_commit) {\n>> +\t\t/* fall back to non-rewritten ref or commit */\n>> +\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n>> +\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n>> +\t}\n>> +\tif (!merge_commit) {\n>> +\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n>> +\t\tstrbuf_release(&ref_name);\n>> +\t\treturn -1;\n>> +\t}\n>> +\n>> +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n>> +\t\treturn -1;\n>> +\n>> +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n>> +\tif (!head_commit) {\n>> +\t\trollback_lock_file(&lock);\n>> +\t\treturn error(_(\"cannot merge without a current revision\"));\n>> +\t}\n>> +\n>> +\tif (commit) {\n>> +\t\tconst char *message = get_commit_buffer(commit, NULL);\n>> +\t\tconst char *body;\n>> +\t\tint len;\n>> +\n>> +\t\tif (!message) {\n>> +\t\t\trollback_lock_file(&lock);\n>> +\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n>> +\t\t\t\t     oid_to_hex(&commit->object.oid));\n>> +\t\t}\n>> +\t\twrite_author_script(message);\n>> +\t\tfind_commit_subject(message, &body);\n>> +\t\tlen = strlen(body);\n>> +\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n>> +\t\t\terror_errno(_(\"could not write '%s'\"),\n>> +\t\t\t\t    git_path_merge_msg());\n>> +\t\t\tunuse_commit_buffer(commit, message);\n>> +\t\t\trollback_lock_file(&lock);\n>> +\t\t\treturn -1;\n>> +\t\t}\n>> +\t\tunuse_commit_buffer(commit, message);\n>> +\t} else {\n>> +\t\tstruct strbuf buf = STRBUF_INIT;\n>> +\t\tint len;\n>> +\n>> +\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n>> +\t\twrite_author_script(buf.buf);\n>> +\t\tstrbuf_reset(&buf);\n>> +\n>> +\t\tif (oneline_offset < arg_len) {\n>> +\t\t\tp = arg + oneline_offset;\n>> +\t\t\tlen = arg_len - oneline_offset;\n>> +\t\t} else {\n>> +\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n>> +\t\t\t\t    merge_arg_len, arg);\n>> +\t\t\tp = buf.buf;\n>> +\t\t\tlen = buf.len;\n>> +\t\t}\n>> +\n>> +\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n>> +\t\t\terror_errno(_(\"could not write '%s'\"),\n>> +\t\t\t\t    git_path_merge_msg());\n>> +\t\t\tstrbuf_release(&buf);\n>> +\t\t\trollback_lock_file(&lock);\n>> +\t\t\treturn -1;\n>> +\t\t}\n>> +\t\tstrbuf_release(&buf);\n>> +\t}\n>> +\n>> +\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n>> +\t\t      git_path_merge_head(), 0);\n>> +\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n>> +\n>> +\tbases = get_merge_bases(head_commit, merge_commit);\n>> +\tfor (j = bases; j; j = j->next)\n>> +\t\tcommit_list_insert(j->item, &reversed);\n>> +\tfree_commit_list(bases);\n>> +\n>> +\tread_cache();\n>> +\tinit_merge_options(&o);\n>> +\to.branch1 = \"HEAD\";\n>> +\to.branch2 = ref_name.buf;\n>> +\to.buffer_output = 2;\n>> +\n>> +\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n>> +\tif (!ret)\n>> +\t\trerere(opts->allow_rerere_auto);\n>> +\tif (ret <= 0)\n>> +\t\tfputs(o.obuf.buf, stdout);\n>> +\tstrbuf_release(&o.obuf);\n>> +\tif (ret < 0) {\n>> +\t\tstrbuf_release(&ref_name);\n>> +\t\trollback_lock_file(&lock);\n>> +\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n>> +\t\t\t     merge_arg_len, arg);\n>> +\t}\n> \n> If there are conflicts then ret == 0 rather than -1\n> \n>> +\n>> +\tif (active_cache_changed &&\n>> +\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n>> +\t\tstrbuf_release(&ref_name);\n>> +\t\treturn error(_(\"merge: Unable to write new index file\"));\n>> +\t}\n>> +\trollback_lock_file(&lock);\n>> +\n>> +\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n> \n> If there were conflicts this will try and run git commit with unmerged\n> cache entries\n> \n>> +\tstrbuf_release(&ref_name);\n>> +\n>> +\treturn ret;\n>> +}\n>> +\n> \n> If the merge fails with an error rather than conflicts then I think it\n> should be rescheduled as we do for picks that fail with an error. The\n> patch below does that and also adjusts the logic following the merge so\n> that it does not call 'git commit' when there are conflicts. I think we\n> may want to say something about fixing the conflicts and running\n> 'git rebase --continue' as we do for conflicts when picking.\n> \n> Best Wishes\n> \n> Phillip\n> \n> --->8---\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n> Subject: [PATCH] fixup! sequencer: introduce the `merge` command\n> \n> \n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  sequencer.c | 20 ++++++++++----------\n>  1 file changed, 10 insertions(+), 10 deletions(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index e1b9be7327..511b7fddca 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -2807,27 +2807,26 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n>  \to.buffer_output = 2;\n>  \n>  \tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n> -\tif (!ret)\n> -\t\trerere(opts->allow_rerere_auto);\n> +\tstrbuf_release(&ref_name);\n>  \tif (ret <= 0)\n>  \t\tfputs(o.obuf.buf, stdout);\n>  \tstrbuf_release(&o.obuf);\n>  \tif (ret < 0) {\n> -\t\tstrbuf_release(&ref_name);\n>  \t\trollback_lock_file(&lock);\n> -\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n> -\t\t\t     merge_arg_len, arg);\n> +\t\treturn ret;\n>  \t}\n>  \n>  \tif (active_cache_changed &&\n> -\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n> -\t\tstrbuf_release(&ref_name);\n> +\t    write_locked_index(&the_index, &lock, COMMIT_LOCK))\n>  \t\treturn error(_(\"merge: Unable to write new index file\"));\n> -\t}\n>  \trollback_lock_file(&lock);\n> +\tif (!ret) {\n> +\t\trerere(opts->allow_rerere_auto);\n> +\t\terror(_(\"conflicts while merging '%.*s'\"), merge_arg_len, arg);\n> +\t\treturn 1;\n> +\t}\n>  \n>  \tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n> -\tstrbuf_release(&ref_name);\n>  \n>  \treturn ret;\n>  }\n> @@ -3030,7 +3029,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n>  \n>  \t\tif (res < 0 && (item->command == TODO_LABEL ||\n> -\t\t\t\titem->command == TODO_RESET)) {\n> +\t\t\t\titem->command == TODO_RESET ||\n> +\t\t\t\titem->command == TODO_MERGE)) {\n\nUnfortunately it's not as simple as that - we only want to reschedule if\nmerge_recursive() fails, not if run_git_commit() does.\n\n\n>  \t\t\t/* Reschedule */\n>  \t\t\ttodo_list->current--;\n>  \t\t\tsave_todo(todo_list, opts);\n> \n\n"},{"id":"344656","messageId":"nycvar.QRO.7.76.6.1804140146450.234@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"5d522cc9-3922-9f24-3a1e-0c121c47ca96@talktalk.net","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-13T23:48:19Z","receivedAt":"2018-04-13T23:48:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Fri, 13 Apr 2018, Phillip Wood wrote:\n\n> On 12/04/18 23:02, Johannes Schindelin wrote:\n> > \n> > [...]\n> > \n> > So: the order of the 3-way merges does matter.\n> >\n> > [...]\n> \n> Those conflicts certainly look intimidating (and the ones in your later\n> reply with the N way merge example still look quite complicated). One\n> option would be just to stop and have the user resolve the conflicts\n> after each conflicting 3-way merge rather than at the end of all the\n> merges. There are some downsides: there would need to be a way to\n> explain to the user that this is an intermediate step (and what that\n> step was); the code would have to do some book keeping to know where it\n> had got to; and it would stop and prompt the user to resolve conflicts\n> more often which could be annoying but hopefully they'd be clearer to\n> resolve because they weren't nested.\n\nI thought about that. But as I pointed out: the order of the merges *does*\nmatter. Otherwise we force the user to resolve conflicts that they\n*already* resolved during this rebase...\n\nCiao,\nDscho\n"},{"id":"344658","messageId":"nycvar.QRO.7.76.6.1804140248110.234@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"85232331-97a0-7989-e095-854ed962c7a0@talktalk.net","subject":"Re: [PATCH v6 05/15] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-14T00:51:58Z","receivedAt":"2018-04-14T00:52:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Fri, 13 Apr 2018, Phillip Wood wrote:\n\n> On 13/04/18 11:12, Phillip Wood wrote:\n> > @@ -3030,7 +3029,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n> >  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n> >  \n> >  \t\tif (res < 0 && (item->command == TODO_LABEL ||\n> > -\t\t\t\titem->command == TODO_RESET)) {\n> > +\t\t\t\titem->command == TODO_RESET ||\n> > +\t\t\t\titem->command == TODO_MERGE)) {\n> \n> Unfortunately it's not as simple as that - we only want to reschedule if\n> merge_recursive() fails, not if run_git_commit() does.\n\nCorrect. How about introducing a flag `reschedule` that is passed to\ndo_label(), do_reset() and do_merge()?\n\nSeeing as do_reset() and do_merge() already have a replay_opts parameter,\nwe could add a field `needs_rescheduling` and pass the replay_opts also to\ndo_label().\n\nCiao,\nDscho\n"},{"id":"344734","messageId":"BA40DE939AF14BC18B496FDA27281CA2@PhilipOakley","threadId":"47635","inReplyTo":"2bec489c-521e-0277-3a15-00b39126e652@talktalk.net","subject":"Re: [PATCH v6 04/15] sequencer: introduce new commands to reset the revision","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-15T17:17:20Z","receivedAt":"2018-04-15T17:17:28Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Phillip Wood\" <phillip.wood@talktalk.net>\n: Friday, April 13, 2018 11:03 AM\n> If a label or reset command fails it is likely to be due to a\n> typo. Rescheduling the command would make it easier for the user to fix\n> the problem as they can just run 'git rebase --edit-todo'. \n\nIs this worth noting in the command documentation? \n\"If the label or reset command fails then fix\nthe problem by runnning 'git rebase --edit-todo'.\" ?\n\nJust a thought.\n\n> It also\n> ensures that the problem has actually been fixed when the rebase\n> continues. I think you could do it like this\n> \n<snip>\n--\nPhilip\n(also @dunelm, 73-79..)\n"},{"id":"344970","messageId":"87a7u12ilz.fsf@javad.com","threadId":"47635","inReplyTo":"CA+P7+xq8LOUZxVd832cOQFi4V=2YvydxtT6_y4NLKvFkki4QdA@mail.gmail.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-18T05:23:20Z","receivedAt":"2018-04-18T05:23:28Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Jacob,\n\nJacob Keller <jacob.keller@gmail.com> writes:\n\n> On Wed, Apr 11, 2018 at 10:42 PM, Sergey Organov <sorganov@gmail.com> wrote:\n>> Hi Jacob,\n>>\n>> Jacob Keller <jacob.keller@gmail.com> writes:\n>>> On Wed, Apr 11, 2018 at 6:13 AM, Sergey Organov <sorganov@gmail.com> wrote:\n>>>> It was rather --recreate-merges just a few weeks ago, and I've seen\n>>>> nobody actually commented either in favor or against the\n>>>> --rebase-merges.\n>>>>\n>>>> git rebase --rebase-merges\n>>>>\n>>>\n>>> I'm going to jump in here and say that *I* prefer --rebase-merges, as\n>>> it clearly mentions merge commits (which is the thing that changes).\n>>\n>> OK, thanks, it's fair and the first argument in favor of --rebase-merges\n>> I see.\n>>\n>\n> I'd be ok with \"--keep-merges\" also. I don't like the idea of\n> \"flatten\" as it, to me, means that anyone who wants to understand the\n> option without prior knowledge must immediately read the man page or\n> they will be confused. Something like \"--rebase-merges\" at least my\n> coworkers got it instantly. The same could be said for \"--keep-merges\"\n> too, but so far no one I asked said the immediately understood\n> \"--no-flatten\".\n\nIf they got --rebase-merges instantly, they should already have known\nwhat \"rebase\" and \"merge\" mean. If so, they are likely Git users that\nare already familiar with \"git rebase\" and thus at least heard about a\nbuddy called --preserve-merges. If it's the case indeed, the outcome\nyou've got was rather predictable, me thinks.\n\nNow, what are the consequences?\n\nWhen pleasing maximum number of users of --preserve-merges (and probably\n--recreate-merges) is number one target of design, while the rest of\nissues are secondary, being in favor of --rebase-merges, --keep-merges,\nor --<whatever>-merges is only natural indeed.\n\nHowever, I don't believe meeting user expectations should be the number\none criteria of a good design. Sound technical design should come first,\nand meeting user expectations, provided they don't contradict the\ndesign, only second. That's how Git was born, that's how it should\ncontinue to evolve. Going in reverse direction, from user expectations\nto design, will give us Bzr, not Git.\n\nIn discussing of these patch series though I rather see care for user\nexpectations or preferences being used as an excuse for questionable\ndesign all the time. That's what actually bothers me much more than\nchoosing particular names for particular options.\n\nNarrowing back to the topic, don't you see, honestly, that there is\nsomething wrong with:\n\ngit rebase --rebase-merges\n\nthat is supposedly easy to understand even without referring to the\nmanual, yet when you do happen to refer to the manual, you suddenly\nrealize it's not that easy to understand:\n\n--rebase-merges[=(rebase-cousins|no-rebase-cousins)]\n\tRebase merge commits instead of flattening the history by replaying\n\tmerges. Merge conflict resolutions or manual amendments to merge\n\tcommits are not rebased automatically, but have to be applied\n\tmanually.\n\n???\n\nPlease read the description. Actually read as if you never knew what's\nall this about.\n\nWhy does it use \"flattening the history\" that is supposedly hard to\nunderstand to explain \"--rebase-merges\" that is supposedly easy to\nunderstand? How comes? And if it's actually a good explanation, why\ndidn't author just call the option --no-flatten-history, to match its\ndescription?\n\nNext, what is \"replaying merges\", exactly? That's explaining one term\nwith another that has not being explained and sounds being even more\nvague.\n\nFurther, \"Merge conflict resolutions or manual amendments to merge\ncommits are not rebased automatically, but have to be applied manually.\"\nis mutually exclusive with \"Rebase merge commits\", making all this even\nmore messy. A merge commit is just content with multiple parents, and\n`git rebase`, by definition, reapplies the changes the content\nintroduces. Any \"amendments\" or \"resolutions\" that could have been\nhappening (or not) when that commit was being created are entirely\nirrelevant.\n\nFurther yet it goes with:\n\n\"By default, or when `no-rebase-cousins` was specified, commits which do\nnot have `<upstream>` as direct ancestor will keep their original branch\npoint.\"\n\nReally? What does it actually mean? What is \"commit branch point\",\nexactly? What \"direct ancestor\" means in this context, exactly? Provided\neven when I do know what the option actually does, the description looks\nwrong, how it could explain anything?\n\nHaving all this right here in the patch series, you guys try to convince\nme that it should not be fixed? That it meets user expectations? You\nknow what? I, as a user, have somewhat higher expectations.\n\nBelow is my final attempt at actually defining a sane alternative. If\nyou still find this approach inferior, please feel free to ignore it. I\nadded \"history\" at the end of original --no-flatten, as a courtesy to\nuser expectations, as you seem to prefer more verbose names:\n\n----------\n\n--flatten-history\n\tFlatten rebased history by reapplying non-merge commits only.\n        This is the default.\n\n--no-flatten-history[=<options>]\n\tDo not flatten rebased history. When this option is specified,\n\tthe original shape of the history being rebased will be\n\tpreserved. <options> is comma-separated list of supported\n\toptions.\n\nThe following options are supported:\n\n'merge-heads' - perform a merge of rebased merge heads instead of\nrebasing original merge commits. Only commit messages will be taken from\noriginal merge commits in this mode.\n\n'rebase-cousins' - commits which do not have <upstream> as\ndirect ancestor will not keep their original branch point.\n\n------------\n\nIn fact I think that 'rebase-cousins' should be removed as making no\nsense, but I've borrowed it from the original anyway, to show how the\nconcept of this option itself works to support multiple additional\noptions.\n\n-- Sergey\n"},{"id":"344999","messageId":"b4ea06e6-462e-061e-2d66-0b2c7ee346ef@talktalk.net","threadId":"47635","inReplyTo":"BA40DE939AF14BC18B496FDA27281CA2@PhilipOakley","subject":"Re: [PATCH v6 04/15] sequencer: introduce new commands to reset the revision","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-18T18:00:25Z","receivedAt":"2018-04-18T18:00:37Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 15/04/18 18:17, Philip Oakley wrote:\n> From: \"Phillip Wood\" <phillip.wood@talktalk.net>\n> : Friday, April 13, 2018 11:03 AM\n>> If a label or reset command fails it is likely to be due to a\n>> typo. Rescheduling the command would make it easier for the user to fix\n>> the problem as they can just run 'git rebase --edit-todo'. \n> \n> Is this worth noting in the command documentation? \"If the label or\n> reset command fails then fix\n> the problem by runnning 'git rebase --edit-todo'.\" ?\n> \n> Just a thought.\n\nYes that's a good idea, thanks\n\n>> It also\n>> ensures that the problem has actually been fixed when the rebase\n>> continues. I think you could do it like this\n>>\n> <snip>\n> -- \n> Philip\n> (also @dunelm, 73-79..)\nThat's a bit before me (94-00) were you there when they were building\nthe hill colleges and some of the science site?\n\nBest Wishes\n\nPhillip\n"},{"id":"345000","messageId":"66b0b2e6-11b6-20f9-da6b-211c9c4dc758@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804140248110.234@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 05/15] sequencer: introduce the `merge` command","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-18T18:04:01Z","receivedAt":"2018-04-18T18:04:08Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 14/04/18 01:51, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Fri, 13 Apr 2018, Phillip Wood wrote:\n> \n>> On 13/04/18 11:12, Phillip Wood wrote:\n>>> @@ -3030,7 +3029,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>>>  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n>>>  \n>>>  \t\tif (res < 0 && (item->command == TODO_LABEL ||\n>>> -\t\t\t\titem->command == TODO_RESET)) {\n>>> +\t\t\t\titem->command == TODO_RESET ||\n>>> +\t\t\t\titem->command == TODO_MERGE)) {\n>>\n>> Unfortunately it's not as simple as that - we only want to reschedule if\n>> merge_recursive() fails, not if run_git_commit() does.\n> \n> Correct. How about introducing a flag `reschedule` that is passed to\n> do_label(), do_reset() and do_merge()?\n\nThat would work (I was thinking about using return codes but having a\nparameter is a better idea). Do you want me to re-roll the fixups or are\nyou happy to make the changes in your next version?\n\n> \n> Seeing as do_reset() and do_merge() already have a replay_opts parameter,\n> we could add a field `needs_rescheduling` and pass the replay_opts also to\n> do_label().\n\nI'm slightly wary of putting state in an options structure but maybe it\ndoesn't matter.\n\nBest Wishes\n\nPhillip\n\n> Ciao,\n> Dscho\n> \n\n"},{"id":"345060","messageId":"87fu3r257z.fsf@javad.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804140146450.234@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-19T04:24:48Z","receivedAt":"2018-04-19T04:24:54Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Phillip,\n>\n> On Fri, 13 Apr 2018, Phillip Wood wrote:\n>\n>> On 12/04/18 23:02, Johannes Schindelin wrote:\n>> > \n>> > [...]\n>> > \n>> > So: the order of the 3-way merges does matter.\n>> >\n>> > [...]\n>> \n>> Those conflicts certainly look intimidating (and the ones in your later\n>> reply with the N way merge example still look quite complicated). One\n>> option would be just to stop and have the user resolve the conflicts\n>> after each conflicting 3-way merge rather than at the end of all the\n>> merges. There are some downsides: there would need to be a way to\n>> explain to the user that this is an intermediate step (and what that\n>> step was); the code would have to do some book keeping to know where it\n>> had got to; and it would stop and prompt the user to resolve conflicts\n>> more often which could be annoying but hopefully they'd be clearer to\n>> resolve because they weren't nested.\n>\n> I thought about that. But as I pointed out: the order of the merges *does*\n> matter. Otherwise we force the user to resolve conflicts that they\n> *already* resolved during this rebase...\n\nHow it's relevant to what Phillip suggested? How the order of taking 2\nsteps, A and B, affects an ability to stop after the first step? It's\nstill either \"A,stop,B\" or \"B,stop,A\", depending on the chosen order.\n\nWhat's the _actual_ problem here, if any?\n\n-- Sergey\n"},{"id":"345078","messageId":"CA+P7+xrv54RBFx1eYbvgVyqQ88uxT73vOf3jazCVsvuXwvd-gA@mail.gmail.com","threadId":"47635","inReplyTo":"87fu3r257z.fsf@javad.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-04-19T08:23:06Z","receivedAt":"2018-04-19T08:23:33Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Wed, Apr 18, 2018 at 9:24 PM, Sergey Organov <sorganov@gmail.com> wrote:\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n>> Hi Phillip,\n>>\n>> On Fri, 13 Apr 2018, Phillip Wood wrote:\n>>\n>>> On 12/04/18 23:02, Johannes Schindelin wrote:\n>>> >\n>>> > [...]\n>>> >\n>>> > So: the order of the 3-way merges does matter.\n>>> >\n>>> > [...]\n>>>\n>>> Those conflicts certainly look intimidating (and the ones in your later\n>>> reply with the N way merge example still look quite complicated). One\n>>> option would be just to stop and have the user resolve the conflicts\n>>> after each conflicting 3-way merge rather than at the end of all the\n>>> merges. There are some downsides: there would need to be a way to\n>>> explain to the user that this is an intermediate step (and what that\n>>> step was); the code would have to do some book keeping to know where it\n>>> had got to; and it would stop and prompt the user to resolve conflicts\n>>> more often which could be annoying but hopefully they'd be clearer to\n>>> resolve because they weren't nested.\n>>\n>> I thought about that. But as I pointed out: the order of the merges *does*\n>> matter. Otherwise we force the user to resolve conflicts that they\n>> *already* resolved during this rebase...\n>\n> How it's relevant to what Phillip suggested? How the order of taking 2\n> steps, A and B, affects an ability to stop after the first step? It's\n> still either \"A,stop,B\" or \"B,stop,A\", depending on the chosen order.\n>\n> What's the _actual_ problem here, if any?\n>\n> -- Sergey\n\nI believe the order of the merges changes which ones cause conflicts,\nbut it's possible to generate pre-images (i.e. a set of parents to\nmerge) which cause conflicts regardless of which ordering we pick, so\nI'm not sure there is a \"best ordering\".\n\nThanks,\nJake\n"},{"id":"345087","messageId":"87bmefzb8f.fsf@javad.com","threadId":"47635","inReplyTo":"CA+P7+xrv54RBFx1eYbvgVyqQ88uxT73vOf3jazCVsvuXwvd-gA@mail.gmail.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-04-19T11:28:32Z","receivedAt":"2018-04-19T11:28:39Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Hi Jacob,\n\nJacob Keller <jacob.keller@gmail.com> writes:\n\n> On Wed, Apr 18, 2018 at 9:24 PM, Sergey Organov <sorganov@gmail.com> wrote:\n>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>>\n>>> Hi Phillip,\n>>>\n>>> On Fri, 13 Apr 2018, Phillip Wood wrote:\n>>>\n>>>> On 12/04/18 23:02, Johannes Schindelin wrote:\n>>>> >\n>>>> > [...]\n>>>> >\n>>>> > So: the order of the 3-way merges does matter.\n>>>> >\n>>>> > [...]\n>>>>\n>>>> Those conflicts certainly look intimidating (and the ones in your later\n>>>> reply with the N way merge example still look quite complicated). One\n>>>> option would be just to stop and have the user resolve the conflicts\n>>>> after each conflicting 3-way merge rather than at the end of all the\n>>>> merges. There are some downsides: there would need to be a way to\n>>>> explain to the user that this is an intermediate step (and what that\n>>>> step was); the code would have to do some book keeping to know where it\n>>>> had got to; and it would stop and prompt the user to resolve conflicts\n>>>> more often which could be annoying but hopefully they'd be clearer to\n>>>> resolve because they weren't nested.\n>>>\n>>> I thought about that. But as I pointed out: the order of the merges *does*\n>>> matter. Otherwise we force the user to resolve conflicts that they\n>>> *already* resolved during this rebase...\n>>\n>> How it's relevant to what Phillip suggested? How the order of taking 2\n>> steps, A and B, affects an ability to stop after the first step? It's\n>> still either \"A,stop,B\" or \"B,stop,A\", depending on the chosen order.\n>>\n>> What's the _actual_ problem here, if any?\n>>\n>> -- Sergey\n>\n> I believe the order of the merges changes which ones cause conflicts,\n> but it's possible to generate pre-images (i.e. a set of parents to\n> merge) which cause conflicts regardless of which ordering we pick, so\n> I'm not sure there is a \"best ordering\".\n\nI totally agree, but this still does not address the problem of\nrecursive conflicts, and it's this particular problem that Phillip's\nsuggestion addresses. Just stop after _every_ conflict and let user\nresolve it, whatever the order is. Recursive conflicts are simply\nshowstoppers. Whatever cleverness is invented to represent them, it will\nstill outsmart most of the users.\n\nAs for your statement, it should be clear the absolute \"best ordering\"\nsimply can't exist, as merges are inherently symmetric in the DAG. One\ncan try all orders in turn and select one that brings less conflicts\nthough. Comparing conflicts is a problem by itself here. Recursive vs\nnon-recursive and conflict vs no-conflict are obvious and could be the\nonly checks adopted, all other cases being considered equal.\n\nIf we do select fixed order method, or can't find the best order, the\ndefault order should simply match the natural one, first parent first.\nBesides, it's the change to the mainline that is most important for an\nactual Git merge, so letting it come first sounds most reasonable.\n\n-- Sergey\n\n"},{"id":"345089","messageId":"nycvar.QRO.7.76.6.1804191402060.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804140248110.234@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 05/15] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:03:38Z","receivedAt":"2018-04-19T12:04:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Sat, 14 Apr 2018, Johannes Schindelin wrote:\n\n> On Fri, 13 Apr 2018, Phillip Wood wrote:\n> \n> > On 13/04/18 11:12, Phillip Wood wrote:\n> > > @@ -3030,7 +3029,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n> > >  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n> > >  \n> > >  \t\tif (res < 0 && (item->command == TODO_LABEL ||\n> > > -\t\t\t\titem->command == TODO_RESET)) {\n> > > +\t\t\t\titem->command == TODO_RESET ||\n> > > +\t\t\t\titem->command == TODO_MERGE)) {\n> > \n> > Unfortunately it's not as simple as that - we only want to reschedule if\n> > merge_recursive() fails, not if run_git_commit() does.\n> \n> Correct. How about introducing a flag `reschedule` that is passed to\n> do_label(), do_reset() and do_merge()?\n> \n> Seeing as do_reset() and do_merge() already have a replay_opts parameter,\n> we could add a field `needs_rescheduling` and pass the replay_opts also to\n> do_label().\n\nNevermind, we already use the trick in do_pick_commit() that -1 means:\nreschedule, 0 means: success, and 1 means: merge conflicts (don't bother\nrescheduling).\n\nIt just had slipped my mind; I use the same convention in do_merge() now.\n\nThank you so much for your review and suggestions. I *think* I\nincorporated it all.\n\nCiao,\nDscho\n"},{"id":"345091","messageId":"cover.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1523362469.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 00/17] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:12:39Z","receivedAt":"2018-04-19T12:13:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, I dreamt of an interactive rebase that would not\nflatten branch structure, but instead recreate the commit topology\nfaithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --rebase-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge -C 3456 D # C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --rebase-merges. And then one\nto allow for rebasing merge commits in a smarter way (this one will need\na bit more work, though, as it can result in very complicated, nested\nmerge conflicts *very* easily).\n\nChanges since v6:\n\n- Reworded the REBASING MERGES section of the man page a bit (thanks, Martin &\n  Phillip!).\n\n- The `reset` todo command now refuses to overwrite untracked files (thanks\n  Phillip!).\n\n- The do_merge() function was prevented from leaking memory left and right.\n\n- Added a nice advice for the case when todo commands were rescheduled.\n\n- Refactored the way we get to the original line of any given todo command in\n  the todo list, simplifying even existing code to make it a lot more readable.\n\n- Failed `label` and `reset` commands, as well as `merge` that failed before\n  even attempting to merge, are now rescheduled automatically (thanks\n  Phillip and Philip!).\n\n- The do_merge() function no longer tries to commit when there are merge\n  conflicts (thanks Phillip!).\n\n- When do_merge() failed to run the recursive merge, it no longer claims that\n  there were conflicts (thanks Phillip!).\n\n- When the merge failed, we now write out the index before giving `rerere` a\n  chance (d'oh!).\n\n\nJohannes Schindelin (15):\n  sequencer: avoid using errno clobbered by rollback_lock_file()\n  sequencer: make rearrange_squash() a bit more obvious\n  sequencer: refactor how original todo list lines are accessed\n  sequencer: offer helpful advice when a command was rescheduled\n  sequencer: introduce new commands to reset the revision\n  # This is a combination of 2 commits. # This is the 1st commit\n    message:\n  sequencer: fast-forward `merge` commands, if possible\n  rebase-helper --make-script: introduce a flag to rebase merges\n  rebase: introduce the --rebase-merges option\n  sequencer: make refs generated by the `label` command worktree-local\n  sequencer: handle post-rewrite for merge commands\n  rebase --rebase-merges: avoid \"empty merges\"\n  pull: accept --rebase=merges to recreate the branch topology\n  rebase -i: introduce --rebase-merges=[no-]rebase-cousins\n  rebase -i --rebase-merges: add a section to the man page\n\nPhillip Wood (1):\n  rebase --rebase-merges: add test for --keep-empty\n\nStefan Beller (1):\n  git-rebase--interactive: clarify arguments\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   5 +-\n Documentation/git-rebase.txt           | 147 ++++-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |  18 +-\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |  22 +-\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 869 +++++++++++++++++++++++--\n sequencer.h                            |   7 +\n t/t3421-rebase-topology-linear.sh      |   1 +\n t/t3430-rebase-merges.sh               | 221 +++++++\n 14 files changed, 1288 insertions(+), 60 deletions(-)\n create mode 100755 t/t3430-rebase-merges.sh\n\n\nbase-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v7\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v7\n\nInterdiff vs v6:\n diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n index be946de2efb..0ff83b62821 100644\n --- a/Documentation/git-rebase.txt\n +++ b/Documentation/git-rebase.txt\n @@ -849,14 +849,18 @@ merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n  In contrast to a regular interactive rebase, there are `label`, `reset` and\n  `merge` commands in addition to `pick` ones.\n  \n -The `label` command puts a label to whatever will be the current\n -revision when that command is executed. Internally, these labels are\n -worktree-local refs that will be deleted when the rebase finishes or\n -when it is aborted. That way, rebase operations in multiple worktrees\n -linked to the same repository do not interfere with one another.\n -\n -The `reset` command is essentially a `git reset --hard` to the specified\n -revision (typically a previously-labeled one).\n +The `label` command associates a label with the current HEAD when that\n +command is executed. These labels are created as worktree-local refs\n +(`refs/rewritten/<label>`) that will be deleted when the rebase\n +finishes. That way, rebase operations in multiple worktrees linked to\n +the same repository do not interfere with one another. If the `label` command\n +fails, it is rescheduled immediately, with a helpful message how to proceed.\n +\n +The `reset` command is essentially a `git read-tree -m -u` (think: `git\n +reset --hard`, but refusing to overwrite untracked files) to the\n +specified revision (typically a previously-labeled one). If the `reset`\n +command fails, it is rescheduled immediately, with a helpful message how to\n +proceed.\n  \n  The `merge` command will merge the specified revision into whatever is\n  HEAD at that time. With `-C <original-commit>`, the commit message of\n @@ -864,13 +868,16 @@ the specified merge commit will be used. When the `-C` is changed to\n  a lower-case `-c`, the message will be opened in an editor after a\n  successful merge so that the user can edit the message.\n  \n +If a `merge` command fails for any reason other than merge conflicts (i.e.\n +when the merge operation did not even start), it is rescheduled immediately.\n +\n  At this time, the `merge` command will *always* use the `recursive`\n  merge strategy, with no way to choose a different one. To work around\n  this, an `exec` command can be used to call `git merge` explicitly,\n  using the fact that the labels are worktree-local refs (the ref\n  `refs/rewritten/onto` would correspond to the label `onto`).\n  \n -Note: the first command (`reset onto`) labels the revision onto which\n +Note: the first command (`label onto`) labels the revision onto which\n  the commits are rebased; The name `onto` is just a convention, as a nod\n  to the `--onto` option.\n  \n diff --git a/sequencer.c b/sequencer.c\n index 809df1ce484..3c7bb5d3fd8 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -1925,6 +1925,23 @@ static int count_commands(struct todo_list *todo_list)\n  \treturn count;\n  }\n  \n +static int get_item_line_offset(struct todo_list *todo_list, int index)\n +{\n +\treturn index < todo_list->nr ?\n +\t\ttodo_list->items[index].offset_in_buf : todo_list->buf.len;\n +}\n +\n +static const char *get_item_line(struct todo_list *todo_list, int index)\n +{\n +\treturn todo_list->buf.buf + get_item_line_offset(todo_list, index);\n +}\n +\n +static int get_item_line_length(struct todo_list *todo_list, int index)\n +{\n +\treturn get_item_line_offset(todo_list, index + 1)\n +\t\t-  get_item_line_offset(todo_list, index);\n +}\n +\n  static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)\n  {\n  \tint fd;\n @@ -2299,29 +2316,27 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n  \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n  \tif (fd < 0)\n  \t\treturn error_errno(_(\"could not lock '%s'\"), todo_path);\n -\toffset = next < todo_list->nr ?\n -\t\ttodo_list->items[next].offset_in_buf : todo_list->buf.len;\n +\toffset = get_item_line_offset(todo_list, next);\n  \tif (write_in_full(fd, todo_list->buf.buf + offset,\n  \t\t\ttodo_list->buf.len - offset) < 0)\n  \t\treturn error_errno(_(\"could not write to '%s'\"), todo_path);\n  \tif (commit_lock_file(&todo_lock) < 0)\n  \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n  \n -\tif (is_rebase_i(opts)) {\n -\t\tconst char *done_path = rebase_path_done();\n -\t\tint fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);\n -\t\tint prev_offset = !next ? 0 :\n -\t\t\ttodo_list->items[next - 1].offset_in_buf;\n +\tif (is_rebase_i(opts) && next > 0) {\n +\t\tconst char *done = rebase_path_done();\n +\t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n +\t\tint ret = 0;\n  \n -\t\tif (fd >= 0 && offset > prev_offset &&\n -\t\t    write_in_full(fd, todo_list->buf.buf + prev_offset,\n -\t\t\t\t  offset - prev_offset) < 0) {\n -\t\t\tclose(fd);\n -\t\t\treturn error_errno(_(\"could not write to '%s'\"),\n -\t\t\t\t\t   done_path);\n -\t\t}\n -\t\tif (fd >= 0)\n -\t\t\tclose(fd);\n +\t\tif (fd < 0)\n +\t\t\treturn 0;\n +\t\tif (write_in_full(fd, get_item_line(todo_list, next - 1),\n +\t\t\t\t  get_item_line_length(todo_list, next - 1))\n +\t\t    < 0)\n +\t\t\tret = error_errno(_(\"could not write to '%s'\"), done);\n +\t\tif (close(fd) < 0)\n +\t\t\tret = error_errno(_(\"failed to finalize '%s'\"), done);\n +\t\treturn ret;\n  \t}\n  \treturn 0;\n  }\n @@ -2619,7 +2634,6 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n  \tunpack_tree_opts.fn = oneway_merge;\n  \tunpack_tree_opts.merge = 1;\n  \tunpack_tree_opts.update = 1;\n -\tunpack_tree_opts.reset = 1;\n  \n  \tif (read_cache_unmerged()) {\n  \t\trollback_lock_file(&lock);\n @@ -2671,6 +2685,17 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \tstatic struct lock_file lock;\n  \tconst char *p;\n  \n +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {\n +\t\tret = -1;\n +\t\tgoto leave_merge;\n +\t}\n +\n +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n +\tif (!head_commit) {\n +\t\tret = error(_(\"cannot merge without a current revision\"));\n +\t\tgoto leave_merge;\n +\t}\n +\n  \toneline_offset = arg_len;\n  \tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n  \tp = arg + merge_arg_len;\n @@ -2688,19 +2713,10 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n  \t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n  \t}\n -\tif (!merge_commit) {\n -\t\terror(_(\"could not resolve '%s'\"), ref_name.buf);\n -\t\tstrbuf_release(&ref_name);\n -\t\treturn -1;\n -\t}\n  \n -\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n -\t\treturn -1;\n -\n -\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n -\tif (!head_commit) {\n -\t\trollback_lock_file(&lock);\n -\t\treturn error(_(\"cannot merge without a current revision\"));\n +\tif (!merge_commit) {\n +\t\tret = error(_(\"could not resolve '%s'\"), ref_name.buf);\n +\t\tgoto leave_merge;\n  \t}\n  \n  \tif (commit) {\n @@ -2709,21 +2725,20 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\tint len;\n  \n  \t\tif (!message) {\n -\t\t\trollback_lock_file(&lock);\n -\t\t\treturn error(_(\"could not get commit message of '%s'\"),\n -\t\t\t\t     oid_to_hex(&commit->object.oid));\n +\t\t\tret = error(_(\"could not get commit message of '%s'\"),\n +\t\t\t\t    oid_to_hex(&commit->object.oid));\n +\t\t\tgoto leave_merge;\n  \t\t}\n  \t\twrite_author_script(message);\n  \t\tfind_commit_subject(message, &body);\n  \t\tlen = strlen(body);\n -\t\tif (write_message(body, len, git_path_merge_msg(), 0) < 0) {\n +\t\tret = write_message(body, len, git_path_merge_msg(), 0);\n +\t\tunuse_commit_buffer(commit, message);\n +\t\tif (ret) {\n  \t\t\terror_errno(_(\"could not write '%s'\"),\n  \t\t\t\t    git_path_merge_msg());\n -\t\t\tunuse_commit_buffer(commit, message);\n -\t\t\trollback_lock_file(&lock);\n -\t\t\treturn -1;\n +\t\t\tgoto leave_merge;\n  \t\t}\n -\t\tunuse_commit_buffer(commit, message);\n  \t} else {\n  \t\tstruct strbuf buf = STRBUF_INIT;\n  \t\tint len;\n @@ -2742,14 +2757,13 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t\t\tlen = buf.len;\n  \t\t}\n  \n -\t\tif (write_message(p, len, git_path_merge_msg(), 0) < 0) {\n +\t\tret = write_message(p, len, git_path_merge_msg(), 0);\n +\t\tstrbuf_release(&buf);\n +\t\tif (ret) {\n  \t\t\terror_errno(_(\"could not write '%s'\"),\n  \t\t\t\t    git_path_merge_msg());\n -\t\t\tstrbuf_release(&buf);\n -\t\t\trollback_lock_file(&lock);\n -\t\t\treturn -1;\n +\t\t\tgoto leave_merge;\n  \t\t}\n -\t\tstrbuf_release(&buf);\n  \t}\n  \n  \t/*\n @@ -2777,10 +2791,10 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \t    !commit->parents->next->next &&\n  \t    !oidcmp(&commit->parents->next->item->object.oid,\n  \t\t    &merge_commit->object.oid)) {\n -\t\tstrbuf_release(&ref_name);\n  \t\trollback_lock_file(&lock);\n -\t\treturn fast_forward_to(&commit->object.oid,\n -\t\t\t\t       &head_commit->object.oid, 0, opts);\n +\t\tret = fast_forward_to(&commit->object.oid,\n +\t\t\t\t      &head_commit->object.oid, 0, opts);\n +\t\tgoto leave_merge;\n  \t}\n  \n  \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n @@ -2790,10 +2804,9 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \tbases = get_merge_bases(head_commit, merge_commit);\n  \tif (bases && !oidcmp(&merge_commit->object.oid,\n  \t\t\t     &bases->item->object.oid)) {\n -\t\tstrbuf_release(&ref_name);\n -\t\trollback_lock_file(&lock);\n +\t\tret = 0;\n  \t\t/* skip merging an ancestor of HEAD */\n -\t\treturn 0;\n +\t\tgoto leave_merge;\n  \t}\n  \n  \tfor (j = bases; j; j = j->next)\n @@ -2807,28 +2820,40 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \to.buffer_output = 2;\n  \n  \tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n -\tif (!ret)\n -\t\trerere(opts->allow_rerere_auto);\n  \tif (ret <= 0)\n  \t\tfputs(o.obuf.buf, stdout);\n  \tstrbuf_release(&o.obuf);\n  \tif (ret < 0) {\n -\t\tstrbuf_release(&ref_name);\n -\t\trollback_lock_file(&lock);\n -\t\treturn error(_(\"conflicts while merging '%.*s'\"),\n -\t\t\t     merge_arg_len, arg);\n +\t\terror(_(\"could not even attempt to merge '%.*s'\"),\n +\t\t      merge_arg_len, arg);\n +\t\tgoto leave_merge;\n  \t}\n +\t/*\n +\t * The return value of merge_recursive() is 1 on clean, and 0 on\n +\t * unclean merge.\n +\t *\n +\t * Let's reverse that, so that do_merge() returns 0 upon success and\n +\t * 1 upon failed merge (keeping the return value -1 for the cases where\n +\t * we will want to reschedule the `merge` command).\n +\t */\n +\tret = !ret;\n  \n  \tif (active_cache_changed &&\n  \t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n -\t\tstrbuf_release(&ref_name);\n -\t\treturn error(_(\"merge: Unable to write new index file\"));\n +\t\tret = error(_(\"merge: Unable to write new index file\"));\n +\t\tgoto leave_merge;\n  \t}\n +\n  \trollback_lock_file(&lock);\n +\tif (ret)\n +\t\trerere(opts->allow_rerere_auto);\n +\telse\n +\t\tret = run_git_commit(git_path_merge_msg(), opts,\n +\t\t\t\t     run_commit_flags);\n  \n -\tret = run_git_commit(git_path_merge_msg(), opts, run_commit_flags);\n +leave_merge:\n  \tstrbuf_release(&ref_name);\n -\n +\trollback_lock_file(&lock);\n  \treturn ret;\n  }\n  \n @@ -2922,6 +2947,17 @@ static const char *reflog_message(struct replay_opts *opts,\n  \treturn buf.buf;\n  }\n  \n +static const char rescheduled_advice[] =\n +N_(\"Could not execute the todo command\\n\"\n +\"\\n\"\n +\"    %.*s\"\n +\"\\n\"\n +\"It has been rescheduled; To edit the command before continuing, please\\n\"\n +\"edit the todo list first:\\n\"\n +\"\\n\"\n +\"    git rebase --edit-todo\\n\"\n +\"    git rebase --continue\\n\");\n +\n  static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  {\n  \tint res = 0;\n @@ -2966,7 +3002,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\tres = do_pick_commit(item->command, item->commit,\n  \t\t\t\t\topts, is_final_fixup(todo_list));\n  \t\t\tif (is_rebase_i(opts) && res < 0) {\n -\t\t\t\t/* Reschedule */\n +reschedule:\n +\t\t\t\tadvise(_(rescheduled_advice),\n +\t\t\t\t       get_item_line_length(todo_list,\n +\t\t\t\t\t\t\t    todo_list->current),\n +\t\t\t\t       get_item_line(todo_list,\n +\t\t\t\t\t\t     todo_list->current));\n  \t\t\t\ttodo_list->current--;\n  \t\t\t\tif (save_todo(todo_list, opts))\n  \t\t\t\t\treturn -1;\n @@ -2990,7 +3031,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\t\t\tintend_to_amend();\n  \t\t\t\treturn error_failed_squash(item->commit, opts,\n  \t\t\t\t\titem->arg_len, item->arg);\n -\t\t\t} else if (res && is_rebase_i(opts))\n +\t\t\t} else if (res && is_rebase_i(opts) && item->commit)\n  \t\t\t\treturn res | error_with_patch(item->commit,\n  \t\t\t\t\titem->arg, item->arg_len, opts, res,\n  \t\t\t\t\titem->command == TODO_REWORD);\n @@ -3016,13 +3057,17 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\t\t/* `current` will be incremented below */\n  \t\t\t\ttodo_list->current = -1;\n  \t\t\t}\n -\t\t} else if (item->command == TODO_LABEL)\n -\t\t\tres = do_label(item->arg, item->arg_len);\n -\t\telse if (item->command == TODO_RESET)\n -\t\t\tres = do_reset(item->arg, item->arg_len, opts);\n -\t\telse if (item->command == TODO_MERGE) {\n +\t\t} else if (item->command == TODO_LABEL) {\n +\t\t\tif ((res = do_label(item->arg, item->arg_len)))\n +\t\t\t\tgoto reschedule;\n +\t\t} else if (item->command == TODO_RESET) {\n +\t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n +\t\t\t\tgoto reschedule;\n +\t\t} else if (item->command == TODO_MERGE) {\n  \t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n  \t\t\t\t       item->flags, opts);\n +\t\t\tif (res < 0)\n +\t\t\t\tgoto reschedule;\n  \t\t\tif (item->commit)\n  \t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n  \t\t\t\t\t\t    peek_command(todo_list, 1));\n @@ -4046,8 +4091,7 @@ int skip_unnecessary_picks(void)\n  \t\toid = &item->commit->object.oid;\n  \t}\n  \tif (i > 0) {\n -\t\tint offset = i < todo_list.nr ?\n -\t\t\ttodo_list.items[i].offset_in_buf : todo_list.buf.len;\n +\t\tint offset = get_item_line_offset(&todo_list, i);\n  \t\tconst char *done_path = rebase_path_done();\n  \n  \t\tfd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);\n @@ -4227,12 +4271,10 @@ int rearrange_squash(void)\n  \t\t\t\tcontinue;\n  \n  \t\t\twhile (cur >= 0) {\n -\t\t\t\tint offset = todo_list.items[cur].offset_in_buf;\n -\t\t\t\tint end_offset = cur + 1 < todo_list.nr ?\n -\t\t\t\t\ttodo_list.items[cur + 1].offset_in_buf :\n -\t\t\t\t\ttodo_list.buf.len;\n -\t\t\t\tchar *bol = todo_list.buf.buf + offset;\n -\t\t\t\tchar *eol = todo_list.buf.buf + end_offset;\n +\t\t\t\tconst char *bol =\n +\t\t\t\t\tget_item_line(&todo_list, cur);\n +\t\t\t\tconst char *eol =\n +\t\t\t\t\tget_item_line(&todo_list, cur + 1);\n  \n  \t\t\t\t/* replace 'pick', by 'fixup' or 'squash' */\n  \t\t\t\tcommand = todo_list.items[cur].command;\n diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n index ee006810573..f2de7059830 100755\n --- a/t/t3430-rebase-merges.sh\n +++ b/t/t3430-rebase-merges.sh\n @@ -52,25 +52,24 @@ test_expect_success 'setup' '\n  \tgit tag -m H H\n  '\n  \n -cat >script-from-scratch <<\\EOF\n -label onto\n -\n -# onebranch\n -pick G\n -pick D\n -label onebranch\n +test_expect_success 'create completely different structure' '\n +\tcat >script-from-scratch <<-\\EOF &&\n +\tlabel onto\n  \n -# second\n -reset onto\n -pick B\n -label second\n +\t# onebranch\n +\tpick G\n +\tpick D\n +\tlabel onebranch\n  \n -reset onto\n -merge -C H second\n -merge onebranch # Merge the topic branch 'onebranch'\n -EOF\n +\t# second\n +\treset onto\n +\tpick B\n +\tlabel second\n  \n -test_expect_success 'create completely different structure' '\n +\treset onto\n +\tmerge -C H second\n +\tmerge onebranch # Merge the topic branch '\\''onebranch'\\''\n +\tEOF\n  \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n  \ttest_tick &&\n  \tgit rebase -i -r A &&\n @@ -115,6 +114,17 @@ test_expect_success 'generate correct todo list' '\n  \ttest_cmp expect output\n  '\n  \n +test_expect_success '`reset` refuses to overwrite untracked files' '\n +\tgit checkout -b refuse-to-reset &&\n +\ttest_commit dont-overwrite-untracked &&\n +\tgit checkout @{-1} &&\n +\t: >dont-overwrite-untracked.t &&\n +\techo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n +\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n +\ttest_must_fail git rebase -r HEAD &&\n +\tgit rebase --abort\n +'\n +\n  test_expect_success 'with a branch tip that was cherry-picked already' '\n  \tgit checkout -b already-upstream master &&\n  \tbase=\"$(git rev-parse --verify HEAD)\" &&\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n"},{"id":"345092","messageId":"ae9847669b70282a0e17e46030b5c4e609d73923.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 01/17] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:15:15Z","receivedAt":"2018-04-19T12:15:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"As pointed out in a review of the `--rebase-merges` patch series,\n`rollback_lock_file()` clobbers errno. Therefore, we have to report the\nerror message that uses errno before calling said function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 10 ++++++----\n 1 file changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 667f35ebdff..096e6d241e0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -345,12 +345,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n \tif (msg_fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n \tif (write_in_full(msg_fd, buf, len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n+\t\terror_errno(_(\"could not write eol to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write eol to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&msg_file) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), filename);\n@@ -2119,9 +2121,9 @@ static int save_head(const char *head)\n \twritten = write_in_full(fd, buf.buf, buf.len);\n \tstrbuf_release(&buf);\n \tif (written < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), git_path_head_file());\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t   git_path_head_file());\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&head_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), git_path_head_file());\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345093","messageId":"1bea3d4cd72e8631780bb460148ad94636427744.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 02/17] sequencer: make rearrange_squash() a bit more obvious","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:18:41Z","receivedAt":"2018-04-19T12:19:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"There are some commands that have to be skipped from rearranging by virtue\nof not handling any commits.\n\nHowever, the logic was not quite obvious: it skipped commands based on\ntheir position in the enum todo_command.\n\nInstead, let's make it explicit that we skip all commands that do not\nhandle any commit. With one exception: the `drop` command, because it,\nwell, drops the commit and is therefore not eligible to rearranging.\n\nNote: this is a bit academic at the moment because the only time we call\n`rearrange_squash()` is directly after generating the todo list, when we\nhave nothing but `pick` commands anyway.\n\nHowever, the upcoming `merge` command *will* want to be handled by that\nfunction, and it *can* handle commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 096e6d241e0..1ee70d843c1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3393,7 +3393,7 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (!item->commit || item->command == TODO_DROP) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345094","messageId":"9b7fc34e8f20205109a6c081671ec8d6e0b1e27f.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 03/17] sequencer: refactor how original todo list lines are accessed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:19:14Z","receivedAt":"2018-04-19T12:19:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Previously, we did a lot of arithmetic gymnastics to get at the line in\nthe todo list (as stored in todo_list.buf). This might have been fast,\nbut only in terms of execution speed, not in terms of developer time.\n\nLet's refactor this to make it a lot easier to read, and hence to\nreason about the correctness of the code. It is not performance-critical\ncode anyway.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 60 ++++++++++++++++++++++++++++++++---------------------\n 1 file changed, 36 insertions(+), 24 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1ee70d843c1..3d0a45ab25a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1870,6 +1870,23 @@ static int count_commands(struct todo_list *todo_list)\n \treturn count;\n }\n \n+static int get_item_line_offset(struct todo_list *todo_list, int index)\n+{\n+\treturn index < todo_list->nr ?\n+\t\ttodo_list->items[index].offset_in_buf : todo_list->buf.len;\n+}\n+\n+static const char *get_item_line(struct todo_list *todo_list, int index)\n+{\n+\treturn todo_list->buf.buf + get_item_line_offset(todo_list, index);\n+}\n+\n+static int get_item_line_length(struct todo_list *todo_list, int index)\n+{\n+\treturn get_item_line_offset(todo_list, index + 1)\n+\t\t-  get_item_line_offset(todo_list, index);\n+}\n+\n static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)\n {\n \tint fd;\n@@ -2244,29 +2261,27 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n \tif (fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), todo_path);\n-\toffset = next < todo_list->nr ?\n-\t\ttodo_list->items[next].offset_in_buf : todo_list->buf.len;\n+\toffset = get_item_line_offset(todo_list, next);\n \tif (write_in_full(fd, todo_list->buf.buf + offset,\n \t\t\ttodo_list->buf.len - offset) < 0)\n \t\treturn error_errno(_(\"could not write to '%s'\"), todo_path);\n \tif (commit_lock_file(&todo_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n \n-\tif (is_rebase_i(opts)) {\n-\t\tconst char *done_path = rebase_path_done();\n-\t\tint fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);\n-\t\tint prev_offset = !next ? 0 :\n-\t\t\ttodo_list->items[next - 1].offset_in_buf;\n+\tif (is_rebase_i(opts) && next > 0) {\n+\t\tconst char *done = rebase_path_done();\n+\t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n+\t\tint ret = 0;\n \n-\t\tif (fd >= 0 && offset > prev_offset &&\n-\t\t    write_in_full(fd, todo_list->buf.buf + prev_offset,\n-\t\t\t\t  offset - prev_offset) < 0) {\n-\t\t\tclose(fd);\n-\t\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t\t   done_path);\n-\t\t}\n-\t\tif (fd >= 0)\n-\t\t\tclose(fd);\n+\t\tif (fd < 0)\n+\t\t\treturn 0;\n+\t\tif (write_in_full(fd, get_item_line(todo_list, next - 1),\n+\t\t\t\t  get_item_line_length(todo_list, next - 1))\n+\t\t    < 0)\n+\t\t\tret = error_errno(_(\"could not write to '%s'\"), done);\n+\t\tif (close(fd) < 0)\n+\t\t\tret = error_errno(_(\"failed to finalize '%s'\"), done);\n+\t\treturn ret;\n \t}\n \treturn 0;\n }\n@@ -3297,8 +3312,7 @@ int skip_unnecessary_picks(void)\n \t\toid = &item->commit->object.oid;\n \t}\n \tif (i > 0) {\n-\t\tint offset = i < todo_list.nr ?\n-\t\t\ttodo_list.items[i].offset_in_buf : todo_list.buf.len;\n+\t\tint offset = get_item_line_offset(&todo_list, i);\n \t\tconst char *done_path = rebase_path_done();\n \n \t\tfd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);\n@@ -3478,12 +3492,10 @@ int rearrange_squash(void)\n \t\t\t\tcontinue;\n \n \t\t\twhile (cur >= 0) {\n-\t\t\t\tint offset = todo_list.items[cur].offset_in_buf;\n-\t\t\t\tint end_offset = cur + 1 < todo_list.nr ?\n-\t\t\t\t\ttodo_list.items[cur + 1].offset_in_buf :\n-\t\t\t\t\ttodo_list.buf.len;\n-\t\t\t\tchar *bol = todo_list.buf.buf + offset;\n-\t\t\t\tchar *eol = todo_list.buf.buf + end_offset;\n+\t\t\t\tconst char *bol =\n+\t\t\t\t\tget_item_line(&todo_list, cur);\n+\t\t\t\tconst char *eol =\n+\t\t\t\t\tget_item_line(&todo_list, cur + 1);\n \n \t\t\t\t/* replace 'pick', by 'fixup' or 'squash' */\n \t\t\t\tcommand = todo_list.items[cur].command;\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345095","messageId":"b768f1bbfdc5c8b03d7bd67bf6915549d18934f2.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 04/17] sequencer: offer helpful advice when a command was rescheduled","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:19:48Z","receivedAt":"2018-04-19T12:20:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Previously, we did that just magically, and potentially left some users\nquite puzzled. Let's err on the safe side instead, telling the user what\nis happening, and how they are supposed to continue.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 16 ++++++++++++++++\n 1 file changed, 16 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 3d0a45ab25a..01443e0f245 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2555,6 +2555,17 @@ static const char *reflog_message(struct replay_opts *opts,\n \treturn buf.buf;\n }\n \n+static const char rescheduled_advice[] =\n+N_(\"Could not execute the todo command\\n\"\n+\"\\n\"\n+\"    %.*s\"\n+\"\\n\"\n+\"It has been rescheduled; To edit the command before continuing, please\\n\"\n+\"edit the todo list first:\\n\"\n+\"\\n\"\n+\"    git rebase --edit-todo\\n\"\n+\"    git rebase --continue\\n\");\n+\n static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n {\n \tint res = 0;\n@@ -2600,6 +2611,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t\topts, is_final_fixup(todo_list));\n \t\t\tif (is_rebase_i(opts) && res < 0) {\n \t\t\t\t/* Reschedule */\n+\t\t\t\tadvise(_(rescheduled_advice),\n+\t\t\t\t       get_item_line_length(todo_list,\n+\t\t\t\t\t\t\t    todo_list->current),\n+\t\t\t\t       get_item_line(todo_list,\n+\t\t\t\t\t\t     todo_list->current));\n \t\t\t\ttodo_list->current--;\n \t\t\t\tif (save_todo(todo_list, opts))\n \t\t\t\t\treturn -1;\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345096","messageId":"b071b84a4cd4d3f3017a6568ffaab0bd19bf16d2.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 05/17] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:20:11Z","receivedAt":"2018-04-19T12:20:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Stefan Beller <stefanbeller@gmail.com>\n\nUp to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we are about to introduce commands that take different\narguments, clarify each command by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 50323fc2735..e1b865f43f2 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,13 +155,13 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345097","messageId":"77c08b61387057af1be57c2b50769651cc8ec898.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 06/17] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:20:35Z","receivedAt":"2018-04-19T12:21:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming commits, we will teach the sequencer to rebase merges.\nThis will be done in a very different way from the unfortunate design of\n`git rebase --preserve-merges` (which does not allow for reordering\ncommits, or changing the branch topology).\n\nThe main idea is to introduce new todo list commands, to support\nlabeling the current revision with a given name, resetting the current\nrevision to a previous state, and  merging labeled revisions.\n\nThis idea was developed in Git for Windows' Git garden shears (that are\nused to maintain Git for Windows' \"thicket of branches\" on top of\nupstream Git), and this patch is part of the effort to make it available\nto a wider audience, as well as to make the entire process more robust\n(by implementing it in a safe and portable language rather than a Unix\nshell script).\n\nThis commit implements the commands to label, and to reset to, given\nrevisions. The syntax is:\n\n\tlabel <name>\n\treset <name>\n\nInternally, the `label <name>` command creates the ref\n`refs/rewritten/<name>`. This makes it possible to work with the labeled\nrevisions interactively, or in a scripted fashion (e.g. via the todo\nlist command `exec`).\n\nThese temporary refs are removed upon sequencer_remove_state(), so that\neven a `git rebase --abort` cleans them up.\n\nWe disallow '#' as label because that character will be used as separator\nin the upcoming `merge` command.\n\nLater in this patch series, we will mark the `refs/rewritten/` refs as\nworktree-local, to allow for interactive rebases to be run in parallel in\nworktrees linked to the same repository.\n\nAs typos happen, a failed `label` or `reset` command will be rescheduled\nimmediately. Note that this needs a little change in the original code to\nperform a reschedule: there is no commit from which to generate a patch\nhere (and we will simply fall through to the regular `return res`). We\nkeep that code path, though, because we will use it for the upcoming\n`merge` command, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   2 +\n sequencer.c                | 201 +++++++++++++++++++++++++++++++++++--\n 2 files changed, 196 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex e1b865f43f2..e8d3a7d7588 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n+l, label <label> = label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 01443e0f245..9e09026b594 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"notes-utils.h\"\n #include \"sigchain.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `label` command to record the need for cleanup.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -244,18 +253,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n \n int sequencer_remove_state(struct replay_opts *opts)\n {\n-\tstruct strbuf dir = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i;\n \n+\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n+\t\tchar *p = buf.buf;\n+\t\twhile (*p) {\n+\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\tif (eol)\n+\t\t\t\t*eol = '\\0';\n+\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n+\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\tif (!eol)\n+\t\t\t\tbreak;\n+\t\t\tp = eol + 1;\n+\t\t}\n+\t}\n+\n \tfree(opts->gpg_sign);\n \tfree(opts->strategy);\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n \tfree(opts->xopts);\n \n-\tstrbuf_addstr(&dir, get_dir(opts));\n-\tremove_dir_recursively(&dir, 0);\n-\tstrbuf_release(&dir);\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, get_dir(opts));\n+\tremove_dir_recursively(&buf, 0);\n+\tstrbuf_release(&buf);\n \n \treturn 0;\n }\n@@ -1279,6 +1303,8 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1297,6 +1323,8 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1802,7 +1830,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -2465,6 +2494,158 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn -1;\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n+\t\terror_errno(_(\"could not read '%s'\"), filename);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tif (len == 1 && *name == '#')\n+\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t} else if (get_oid(\"HEAD\", &head_oid)) {\n+\t\terror(_(\"could not read HEAD\"));\n+\t\tret = -1;\n+\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n+\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t   ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static const char *reflog_message(struct replay_opts *opts,\n+\tconst char *sub_action, const char *fmt, ...);\n+\n+static int do_reset(const char *name, int len, struct replay_opts *opts)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options unpack_tree_opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\t/* Determine the length of the label */\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n+\tunpack_tree_opts.head_idx = 1;\n+\tunpack_tree_opts.src_index = &the_index;\n+\tunpack_tree_opts.dst_index = &the_index;\n+\tunpack_tree_opts.fn = oneway_merge;\n+\tunpack_tree_opts.merge = 1;\n+\tunpack_tree_opts.update = 1;\n+\n+\tif (read_cache_unmerged()) {\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error_resolve_conflict(_(action_name(opts)));\n+\t}\n+\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret)\n+\t\tret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n+\t\t\t\t\t\tlen, name), \"HEAD\", &oid,\n+\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2610,7 +2791,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tres = do_pick_commit(item->command, item->commit,\n \t\t\t\t\topts, is_final_fixup(todo_list));\n \t\t\tif (is_rebase_i(opts) && res < 0) {\n-\t\t\t\t/* Reschedule */\n+reschedule:\n \t\t\t\tadvise(_(rescheduled_advice),\n \t\t\t\t       get_item_line_length(todo_list,\n \t\t\t\t\t\t\t    todo_list->current),\n@@ -2639,7 +2820,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t\tintend_to_amend();\n \t\t\t\treturn error_failed_squash(item->commit, opts,\n \t\t\t\t\titem->arg_len, item->arg);\n-\t\t\t} else if (res && is_rebase_i(opts))\n+\t\t\t} else if (res && is_rebase_i(opts) && item->commit)\n \t\t\t\treturn res | error_with_patch(item->commit,\n \t\t\t\t\titem->arg, item->arg_len, opts, res,\n \t\t\t\t\titem->command == TODO_REWORD);\n@@ -2665,6 +2846,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n+\t\t} else if (item->command == TODO_LABEL) {\n+\t\t\tif ((res = do_label(item->arg, item->arg_len)))\n+\t\t\t\tgoto reschedule;\n+\t\t} else if (item->command == TODO_RESET) {\n+\t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n+\t\t\t\tgoto reschedule;\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345098","messageId":"f3d13a304aa8586dd49a19947f09c00966590340.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 07/17] # This is a combination of 2 commits. # This is the 1st commit message:","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:20:54Z","receivedAt":"2018-04-19T12:21:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"sequencer: introduce the `merge` command\n\nThis patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label` and `reset` commands to label\ncommits and to reset to labeled commits. This patch adds the `merge`\ncommand, with the following syntax:\n\n\tmerge [-C <commit>] <rev> # <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the merge commit that is about\nto be created.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list:\n\n\tlabel onto\n\n\t# Branch abc\n\treset onto\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\treset onto\n\tpick cafecafe And now for something completely different\n\tmerge -C baaabaaa abc # Merge the branch 'abc' into master\n\nTo edit the merge commit's message (a \"reword\" for merges, if you will),\nuse `-c` (lower-case) instead of `-C`; this convention was borrowed from\n`git commit` that also supports `-c` and `-C` with similar meanings.\n\nTo create *new* merges, i.e. without copying the commit message from an\nexisting commit, simply omit the `-C <commit>` parameter (which will\nopen an editor for the merge message):\n\n\tmerge abc\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in a separate\npatch series, support for merges using strategies other than the\nrecursive merge is left for the future.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n\n# The commit message #2 will be skipped:\n\n# fixup! sequencer: introduce the `merge` command\n---\n git-rebase--interactive.sh |   4 +\n sequencer.c                | 184 +++++++++++++++++++++++++++++++++++++\n 2 files changed, 188 insertions(+)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex e8d3a7d7588..ccd5254d1c9 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -164,6 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n l, label <label> = label current HEAD with a name\n t, reset <label> = reset HEAD to a label\n+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n+.       create a merge commit using the original merge commit's\n+.       message (or the oneline, if no original merge commit was\n+.       specified). Use -c <commit> to reword the commit message.\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 9e09026b594..90b2fac96b1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1305,6 +1305,7 @@ enum todo_command {\n \tTODO_EXEC,\n \tTODO_LABEL,\n \tTODO_RESET,\n+\tTODO_MERGE,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1325,6 +1326,7 @@ static struct {\n \t{ 'x', \"exec\" },\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n+\t{ 'm', \"merge\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1752,9 +1754,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)\n \treturn 0;\n }\n \n+enum todo_item_flags {\n+\tTODO_EDIT_MERGE_MSG = 1\n+};\n+\n struct todo_item {\n \tenum todo_command command;\n \tstruct commit *commit;\n+\tunsigned int flags;\n \tconst char *arg;\n \tint arg_len;\n \tsize_t offset_in_buf;\n@@ -1789,6 +1796,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \tchar *end_of_object_name;\n \tint i, saved, status, padding;\n \n+\titem->flags = 0;\n+\n \t/* left-trim */\n \tbol += strspn(bol, \" \\t\");\n \n@@ -1838,6 +1847,21 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn 0;\n \t}\n \n+\tif (item->command == TODO_MERGE) {\n+\t\tif (skip_prefix(bol, \"-C\", &bol))\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n+\t\t} else {\n+\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n+\t\t\titem->commit = NULL;\n+\t\t\titem->arg = bol;\n+\t\t\titem->arg_len = (int)(eol - bol);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n \tsaved = *end_of_object_name;\n \t*end_of_object_name = '\\0';\n@@ -2646,6 +2670,153 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n \treturn ret;\n }\n \n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    int flags, struct replay_opts *opts)\n+{\n+\tint run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n+\t\tEDIT_MSG | VERIFY_MSG : 0;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *bases, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint merge_arg_len, oneline_offset, ret;\n+\tstatic struct lock_file lock;\n+\tconst char *p;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {\n+\t\tret = -1;\n+\t\tgoto leave_merge;\n+\t}\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\tret = error(_(\"cannot merge without a current revision\"));\n+\t\tgoto leave_merge;\n+\t}\n+\n+\toneline_offset = arg_len;\n+\tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n+\tp = arg + merge_arg_len;\n+\tp += strspn(p, \" \\t\\n\");\n+\tif (*p == '#' && (!p[1] || isspace(p[1]))) {\n+\t\tp += 1 + strspn(p + 1, \" \\t\\n\");\n+\t\toneline_offset = p - arg;\n+\t} else if (p - arg < arg_len)\n+\t\tBUG(\"octopus merges are not supported yet: '%s'\", p);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\n+\tif (!merge_commit) {\n+\t\tret = error(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tgoto leave_merge;\n+\t}\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\tret = error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tret = write_message(body, len, git_path_merge_msg(), 0);\n+\t\tunuse_commit_buffer(commit, message);\n+\t\tif (ret) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t} else {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tif (oneline_offset < arg_len) {\n+\t\t\tp = arg + oneline_offset;\n+\t\t\tlen = arg_len - oneline_offset;\n+\t\t} else {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tret = write_message(p, len, git_path_merge_msg(), 0);\n+\t\tstrbuf_release(&buf);\n+\t\tif (ret) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t}\n+\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tbases = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = bases; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(bases);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\terror(_(\"could not even attempt to merge '%.*s'\"),\n+\t\t      merge_arg_len, arg);\n+\t\tgoto leave_merge;\n+\t}\n+\t/*\n+\t * The return value of merge_recursive() is 1 on clean, and 0 on\n+\t * unclean merge.\n+\t *\n+\t * Let's reverse that, so that do_merge() returns 0 upon success and\n+\t * 1 upon failed merge (keeping the return value -1 for the cases where\n+\t * we will want to reschedule the `merge` command).\n+\t */\n+\tret = !ret;\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tret = error(_(\"merge: Unable to write new index file\"));\n+\t\tgoto leave_merge;\n+\t}\n+\n+\trollback_lock_file(&lock);\n+\tif (ret)\n+\t\trerere(opts->allow_rerere_auto);\n+\telse\n+\t\tret = run_git_commit(git_path_merge_msg(), opts,\n+\t\t\t\t     run_commit_flags);\n+\n+leave_merge:\n+\tstrbuf_release(&ref_name);\n+\trollback_lock_file(&lock);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2852,6 +3023,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t} else if (item->command == TODO_RESET) {\n \t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n \t\t\t\tgoto reschedule;\n+\t\t} else if (item->command == TODO_MERGE) {\n+\t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n+\t\t\t\t       item->flags, opts);\n+\t\t\tif (res < 0)\n+\t\t\t\tgoto reschedule;\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n@@ -3334,8 +3510,16 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  short_commit_name(item->commit) :\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n+\t\t\tif (item->command == TODO_MERGE) {\n+\t\t\t\tif (item->flags & TODO_EDIT_MERGE_MSG)\n+\t\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n+\t\t\t}\n+\n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n \t\t}\n+\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345099","messageId":"93eaf4f01ce471a08150113a0e690d2ce2cdec74.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 08/17] sequencer: fast-forward `merge` commands, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:21:13Z","receivedAt":"2018-04-19T12:21:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to rebase a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 33 ++++++++++++++++++++++++++++++++-\n 1 file changed, 32 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 90b2fac96b1..2ae2294272b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2679,7 +2679,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint merge_arg_len, oneline_offset, ret;\n+\tint merge_arg_len, oneline_offset, can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \tconst char *p;\n \n@@ -2764,6 +2764,37 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\t}\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the first parent of the original merge\n+\t * commit, we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n+\t/*\n+\t * If the merge head is different from the original one, we cannot\n+\t * fast-forward.\n+\t */\n+\tif (can_fast_forward) {\n+\t\tstruct commit_list *second_parent = commit->parents->next;\n+\n+\t\tif (second_parent && !second_parent->next &&\n+\t\t    oidcmp(&merge_commit->object.oid,\n+\t\t\t   &second_parent->item->object.oid))\n+\t\t\tcan_fast_forward = 0;\n+\t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\trollback_lock_file(&lock);\n+\t\tret = fast_forward_to(&commit->object.oid,\n+\t\t\t\t      &head_commit->object.oid, 0, opts);\n+\t\tgoto leave_merge;\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345100","messageId":"448caa01bd6624e10aa67752efc44ca8b08196d7.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 09/17] rebase-helper --make-script: introduce a flag to rebase merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:21:32Z","receivedAt":"2018-04-19T12:22:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --rebase-merges option. For a\ncommit topology like this (where the HEAD points to C):\n\n\t- A - B - C\n\t    \\   /\n\t      D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge -C 3456 D # C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch series.\n\nAs a special, hard-coded label, all merge-rebasing todo lists start with\nthe command `label onto` so that we can later always refer to the revision\nonto which everything is rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 351 ++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 353 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex ad074705bb5..781782e7272 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -24,6 +24,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n+\t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,6 +58,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex 2ae2294272b..32ebbc002c1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -25,6 +25,8 @@\n #include \"sigchain.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -3414,6 +3416,343 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   (len == 1 && *label == '#') ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, or the label is a '#' (which we use as a separator\n+\t\t * between merge heads and oneline), we append a dash and a\n+\t\t * number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n+\t\t*cmd_label = abbr ? \"l\" : \"label\",\n+\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n+\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\t\tint is_empty;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tis_empty = is_original_commit_empty(commit);\n+\t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_empty)\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s -C %s\",\n+\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", cmd_label);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(to, \"onto\"))\n+\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n+\t\t\t\t\tcmd_reset, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\",\n+\t\t\t\t\tcmd_label, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -3424,11 +3763,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_pick = 1;\n+\tif (rebase_merges)\n+\t\trevs.cherry_mark = 1;\n+\telse {\n+\t\trevs.max_parents = 1;\n+\t\trevs.cherry_pick = 1;\n+\t}\n \trevs.limited = 1;\n \trevs.reverse = 1;\n \trevs.right_only = 1;\n@@ -3452,6 +3796,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (rebase_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tstrbuf_reset(&buf);\n \t\tif (!keep_empty && is_original_commit_empty(commit))\ndiff --git a/sequencer.h b/sequencer.h\nindex e45b178dfc4..6bc4da17243 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -59,6 +59,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_REBASE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345101","messageId":"2a8aad696b1af9a218ab1581cc6906ca32302d3f.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 10/17] rebase: introduce the --rebase-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:21:53Z","receivedAt":"2018-04-19T12:22:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt to answer this was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto flatten the branch topology or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--rebase-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge <label>`. And once this mode will\nhave become stable and universally accepted, we can deprecate the design\nmistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |  10 +-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 +\n t/t3430-rebase-merges.sh               | 156 +++++++++++++++++++++++++\n 5 files changed, 173 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 3277ca14327..936c5619b42 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -378,6 +378,13 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+-r::\n+--rebase-merges::\n+\tRebase merge commits instead of flattening the history by replaying\n+\tmerges. Merge conflict resolutions or manual amendments to merge\n+\tcommits are not rebased automatically, but have to be applied\n+\tmanually.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -780,7 +787,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+--rebase-merges for a more faithful representation.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex a7570739454..d4c0a995c39 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1949,7 +1949,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--rebase-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex ccd5254d1c9..7a3daf3e40c 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -970,6 +970,7 @@ git_rebase__interactive () {\n \tinit_revisions_and_shortrevisions\n \n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${rebase_merges:+--rebase-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n \ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex fb64ee1fe42..a64460fd25a 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+r,rebase-merges!   try to rebase merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -88,6 +89,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+rebase_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -270,6 +272,10 @@ do\n \t--allow-empty-message)\n \t\tallow_empty_message=--allow-empty-message\n \t\t;;\n+\t--rebase-merges)\n+\t\trebase_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nnew file mode 100755\nindex 00000000000..e80fa068d05\n--- /dev/null\n+++ b/t/t3430-rebase-merges.sh\n@@ -0,0 +1,156 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2018 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --rebase-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+test_expect_success 'create completely different structure' '\n+\tcat >script-from-scratch <<-\\EOF &&\n+\tlabel onto\n+\n+\t# onebranch\n+\tpick G\n+\tpick D\n+\tlabel onebranch\n+\n+\t# second\n+\treset onto\n+\tpick B\n+\tlabel second\n+\n+\treset onto\n+\tmerge -C H second\n+\tmerge onebranch # Merge the topic branch '\\''onebranch'\\''\n+\tEOF\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i -r A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\treset onto\n+\tpick d9df450 B\n+\tlabel E\n+\n+\treset onto\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point # C\n+\tpick 12bd07b D\n+\tmerge -C 2051b56 E # E\n+\tmerge -C 233d48a H # H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success '`reset` refuses to overwrite untracked files' '\n+\tgit checkout -b refuse-to-reset &&\n+\ttest_commit dont-overwrite-untracked &&\n+\tgit checkout @{-1} &&\n+\t: >dont-overwrite-untracked.t &&\n+\techo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_must_fail git rebase -r HEAD &&\n+\tgit rebase --abort\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i -r upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345102","messageId":"95863516e312bca6f8ae3cd884c1d2dbbb411f84.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 11/17] rebase --rebase-merges: add test for --keep-empty","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:22:27Z","receivedAt":"2018-04-19T12:22:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf there are empty commits on the left hand side of $upstream...HEAD\nthen the empty commits on the right hand side that we want to keep are\nbeing pruned.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n t/t3421-rebase-topology-linear.sh | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 68fe2003ef5..fbae5dab7e2 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -217,6 +217,7 @@ test_run_rebase success ''\n test_run_rebase failure -m\n test_run_rebase failure -i\n test_run_rebase failure -p\n+test_run_rebase success --rebase-merges\n \n #       m\n #      /\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345103","messageId":"cdf3b069c81d8888b2cc69717bdb9191c8a45a6e.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 12/17] sequencer: make refs generated by the `label` command worktree-local","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:22:46Z","receivedAt":"2018-04-19T12:23:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                   |  3 ++-\n t/t3430-rebase-merges.sh | 14 ++++++++++++++\n 2 files changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 8b7a77fe5ee..f61ec58d1df 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex e80fa068d05..4b553fc78b1 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -153,4 +153,18 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345104","messageId":"ffe35be70e7c7a6c5499e5400ee4f649a231298e.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 13/17] sequencer: handle post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:23:05Z","receivedAt":"2018-04-19T12:23:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --rebase-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits.\n\nThis patch implements the post-rewrite handling for the `merge` command\nwe just introduced. The other commands that were added recently (`label`\nand `reset`) do not create new commits, therefore post-rewrite hooks do\nnot need to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c              |  3 +++\n t/t3430-rebase-merges.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 28 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 32ebbc002c1..620a4c3a506 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3061,6 +3061,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t       item->flags, opts);\n \t\t\tif (res < 0)\n \t\t\t\tgoto reschedule;\n+\t\t\tif (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 4b553fc78b1..dedfa09d761 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -167,4 +167,29 @@ test_expect_success 'refs/rewritten/* is worktree-local' '\n \ttest_cmp_rev HEAD \"$(cat wt/b)\"\n '\n \n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash -r HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345105","messageId":"61a4f19f84c21632614eb1773f921701e1351638.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 14/17] rebase --rebase-merges: avoid \"empty merges\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:23:24Z","receivedAt":"2018-04-19T12:23:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The `git merge` command does not allow merging commits that are already\nreachable from HEAD: `git merge HEAD^`, for example, will report that we\nare already up to date and not change a thing.\n\nIn an interactive rebase, such a merge could occur previously, e.g. when\ncompeting (or slightly modified) versions of a patch series were applied\nupstream, and the user had to `git rebase --skip` all of the local\ncommits, and the topic branch becomes \"empty\" as a consequence.\n\nLet's teach the todo command `merge` to behave the same as `git merge`.\n\nSeeing as it requires some low-level trickery to create such merges with\nGit's commands in the first place, we do not even have to bother to\nintroduce an option to force `merge` to create such merge commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c              | 7 +++++++\n t/t3430-rebase-merges.sh | 8 ++++++++\n 2 files changed, 15 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 620a4c3a506..708b8719965 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2802,6 +2802,13 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n \n \tbases = get_merge_bases(head_commit, merge_commit);\n+\tif (bases && !oidcmp(&merge_commit->object.oid,\n+\t\t\t     &bases->item->object.oid)) {\n+\t\tret = 0;\n+\t\t/* skip merging an ancestor of HEAD */\n+\t\tgoto leave_merge;\n+\t}\n+\n \tfor (j = bases; j; j = j->next)\n \t\tcommit_list_insert(j->item, &reversed);\n \tfree_commit_list(bases);\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex dedfa09d761..37c3f73784a 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -192,4 +192,12 @@ test_expect_success 'post-rewrite hook and fixups work for merges' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'refuse to merge ancestors of HEAD' '\n+\techo \"merge HEAD^\" >script-from-scratch &&\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tbefore=\"$(git rev-parse HEAD)\" &&\n+\tgit rebase -i HEAD &&\n+\ttest_cmp_rev HEAD $before\n+'\n+\n test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345106","messageId":"21d4f142b5f07e33f1f98a5d328981eb8a64afc9.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 15/17] pull: accept --rebase=merges to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:23:44Z","receivedAt":"2018-04-19T12:24:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `merges` mode simply passes the\n`--rebase-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  5 ++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       | 18 ++++++++++++++----\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 37 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 2659153cb37..da46f154bb3 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When `merges`, pass the `--rebase-merges` option to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2617,6 +2621,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When `merges`, pass the `--rebase-merges` option to 'git rebase'\n+so that locally committed merge commits will not be flattened\n+by running 'git pull'.\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..6f76d815dd3 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,16 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|merges|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to `merges`, rebase using `git rebase --rebase-merges` so that\n+locally created merge commits will not be flattened.\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e32d6cd5b4c..70b44146ce4 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_MERGES,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"merges\", returns REBASE_MERGES. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"merges\"))\n+\t\treturn REBASE_MERGES;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|merges|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_MERGES)\n+\t\targv_array_push(&args, \"--rebase-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 805ffc05cdb..45c9219e07a 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -245,7 +245,9 @@ static int add(int argc, const char **argv)\n struct branch_info {\n \tchar *remote_name;\n \tstruct string_list merge;\n-\tenum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;\n+\tenum {\n+\t\tNO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES\n+\t} rebase;\n };\n \n static struct string_list branch_list = STRING_LIST_INIT_NODUP;\n@@ -306,6 +308,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"merges\"))\n+\t\t\t\tinfo->rebase = REBASE_MERGES;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\n@@ -963,9 +967,15 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)\n \n \tprintf(\"    %-*s \", show_info->width, item->string);\n \tif (branch_info->rebase) {\n-\t\tprintf_ln(branch_info->rebase == INTERACTIVE_REBASE\n-\t\t\t  ? _(\"rebases interactively onto remote %s\")\n-\t\t\t  : _(\"rebases onto remote %s\"), merge->items[0].string);\n+\t\tconst char *msg;\n+\t\tif (branch_info->rebase == INTERACTIVE_REBASE)\n+\t\t\tmsg = _(\"rebases interactively onto remote %s\");\n+\t\telse if (branch_info->rebase == REBASE_MERGES)\n+\t\t\tmsg = _(\"rebases interactively (with merges) onto \"\n+\t\t\t\t\"remote %s\");\n+\t\telse\n+\t\t\tmsg = _(\"rebases onto remote %s\");\n+\t\tprintf_ln(msg, merge->items[0].string);\n \t\treturn 0;\n \t} else if (show_info->any_rebase) {\n \t\tprintf_ln(_(\" merges with remote %s\"), merge->items[0].string);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex d4c0a995c39..6af65155c59 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2120,7 +2120,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true merges preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345107","messageId":"162d94cd2e6c7faece47dffc58fd9f6d1aadd226.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 16/17] rebase -i: introduce --rebase-merges=[no-]rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:24:03Z","receivedAt":"2018-04-19T12:24:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This one is a bit tricky to explain, so let's try with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nTo illustrate what this new mode is all about, let's consider what\nhappens upon `git rebase -i --rebase-merges B`, in particular to\nthe commit `D`. So far, the new branch structure would be:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nThis is unintuitive behavior. Even worse, when recreating branch\nstructure, most use cases would appear to want cousins *not* to be\nrebased onto the new base commit. For example, Git for Windows (the\nheaviest user of the Git garden shears, which served as the blueprint\nfor --rebase-merges) frequently merges branches from `next` early, and\nthese branches certainly do *not* want to be rebased. In the example\nabove, the desired outcome would look like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and let's not rebase them by default, introducing the new\n\"rebase-cousins\" mode for use cases where they should be rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt |  7 ++++++-\n builtin/rebase--helper.c     |  9 ++++++++-\n git-rebase--interactive.sh   |  1 +\n git-rebase.sh                | 12 +++++++++++-\n sequencer.c                  |  4 ++++\n sequencer.h                  |  6 ++++++\n t/t3430-rebase-merges.sh     | 18 ++++++++++++++++++\n 7 files changed, 54 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 936c5619b42..8feadf6e663 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -379,11 +379,16 @@ rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n -r::\n---rebase-merges::\n+--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tRebase merge commits instead of flattening the history by replaying\n \tmerges. Merge conflict resolutions or manual amendments to merge\n \tcommits are not rebased automatically, but have to be applied\n \tmanually.\n++\n+By default, or when `no-rebase-cousins` was specified, commits which do not\n+have `<upstream>` as direct ancestor will keep their original branch point.\n+If the `rebase-cousins` mode is turned on, such commits are rebased onto\n+`<upstream>` (or `<onto>`, if specified).\n \n -p::\n --preserve-merges::\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex 781782e7272..f7c2a5fdc81 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -25,6 +25,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n+\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -59,8 +61,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n+\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (rebase_cousins >= 0 && !rebase_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--rebase-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 7a3daf3e40c..b4ad130e8b1 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -971,6 +971,7 @@ git_rebase__interactive () {\n \n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${rebase_merges:+--rebase-merges} \\\n+\t\t${rebase_cousins:+--rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n \ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex a64460fd25a..157705d2a72 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-r,rebase-merges!   try to rebase merges instead of skipping them\n+r,rebase-merges?   try to rebase merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -90,6 +90,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n rebase_merges=\n+rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -276,6 +277,15 @@ do\n \t\trebase_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--rebase-merges=*)\n+\t\trebase_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) rebase_cousins=t;;\n+\t\tno-rebase-cousins) rebase_cousins=;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex 708b8719965..3c7bb5d3fd8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3544,6 +3544,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3721,6 +3722,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (!rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(to, \"onto\"))\n \t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\ndiff --git a/sequencer.h b/sequencer.h\nindex 6bc4da17243..d9570d92b11 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -60,6 +60,12 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\n+/*\n+ * When rebasing merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are *not* rebased onto the new base by default. If those\n+ * commits should be rebased onto the new base, this flag needs to be passed.\n+ */\n+#define TODO_LIST_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 37c3f73784a..f2de7059830 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -153,6 +153,24 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase cousins unless asked for' '\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -r HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase --rebase-merges=rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345108","messageId":"3fcb3ec73d3c9e638042ac3a39e655ba3664f58c.1524139900.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 17/17] rebase -i --rebase-merges: add a section to the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T12:24:22Z","receivedAt":"2018-04-19T12:24:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The --rebase-merges mode is probably not half as intuitive to use as\nits inventor hopes, so let's document it some.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt | 132 +++++++++++++++++++++++++++++++++++\n 1 file changed, 132 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 8feadf6e663..0ff83b62821 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -389,6 +389,8 @@ By default, or when `no-rebase-cousins` was specified, commits which do not\n have `<upstream>` as direct ancestor will keep their original branch point.\n If the `rebase-cousins` mode is turned on, such commits are rebased onto\n `<upstream>` (or `<onto>`, if specified).\n++\n+See also REBASING MERGES below.\n \n -p::\n --preserve-merges::\n@@ -787,6 +789,136 @@ The ripple effect of a \"hard case\" recovery is especially bad:\n 'everyone' downstream from 'topic' will now have to perform a \"hard\n case\" recovery too!\n \n+REBASING MERGES\n+-----------------\n+\n+The interactive rebase command was originally designed to handle\n+individual patch series. As such, it makes sense to exclude merge\n+commits from the todo list, as the developer may have merged the\n+current `master` while working on the branch, only to eventually\n+rebase all the commits onto `master` (skipping the merge commits).\n+\n+However, there are legitimate reasons why a developer may want to\n+recreate merge commits: to keep the branch structure (or \"commit\n+topology\") when working on multiple, inter-related branches.\n+\n+In the following example, the developer works on a topic branch that\n+refactors the way buttons are defined, and on another topic branch\n+that uses that refactoring to implement a \"Report a bug\" button. The\n+output of `git log --graph --format=%s -5` may look like this:\n+\n+------------\n+*   Merge branch 'report-a-bug'\n+|\\\n+| * Add the feedback button\n+* | Merge branch 'refactor-button'\n+|\\ \\\n+| |/\n+| * Use the Button class for all buttons\n+| * Extract a generic Button class from the DownloadButton one\n+------------\n+\n+The developer might want to rebase those commits to a newer `master`\n+while keeping the branch topology, for example when the first topic\n+branch is expected to be integrated into `master` much earlier than the\n+second one, say, to resolve merge conflicts with changes to the\n+DownloadButton class that made it into `master`.\n+\n+This rebase can be performed using the `--rebase-merges` option.\n+It will generate a todo list looking like this:\n+\n+------------\n+label onto\n+\n+# Branch: refactor-button\n+reset onto\n+pick 123456 Extract a generic Button class from the DownloadButton one\n+pick 654321 Use the Button class for all buttons\n+label refactor-button\n+\n+# Branch: report-a-bug\n+reset refactor-button # Use the Button class for all buttons\n+pick abcdef Add the feedback button\n+label report-a-bug\n+\n+reset onto\n+merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n+merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n+------------\n+\n+In contrast to a regular interactive rebase, there are `label`, `reset` and\n+`merge` commands in addition to `pick` ones.\n+\n+The `label` command associates a label with the current HEAD when that\n+command is executed. These labels are created as worktree-local refs\n+(`refs/rewritten/<label>`) that will be deleted when the rebase\n+finishes. That way, rebase operations in multiple worktrees linked to\n+the same repository do not interfere with one another. If the `label` command\n+fails, it is rescheduled immediately, with a helpful message how to proceed.\n+\n+The `reset` command is essentially a `git read-tree -m -u` (think: `git\n+reset --hard`, but refusing to overwrite untracked files) to the\n+specified revision (typically a previously-labeled one). If the `reset`\n+command fails, it is rescheduled immediately, with a helpful message how to\n+proceed.\n+\n+The `merge` command will merge the specified revision into whatever is\n+HEAD at that time. With `-C <original-commit>`, the commit message of\n+the specified merge commit will be used. When the `-C` is changed to\n+a lower-case `-c`, the message will be opened in an editor after a\n+successful merge so that the user can edit the message.\n+\n+If a `merge` command fails for any reason other than merge conflicts (i.e.\n+when the merge operation did not even start), it is rescheduled immediately.\n+\n+At this time, the `merge` command will *always* use the `recursive`\n+merge strategy, with no way to choose a different one. To work around\n+this, an `exec` command can be used to call `git merge` explicitly,\n+using the fact that the labels are worktree-local refs (the ref\n+`refs/rewritten/onto` would correspond to the label `onto`).\n+\n+Note: the first command (`label onto`) labels the revision onto which\n+the commits are rebased; The name `onto` is just a convention, as a nod\n+to the `--onto` option.\n+\n+It is also possible to introduce completely new merge commits from scratch\n+by adding a command of the form `merge <merge-head>`. This form will\n+generate a tentative commit message and always open an editor to let the\n+user edit it. This can be useful e.g. when a topic branch turns out to\n+address more than a single concern and wants to be split into two or\n+even more topic branches. Consider this todo list:\n+\n+------------\n+pick 192837 Switch from GNU Makefiles to CMake\n+pick 5a6c7e Document the switch to CMake\n+pick 918273 Fix detection of OpenSSL in CMake\n+pick afbecd http: add support for TLS v1.3\n+pick fdbaec Fix detection of cURL in CMake on Windows\n+------------\n+\n+The one commit in this list that is not related to CMake may very well\n+have been motivated by working on fixing all those bugs introduced by\n+switching to CMake, but it addresses a different concern. To split this\n+branch into two topic branches, the todo list could be edited like this:\n+\n+------------\n+label onto\n+\n+pick afbecd http: add support for TLS v1.3\n+label tlsv1.3\n+\n+reset onto\n+pick 192837 Switch from GNU Makefiles to CMake\n+pick 918273 Fix detection of OpenSSL in CMake\n+pick fdbaec Fix detection of cURL in CMake on Windows\n+pick 5a6c7e Document the switch to CMake\n+label cmake\n+\n+reset onto\n+merge tlsv1.3\n+merge cmake\n+------------\n+\n BUGS\n ----\n The todo list presented by `--preserve-merges --interactive` does not\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n"},{"id":"345188","messageId":"CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com","threadId":"47635","inReplyTo":"f3d13a304aa8586dd49a19947f09c00966590340.1524139900.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v7 07/17] # This is a combination of 2 commits. # This is the 1st commit message:","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-04-20T05:38:32Z","receivedAt":"2018-04-20T05:38:36Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Apr 19, 2018 at 8:20 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> # This is a combination of 2 commits. # This is the 1st commit message:\n\nBotched squash/fixup?\n\n> sequencer: introduce the `merge` command\n>\n> This patch is part of the effort to reimplement `--preserve-merges` with\n> a substantially improved design, a design that has been developed in the\n> Git for Windows project to maintain the dozens of Windows-specific patch\n> series on top of upstream Git.\n> [...]\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> # The commit message #2 will be skipped:\n>\n> # fixup! sequencer: introduce the `merge` command\n\nBloop.\n"},{"id":"345199","messageId":"nycvar.QRO.7.76.6.1804201017450.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CA+P7+xrv54RBFx1eYbvgVyqQ88uxT73vOf3jazCVsvuXwvd-gA@mail.gmail.com","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T08:26:25Z","receivedAt":"2018-04-20T08:26:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jake,\n\nOn Thu, 19 Apr 2018, Jacob Keller wrote:\n\n> On Wed, Apr 18, 2018 at 9:24 PM, Sergey Organov <sorganov@gmail.com> wrote:\n> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >\n> >> On Fri, 13 Apr 2018, Phillip Wood wrote:\n> >>\n> >>> On 12/04/18 23:02, Johannes Schindelin wrote:\n> >>> >\n> >>> > [...]\n> >>> >\n> >>> > So: the order of the 3-way merges does matter.\n> >>> >\n> >>> > [...]\n> >>>\n> >>> Those conflicts certainly look intimidating (and the ones in your later\n> >>> reply with the N way merge example still look quite complicated). One\n> >>> option would be just to stop and have the user resolve the conflicts\n> >>> after each conflicting 3-way merge rather than at the end of all the\n> >>> merges. There are some downsides: there would need to be a way to\n> >>> explain to the user that this is an intermediate step (and what that\n> >>> step was); the code would have to do some book keeping to know where it\n> >>> had got to; and it would stop and prompt the user to resolve conflicts\n> >>> more often which could be annoying but hopefully they'd be clearer to\n> >>> resolve because they weren't nested.\n> >>\n> >> I thought about that. But as I pointed out: the order of the merges *does*\n> >> matter. Otherwise we force the user to resolve conflicts that they\n> >> *already* resolved during this rebase...\n> >\n> > How it's relevant to what Phillip suggested? How the order of taking 2\n> > steps, A and B, affects an ability to stop after the first step? It's\n> > still either \"A,stop,B\" or \"B,stop,A\", depending on the chosen order.\n> >\n> > What's the _actual_ problem here, if any?\n> >\n> > -- Sergey\n> \n> I believe the order of the merges changes which ones cause conflicts,\n\nThat is a correct interpretation of the example I showed.\n\n> but it's possible to generate pre-images (i.e. a set of parents to\n> merge) which cause conflicts regardless of which ordering we pick, so\n> I'm not sure there is a \"best ordering\".\n\nIn general, there is no best ordering, you are right. There is no silver\nbullet.\n\nI am not satisfied with stating that and then leaving it at that.\n\nIn the example I presented, you can see that there are common cases where\nthere *is* a best ordering. In the wrong order, even if you would force\nthe user to resolve the merge conflict in an intermediate merge (which\nwould introduce a nightmare for the user interface, I am sure you see\nthat), then the next merge would *again* show merge conflicts.\n\nAnd I, for one, am *really* certain what my decision would be when offered\nthe two options 1) force the user to resolve merge conflicts *twice*, or\n2) reorder the intermediate merges and present the user with exactly one\nset of merge conflicts.\n\nSo it is irrelevant that there might not be a \"best order\" in the general\ncase, when in the common cases quite frequently there is.\n\nIt is just another example where theory disagrees with practice. Don't get\nme wrong: it is good to start with theory. And likewise it is simply\nnecessary to continue from there, and put your theory to the test. And\nthen you need to turn this into something practical.\n\nCiao,\nDscho\n"},{"id":"345200","messageId":"nycvar.QRO.7.76.6.1804201027010.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com","subject":"Re: [PATCH v7 07/17] # This is a combination of 2 commits. # This is the 1st commit message:","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T08:34:25Z","receivedAt":"2018-04-20T08:34:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Fri, 20 Apr 2018, Eric Sunshine wrote:\n\n> On Thu, Apr 19, 2018 at 8:20 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > # This is a combination of 2 commits. # This is the 1st commit message:\n> \n> Botched squash/fixup?\n\nSure was!\n\n> > sequencer: introduce the `merge` command\n> >\n> > This patch is part of the effort to reimplement `--preserve-merges` with\n> > a substantially improved design, a design that has been developed in the\n> > Git for Windows project to maintain the dozens of Windows-specific patch\n> > series on top of upstream Git.\n> > [...]\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > # The commit message #2 will be skipped:\n> >\n> > # fixup! sequencer: introduce the `merge` command\n> \n> Bloop.\n\nObviously, this was not processed properly (I certainly did not have this\nin my editor, otherwise it would have been stripped). This is not the\nfirst time this happened. I think there is a bug in sequencer.c (i.e. in\nmy partial reimplementation of git-rebase--interactive in C), but I do not\nhave time right now to investigate further... Will try to find some time\nsoon.\n\nOr maybe I'll just install a post-rewrite hook that notifies me of this\nproblem so that I can investigate right when it happened...\n\nOh scratch that. Now I am too annoyed with what I perceive a bug in *my*\ncode. I'll hunt it down.\n\nThanks,\nDscho\n"},{"id":"345202","messageId":"fa4d22ec-0c40-b7ec-6715-67507b15181d@talktalk.net","threadId":"47635","inReplyTo":"77c08b61387057af1be57c2b50769651cc8ec898.1524139900.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v7 06/17] sequencer: introduce new commands to reset the revision","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-20T09:39:38Z","receivedAt":"2018-04-20T09:39:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 19/04/18 13:20, Johannes Schindelin wrote:\n> In the upcoming commits, we will teach the sequencer to rebase merges.\n> This will be done in a very different way from the unfortunate design of\n> `git rebase --preserve-merges` (which does not allow for reordering\n> commits, or changing the branch topology).\n> \n> The main idea is to introduce new todo list commands, to support\n> labeling the current revision with a given name, resetting the current\n> revision to a previous state, and  merging labeled revisions.\n> \n> This idea was developed in Git for Windows' Git garden shears (that are\n> used to maintain Git for Windows' \"thicket of branches\" on top of\n> upstream Git), and this patch is part of the effort to make it available\n> to a wider audience, as well as to make the entire process more robust\n> (by implementing it in a safe and portable language rather than a Unix\n> shell script).\n> \n> This commit implements the commands to label, and to reset to, given\n> revisions. The syntax is:\n> \n> \tlabel <name>\n> \treset <name>\n> \n> Internally, the `label <name>` command creates the ref\n> `refs/rewritten/<name>`. This makes it possible to work with the labeled\n> revisions interactively, or in a scripted fashion (e.g. via the todo\n> list command `exec`).\n> \n> These temporary refs are removed upon sequencer_remove_state(), so that\n> even a `git rebase --abort` cleans them up.\n> \n> We disallow '#' as label because that character will be used as separator\n> in the upcoming `merge` command.\n> \n> Later in this patch series, we will mark the `refs/rewritten/` refs as\n> worktree-local, to allow for interactive rebases to be run in parallel in\n> worktrees linked to the same repository.\n> \n> As typos happen, a failed `label` or `reset` command will be rescheduled\n> immediately. Note that this needs a little change in the original code to\n> perform a reschedule: there is no commit from which to generate a patch\n> here (and we will simply fall through to the regular `return res`). We\n> keep that code path, though, because we will use it for the upcoming\n> `merge` command, too.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  git-rebase--interactive.sh |   2 +\n>  sequencer.c                | 201 +++++++++++++++++++++++++++++++++++--\n>  2 files changed, 196 insertions(+), 7 deletions(-)\n> \n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index e1b865f43f2..e8d3a7d7588 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n>  f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n>  x, exec <commit> = run command (the rest of the line) using shell\n>  d, drop <commit> = remove commit\n> +l, label <label> = label current HEAD with a name\n> +t, reset <label> = reset HEAD to a label\n>  \n>  These lines can be re-ordered; they are executed from top to bottom.\n>  \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 01443e0f245..9e09026b594 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -23,6 +23,8 @@\n>  #include \"hashmap.h\"\n>  #include \"notes-utils.h\"\n>  #include \"sigchain.h\"\n> +#include \"unpack-trees.h\"\n> +#include \"worktree.h\"\n>  \n>  #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>  \n> @@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n>  static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n>  static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n>  \t\"rebase-merge/rewritten-pending\")\n> +\n> +/*\n> + * The path of the file listing refs that need to be deleted after the rebase\n> + * finishes. This is used by the `label` command to record the need for cleanup.\n> + */\n> +static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n> +\n>  /*\n>   * The following files are written by git-rebase just after parsing the\n>   * command-line (and are only consumed, not modified, by the sequencer).\n> @@ -244,18 +253,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n>  \n>  int sequencer_remove_state(struct replay_opts *opts)\n>  {\n> -\tstruct strbuf dir = STRBUF_INIT;\n> +\tstruct strbuf buf = STRBUF_INIT;\n>  \tint i;\n>  \n> +\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n> +\t\tchar *p = buf.buf;\n> +\t\twhile (*p) {\n> +\t\t\tchar *eol = strchr(p, '\\n');\n> +\t\t\tif (eol)\n> +\t\t\t\t*eol = '\\0';\n> +\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n> +\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n> +\t\t\tif (!eol)\n> +\t\t\t\tbreak;\n> +\t\t\tp = eol + 1;\n> +\t\t}\n> +\t}\n> +\n>  \tfree(opts->gpg_sign);\n>  \tfree(opts->strategy);\n>  \tfor (i = 0; i < opts->xopts_nr; i++)\n>  \t\tfree(opts->xopts[i]);\n>  \tfree(opts->xopts);\n>  \n> -\tstrbuf_addstr(&dir, get_dir(opts));\n> -\tremove_dir_recursively(&dir, 0);\n> -\tstrbuf_release(&dir);\n> +\tstrbuf_reset(&buf);\n> +\tstrbuf_addstr(&buf, get_dir(opts));\n> +\tremove_dir_recursively(&buf, 0);\n> +\tstrbuf_release(&buf);\n>  \n>  \treturn 0;\n>  }\n> @@ -1279,6 +1303,8 @@ enum todo_command {\n>  \tTODO_SQUASH,\n>  \t/* commands that do something else than handling a single commit */\n>  \tTODO_EXEC,\n> +\tTODO_LABEL,\n> +\tTODO_RESET,\n>  \t/* commands that do nothing but are counted for reporting progress */\n>  \tTODO_NOOP,\n>  \tTODO_DROP,\n> @@ -1297,6 +1323,8 @@ static struct {\n>  \t{ 'f', \"fixup\" },\n>  \t{ 's', \"squash\" },\n>  \t{ 'x', \"exec\" },\n> +\t{ 'l', \"label\" },\n> +\t{ 't', \"reset\" },\n>  \t{ 0,   \"noop\" },\n>  \t{ 'd', \"drop\" },\n>  \t{ 0,   NULL }\n> @@ -1802,7 +1830,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>  \t\treturn error(_(\"missing arguments for %s\"),\n>  \t\t\t     command_to_string(item->command));\n>  \n> -\tif (item->command == TODO_EXEC) {\n> +\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n> +\t    item->command == TODO_RESET) {\n>  \t\titem->commit = NULL;\n>  \t\titem->arg = bol;\n>  \t\titem->arg_len = (int)(eol - bol);\n> @@ -2465,6 +2494,158 @@ static int do_exec(const char *command_line)\n>  \treturn status;\n>  }\n>  \n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> +\tva_list ap;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tint fd = hold_lock_file_for_update(&lock, filename,\n> +\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\n> +\tif (fd < 0)\n> +\t\treturn -1;\n> +\n> +\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n> +\t\terror_errno(_(\"could not read '%s'\"), filename);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn -1;\n> +\t}\n> +\tstrbuf_complete(&buf, '\\n');\n> +\tva_start(ap, fmt);\n> +\tstrbuf_vaddf(&buf, fmt, ap);\n> +\tva_end(ap);\n> +\n> +\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n> +\t\terror_errno(_(\"could not write to '%s'\"), filename);\n> +\t\tstrbuf_release(&buf);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn -1;\n> +\t}\n> +\tif (commit_lock_file(&lock) < 0) {\n> +\t\tstrbuf_release(&buf);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n> +\t}\n> +\n> +\tstrbuf_release(&buf);\n> +\treturn 0;\n> +}\n> +\n> +static int do_label(const char *name, int len)\n> +{\n> +\tstruct ref_store *refs = get_main_ref_store();\n> +\tstruct ref_transaction *transaction;\n> +\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n> +\tstruct strbuf msg = STRBUF_INIT;\n> +\tint ret = 0;\n> +\tstruct object_id head_oid;\n> +\n> +\tif (len == 1 && *name == '#')\n> +\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n> +\n> +\ttransaction = ref_store_transaction_begin(refs, &err);\n> +\tif (!transaction) {\n> +\t\terror(\"%s\", err.buf);\n> +\t\tret = -1;\n> +\t} else if (get_oid(\"HEAD\", &head_oid)) {\n> +\t\terror(_(\"could not read HEAD\"));\n> +\t\tret = -1;\n> +\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n> +\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n> +\t\t   ref_transaction_commit(transaction, &err)) {\n> +\t\terror(\"%s\", err.buf);\n> +\t\tret = -1;\n> +\t}\n> +\tref_transaction_free(transaction);\n> +\tstrbuf_release(&err);\n> +\tstrbuf_release(&msg);\n> +\n> +\tif (!ret)\n> +\t\tret = safe_append(rebase_path_refs_to_delete(),\n> +\t\t\t\t  \"%s\\n\", ref_name.buf);\n> +\tstrbuf_release(&ref_name);\n> +\n> +\treturn ret;\n> +}\n> +\n> +static const char *reflog_message(struct replay_opts *opts,\n> +\tconst char *sub_action, const char *fmt, ...);\n> +\n> +static int do_reset(const char *name, int len, struct replay_opts *opts)\n> +{\n> +\tstruct strbuf ref_name = STRBUF_INIT;\n> +\tstruct object_id oid;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tstruct tree_desc desc;\n> +\tstruct tree *tree;\n> +\tstruct unpack_trees_options unpack_tree_opts;\n> +\tint ret = 0, i;\n> +\n> +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> +\t\treturn -1;\n> +\n> +\t/* Determine the length of the label */\n> +\tfor (i = 0; i < len; i++)\n> +\t\tif (isspace(name[i]))\n> +\t\t\tlen = i;\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +\tif (get_oid(ref_name.buf, &oid) &&\n> +\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> +\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n> +\t\trollback_lock_file(&lock);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n> +\tunpack_tree_opts.head_idx = 1;\n> +\tunpack_tree_opts.src_index = &the_index;\n> +\tunpack_tree_opts.dst_index = &the_index;\n> +\tunpack_tree_opts.fn = oneway_merge;\n> +\tunpack_tree_opts.merge = 1;\n> +\tunpack_tree_opts.update = 1;\n> +\n> +\tif (read_cache_unmerged()) {\n> +\t\trollback_lock_file(&lock);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn error_resolve_conflict(_(action_name(opts)));\n> +\t}\n> +\n> +\tif (!fill_tree_descriptor(&desc, &oid)) {\n> +\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n> +\t\trollback_lock_file(&lock);\n> +\t\tfree((void *)desc.buffer);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n> +\t\trollback_lock_file(&lock);\n> +\t\tfree((void *)desc.buffer);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttree = parse_tree_indirect(&oid);\n> +\tprime_cache_tree(&the_index, tree);\n> +\n> +\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n> +\t\tret = error(_(\"could not write index\"));\n> +\tfree((void *)desc.buffer);\n> +\n> +\tif (!ret)\n> +\t\tret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n> +\t\t\t\t\t\tlen, name), \"HEAD\", &oid,\n> +\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n> +\n> +\tstrbuf_release(&ref_name);\n> +\treturn ret;\n> +}\n> +\n>  static int is_final_fixup(struct todo_list *todo_list)\n>  {\n>  \tint i = todo_list->current;\n> @@ -2610,7 +2791,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t\t\tres = do_pick_commit(item->command, item->commit,\n>  \t\t\t\t\topts, is_final_fixup(todo_list));\n>  \t\t\tif (is_rebase_i(opts) && res < 0) {\n> -\t\t\t\t/* Reschedule */\n> +reschedule:\n>  \t\t\t\tadvise(_(rescheduled_advice),\n>  \t\t\t\t       get_item_line_length(todo_list,\n>  \t\t\t\t\t\t\t    todo_list->current),\n> @@ -2639,7 +2820,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t\t\t\t\tintend_to_amend();\n>  \t\t\t\treturn error_failed_squash(item->commit, opts,\n>  \t\t\t\t\titem->arg_len, item->arg);\n> -\t\t\t} else if (res && is_rebase_i(opts))\n> +\t\t\t} else if (res && is_rebase_i(opts) && item->commit)\n>  \t\t\t\treturn res | error_with_patch(item->commit,\n>  \t\t\t\t\titem->arg, item->arg_len, opts, res,\n>  \t\t\t\t\titem->command == TODO_REWORD);\n> @@ -2665,6 +2846,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t\t\t\t/* `current` will be incremented below */\n>  \t\t\t\ttodo_list->current = -1;\n>  \t\t\t}\n> +\t\t} else if (item->command == TODO_LABEL) {\n> +\t\t\tif ((res = do_label(item->arg, item->arg_len)))\n> +\t\t\t\tgoto reschedule;\n\nI can see why you've implemented like this but I'm uneasy with jumping\ninto a block guarded with \"if (item->command <= TODO_SQUASH)\" when\nitem->command > TODO_SQUASH. I think it works OK at the moment but it's\npossible that in the future someone will edit that block of code and add\nsomething like\n\nif (item->command == TODO_PICK)\n\tdo_something()\nelse\n\tdo_something_else()\n\nassuming that item->command <= TODO_SQUASH because they haven't noticed\nthe goto jumping back into that block.\n\nBest Wishes\n\nPhillip\n\n\n> +\t\t} else if (item->command == TODO_RESET) {\n> +\t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n> +\t\t\t\tgoto reschedule;\n>  \t\t} else if (!is_noop(item->command))\n>  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n>  \n> \n\n"},{"id":"345261","messageId":"CA+P7+xpA-1L83fGkUr+bnG7b9P3z+huOuSJO=eG5Em+Cpb759w@mail.gmail.com","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804201017450.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v6 00/15] rebase -i: offer to recreate commit topology","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-04-20T20:39:53Z","receivedAt":"2018-04-20T20:40:18Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Fri, Apr 20, 2018 at 1:26 AM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Hi Jake,\n>\n> On Thu, 19 Apr 2018, Jacob Keller wrote:\n>\n>> On Wed, Apr 18, 2018 at 9:24 PM, Sergey Organov <sorganov@gmail.com> wrote:\n>> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>> >\n>> >> On Fri, 13 Apr 2018, Phillip Wood wrote:\n>> >>\n>> >>> On 12/04/18 23:02, Johannes Schindelin wrote:\n>> >>> >\n>> >>> > [...]\n>> >>> >\n>> >>> > So: the order of the 3-way merges does matter.\n>> >>> >\n>> >>> > [...]\n>> >>>\n>> >>> Those conflicts certainly look intimidating (and the ones in your later\n>> >>> reply with the N way merge example still look quite complicated). One\n>> >>> option would be just to stop and have the user resolve the conflicts\n>> >>> after each conflicting 3-way merge rather than at the end of all the\n>> >>> merges. There are some downsides: there would need to be a way to\n>> >>> explain to the user that this is an intermediate step (and what that\n>> >>> step was); the code would have to do some book keeping to know where it\n>> >>> had got to; and it would stop and prompt the user to resolve conflicts\n>> >>> more often which could be annoying but hopefully they'd be clearer to\n>> >>> resolve because they weren't nested.\n>> >>\n>> >> I thought about that. But as I pointed out: the order of the merges *does*\n>> >> matter. Otherwise we force the user to resolve conflicts that they\n>> >> *already* resolved during this rebase...\n>> >\n>> > How it's relevant to what Phillip suggested? How the order of taking 2\n>> > steps, A and B, affects an ability to stop after the first step? It's\n>> > still either \"A,stop,B\" or \"B,stop,A\", depending on the chosen order.\n>> >\n>> > What's the _actual_ problem here, if any?\n>> >\n>> > -- Sergey\n>>\n>> I believe the order of the merges changes which ones cause conflicts,\n>\n> That is a correct interpretation of the example I showed.\n>\n>> but it's possible to generate pre-images (i.e. a set of parents to\n>> merge) which cause conflicts regardless of which ordering we pick, so\n>> I'm not sure there is a \"best ordering\".\n>\n> In general, there is no best ordering, you are right. There is no silver\n> bullet.\n>\n> I am not satisfied with stating that and then leaving it at that.\n>\n> In the example I presented, you can see that there are common cases where\n> there *is* a best ordering. In the wrong order, even if you would force\n> the user to resolve the merge conflict in an intermediate merge (which\n> would introduce a nightmare for the user interface, I am sure you see\n> that), then the next merge would *again* show merge conflicts.\n>\n> And I, for one, am *really* certain what my decision would be when offered\n> the two options 1) force the user to resolve merge conflicts *twice*, or\n> 2) reorder the intermediate merges and present the user with exactly one\n> set of merge conflicts.\n>\n> So it is irrelevant that there might not be a \"best order\" in the general\n> case, when in the common cases quite frequently there is.\n>\n> It is just another example where theory disagrees with practice. Don't get\n> me wrong: it is good to start with theory. And likewise it is simply\n> necessary to continue from there, and put your theory to the test. And\n> then you need to turn this into something practical.\n>\n> Ciao,\n> Dscho\n\nI recall you suggested an approach of \"try one way, if there are\nconflicts, check the other way and see if it had conflicts\".\n\nAnd I also agree that forcing the user to resolve conflicts in the\nmiddle of the operation is a huge nightmare of a user interface,\nprobably worse than the issues with nested merge conflicts.\n\nThanks,\nJake\n"},{"id":"345266","messageId":"cover.1524258351.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com","subject":"[PATCH v2 0/4] rebase -i: avoid stale \"# This is a combination of\" in commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T21:06:41Z","receivedAt":"2018-04-20T21:07:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Eric Sunshine pointed out that I had such a commit message in\nhttps://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\nand I went on a hunt to figure out how the heck this happened.\n\nTurns out that if there is a fixup/squash chain where the *last* command\nfails with merge conflicts, and we either --skip ahead or resolve the\nconflict to a clean tree and then --continue, our code does not do a\nfinal cleanup.\n\nContrary to my initial gut feeling, this bug was not introduced by my\nrewrite in C of the core parts of rebase -i, but it looks to me as if\nthat bug was with us for a very long time (at least the --skip part).\n\nThe developer (read: user of rebase -i) in me says that we would want to\nfast-track this, but the author of rebase -i in me says that we should\nbe cautious and cook this in `next` for a while.\n\nFixes since v1:\n\n- Using test_i18ngrep instead of grep, because \"This is a combination of\n  <N> commits\" is marked for translation.\n\n- Added a patch to actually fix `rebase -i` when building with\n  GETTEXT_POISON, because we used to assume that numbers are encoded as\n  ASCII so that we can increment it when writing the next commit message\n  in the fixup/squash chain. This also seems to be a long-standing bug\n  that has been with us since the the beginning of the localization of\n  rebase -i's commit messages.\n\n- The test case now starts with test_when_finished \"test_might_fail git rebase\n  --abort\" to be allow for failing more gently.\n\n- Fixed grammar of 2/3 (now 3/4): thanks, Eric!\n\n- Fixed the description of the new test case (it purported to test --continue,\n  but it really tests --skip).\n\n\nJohannes Schindelin (4):\n  rebase -i: demonstrate bugs with fixup!/squash! commit messages\n  rebase -i: Handle \"combination of <n> commits\" with GETTEXT_POISON\n  sequencer: leave a tell-tale when a fixup/squash failed\n  rebase --skip: clean up commit message after a failed fixup/squash\n\n sequencer.c                | 93 ++++++++++++++++++++++++++++----------\n t/t3418-rebase-continue.sh | 22 +++++++++\n 2 files changed, 92 insertions(+), 23 deletions(-)\n\n\nbase-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\nPublished-As: https://github.com/dscho/git/releases/tag/clean-msg-after-fixup-continue-v2\nFetch-It-Via: git fetch https://github.com/dscho/git clean-msg-after-fixup-continue-v2\n\nInterdiff vs v1:\n diff --git a/sequencer.c b/sequencer.c\n index f067b7b24c5..881503a6463 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -1350,19 +1350,18 @@ static int update_squash_messages(enum todo_command command,\n  \t\teol = strchrnul(buf.buf, '\\n');\n  \t\tif (buf.buf[0] != comment_line_char ||\n  \t\t    (p += strcspn(p, \"0123456789\\n\")) == eol)\n -\t\t\treturn error(_(\"unexpected 1st line of squash message:\"\n -\t\t\t\t       \"\\n\\n\\t%.*s\"),\n -\t\t\t\t     (int)(eol - buf.buf), buf.buf);\n -\t\tcount = strtol(p, NULL, 10);\n -\n -\t\tif (count < 1)\n -\t\t\treturn error(_(\"invalid 1st line of squash message:\\n\"\n -\t\t\t\t       \"\\n\\t%.*s\"),\n -\t\t\t\t     (int)(eol - buf.buf), buf.buf);\n +\t\t\tcount = -1;\n +\t\telse\n +\t\t\tcount = strtol(p, NULL, 10);\n  \n  \t\tstrbuf_addf(&header, \"%c \", comment_line_char);\n -\t\tstrbuf_addf(&header,\n -\t\t\t    _(\"This is a combination of %d commits.\"), ++count);\n +\t\tif (count < 1)\n +\t\t\tstrbuf_addf(&header, _(\"This is a combination of \"\n +\t\t\t\t\t       \"several commits.\"));\n +\t\telse\n +\t\t\tstrbuf_addf(&header,\n +\t\t\t\t    _(\"This is a combination of %d commits.\"),\n +\t\t\t\t    ++count);\n  \t\tstrbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);\n  \t\tstrbuf_release(&header);\n  \t} else {\n @@ -1405,13 +1404,22 @@ static int update_squash_messages(enum todo_command command,\n  \tif (command == TODO_SQUASH) {\n  \t\tunlink(rebase_path_fixup_msg());\n  \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n -\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"), count);\n +\t\tif (count < 2)\n +\t\t\tstrbuf_addf(&buf, _(\"This is the next commit \"\n +\t\t\t\t\t    \"message:\"));\n +\t\telse\n +\t\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"),\n +\t\t\t\t    count);\n  \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n  \t\tstrbuf_addstr(&buf, body);\n  \t} else if (command == TODO_FIXUP) {\n  \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n -\t\tstrbuf_addf(&buf, _(\"The commit message #%d will be skipped:\"),\n -\t\t\t    count);\n +\t\tif (count < 2)\n +\t\t\tstrbuf_addf(&buf, _(\"The next commit message will be \"\n +\t\t\t\t\t    \"skipped:\"));\n +\t\telse\n +\t\t\tstrbuf_addf(&buf, _(\"The commit message #%d will be \"\n +\t\t\t\t\t    \"skipped:\"), count);\n  \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n  \t\tstrbuf_add_commented_lines(&buf, body, strlen(body));\n  \t} else\n diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n index 4880bff82ff..693f92409ec 100755\n --- a/t/t3418-rebase-continue.sh\n +++ b/t/t3418-rebase-continue.sh\n @@ -88,7 +88,8 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n  \tgit rebase --continue\n  '\n  \n -test_expect_success '--continue after failed fixup cleans commit message' '\n +test_expect_success '--skip after failed fixup cleans commit message' '\n +\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n  \tgit checkout -b with-conflicting-fixup &&\n  \ttest_commit wants-fixup &&\n  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n @@ -99,14 +100,14 @@ test_expect_success '--continue after failed fixup cleans commit message' '\n  \n  \t: now there is a conflict, and comments in the commit message &&\n  \tgit show HEAD >out &&\n -\tgrep \"This is a combination of\" out &&\n +\ttest_i18ngrep \"This is a combination of\" out &&\n  \n  \t: skip and continue &&\n  \tgit rebase --skip &&\n  \n  \t: now the comments in the commit message should have been cleaned up &&\n  \tgit show HEAD >out &&\n -\t! grep \"This is a combination of\" out\n +\ttest_i18ngrep ! \"This is a combination of\" out\n  '\n  \n  test_expect_success 'setup rerere database' '\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n"},{"id":"345267","messageId":"a0ed6c98095b73e5c2475de3a3526c93f1967b94.1524258351.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524258351.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 1/4] rebase -i: demonstrate bugs with fixup!/squash! commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T21:07:01Z","receivedAt":"2018-04-20T21:07:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When multiple fixup/squash commands are processed and the last one\ncauses merge conflicts and is skipped, we leave the \"This is a\ncombination of ...\" comments in the commit message.\n\nNoticed by Eric Sunshine.\n\nThis regression test also demonstrates that we rely on the localized\nversion of\n\n\t# This is a combination of <number> commits\n\nto contain the <number> in ASCII, which breaks under GETTEXT_POISON.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t3418-rebase-continue.sh | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 9214d0bb511..6ddf952b7b9 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -88,6 +88,28 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n \tgit rebase --continue\n '\n \n+test_expect_failure '--skip after failed fixup cleans commit message' '\n+\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\tgit checkout -b with-conflicting-fixup &&\n+\ttest_commit wants-fixup &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n+\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n+\t\tgit rebase -i HEAD~4 &&\n+\n+\t: now there is a conflict, and comments in the commit message &&\n+\tgit show HEAD >out &&\n+\ttest_i18ngrep \"This is a combination of\" out &&\n+\n+\t: skip and continue &&\n+\tgit rebase --skip &&\n+\n+\t: now the comments in the commit message should have been cleaned up &&\n+\tgit show HEAD >out &&\n+\ttest_i18ngrep ! \"This is a combination of\" out\n+'\n+\n test_expect_success 'setup rerere database' '\n \trm -fr .git/rebase-* &&\n \tgit reset --hard commit-new-file-F3-on-topic-branch &&\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345268","messageId":"6fe8b38c7097c826f17e5f45e39ffbc35a529849.1524258351.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524258351.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 2/4] rebase -i: Handle \"combination of <n> commits\" with GETTEXT_POISON","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T21:07:20Z","receivedAt":"2018-04-20T21:07:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We previously relied on the localized versions of\n\n\t# This is a combination of <N> commits\n\n(which we write into the commit messages during fixup/squash chains)\nto contain <N> as ASCII.\n\nThisis not true in general, and certainly not in GETTEXT_POISON, as\ndemonstrated by the regression test we just introduced in t3418.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 36 ++++++++++++++++++++++--------------\n 1 file changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 667f35ebdff..dc482e76a28 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1343,19 +1343,18 @@ static int update_squash_messages(enum todo_command command,\n \t\teol = strchrnul(buf.buf, '\\n');\n \t\tif (buf.buf[0] != comment_line_char ||\n \t\t    (p += strcspn(p, \"0123456789\\n\")) == eol)\n-\t\t\treturn error(_(\"unexpected 1st line of squash message:\"\n-\t\t\t\t       \"\\n\\n\\t%.*s\"),\n-\t\t\t\t     (int)(eol - buf.buf), buf.buf);\n-\t\tcount = strtol(p, NULL, 10);\n-\n-\t\tif (count < 1)\n-\t\t\treturn error(_(\"invalid 1st line of squash message:\\n\"\n-\t\t\t\t       \"\\n\\t%.*s\"),\n-\t\t\t\t     (int)(eol - buf.buf), buf.buf);\n+\t\t\tcount = -1;\n+\t\telse\n+\t\t\tcount = strtol(p, NULL, 10);\n \n \t\tstrbuf_addf(&header, \"%c \", comment_line_char);\n-\t\tstrbuf_addf(&header,\n-\t\t\t    _(\"This is a combination of %d commits.\"), ++count);\n+\t\tif (count < 1)\n+\t\t\tstrbuf_addf(&header, _(\"This is a combination of \"\n+\t\t\t\t\t       \"several commits.\"));\n+\t\telse\n+\t\t\tstrbuf_addf(&header,\n+\t\t\t\t    _(\"This is a combination of %d commits.\"),\n+\t\t\t\t    ++count);\n \t\tstrbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);\n \t\tstrbuf_release(&header);\n \t} else {\n@@ -1398,13 +1397,22 @@ static int update_squash_messages(enum todo_command command,\n \tif (command == TODO_SQUASH) {\n \t\tunlink(rebase_path_fixup_msg());\n \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n-\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"), count);\n+\t\tif (count < 2)\n+\t\t\tstrbuf_addf(&buf, _(\"This is the next commit \"\n+\t\t\t\t\t    \"message:\"));\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"),\n+\t\t\t\t    count);\n \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\tstrbuf_addstr(&buf, body);\n \t} else if (command == TODO_FIXUP) {\n \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n-\t\tstrbuf_addf(&buf, _(\"The commit message #%d will be skipped:\"),\n-\t\t\t    count);\n+\t\tif (count < 2)\n+\t\t\tstrbuf_addf(&buf, _(\"The next commit message will be \"\n+\t\t\t\t\t    \"skipped:\"));\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, _(\"The commit message #%d will be \"\n+\t\t\t\t\t    \"skipped:\"), count);\n \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\tstrbuf_add_commented_lines(&buf, body, strlen(body));\n \t} else\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345269","messageId":"c23608f35af1ac9b8f7e0b9c17e2fa1fdf189ab1.1524258351.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524258351.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 3/4] sequencer: leave a tell-tale when a fixup/squash failed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T21:07:42Z","receivedAt":"2018-04-20T21:08:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming patch to clean up fixup/squash commit messages even when\nskipping a final fixup/squash that failed with merge conflicts, we will\nneed to have some indicator what happened.\n\nAs we need to remove the message-fixup and message-squash files upon\nfailure, we cannot use those. So let's just write an explicit amend-type\nfile, containing either `fixup` or `squash`. The absence of that file\nindicates that we were not in the middle of a fixup or squash when merge\nconflicts were happening.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 21 ++++++++++++++++++++-\n 1 file changed, 20 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex dc482e76a28..a6a4efeaae2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -106,6 +106,13 @@ static GIT_PATH_FUNC(rebase_path_author_script, \"rebase-merge/author-script\")\n  * command is processed, this file is deleted.\n  */\n static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n+/*\n+ * If there was a merge conflict in a fixup/squash series, we need to\n+ * record the type so that a `git rebase --skip` can clean up the commit\n+ * message as appropriate. This file will contain that type (`fixup` or\n+ * `squash`), and not exist otherwise.\n+ */\n+static GIT_PATH_FUNC(rebase_path_amend_type, \"rebase-merge/amend-type\")\n /*\n  * When we stop at a given patch via the \"edit\" command, this file contains\n  * the abbreviated commit name of the corresponding patch.\n@@ -2400,10 +2407,20 @@ static int error_with_patch(struct commit *commit,\n static int error_failed_squash(struct commit *commit,\n \tstruct replay_opts *opts, int subject_len, const char *subject)\n {\n+\tconst char *amend_type = \"squash\";\n+\n+\tif (file_exists(rebase_path_fixup_msg())) {\n+\t\tunlink(rebase_path_fixup_msg());\n+\t\tamend_type = \"fixup\";\n+\t}\n+\tif (write_message(amend_type, strlen(amend_type),\n+\t\t       rebase_path_amend_type(), 0))\n+\t\treturn error(_(\"could not write '%s'\"),\n+\t\t\t     rebase_path_amend_type());\n+\n \tif (rename(rebase_path_squash_msg(), rebase_path_message()))\n \t\treturn error(_(\"could not rename '%s' to '%s'\"),\n \t\t\trebase_path_squash_msg(), rebase_path_message());\n-\tunlink(rebase_path_fixup_msg());\n \tunlink(git_path_merge_msg());\n \tif (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))\n \t\treturn error(_(\"could not copy '%s' to '%s'\"),\n@@ -2580,6 +2597,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tunlink(rebase_path_author_script());\n \t\t\tunlink(rebase_path_stopped_sha());\n \t\t\tunlink(rebase_path_amend());\n+\t\t\tunlink(rebase_path_amend_type());\n \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n \t\t}\n \t\tif (item->command <= TODO_SQUASH) {\n@@ -2807,6 +2825,7 @@ static int commit_staged_changes(struct replay_opts *opts)\n \tif (run_git_commit(rebase_path_message(), opts, flags))\n \t\treturn error(_(\"could not commit staged changes.\"));\n \tunlink(rebase_path_amend());\n+\tunlink(rebase_path_amend_type());\n \treturn 0;\n }\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345270","messageId":"31cb9f9c534a4d0c47d67cc61619c0a9bb56c56c.1524258351.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524258351.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T21:08:04Z","receivedAt":"2018-04-20T21:08:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"During a series of fixup/squash commands, the interactive rebase builds\nup a commit message with comments. This will be presented to the user in\nthe editor if at least one of those commands was a `squash`.\n\nHowever, if the last of these fixup/squash commands fails with merge\nconflicts, and if the user then decides to skip it (or resolve it to a\nclean worktree and then continue the rebase), the current code fails to\nclean up the commit message.\n\nThis commit fixes that behavior.\n\nThe diff is best viewed with --color-moved.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c                | 36 ++++++++++++++++++++++++++++--------\n t/t3418-rebase-continue.sh |  2 +-\n 2 files changed, 29 insertions(+), 9 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex a6a4efeaae2..881503a6463 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2789,17 +2789,12 @@ static int continue_single_pick(void)\n \n static int commit_staged_changes(struct replay_opts *opts)\n {\n-\tunsigned int flags = ALLOW_EMPTY | EDIT_MSG;\n+\tunsigned int flags = ALLOW_EMPTY | EDIT_MSG, is_fixup = 0, is_clean;\n \n \tif (has_unstaged_changes(1))\n \t\treturn error(_(\"cannot rebase: You have unstaged changes.\"));\n-\tif (!has_uncommitted_changes(0)) {\n-\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n \n-\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n-\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n-\t\treturn 0;\n-\t}\n+\tis_clean = !has_uncommitted_changes(0);\n \n \tif (file_exists(rebase_path_amend())) {\n \t\tstruct strbuf rev = STRBUF_INIT;\n@@ -2812,16 +2807,41 @@ static int commit_staged_changes(struct replay_opts *opts)\n \t\tif (get_oid_hex(rev.buf, &to_amend))\n \t\t\treturn error(_(\"invalid contents: '%s'\"),\n \t\t\t\trebase_path_amend());\n-\t\tif (oidcmp(&head, &to_amend))\n+\t\tif (!is_clean && oidcmp(&head, &to_amend))\n \t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n \t\t\t\t       \"working tree. Please, commit them\\n\"\n \t\t\t\t       \"first and then run 'git rebase \"\n \t\t\t\t       \"--continue' again.\"));\n+\t\tif (is_clean && !oidcmp(&head, &to_amend)) {\n+\t\t\tstrbuf_reset(&rev);\n+\t\t\t/*\n+\t\t\t * Clean tree, but we may need to finalize a\n+\t\t\t * fixup/squash chain. A failed fixup/squash leaves the\n+\t\t\t * file amend-type in rebase-merge/; It is okay if that\n+\t\t\t * file is missing, in which case there is no such\n+\t\t\t * chain to finalize.\n+\t\t\t */\n+\t\t\tread_oneliner(&rev, rebase_path_amend_type(), 0);\n+\t\t\tif (!strcmp(\"squash\", rev.buf))\n+\t\t\t\tis_fixup = TODO_SQUASH;\n+\t\t\telse if (!strcmp(\"fixup\", rev.buf)) {\n+\t\t\t\tis_fixup = TODO_FIXUP;\n+\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n+\t\t\t}\n+\t\t}\n \n \t\tstrbuf_release(&rev);\n \t\tflags |= AMEND_MSG;\n \t}\n \n+\tif (is_clean && !is_fixup) {\n+\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n+\n+\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n+\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n+\t\treturn 0;\n+\t}\n+\n \tif (run_git_commit(rebase_path_message(), opts, flags))\n \t\treturn error(_(\"could not commit staged changes.\"));\n \tunlink(rebase_path_amend());\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 6ddf952b7b9..693f92409ec 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -88,7 +88,7 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n \tgit rebase --continue\n '\n \n-test_expect_failure '--skip after failed fixup cleans commit message' '\n+test_expect_success '--skip after failed fixup cleans commit message' '\n \ttest_when_finished \"test_might_fail git rebase --abort\" &&\n \tgit checkout -b with-conflicting-fixup &&\n \ttest_commit wants-fixup &&\n-- \n2.17.0.windows.1.15.gaa56ade3205\n"},{"id":"345271","messageId":"CAGZ79kZM3Q-HnPAqMUZM_NtfCumsWBtwrFT2HPkU_d95iJdzOQ@mail.gmail.com","threadId":"47635","inReplyTo":"6fe8b38c7097c826f17e5f45e39ffbc35a529849.1524258351.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 2/4] rebase -i: Handle \"combination of <n> commits\" with GETTEXT_POISON","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-20T21:16:47Z","receivedAt":"2018-04-20T21:16:52Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Apr 20, 2018 at 2:07 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> We previously relied on the localized versions of\n>\n>         # This is a combination of <N> commits\n>\n> (which we write into the commit messages during fixup/squash chains)\n> to contain <N> as ASCII.\n>\n> Thisis not true in general, and certainly not in GETTEXT_POISON, as\n\nThis is\n\nApart from this typo, this patch looks good.\n"},{"id":"345272","messageId":"CAGZ79kaQXWi0GmHH_6D_yeQU=M3W_esAy6Y7QweDzp07x7z7dQ@mail.gmail.com","threadId":"47635","inReplyTo":"c23608f35af1ac9b8f7e0b9c17e2fa1fdf189ab1.1524258351.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 3/4] sequencer: leave a tell-tale when a fixup/squash failed","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-20T21:25:35Z","receivedAt":"2018-04-20T21:25:40Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":">  static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n> +/*\n> + * If there was a merge conflict in a fixup/squash series, we need to\n> + * record the type so that a `git rebase --skip` can clean up the commit\n> + * message as appropriate. This file will contain that type (`fixup` or\n> + * `squash`), and not exist otherwise.\n> + */\n\nThanks for the documentation here, is there some other high level doc that\ndescribes all things to know about the internals of the rebase-merge dir\nor is this the definitive guide?\n\n> +static GIT_PATH_FUNC(rebase_path_amend_type, \"rebase-merge/amend-type\")\n>  /*\n>   * When we stop at a given patch via the \"edit\" command, this file contains\n>   * the abbreviated commit name of the corresponding patch.\n> @@ -2400,10 +2407,20 @@ static int error_with_patch(struct commit *commit,\n>  static int error_failed_squash(struct commit *commit,\n>         struct replay_opts *opts, int subject_len, const char *subject)\n>  {\n> +       const char *amend_type = \"squash\";\n> +\n> +       if (file_exists(rebase_path_fixup_msg())) {\n> +               unlink(rebase_path_fixup_msg());\n> +               amend_type = \"fixup\";\n> +       }\n> +       if (write_message(amend_type, strlen(amend_type),\n> +                      rebase_path_amend_type(), 0))\n> +               return error(_(\"could not write '%s'\"),\n> +                            rebase_path_amend_type());\n\nDo we want to wait with unlinking rebase_path_fixup_msg()\nuntil after we are sure there is no error returned?\nI first thought so as to preserve the state as before, but\nthen it only signals the amend type. But we're downgrading the\namend type from \"squash\" to \"fixup\", which means that if\nthis error happens and the user just retries the git command\nwe'll end up with a \"fixup\", i.e. not opening their editor?\n"},{"id":"345290","messageId":"nycvar.QRO.7.76.6.1804210036400.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"fa4d22ec-0c40-b7ec-6715-67507b15181d@talktalk.net","subject":"Re: [PATCH v7 06/17] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:39:26Z","receivedAt":"2018-04-20T22:40:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Fri, 20 Apr 2018, Phillip Wood wrote:\n\n> On 19/04/18 13:20, Johannes Schindelin wrote:\n>\n> [... please cull long stretches of quoted mail that is not responded to ...]\n>\n> > @@ -2665,6 +2846,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n> >  \t\t\t\t/* `current` will be incremented below */\n> >  \t\t\t\ttodo_list->current = -1;\n> >  \t\t\t}\n> > +\t\t} else if (item->command == TODO_LABEL) {\n> > +\t\t\tif ((res = do_label(item->arg, item->arg_len)))\n> > +\t\t\t\tgoto reschedule;\n> \n> I can see why you've implemented like this but I'm uneasy with jumping\n> into a block guarded with \"if (item->command <= TODO_SQUASH)\" when\n> item->command > TODO_SQUASH. I think it works OK at the moment but it's\n> possible that in the future someone will edit that block of code and add\n> something like\n> \n> if (item->command == TODO_PICK)\n> \tdo_something()\n> else\n> \tdo_something_else()\n> \n> assuming that item->command <= TODO_SQUASH because they haven't noticed\n> the goto jumping back into that block.\n\nI changed it by duplicating the rescheduling, as I agree that it is\nsomewhat dangerous what with all the code going on after the rescheduling\nof a pick/fixup/squash/reword.\n\nMy plan is to go over the documentation changes once more tomorrow, with a\nfresh set of eyes, and then submit the hopefully final iteration of this\npatch series.\n\nCiao,\nDscho\n"},{"id":"345316","messageId":"nycvar.QRO.7.76.6.1804210920390.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAGZ79kZM3Q-HnPAqMUZM_NtfCumsWBtwrFT2HPkU_d95iJdzOQ@mail.gmail.com","subject":"Re: [PATCH v2 2/4] rebase -i: Handle \"combination of <n> commits\" with GETTEXT_POISON","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T07:20:56Z","receivedAt":"2018-04-21T07:21:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Fri, 20 Apr 2018, Stefan Beller wrote:\n\n> On Fri, Apr 20, 2018 at 2:07 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > We previously relied on the localized versions of\n> >\n> >         # This is a combination of <N> commits\n> >\n> > (which we write into the commit messages during fixup/squash chains)\n> > to contain <N> as ASCII.\n> >\n> > Thisis not true in general, and certainly not in GETTEXT_POISON, as\n> \n> This is\n> \n> Apart from this typo, this patch looks good.\n\nOuch. Fixed, locally.\n\nCiao,\nDscho\n"},{"id":"345317","messageId":"nycvar.QRO.7.76.6.1804210922240.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAGZ79kaQXWi0GmHH_6D_yeQU=M3W_esAy6Y7QweDzp07x7z7dQ@mail.gmail.com","subject":"Re: [PATCH v2 3/4] sequencer: leave a tell-tale when a fixup/squash failed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T07:24:57Z","receivedAt":"2018-04-21T07:25:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Fri, 20 Apr 2018, Stefan Beller wrote:\n\n> >  static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n> > +/*\n> > + * If there was a merge conflict in a fixup/squash series, we need to\n> > + * record the type so that a `git rebase --skip` can clean up the commit\n> > + * message as appropriate. This file will contain that type (`fixup` or\n> > + * `squash`), and not exist otherwise.\n> > + */\n> \n> Thanks for the documentation here, is there some other high level doc that\n> describes all things to know about the internals of the rebase-merge dir\n> or is this the definitive guide?\n> \n> > +static GIT_PATH_FUNC(rebase_path_amend_type, \"rebase-merge/amend-type\")\n> >  /*\n> >   * When we stop at a given patch via the \"edit\" command, this file contains\n> >   * the abbreviated commit name of the corresponding patch.\n> > @@ -2400,10 +2407,20 @@ static int error_with_patch(struct commit *commit,\n> >  static int error_failed_squash(struct commit *commit,\n> >         struct replay_opts *opts, int subject_len, const char *subject)\n> >  {\n> > +       const char *amend_type = \"squash\";\n> > +\n> > +       if (file_exists(rebase_path_fixup_msg())) {\n> > +               unlink(rebase_path_fixup_msg());\n> > +               amend_type = \"fixup\";\n> > +       }\n> > +       if (write_message(amend_type, strlen(amend_type),\n> > +                      rebase_path_amend_type(), 0))\n> > +               return error(_(\"could not write '%s'\"),\n> > +                            rebase_path_amend_type());\n> \n> Do we want to wait with unlinking rebase_path_fixup_msg()\n> until after we are sure there is no error returned?\n\nActually until after the rename() of `rebase_path_squash_msg()` succeeded,\nyou are right. I had changed the behavior unintentionally.\n\n> I first thought so as to preserve the state as before, but\n> then it only signals the amend type. But we're downgrading the\n> amend type from \"squash\" to \"fixup\", which means that if\n> this error happens and the user just retries the git command\n> we'll end up with a \"fixup\", i.e. not opening their editor?\n\nI am actually more worried about the rename() call failing... ;-) I\nchanged the order back to where it was before.\n\nThanks,\nDscho\n"},{"id":"345318","messageId":"cover.1524296064.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com","subject":"[PATCH v3 0/4] rebase -i: avoid stale \"# This is a combination of\" in commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T07:34:41Z","receivedAt":"2018-04-21T07:35:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Eric Sunshine pointed out that I had such a commit message in\nhttps://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\nand I went on a hunt to figure out how the heck this happened.\n\nTurns out that if there is a fixup/squash chain where the *last* command\nfails with merge conflicts, and we either --skip ahead or resolve the\nconflict to a clean tree and then --continue, our code does not do a\nfinal cleanup.\n\nContrary to my initial gut feeling, this bug was not introduced by my\nrewrite in C of the core parts of rebase -i, but it looks to me as if\nthat bug was with us for a very long time (at least the --skip part).\n\nThe developer (read: user of rebase -i) in me says that we would want to\nfast-track this, but the author of rebase -i in me says that we should\nbe cautious and cook this in `next` for a while.\n\nFixes since v2 (thanks, Stefan!):\n\n- Fixed commit message of 2/4: \"Thisis\" -> \"This is\".\n\n- Reinstated the order where the `message-squash` file is renamed to\n  `message` first, and only if that succeeded, we delete the\n  `message-fixup` file.\n\n\nJohannes Schindelin (4):\n  rebase -i: demonstrate bugs with fixup!/squash! commit messages\n  rebase -i: Handle \"combination of <n> commits\" with GETTEXT_POISON\n  sequencer: leave a tell-tale when a fixup/squash failed\n  rebase --skip: clean up commit message after a failed fixup/squash\n\n sequencer.c                | 94 ++++++++++++++++++++++++++++----------\n t/t3418-rebase-continue.sh | 22 +++++++++\n 2 files changed, 93 insertions(+), 23 deletions(-)\n\n\nbase-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\nPublished-As: https://github.com/dscho/git/releases/tag/clean-msg-after-fixup-continue-v3\nFetch-It-Via: git fetch https://github.com/dscho/git clean-msg-after-fixup-continue-v3\n\nInterdiff vs v2:\n diff --git a/sequencer.c b/sequencer.c\n index 881503a6463..b8b72fd540f 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -2409,6 +2409,10 @@ static int error_failed_squash(struct commit *commit,\n  {\n  \tconst char *amend_type = \"squash\";\n  \n +\tif (rename(rebase_path_squash_msg(), rebase_path_message()))\n +\t\treturn error(_(\"could not rename '%s' to '%s'\"),\n +\t\t\trebase_path_squash_msg(), rebase_path_message());\n +\n  \tif (file_exists(rebase_path_fixup_msg())) {\n  \t\tunlink(rebase_path_fixup_msg());\n  \t\tamend_type = \"fixup\";\n @@ -2418,9 +2422,6 @@ static int error_failed_squash(struct commit *commit,\n  \t\treturn error(_(\"could not write '%s'\"),\n  \t\t\t     rebase_path_amend_type());\n  \n -\tif (rename(rebase_path_squash_msg(), rebase_path_message()))\n -\t\treturn error(_(\"could not rename '%s' to '%s'\"),\n -\t\t\trebase_path_squash_msg(), rebase_path_message());\n  \tunlink(git_path_merge_msg());\n  \tif (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))\n  \t\treturn error(_(\"could not copy '%s' to '%s'\"),\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n"},{"id":"345319","messageId":"a0ed6c98095b73e5c2475de3a3526c93f1967b94.1524296064.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524296064.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 1/4] rebase -i: demonstrate bugs with fixup!/squash! commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T07:35:02Z","receivedAt":"2018-04-21T07:35:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When multiple fixup/squash commands are processed and the last one\ncauses merge conflicts and is skipped, we leave the \"This is a\ncombination of ...\" comments in the commit message.\n\nNoticed by Eric Sunshine.\n\nThis regression test also demonstrates that we rely on the localized\nversion of\n\n\t# This is a combination of <number> commits\n\nto contain the <number> in ASCII, which breaks under GETTEXT_POISON.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t3418-rebase-continue.sh | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 9214d0bb511..6ddf952b7b9 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -88,6 +88,28 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n \tgit rebase --continue\n '\n \n+test_expect_failure '--skip after failed fixup cleans commit message' '\n+\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\tgit checkout -b with-conflicting-fixup &&\n+\ttest_commit wants-fixup &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n+\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n+\t\tgit rebase -i HEAD~4 &&\n+\n+\t: now there is a conflict, and comments in the commit message &&\n+\tgit show HEAD >out &&\n+\ttest_i18ngrep \"This is a combination of\" out &&\n+\n+\t: skip and continue &&\n+\tgit rebase --skip &&\n+\n+\t: now the comments in the commit message should have been cleaned up &&\n+\tgit show HEAD >out &&\n+\ttest_i18ngrep ! \"This is a combination of\" out\n+'\n+\n test_expect_success 'setup rerere database' '\n \trm -fr .git/rebase-* &&\n \tgit reset --hard commit-new-file-F3-on-topic-branch &&\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345320","messageId":"f3ed66a2790cb290e065537a4fdf1f401e6ac835.1524296064.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524296064.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 2/4] rebase -i: Handle \"combination of <n> commits\" with GETTEXT_POISON","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T07:35:21Z","receivedAt":"2018-04-21T07:35:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We previously relied on the localized versions of\n\n\t# This is a combination of <N> commits\n\n(which we write into the commit messages during fixup/squash chains)\nto contain <N> as ASCII.\n\nThis is not true in general, and certainly not in GETTEXT_POISON, as\ndemonstrated by the regression test we just introduced in t3418.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 36 ++++++++++++++++++++++--------------\n 1 file changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 667f35ebdff..dc482e76a28 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1343,19 +1343,18 @@ static int update_squash_messages(enum todo_command command,\n \t\teol = strchrnul(buf.buf, '\\n');\n \t\tif (buf.buf[0] != comment_line_char ||\n \t\t    (p += strcspn(p, \"0123456789\\n\")) == eol)\n-\t\t\treturn error(_(\"unexpected 1st line of squash message:\"\n-\t\t\t\t       \"\\n\\n\\t%.*s\"),\n-\t\t\t\t     (int)(eol - buf.buf), buf.buf);\n-\t\tcount = strtol(p, NULL, 10);\n-\n-\t\tif (count < 1)\n-\t\t\treturn error(_(\"invalid 1st line of squash message:\\n\"\n-\t\t\t\t       \"\\n\\t%.*s\"),\n-\t\t\t\t     (int)(eol - buf.buf), buf.buf);\n+\t\t\tcount = -1;\n+\t\telse\n+\t\t\tcount = strtol(p, NULL, 10);\n \n \t\tstrbuf_addf(&header, \"%c \", comment_line_char);\n-\t\tstrbuf_addf(&header,\n-\t\t\t    _(\"This is a combination of %d commits.\"), ++count);\n+\t\tif (count < 1)\n+\t\t\tstrbuf_addf(&header, _(\"This is a combination of \"\n+\t\t\t\t\t       \"several commits.\"));\n+\t\telse\n+\t\t\tstrbuf_addf(&header,\n+\t\t\t\t    _(\"This is a combination of %d commits.\"),\n+\t\t\t\t    ++count);\n \t\tstrbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);\n \t\tstrbuf_release(&header);\n \t} else {\n@@ -1398,13 +1397,22 @@ static int update_squash_messages(enum todo_command command,\n \tif (command == TODO_SQUASH) {\n \t\tunlink(rebase_path_fixup_msg());\n \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n-\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"), count);\n+\t\tif (count < 2)\n+\t\t\tstrbuf_addf(&buf, _(\"This is the next commit \"\n+\t\t\t\t\t    \"message:\"));\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"),\n+\t\t\t\t    count);\n \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\tstrbuf_addstr(&buf, body);\n \t} else if (command == TODO_FIXUP) {\n \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n-\t\tstrbuf_addf(&buf, _(\"The commit message #%d will be skipped:\"),\n-\t\t\t    count);\n+\t\tif (count < 2)\n+\t\t\tstrbuf_addf(&buf, _(\"The next commit message will be \"\n+\t\t\t\t\t    \"skipped:\"));\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, _(\"The commit message #%d will be \"\n+\t\t\t\t\t    \"skipped:\"), count);\n \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\tstrbuf_add_commented_lines(&buf, body, strlen(body));\n \t} else\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345321","messageId":"55cc358a9baf4e96b50087da1f295f1c8e301d63.1524296064.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524296064.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 3/4] sequencer: leave a tell-tale when a fixup/squash failed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T07:35:42Z","receivedAt":"2018-04-21T07:36:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming patch to clean up fixup/squash commit messages even when\nskipping a final fixup/squash that failed with merge conflicts, we will\nneed to have some indicator what happened.\n\nAs we need to remove the message-fixup and message-squash files upon\nfailure, we cannot use those. So let's just write an explicit amend-type\nfile, containing either `fixup` or `squash`. The absence of that file\nindicates that we were not in the middle of a fixup or squash when merge\nconflicts were happening.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 22 +++++++++++++++++++++-\n 1 file changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex dc482e76a28..9a85b705a84 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -106,6 +106,13 @@ static GIT_PATH_FUNC(rebase_path_author_script, \"rebase-merge/author-script\")\n  * command is processed, this file is deleted.\n  */\n static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n+/*\n+ * If there was a merge conflict in a fixup/squash series, we need to\n+ * record the type so that a `git rebase --skip` can clean up the commit\n+ * message as appropriate. This file will contain that type (`fixup` or\n+ * `squash`), and not exist otherwise.\n+ */\n+static GIT_PATH_FUNC(rebase_path_amend_type, \"rebase-merge/amend-type\")\n /*\n  * When we stop at a given patch via the \"edit\" command, this file contains\n  * the abbreviated commit name of the corresponding patch.\n@@ -2400,10 +2407,21 @@ static int error_with_patch(struct commit *commit,\n static int error_failed_squash(struct commit *commit,\n \tstruct replay_opts *opts, int subject_len, const char *subject)\n {\n+\tconst char *amend_type = \"squash\";\n+\n \tif (rename(rebase_path_squash_msg(), rebase_path_message()))\n \t\treturn error(_(\"could not rename '%s' to '%s'\"),\n \t\t\trebase_path_squash_msg(), rebase_path_message());\n-\tunlink(rebase_path_fixup_msg());\n+\n+\tif (file_exists(rebase_path_fixup_msg())) {\n+\t\tunlink(rebase_path_fixup_msg());\n+\t\tamend_type = \"fixup\";\n+\t}\n+\tif (write_message(amend_type, strlen(amend_type),\n+\t\t       rebase_path_amend_type(), 0))\n+\t\treturn error(_(\"could not write '%s'\"),\n+\t\t\t     rebase_path_amend_type());\n+\n \tunlink(git_path_merge_msg());\n \tif (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))\n \t\treturn error(_(\"could not copy '%s' to '%s'\"),\n@@ -2580,6 +2598,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\tunlink(rebase_path_author_script());\n \t\t\tunlink(rebase_path_stopped_sha());\n \t\t\tunlink(rebase_path_amend());\n+\t\t\tunlink(rebase_path_amend_type());\n \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n \t\t}\n \t\tif (item->command <= TODO_SQUASH) {\n@@ -2807,6 +2826,7 @@ static int commit_staged_changes(struct replay_opts *opts)\n \tif (run_git_commit(rebase_path_message(), opts, flags))\n \t\treturn error(_(\"could not commit staged changes.\"));\n \tunlink(rebase_path_amend());\n+\tunlink(rebase_path_amend_type());\n \treturn 0;\n }\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345322","messageId":"5523b5b5ed7a7fa8433465a8eaf7909361501365.1524296064.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524296064.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T07:39:05Z","receivedAt":"2018-04-21T07:39:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"During a series of fixup/squash commands, the interactive rebase builds\nup a commit message with comments. This will be presented to the user in\nthe editor if at least one of those commands was a `squash`.\n\nHowever, if the last of these fixup/squash commands fails with merge\nconflicts, and if the user then decides to skip it (or resolve it to a\nclean worktree and then continue the rebase), the current code fails to\nclean up the commit message.\n\nThis commit fixes that behavior.\n\nThe diff is best viewed with --color-moved.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c                | 36 ++++++++++++++++++++++++++++--------\n t/t3418-rebase-continue.sh |  2 +-\n 2 files changed, 29 insertions(+), 9 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 9a85b705a84..b8b72fd540f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2790,17 +2790,12 @@ static int continue_single_pick(void)\n \n static int commit_staged_changes(struct replay_opts *opts)\n {\n-\tunsigned int flags = ALLOW_EMPTY | EDIT_MSG;\n+\tunsigned int flags = ALLOW_EMPTY | EDIT_MSG, is_fixup = 0, is_clean;\n \n \tif (has_unstaged_changes(1))\n \t\treturn error(_(\"cannot rebase: You have unstaged changes.\"));\n-\tif (!has_uncommitted_changes(0)) {\n-\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n \n-\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n-\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n-\t\treturn 0;\n-\t}\n+\tis_clean = !has_uncommitted_changes(0);\n \n \tif (file_exists(rebase_path_amend())) {\n \t\tstruct strbuf rev = STRBUF_INIT;\n@@ -2813,16 +2808,41 @@ static int commit_staged_changes(struct replay_opts *opts)\n \t\tif (get_oid_hex(rev.buf, &to_amend))\n \t\t\treturn error(_(\"invalid contents: '%s'\"),\n \t\t\t\trebase_path_amend());\n-\t\tif (oidcmp(&head, &to_amend))\n+\t\tif (!is_clean && oidcmp(&head, &to_amend))\n \t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n \t\t\t\t       \"working tree. Please, commit them\\n\"\n \t\t\t\t       \"first and then run 'git rebase \"\n \t\t\t\t       \"--continue' again.\"));\n+\t\tif (is_clean && !oidcmp(&head, &to_amend)) {\n+\t\t\tstrbuf_reset(&rev);\n+\t\t\t/*\n+\t\t\t * Clean tree, but we may need to finalize a\n+\t\t\t * fixup/squash chain. A failed fixup/squash leaves the\n+\t\t\t * file amend-type in rebase-merge/; It is okay if that\n+\t\t\t * file is missing, in which case there is no such\n+\t\t\t * chain to finalize.\n+\t\t\t */\n+\t\t\tread_oneliner(&rev, rebase_path_amend_type(), 0);\n+\t\t\tif (!strcmp(\"squash\", rev.buf))\n+\t\t\t\tis_fixup = TODO_SQUASH;\n+\t\t\telse if (!strcmp(\"fixup\", rev.buf)) {\n+\t\t\t\tis_fixup = TODO_FIXUP;\n+\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n+\t\t\t}\n+\t\t}\n \n \t\tstrbuf_release(&rev);\n \t\tflags |= AMEND_MSG;\n \t}\n \n+\tif (is_clean && !is_fixup) {\n+\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n+\n+\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n+\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n+\t\treturn 0;\n+\t}\n+\n \tif (run_git_commit(rebase_path_message(), opts, flags))\n \t\treturn error(_(\"could not commit staged changes.\"));\n \tunlink(rebase_path_amend());\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 6ddf952b7b9..693f92409ec 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -88,7 +88,7 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n \tgit rebase --continue\n '\n \n-test_expect_failure '--skip after failed fixup cleans commit message' '\n+test_expect_success '--skip after failed fixup cleans commit message' '\n \ttest_when_finished \"test_might_fail git rebase --abort\" &&\n \tgit checkout -b with-conflicting-fixup &&\n \ttest_commit wants-fixup &&\n-- \n2.17.0.windows.1.15.gaa56ade3205\n"},{"id":"345346","messageId":"cover.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524139900.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 00/16] rebase -i: offer to recreate commit topology by rebasing merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:29:46Z","receivedAt":"2018-04-21T10:30:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Junio, I think this is now ready for `next`. Thank you for your patience\nand help with this.\n\nOnce upon a time, I dreamed of an interactive rebase that would not\nlinearize all patches and drop all merge commits, but instead recreate\nthe commit topology faithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --rebase-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge -C 3456 D # C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --rebase-merges. And then one\nto allow for rebasing merge commits in a smarter way (this one will need\na bit more work, though, as it can result in very complicated, nested\nmerge conflicts *very* easily).\n\nChanges since v7:\n\n- Touched up all the documentation (it was a mistake to copy-edit the\n  --preserve-merges description, for example).\n\n- Disentangled the rescheduling of label/reset/merge from the one of the\n  pick/fixup/squash code path (thanks Phillip!).\n\n- When the merge failed, we now write out .git/rebase-merge/patch.\n\n- An `exec git cherry-pick` or `exec git revert` will no longer mess\n  with refs/rewritten/ in sequencer_remove_state() (d'oh....).\n\n\nJohannes Schindelin (14):\n  sequencer: avoid using errno clobbered by rollback_lock_file()\n  sequencer: make rearrange_squash() a bit more obvious\n  sequencer: refactor how original todo list lines are accessed\n  sequencer: offer helpful advice when a command was rescheduled\n  sequencer: introduce the `merge` command\n  sequencer: fast-forward `merge` commands, if possible\n  rebase-helper --make-script: introduce a flag to rebase merges\n  rebase: introduce the --rebase-merges option\n  sequencer: make refs generated by the `label` command worktree-local\n  sequencer: handle post-rewrite for merge commands\n  rebase --rebase-merges: avoid \"empty merges\"\n  pull: accept --rebase=merges to recreate the branch topology\n  rebase -i: introduce --rebase-merges=[no-]rebase-cousins\n  rebase -i --rebase-merges: add a section to the man page\n\nPhillip Wood (1):\n  rebase --rebase-merges: add test for --keep-empty\n\nStefan Beller (1):\n  git-rebase--interactive: clarify arguments\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   6 +-\n Documentation/git-rebase.txt           | 160 ++++-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |  18 +-\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |  22 +-\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 891 +++++++++++++++++++++++--\n sequencer.h                            |   7 +\n t/t3421-rebase-topology-linear.sh      |   1 +\n t/t3430-rebase-merges.sh               | 244 +++++++\n 14 files changed, 1347 insertions(+), 60 deletions(-)\n create mode 100755 t/t3430-rebase-merges.sh\n\n\nbase-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v8\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v8\n\nInterdiff vs v7:\n diff --git a/Documentation/config.txt b/Documentation/config.txt\n index da46f154bb3..d6bcb5dcb67 100644\n --- a/Documentation/config.txt\n +++ b/Documentation/config.txt\n @@ -1059,8 +1059,8 @@ branch.<name>.rebase::\n  \tbranch-specific manner.\n  +\n  When `merges`, pass the `--rebase-merges` option to 'git rebase'\n -so that locally committed merge commits will not be flattened\n -by running 'git pull'.\n +so that the local merge commits are included in the rebase (see\n +linkgit:git-rebase[1] for details).\n  +\n  When preserve, also pass `--preserve-merges` along to 'git rebase'\n  so that locally committed merge commits will not be flattened\n @@ -2622,8 +2622,8 @@ pull.rebase::\n  \tper-branch basis.\n  +\n  When `merges`, pass the `--rebase-merges` option to 'git rebase'\n -so that locally committed merge commits will not be flattened\n -by running 'git pull'.\n +so that the local merge commits are included in the rebase (see\n +linkgit:git-rebase[1] for details).\n  +\n  When preserve, also pass `--preserve-merges` along to 'git rebase'\n  so that locally committed merge commits will not be flattened\n diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\n index 6f76d815dd3..4e0ad6fd8e0 100644\n --- a/Documentation/git-pull.txt\n +++ b/Documentation/git-pull.txt\n @@ -109,7 +109,8 @@ include::merge-options.txt[]\n  \tto avoid rebasing non-local changes.\n  +\n  When set to `merges`, rebase using `git rebase --rebase-merges` so that\n -locally created merge commits will not be flattened.\n +the local merge commits are included in the rebase (see\n +linkgit:git-rebase[1] for details).\n  +\n  When set to preserve, rebase with the `--preserve-merges` option passed\n  to `git rebase` so that locally created merge commits will not be flattened.\n diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n index 0ff83b62821..3b996e46d6a 100644\n --- a/Documentation/git-rebase.txt\n +++ b/Documentation/git-rebase.txt\n @@ -380,15 +380,25 @@ have the long commit hash prepended to the format.\n  \n  -r::\n  --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n -\tRebase merge commits instead of flattening the history by replaying\n -\tmerges. Merge conflict resolutions or manual amendments to merge\n -\tcommits are not rebased automatically, but have to be applied\n +\tBy default, a rebase will simply drop merge commits and only rebase\n +\tthe non-merge commits. With this option, it will try to preserve\n +\tthe branching structure within the commits that are to be rebased,\n +\tby recreating the merge commits. If a merge commit resolved any merge\n +\tor contained manual amendments, then they will have to be re-applied\n  \tmanually.\n  +\n  By default, or when `no-rebase-cousins` was specified, commits which do not\n  have `<upstream>` as direct ancestor will keep their original branch point.\n -If the `rebase-cousins` mode is turned on, such commits are rebased onto\n -`<upstream>` (or `<onto>`, if specified).\n +If the `rebase-cousins` mode is turned on, such commits are instead rebased\n +onto `<upstream>` (or `<onto>`, if specified).\n ++\n +This mode is similar in spirit to `--preserve-merges`, but in contrast to\n +that option works well in interactive rebases: commits can be reordered,\n +inserted and dropped at will.\n ++\n +It is currently only possible to recreate the merge commits using the\n +`recursive` merge strategy; Different merge strategies can be used only via\n +explicit `exec git merge -s <strategy> [...]` commands.\n  +\n  See also REBASING MERGES below.\n  \n @@ -795,8 +805,9 @@ REBASING MERGES\n  The interactive rebase command was originally designed to handle\n  individual patch series. As such, it makes sense to exclude merge\n  commits from the todo list, as the developer may have merged the\n -current `master` while working on the branch, only to eventually\n -rebase all the commits onto `master` (skipping the merge commits).\n +then-current `master` while working on the branch, only to rebase\n +all the commits onto `master` eventually (skipping the merge\n +commits).\n  \n  However, there are legitimate reasons why a developer may want to\n  recreate merge commits: to keep the branch structure (or \"commit\n @@ -846,21 +857,23 @@ merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n  merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n  ------------\n  \n -In contrast to a regular interactive rebase, there are `label`, `reset` and\n -`merge` commands in addition to `pick` ones.\n +In contrast to a regular interactive rebase, there are `label`, `reset`\n +and `merge` commands in addition to `pick` ones.\n  \n  The `label` command associates a label with the current HEAD when that\n  command is executed. These labels are created as worktree-local refs\n  (`refs/rewritten/<label>`) that will be deleted when the rebase\n  finishes. That way, rebase operations in multiple worktrees linked to\n -the same repository do not interfere with one another. If the `label` command\n -fails, it is rescheduled immediately, with a helpful message how to proceed.\n +the same repository do not interfere with one another. If the `label`\n +command fails, it is rescheduled immediately, with a helpful message how\n +to proceed.\n  \n -The `reset` command is essentially a `git read-tree -m -u` (think: `git\n -reset --hard`, but refusing to overwrite untracked files) to the\n -specified revision (typically a previously-labeled one). If the `reset`\n -command fails, it is rescheduled immediately, with a helpful message how to\n -proceed.\n +The `reset` command resets the HEAD, index and worktree to the specified\n +revision. It is isimilar to an `exec git reset --hard <label>`, but\n +refuses to overwrite untracked files. If the `reset` command fails, it is\n +rescheduled immediately, with a helpful message how to edit the todo list\n +(this typically happens when a `reset` command was inserted into the todo\n +list manually and contains a typo).\n  \n  The `merge` command will merge the specified revision into whatever is\n  HEAD at that time. With `-C <original-commit>`, the commit message of\n @@ -875,7 +888,7 @@ At this time, the `merge` command will *always* use the `recursive`\n  merge strategy, with no way to choose a different one. To work around\n  this, an `exec` command can be used to call `git merge` explicitly,\n  using the fact that the labels are worktree-local refs (the ref\n -`refs/rewritten/onto` would correspond to the label `onto`).\n +`refs/rewritten/onto` would correspond to the label `onto`, for example).\n  \n  Note: the first command (`label onto`) labels the revision onto which\n  the commits are rebased; The name `onto` is just a convention, as a nod\n @@ -925,7 +938,7 @@ The todo list presented by `--preserve-merges --interactive` does not\n  represent the topology of the revision graph.  Editing commits and\n  rewording their commit messages should work fine, but attempts to\n  reorder commits tend to produce counterintuitive results. Use\n ---rebase-merges for a more faithful representation.\n +`--rebase-merges` in such scenarios instead.\n  \n  For example, an attempt to rearrange\n  ------------\n diff --git a/sequencer.c b/sequencer.c\n index 3c7bb5d3fd8..9ffadbb3d3c 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -258,7 +258,8 @@ int sequencer_remove_state(struct replay_opts *opts)\n  \tstruct strbuf buf = STRBUF_INIT;\n  \tint i;\n  \n -\tif (strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n +\tif (is_rebase_i(opts) &&\n +\t    strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n  \t\tchar *p = buf.buf;\n  \t\twhile (*p) {\n  \t\t\tchar *eol = strchr(p, '\\n');\n @@ -2960,7 +2961,7 @@ N_(\"Could not execute the todo command\\n\"\n  \n  static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  {\n -\tint res = 0;\n +\tint res = 0, reschedule = 0;\n  \n  \tsetenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n  \tif (opts->allow_ff)\n @@ -3002,7 +3003,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\tres = do_pick_commit(item->command, item->commit,\n  \t\t\t\t\topts, is_final_fixup(todo_list));\n  \t\t\tif (is_rebase_i(opts) && res < 0) {\n -reschedule:\n +\t\t\t\t/* Reschedule */\n  \t\t\t\tadvise(_(rescheduled_advice),\n  \t\t\t\t       get_item_line_length(todo_list,\n  \t\t\t\t\t\t\t    todo_list->current),\n @@ -3059,21 +3060,42 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\t}\n  \t\t} else if (item->command == TODO_LABEL) {\n  \t\t\tif ((res = do_label(item->arg, item->arg_len)))\n -\t\t\t\tgoto reschedule;\n +\t\t\t\treschedule = 1;\n  \t\t} else if (item->command == TODO_RESET) {\n  \t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n -\t\t\t\tgoto reschedule;\n +\t\t\t\treschedule = 1;\n  \t\t} else if (item->command == TODO_MERGE) {\n -\t\t\tres = do_merge(item->commit, item->arg, item->arg_len,\n -\t\t\t\t       item->flags, opts);\n -\t\t\tif (res < 0)\n -\t\t\t\tgoto reschedule;\n -\t\t\tif (item->commit)\n +\t\t\tif ((res = do_merge(item->commit,\n +\t\t\t\t\t    item->arg, item->arg_len,\n +\t\t\t\t\t    item->flags, opts)) < 0)\n +\t\t\t\treschedule = 1;\n +\t\t\telse if (item->commit)\n  \t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n  \t\t\t\t\t\t    peek_command(todo_list, 1));\n +\t\t\tif (res > 0)\n +\t\t\t\t/* failed with merge conflicts */\n +\t\t\t\treturn error_with_patch(item->commit,\n +\t\t\t\t\t\t\titem->arg,\n +\t\t\t\t\t\t\titem->arg_len, opts,\n +\t\t\t\t\t\t\tres, 0);\n  \t\t} else if (!is_noop(item->command))\n  \t\t\treturn error(_(\"unknown command %d\"), item->command);\n  \n +\t\tif (reschedule) {\n +\t\t\tadvise(_(rescheduled_advice),\n +\t\t\t       get_item_line_length(todo_list,\n +\t\t\t\t\t\t    todo_list->current),\n +\t\t\t       get_item_line(todo_list, todo_list->current));\n +\t\t\ttodo_list->current--;\n +\t\t\tif (save_todo(todo_list, opts))\n +\t\t\t\treturn -1;\n +\t\t\tif (item->commit)\n +\t\t\t\treturn error_with_patch(item->commit,\n +\t\t\t\t\t\t\titem->arg,\n +\t\t\t\t\t\t\titem->arg_len, opts,\n +\t\t\t\t\t\t\tres, 0);\n +\t\t}\n +\n  \t\ttodo_list->current++;\n  \t\tif (res)\n  \t\t\treturn res;\n diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n index f2de7059830..3d4dfdf7bec 100755\n --- a/t/t3430-rebase-merges.sh\n +++ b/t/t3430-rebase-merges.sh\n @@ -125,6 +125,29 @@ test_expect_success '`reset` refuses to overwrite untracked files' '\n  \tgit rebase --abort\n  '\n  \n +test_expect_success 'failed `merge` writes patch (may be rescheduled, too)' '\n +\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n +\tgit checkout -b conflicting-merge A &&\n +\n +\t: fail because of conflicting untracked file &&\n +\t>G.t &&\n +\techo \"merge -C H G\" >script-from-scratch &&\n +\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n +\ttest_tick &&\n +\ttest_must_fail git rebase -ir HEAD &&\n +\tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n +\tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n +\ttest_path_is_file .git/rebase-merge/patch &&\n +\n +\t: fail because of merge conflict &&\n +\trm G.t .git/rebase-merge/patch &&\n +\tgit reset --hard &&\n +\ttest_commit conflicting-G G.t not-G conflicting-G &&\n +\ttest_must_fail git rebase --continue &&\n +\t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n +\ttest_path_is_file .git/rebase-merge/patch\n +'\n +\n  test_expect_success 'with a branch tip that was cherry-picked already' '\n  \tgit checkout -b already-upstream master &&\n  \tbase=\"$(git rev-parse --verify HEAD)\" &&\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n"},{"id":"345347","messageId":"ae9847669b70282a0e17e46030b5c4e609d73923.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 01/16] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:30:08Z","receivedAt":"2018-04-21T10:30:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"As pointed out in a review of the `--rebase-merges` patch series,\n`rollback_lock_file()` clobbers errno. Therefore, we have to report the\nerror message that uses errno before calling said function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 10 ++++++----\n 1 file changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 667f35ebdff..096e6d241e0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -345,12 +345,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n \tif (msg_fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n \tif (write_in_full(msg_fd, buf, len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n+\t\terror_errno(_(\"could not write eol to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write eol to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&msg_file) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), filename);\n@@ -2119,9 +2121,9 @@ static int save_head(const char *head)\n \twritten = write_in_full(fd, buf.buf, buf.len);\n \tstrbuf_release(&buf);\n \tif (written < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), git_path_head_file());\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t   git_path_head_file());\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&head_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), git_path_head_file());\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345348","messageId":"1bea3d4cd72e8631780bb460148ad94636427744.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 02/16] sequencer: make rearrange_squash() a bit more obvious","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:30:28Z","receivedAt":"2018-04-21T10:31:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"There are some commands that have to be skipped from rearranging by virtue\nof not handling any commits.\n\nHowever, the logic was not quite obvious: it skipped commands based on\ntheir position in the enum todo_command.\n\nInstead, let's make it explicit that we skip all commands that do not\nhandle any commit. With one exception: the `drop` command, because it,\nwell, drops the commit and is therefore not eligible to rearranging.\n\nNote: this is a bit academic at the moment because the only time we call\n`rearrange_squash()` is directly after generating the todo list, when we\nhave nothing but `pick` commands anyway.\n\nHowever, the upcoming `merge` command *will* want to be handled by that\nfunction, and it *can* handle commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 096e6d241e0..1ee70d843c1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3393,7 +3393,7 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (!item->commit || item->command == TODO_DROP) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345349","messageId":"9b7fc34e8f20205109a6c081671ec8d6e0b1e27f.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 03/16] sequencer: refactor how original todo list lines are accessed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:31:00Z","receivedAt":"2018-04-21T10:31:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Previously, we did a lot of arithmetic gymnastics to get at the line in\nthe todo list (as stored in todo_list.buf). This might have been fast,\nbut only in terms of execution speed, not in terms of developer time.\n\nLet's refactor this to make it a lot easier to read, and hence to\nreason about the correctness of the code. It is not performance-critical\ncode anyway.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 60 ++++++++++++++++++++++++++++++++---------------------\n 1 file changed, 36 insertions(+), 24 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1ee70d843c1..3d0a45ab25a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1870,6 +1870,23 @@ static int count_commands(struct todo_list *todo_list)\n \treturn count;\n }\n \n+static int get_item_line_offset(struct todo_list *todo_list, int index)\n+{\n+\treturn index < todo_list->nr ?\n+\t\ttodo_list->items[index].offset_in_buf : todo_list->buf.len;\n+}\n+\n+static const char *get_item_line(struct todo_list *todo_list, int index)\n+{\n+\treturn todo_list->buf.buf + get_item_line_offset(todo_list, index);\n+}\n+\n+static int get_item_line_length(struct todo_list *todo_list, int index)\n+{\n+\treturn get_item_line_offset(todo_list, index + 1)\n+\t\t-  get_item_line_offset(todo_list, index);\n+}\n+\n static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)\n {\n \tint fd;\n@@ -2244,29 +2261,27 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n \tif (fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), todo_path);\n-\toffset = next < todo_list->nr ?\n-\t\ttodo_list->items[next].offset_in_buf : todo_list->buf.len;\n+\toffset = get_item_line_offset(todo_list, next);\n \tif (write_in_full(fd, todo_list->buf.buf + offset,\n \t\t\ttodo_list->buf.len - offset) < 0)\n \t\treturn error_errno(_(\"could not write to '%s'\"), todo_path);\n \tif (commit_lock_file(&todo_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n \n-\tif (is_rebase_i(opts)) {\n-\t\tconst char *done_path = rebase_path_done();\n-\t\tint fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);\n-\t\tint prev_offset = !next ? 0 :\n-\t\t\ttodo_list->items[next - 1].offset_in_buf;\n+\tif (is_rebase_i(opts) && next > 0) {\n+\t\tconst char *done = rebase_path_done();\n+\t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n+\t\tint ret = 0;\n \n-\t\tif (fd >= 0 && offset > prev_offset &&\n-\t\t    write_in_full(fd, todo_list->buf.buf + prev_offset,\n-\t\t\t\t  offset - prev_offset) < 0) {\n-\t\t\tclose(fd);\n-\t\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t\t   done_path);\n-\t\t}\n-\t\tif (fd >= 0)\n-\t\t\tclose(fd);\n+\t\tif (fd < 0)\n+\t\t\treturn 0;\n+\t\tif (write_in_full(fd, get_item_line(todo_list, next - 1),\n+\t\t\t\t  get_item_line_length(todo_list, next - 1))\n+\t\t    < 0)\n+\t\t\tret = error_errno(_(\"could not write to '%s'\"), done);\n+\t\tif (close(fd) < 0)\n+\t\t\tret = error_errno(_(\"failed to finalize '%s'\"), done);\n+\t\treturn ret;\n \t}\n \treturn 0;\n }\n@@ -3297,8 +3312,7 @@ int skip_unnecessary_picks(void)\n \t\toid = &item->commit->object.oid;\n \t}\n \tif (i > 0) {\n-\t\tint offset = i < todo_list.nr ?\n-\t\t\ttodo_list.items[i].offset_in_buf : todo_list.buf.len;\n+\t\tint offset = get_item_line_offset(&todo_list, i);\n \t\tconst char *done_path = rebase_path_done();\n \n \t\tfd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);\n@@ -3478,12 +3492,10 @@ int rearrange_squash(void)\n \t\t\t\tcontinue;\n \n \t\t\twhile (cur >= 0) {\n-\t\t\t\tint offset = todo_list.items[cur].offset_in_buf;\n-\t\t\t\tint end_offset = cur + 1 < todo_list.nr ?\n-\t\t\t\t\ttodo_list.items[cur + 1].offset_in_buf :\n-\t\t\t\t\ttodo_list.buf.len;\n-\t\t\t\tchar *bol = todo_list.buf.buf + offset;\n-\t\t\t\tchar *eol = todo_list.buf.buf + end_offset;\n+\t\t\t\tconst char *bol =\n+\t\t\t\t\tget_item_line(&todo_list, cur);\n+\t\t\t\tconst char *eol =\n+\t\t\t\t\tget_item_line(&todo_list, cur + 1);\n \n \t\t\t\t/* replace 'pick', by 'fixup' or 'squash' */\n \t\t\t\tcommand = todo_list.items[cur].command;\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345350","messageId":"b768f1bbfdc5c8b03d7bd67bf6915549d18934f2.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 04/16] sequencer: offer helpful advice when a command was rescheduled","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:31:41Z","receivedAt":"2018-04-21T10:32:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Previously, we did that just magically, and potentially left some users\nquite puzzled. Let's err on the safe side instead, telling the user what\nis happening, and how they are supposed to continue.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 16 ++++++++++++++++\n 1 file changed, 16 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 3d0a45ab25a..01443e0f245 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2555,6 +2555,17 @@ static const char *reflog_message(struct replay_opts *opts,\n \treturn buf.buf;\n }\n \n+static const char rescheduled_advice[] =\n+N_(\"Could not execute the todo command\\n\"\n+\"\\n\"\n+\"    %.*s\"\n+\"\\n\"\n+\"It has been rescheduled; To edit the command before continuing, please\\n\"\n+\"edit the todo list first:\\n\"\n+\"\\n\"\n+\"    git rebase --edit-todo\\n\"\n+\"    git rebase --continue\\n\");\n+\n static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n {\n \tint res = 0;\n@@ -2600,6 +2611,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t\topts, is_final_fixup(todo_list));\n \t\t\tif (is_rebase_i(opts) && res < 0) {\n \t\t\t\t/* Reschedule */\n+\t\t\t\tadvise(_(rescheduled_advice),\n+\t\t\t\t       get_item_line_length(todo_list,\n+\t\t\t\t\t\t\t    todo_list->current),\n+\t\t\t\t       get_item_line(todo_list,\n+\t\t\t\t\t\t     todo_list->current));\n \t\t\t\ttodo_list->current--;\n \t\t\t\tif (save_todo(todo_list, opts))\n \t\t\t\t\treturn -1;\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345351","messageId":"b071b84a4cd4d3f3017a6568ffaab0bd19bf16d2.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 05/16] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:32:14Z","receivedAt":"2018-04-21T10:32:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Stefan Beller <stefanbeller@gmail.com>\n\nUp to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we are about to introduce commands that take different\narguments, clarify each command by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 50323fc2735..e1b865f43f2 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,13 +155,13 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345352","messageId":"7360a072f6fdd276d30839613434329b645e2cce.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:33:02Z","receivedAt":"2018-04-21T10:33:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label` and `reset` commands to label\ncommits and to reset to labeled commits. This patch adds the `merge`\ncommand, with the following syntax:\n\n\tmerge [-C <commit>] <rev> # <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the merge commit that is about\nto be created.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list:\n\n\tlabel onto\n\n\t# Branch abc\n\treset onto\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\treset onto\n\tpick cafecafe And now for something completely different\n\tmerge -C baaabaaa abc # Merge the branch 'abc' into master\n\nTo edit the merge commit's message (a \"reword\" for merges, if you will),\nuse `-c` (lower-case) instead of `-C`; this convention was borrowed from\n`git commit` that also supports `-c` and `-C` with similar meanings.\n\nTo create *new* merges, i.e. without copying the commit message from an\nexisting commit, simply omit the `-C <commit>` parameter (which will\nopen an editor for the merge message):\n\n\tmerge abc\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in a separate\npatch series, support for merges using strategies other than the\nrecursive merge is left for the future.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   6 +\n sequencer.c                | 407 ++++++++++++++++++++++++++++++++++++-\n 2 files changed, 406 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex e1b865f43f2..ccd5254d1c9 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,12 @@ s, squash <commit> = use commit, but meld into previous commit\n f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n+l, label <label> = label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n+.       create a merge commit using the original merge commit's\n+.       message (or the oneline, if no original merge commit was\n+.       specified). Use -c <commit> to reword the commit message.\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex 01443e0f245..35fcacbdf0f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"notes-utils.h\"\n #include \"sigchain.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `label` command to record the need for cleanup.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -244,18 +253,34 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n \n int sequencer_remove_state(struct replay_opts *opts)\n {\n-\tstruct strbuf dir = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i;\n \n+\tif (is_rebase_i(opts) &&\n+\t    strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n+\t\tchar *p = buf.buf;\n+\t\twhile (*p) {\n+\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\tif (eol)\n+\t\t\t\t*eol = '\\0';\n+\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n+\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\tif (!eol)\n+\t\t\t\tbreak;\n+\t\t\tp = eol + 1;\n+\t\t}\n+\t}\n+\n \tfree(opts->gpg_sign);\n \tfree(opts->strategy);\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n \tfree(opts->xopts);\n \n-\tstrbuf_addstr(&dir, get_dir(opts));\n-\tremove_dir_recursively(&dir, 0);\n-\tstrbuf_release(&dir);\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, get_dir(opts));\n+\tremove_dir_recursively(&buf, 0);\n+\tstrbuf_release(&buf);\n \n \treturn 0;\n }\n@@ -1279,6 +1304,9 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n+\tTODO_MERGE,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1297,6 +1325,9 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n+\t{ 'm', \"merge\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1724,9 +1755,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)\n \treturn 0;\n }\n \n+enum todo_item_flags {\n+\tTODO_EDIT_MERGE_MSG = 1\n+};\n+\n struct todo_item {\n \tenum todo_command command;\n \tstruct commit *commit;\n+\tunsigned int flags;\n \tconst char *arg;\n \tint arg_len;\n \tsize_t offset_in_buf;\n@@ -1761,6 +1797,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \tchar *end_of_object_name;\n \tint i, saved, status, padding;\n \n+\titem->flags = 0;\n+\n \t/* left-trim */\n \tbol += strspn(bol, \" \\t\");\n \n@@ -1802,13 +1840,29 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n \t\treturn 0;\n \t}\n \n+\tif (item->command == TODO_MERGE) {\n+\t\tif (skip_prefix(bol, \"-C\", &bol))\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n+\t\t} else {\n+\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n+\t\t\titem->commit = NULL;\n+\t\t\titem->arg = bol;\n+\t\t\titem->arg_len = (int)(eol - bol);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n \tsaved = *end_of_object_name;\n \t*end_of_object_name = '\\0';\n@@ -2465,6 +2519,305 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn -1;\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n+\t\terror_errno(_(\"could not read '%s'\"), filename);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tif (len == 1 && *name == '#')\n+\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t} else if (get_oid(\"HEAD\", &head_oid)) {\n+\t\terror(_(\"could not read HEAD\"));\n+\t\tret = -1;\n+\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n+\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t   ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static const char *reflog_message(struct replay_opts *opts,\n+\tconst char *sub_action, const char *fmt, ...);\n+\n+static int do_reset(const char *name, int len, struct replay_opts *opts)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options unpack_tree_opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\t/* Determine the length of the label */\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n+\tunpack_tree_opts.head_idx = 1;\n+\tunpack_tree_opts.src_index = &the_index;\n+\tunpack_tree_opts.dst_index = &the_index;\n+\tunpack_tree_opts.fn = oneway_merge;\n+\tunpack_tree_opts.merge = 1;\n+\tunpack_tree_opts.update = 1;\n+\n+\tif (read_cache_unmerged()) {\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error_resolve_conflict(_(action_name(opts)));\n+\t}\n+\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret)\n+\t\tret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n+\t\t\t\t\t\tlen, name), \"HEAD\", &oid,\n+\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    int flags, struct replay_opts *opts)\n+{\n+\tint run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n+\t\tEDIT_MSG | VERIFY_MSG : 0;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *bases, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint merge_arg_len, oneline_offset, ret;\n+\tstatic struct lock_file lock;\n+\tconst char *p;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {\n+\t\tret = -1;\n+\t\tgoto leave_merge;\n+\t}\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\tret = error(_(\"cannot merge without a current revision\"));\n+\t\tgoto leave_merge;\n+\t}\n+\n+\toneline_offset = arg_len;\n+\tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n+\tp = arg + merge_arg_len;\n+\tp += strspn(p, \" \\t\\n\");\n+\tif (*p == '#' && (!p[1] || isspace(p[1]))) {\n+\t\tp += 1 + strspn(p + 1, \" \\t\\n\");\n+\t\toneline_offset = p - arg;\n+\t} else if (p - arg < arg_len)\n+\t\tBUG(\"octopus merges are not supported yet: '%s'\", p);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\n+\tif (!merge_commit) {\n+\t\tret = error(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tgoto leave_merge;\n+\t}\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\tret = error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tret = write_message(body, len, git_path_merge_msg(), 0);\n+\t\tunuse_commit_buffer(commit, message);\n+\t\tif (ret) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t} else {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tif (oneline_offset < arg_len) {\n+\t\t\tp = arg + oneline_offset;\n+\t\t\tlen = arg_len - oneline_offset;\n+\t\t} else {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tret = write_message(p, len, git_path_merge_msg(), 0);\n+\t\tstrbuf_release(&buf);\n+\t\tif (ret) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t}\n+\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tbases = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = bases; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(bases);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\terror(_(\"could not even attempt to merge '%.*s'\"),\n+\t\t      merge_arg_len, arg);\n+\t\tgoto leave_merge;\n+\t}\n+\t/*\n+\t * The return value of merge_recursive() is 1 on clean, and 0 on\n+\t * unclean merge.\n+\t *\n+\t * Let's reverse that, so that do_merge() returns 0 upon success and\n+\t * 1 upon failed merge (keeping the return value -1 for the cases where\n+\t * we will want to reschedule the `merge` command).\n+\t */\n+\tret = !ret;\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tret = error(_(\"merge: Unable to write new index file\"));\n+\t\tgoto leave_merge;\n+\t}\n+\n+\trollback_lock_file(&lock);\n+\tif (ret)\n+\t\trerere(opts->allow_rerere_auto);\n+\telse\n+\t\tret = run_git_commit(git_path_merge_msg(), opts,\n+\t\t\t\t     run_commit_flags);\n+\n+leave_merge:\n+\tstrbuf_release(&ref_name);\n+\trollback_lock_file(&lock);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2568,7 +2921,7 @@ N_(\"Could not execute the todo command\\n\"\n \n static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n {\n-\tint res = 0;\n+\tint res = 0, reschedule = 0;\n \n \tsetenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n \tif (opts->allow_ff)\n@@ -2639,7 +2992,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t\tintend_to_amend();\n \t\t\t\treturn error_failed_squash(item->commit, opts,\n \t\t\t\t\titem->arg_len, item->arg);\n-\t\t\t} else if (res && is_rebase_i(opts))\n+\t\t\t} else if (res && is_rebase_i(opts) && item->commit)\n \t\t\t\treturn res | error_with_patch(item->commit,\n \t\t\t\t\titem->arg, item->arg_len, opts, res,\n \t\t\t\t\titem->command == TODO_REWORD);\n@@ -2665,9 +3018,41 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n+\t\t} else if (item->command == TODO_LABEL) {\n+\t\t\tif ((res = do_label(item->arg, item->arg_len)))\n+\t\t\t\treschedule = 1;\n+\t\t} else if (item->command == TODO_RESET) {\n+\t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n+\t\t\t\treschedule = 1;\n+\t\t} else if (item->command == TODO_MERGE) {\n+\t\t\tif ((res = do_merge(item->commit,\n+\t\t\t\t\t    item->arg, item->arg_len,\n+\t\t\t\t\t    item->flags, opts)) < 0)\n+\t\t\t\treschedule = 1;\n+\t\t\telse if (res > 0)\n+\t\t\t\t/* failed with merge conflicts */\n+\t\t\t\treturn error_with_patch(item->commit,\n+\t\t\t\t\t\t\titem->arg,\n+\t\t\t\t\t\t\titem->arg_len, opts,\n+\t\t\t\t\t\t\tres, 0);\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n+\t\tif (reschedule) {\n+\t\t\tadvise(_(rescheduled_advice),\n+\t\t\t       get_item_line_length(todo_list,\n+\t\t\t\t\t\t    todo_list->current),\n+\t\t\t       get_item_line(todo_list, todo_list->current));\n+\t\t\ttodo_list->current--;\n+\t\t\tif (save_todo(todo_list, opts))\n+\t\t\t\treturn -1;\n+\t\t\tif (item->commit)\n+\t\t\t\treturn error_with_patch(item->commit,\n+\t\t\t\t\t\t\titem->arg,\n+\t\t\t\t\t\t\titem->arg_len, opts,\n+\t\t\t\t\t\t\tres, 0);\n+\t\t}\n+\n \t\ttodo_list->current++;\n \t\tif (res)\n \t\t\treturn res;\n@@ -3147,8 +3532,16 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  short_commit_name(item->commit) :\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n+\t\t\tif (item->command == TODO_MERGE) {\n+\t\t\t\tif (item->flags & TODO_EDIT_MERGE_MSG)\n+\t\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n+\t\t\t}\n+\n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n \t\t}\n+\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345353","messageId":"7373d7c4d416182f26a18b79a5a26f5819d31b3b.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 07/16] sequencer: fast-forward `merge` commands, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:33:41Z","receivedAt":"2018-04-21T10:34:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to rebase a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 33 ++++++++++++++++++++++++++++++++-\n 1 file changed, 32 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 35fcacbdf0f..5944d3a34eb 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2680,7 +2680,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint merge_arg_len, oneline_offset, ret;\n+\tint merge_arg_len, oneline_offset, can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \tconst char *p;\n \n@@ -2765,6 +2765,37 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\t}\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the first parent of the original merge\n+\t * commit, we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n+\t/*\n+\t * If the merge head is different from the original one, we cannot\n+\t * fast-forward.\n+\t */\n+\tif (can_fast_forward) {\n+\t\tstruct commit_list *second_parent = commit->parents->next;\n+\n+\t\tif (second_parent && !second_parent->next &&\n+\t\t    oidcmp(&merge_commit->object.oid,\n+\t\t\t   &second_parent->item->object.oid))\n+\t\t\tcan_fast_forward = 0;\n+\t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\trollback_lock_file(&lock);\n+\t\tret = fast_forward_to(&commit->object.oid,\n+\t\t\t\t      &head_commit->object.oid, 0, opts);\n+\t\tgoto leave_merge;\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345354","messageId":"ff71f636d5171359d94097423b8e9e340ab2152a.1524306546.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 08/16] rebase-helper --make-script: introduce a flag to rebase merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:34:19Z","receivedAt":"2018-04-21T10:34:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --rebase-merges option. For a\ncommit topology like this (where the HEAD points to C):\n\n\t- A - B - C\n\t    \\   /\n\t      D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge -C 3456 D # C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch series.\n\nAs a special, hard-coded label, all merge-rebasing todo lists start with\nthe command `label onto` so that we can later always refer to the revision\nonto which everything is rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 351 ++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 353 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex ad074705bb5..781782e7272 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -24,6 +24,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n+\t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,6 +58,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex 5944d3a34eb..1e17a11ca32 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -25,6 +25,8 @@\n #include \"sigchain.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -3436,6 +3438,343 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   (len == 1 && *label == '#') ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, or the label is a '#' (which we use as a separator\n+\t\t * between merge heads and oneline), we append a dash and a\n+\t\t * number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n+\t\t*cmd_label = abbr ? \"l\" : \"label\",\n+\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n+\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\t\tint is_empty;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tis_empty = is_original_commit_empty(commit);\n+\t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_empty)\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s -C %s\",\n+\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", cmd_label);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(to, \"onto\"))\n+\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n+\t\t\t\t\tcmd_reset, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\",\n+\t\t\t\t\tcmd_label, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -3446,11 +3785,16 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_pick = 1;\n+\tif (rebase_merges)\n+\t\trevs.cherry_mark = 1;\n+\telse {\n+\t\trevs.max_parents = 1;\n+\t\trevs.cherry_pick = 1;\n+\t}\n \trevs.limited = 1;\n \trevs.reverse = 1;\n \trevs.right_only = 1;\n@@ -3474,6 +3818,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (rebase_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tstrbuf_reset(&buf);\n \t\tif (!keep_empty && is_original_commit_empty(commit))\ndiff --git a/sequencer.h b/sequencer.h\nindex e45b178dfc4..6bc4da17243 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -59,6 +59,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_REBASE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345355","messageId":"0c92bdd1829328544269722cbbd3edcd169bb148.1524306547.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 09/16] rebase: introduce the --rebase-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:34:46Z","receivedAt":"2018-04-21T10:35:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt to answer this was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto flatten the branch topology or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--rebase-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge <label>`. And once this mode will\nhave become stable and universally accepted, we can deprecate the design\nmistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |  20 ++-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 +\n t/t3430-rebase-merges.sh               | 179 +++++++++++++++++++++++++\n 5 files changed, 206 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 3277ca14327..34e0f6a69c1 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -378,6 +378,23 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+-r::\n+--rebase-merges::\n+\tBy default, a rebase will simply drop merge commits and only rebase\n+\tthe non-merge commits. With this option, it will try to preserve\n+\tthe branching structure within the commits that are to be rebased,\n+\tby recreating the merge commits. If a merge commit resolved any merge\n+\tor contained manual amendments, then they will have to be re-applied\n+\tmanually.\n++\n+This mode is similar in spirit to `--preserve-merges`, but in contrast to\n+that option works well in interactive rebases: commits can be reordered,\n+inserted and dropped at will.\n++\n+It is currently only possible to recreate the merge commits using the\n+`recursive` merge strategy; Different merge strategies can be used only via\n+explicit `exec git merge -s <strategy> [...]` commands.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -780,7 +797,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+`--rebase-merges` in such scenarios instead.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex a7570739454..d4c0a995c39 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1949,7 +1949,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--rebase-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex ccd5254d1c9..7a3daf3e40c 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -970,6 +970,7 @@ git_rebase__interactive () {\n \tinit_revisions_and_shortrevisions\n \n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${rebase_merges:+--rebase-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n \ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex fb64ee1fe42..a64460fd25a 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+r,rebase-merges!   try to rebase merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -88,6 +89,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+rebase_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -270,6 +272,10 @@ do\n \t--allow-empty-message)\n \t\tallow_empty_message=--allow-empty-message\n \t\t;;\n+\t--rebase-merges)\n+\t\trebase_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nnew file mode 100755\nindex 00000000000..5f0febb9970\n--- /dev/null\n+++ b/t/t3430-rebase-merges.sh\n@@ -0,0 +1,179 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2018 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --rebase-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+test_expect_success 'create completely different structure' '\n+\tcat >script-from-scratch <<-\\EOF &&\n+\tlabel onto\n+\n+\t# onebranch\n+\tpick G\n+\tpick D\n+\tlabel onebranch\n+\n+\t# second\n+\treset onto\n+\tpick B\n+\tlabel second\n+\n+\treset onto\n+\tmerge -C H second\n+\tmerge onebranch # Merge the topic branch '\\''onebranch'\\''\n+\tEOF\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i -r A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\treset onto\n+\tpick d9df450 B\n+\tlabel E\n+\n+\treset onto\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point # C\n+\tpick 12bd07b D\n+\tmerge -C 2051b56 E # E\n+\tmerge -C 233d48a H # H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success '`reset` refuses to overwrite untracked files' '\n+\tgit checkout -b refuse-to-reset &&\n+\ttest_commit dont-overwrite-untracked &&\n+\tgit checkout @{-1} &&\n+\t: >dont-overwrite-untracked.t &&\n+\techo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_must_fail git rebase -r HEAD &&\n+\tgit rebase --abort\n+'\n+\n+test_expect_success 'failed `merge` writes patch (may be rescheduled, too)' '\n+\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\tgit checkout -b conflicting-merge A &&\n+\n+\t: fail because of conflicting untracked file &&\n+\t>G.t &&\n+\techo \"merge -C H G\" >script-from-scratch &&\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\ttest_must_fail git rebase -ir HEAD &&\n+\tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n+\tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n+\ttest_path_is_file .git/rebase-merge/patch &&\n+\n+\t: fail because of merge conflict &&\n+\trm G.t .git/rebase-merge/patch &&\n+\tgit reset --hard &&\n+\ttest_commit conflicting-G G.t not-G conflicting-G &&\n+\ttest_must_fail git rebase --continue &&\n+\t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n+\ttest_path_is_file .git/rebase-merge/patch\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i -r upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345356","messageId":"3e490b44d2e745dbc8bbb1a95ef17ce1252052c0.1524306547.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 10/16] rebase --rebase-merges: add test for --keep-empty","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:35:35Z","receivedAt":"2018-04-21T10:36:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf there are empty commits on the left hand side of $upstream...HEAD\nthen the empty commits on the right hand side that we want to keep are\nbeing pruned.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n t/t3421-rebase-topology-linear.sh | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 68fe2003ef5..fbae5dab7e2 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -217,6 +217,7 @@ test_run_rebase success ''\n test_run_rebase failure -m\n test_run_rebase failure -i\n test_run_rebase failure -p\n+test_run_rebase success --rebase-merges\n \n #       m\n #      /\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345357","messageId":"nycvar.QRO.7.76.6.1804211241320.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 11/16] sequencer: make refs generated by the `label` command worktree-local","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:43:59Z","receivedAt":"2018-04-21T10:44:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                   |  3 ++-\n t/t3430-rebase-merges.sh | 14 ++++++++++++++\n 2 files changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 8b7a77fe5ee..f61ec58d1df 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 5f0febb9970..96853784ec0 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -176,4 +176,18 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345358","messageId":"nycvar.QRO.7.76.6.1804211246250.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 12/16] sequencer: handle post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:46:42Z","receivedAt":"2018-04-21T10:47:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --rebase-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits.\n\nThis patch implements the post-rewrite handling for the `merge` command\nwe just introduced. The other commands that were added recently (`label`\nand `reset`) do not create new commits, therefore post-rewrite hooks do\nnot need to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c              |  5 ++++-\n t/t3430-rebase-merges.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 29 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1e17a11ca32..f3b4fe4d75f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3062,7 +3062,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t\t    item->arg, item->arg_len,\n \t\t\t\t\t    item->flags, opts)) < 0)\n \t\t\t\treschedule = 1;\n-\t\t\telse if (res > 0)\n+\t\t\telse if (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n+\t\t\tif (res > 0)\n \t\t\t\t/* failed with merge conflicts */\n \t\t\t\treturn error_with_patch(item->commit,\n \t\t\t\t\t\t\titem->arg,\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 96853784ec0..e9c5dc1cd95 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -190,4 +190,29 @@ test_expect_success 'refs/rewritten/* is worktree-local' '\n \ttest_cmp_rev HEAD \"$(cat wt/b)\"\n '\n \n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash -r HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345359","messageId":"nycvar.QRO.7.76.6.1804211247230.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 13/16] rebase --rebase-merges: avoid \"empty merges\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:47:47Z","receivedAt":"2018-04-21T10:48:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The `git merge` command does not allow merging commits that are already\nreachable from HEAD: `git merge HEAD^`, for example, will report that we\nare already up to date and not change a thing.\n\nIn an interactive rebase, such a merge could occur previously, e.g. when\ncompeting (or slightly modified) versions of a patch series were applied\nupstream, and the user had to `git rebase --skip` all of the local\ncommits, and the topic branch becomes \"empty\" as a consequence.\n\nLet's teach the todo command `merge` to behave the same as `git merge`.\n\nSeeing as it requires some low-level trickery to create such merges with\nGit's commands in the first place, we do not even have to bother to\nintroduce an option to force `merge` to create such merge commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c              | 7 +++++++\n t/t3430-rebase-merges.sh | 8 ++++++++\n 2 files changed, 15 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex f3b4fe4d75f..443a0a0ee87 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2803,6 +2803,13 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n \n \tbases = get_merge_bases(head_commit, merge_commit);\n+\tif (bases && !oidcmp(&merge_commit->object.oid,\n+\t\t\t     &bases->item->object.oid)) {\n+\t\tret = 0;\n+\t\t/* skip merging an ancestor of HEAD */\n+\t\tgoto leave_merge;\n+\t}\n+\n \tfor (j = bases; j; j = j->next)\n \t\tcommit_list_insert(j->item, &reversed);\n \tfree_commit_list(bases);\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex e9c5dc1cd95..1628c8dcc20 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -215,4 +215,12 @@ test_expect_success 'post-rewrite hook and fixups work for merges' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'refuse to merge ancestors of HEAD' '\n+\techo \"merge HEAD^\" >script-from-scratch &&\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tbefore=\"$(git rev-parse HEAD)\" &&\n+\tgit rebase -i HEAD &&\n+\ttest_cmp_rev HEAD $before\n+'\n+\n test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345360","messageId":"nycvar.QRO.7.76.6.1804211249260.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 14/16] pull: accept --rebase=merges to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:49:33Z","receivedAt":"2018-04-21T10:50:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `merges` mode simply passes the\n`--rebase-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  6 +++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       | 18 ++++++++++++++----\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 38 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 2659153cb37..d6bcb5dcb67 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When `merges`, pass the `--rebase-merges` option to 'git rebase'\n+so that the local merge commits are included in the rebase (see\n+linkgit:git-rebase[1] for details).\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2617,6 +2621,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When `merges`, pass the `--rebase-merges` option to 'git rebase'\n+so that the local merge commits are included in the rebase (see\n+linkgit:git-rebase[1] for details).\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..4e0ad6fd8e0 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,17 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|merges|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to `merges`, rebase using `git rebase --rebase-merges` so that\n+the local merge commits are included in the rebase (see\n+linkgit:git-rebase[1] for details).\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e32d6cd5b4c..70b44146ce4 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_MERGES,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"merges\", returns REBASE_MERGES. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"merges\"))\n+\t\treturn REBASE_MERGES;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|merges|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_MERGES)\n+\t\targv_array_push(&args, \"--rebase-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 805ffc05cdb..45c9219e07a 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -245,7 +245,9 @@ static int add(int argc, const char **argv)\n struct branch_info {\n \tchar *remote_name;\n \tstruct string_list merge;\n-\tenum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;\n+\tenum {\n+\t\tNO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES\n+\t} rebase;\n };\n \n static struct string_list branch_list = STRING_LIST_INIT_NODUP;\n@@ -306,6 +308,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"merges\"))\n+\t\t\t\tinfo->rebase = REBASE_MERGES;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\n@@ -963,9 +967,15 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)\n \n \tprintf(\"    %-*s \", show_info->width, item->string);\n \tif (branch_info->rebase) {\n-\t\tprintf_ln(branch_info->rebase == INTERACTIVE_REBASE\n-\t\t\t  ? _(\"rebases interactively onto remote %s\")\n-\t\t\t  : _(\"rebases onto remote %s\"), merge->items[0].string);\n+\t\tconst char *msg;\n+\t\tif (branch_info->rebase == INTERACTIVE_REBASE)\n+\t\t\tmsg = _(\"rebases interactively onto remote %s\");\n+\t\telse if (branch_info->rebase == REBASE_MERGES)\n+\t\t\tmsg = _(\"rebases interactively (with merges) onto \"\n+\t\t\t\t\"remote %s\");\n+\t\telse\n+\t\t\tmsg = _(\"rebases onto remote %s\");\n+\t\tprintf_ln(msg, merge->items[0].string);\n \t\treturn 0;\n \t} else if (show_info->any_rebase) {\n \t\tprintf_ln(_(\" merges with remote %s\"), merge->items[0].string);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex d4c0a995c39..6af65155c59 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2120,7 +2120,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true merges preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345361","messageId":"nycvar.QRO.7.76.6.1804211255440.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 15/16] rebase -i: introduce --rebase-merges=[no-]rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T10:57:08Z","receivedAt":"2018-04-21T10:57:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This one is a bit tricky to explain, so let's try with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nTo illustrate what this new mode is all about, let's consider what\nhappens upon `git rebase -i --rebase-merges B`, in particular to\nthe commit `D`. So far, the new branch structure would be:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nThis is unintuitive behavior. Even worse, when recreating branch\nstructure, most use cases would appear to want cousins *not* to be\nrebased onto the new base commit. For example, Git for Windows (the\nheaviest user of the Git garden shears, which served as the blueprint\nfor --rebase-merges) frequently merges branches from `next` early, and\nthese branches certainly do *not* want to be rebased. In the example\nabove, the desired outcome would look like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and let's not rebase them by default, introducing the new\n\"rebase-cousins\" mode for use cases where they should be rebased.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt |  7 ++++++-\n builtin/rebase--helper.c     |  9 ++++++++-\n git-rebase--interactive.sh   |  1 +\n git-rebase.sh                | 12 +++++++++++-\n sequencer.c                  |  4 ++++\n sequencer.h                  |  6 ++++++\n t/t3430-rebase-merges.sh     | 18 ++++++++++++++++++\n 7 files changed, 54 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 34e0f6a69c1..841cf9cf385 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -379,7 +379,7 @@ rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n -r::\n---rebase-merges::\n+--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tBy default, a rebase will simply drop merge commits and only rebase\n \tthe non-merge commits. With this option, it will try to preserve\n \tthe branching structure within the commits that are to be rebased,\n@@ -387,6 +387,11 @@ have the long commit hash prepended to the format.\n \tor contained manual amendments, then they will have to be re-applied\n \tmanually.\n +\n+By default, or when `no-rebase-cousins` was specified, commits which do not\n+have `<upstream>` as direct ancestor will keep their original branch point.\n+If the `rebase-cousins` mode is turned on, such commits are instead rebased\n+onto `<upstream>` (or `<onto>`, if specified).\n++\n This mode is similar in spirit to `--preserve-merges`, but in contrast to\n that option works well in interactive rebases: commits can be reordered,\n inserted and dropped at will.\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex 781782e7272..f7c2a5fdc81 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -25,6 +25,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n+\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -59,8 +61,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n+\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (rebase_cousins >= 0 && !rebase_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--rebase-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 7a3daf3e40c..b4ad130e8b1 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -971,6 +971,7 @@ git_rebase__interactive () {\n \n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${rebase_merges:+--rebase-merges} \\\n+\t\t${rebase_cousins:+--rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n \ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex a64460fd25a..157705d2a72 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-r,rebase-merges!   try to rebase merges instead of skipping them\n+r,rebase-merges?   try to rebase merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -90,6 +90,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n rebase_merges=\n+rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -276,6 +277,15 @@ do\n \t\trebase_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--rebase-merges=*)\n+\t\trebase_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) rebase_cousins=t;;\n+\t\tno-rebase-cousins) rebase_cousins=;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex 443a0a0ee87..9ffadbb3d3c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3566,6 +3566,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3743,6 +3744,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (!rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(to, \"onto\"))\n \t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\ndiff --git a/sequencer.h b/sequencer.h\nindex 6bc4da17243..d9570d92b11 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -60,6 +60,12 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\n+/*\n+ * When rebasing merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are *not* rebased onto the new base by default. If those\n+ * commits should be rebased onto the new base, this flag needs to be passed.\n+ */\n+#define TODO_LIST_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 1628c8dcc20..3d4dfdf7bec 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -176,6 +176,24 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase cousins unless asked for' '\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -r HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase --rebase-merges=rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345362","messageId":"nycvar.QRO.7.76.6.1804211308590.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v8 16/16] rebase -i --rebase-merges: add a section to the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T11:09:17Z","receivedAt":"2018-04-21T11:09:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The --rebase-merges mode is probably not half as intuitive to use as\nits inventor hopes, so let's document it some.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt | 135 +++++++++++++++++++++++++++++++++++\n 1 file changed, 135 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 841cf9cf385..3b996e46d6a 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -399,6 +399,8 @@ inserted and dropped at will.\n It is currently only possible to recreate the merge commits using the\n `recursive` merge strategy; Different merge strategies can be used only via\n explicit `exec git merge -s <strategy> [...]` commands.\n++\n+See also REBASING MERGES below.\n \n -p::\n --preserve-merges::\n@@ -797,6 +799,139 @@ The ripple effect of a \"hard case\" recovery is especially bad:\n 'everyone' downstream from 'topic' will now have to perform a \"hard\n case\" recovery too!\n \n+REBASING MERGES\n+-----------------\n+\n+The interactive rebase command was originally designed to handle\n+individual patch series. As such, it makes sense to exclude merge\n+commits from the todo list, as the developer may have merged the\n+then-current `master` while working on the branch, only to rebase\n+all the commits onto `master` eventually (skipping the merge\n+commits).\n+\n+However, there are legitimate reasons why a developer may want to\n+recreate merge commits: to keep the branch structure (or \"commit\n+topology\") when working on multiple, inter-related branches.\n+\n+In the following example, the developer works on a topic branch that\n+refactors the way buttons are defined, and on another topic branch\n+that uses that refactoring to implement a \"Report a bug\" button. The\n+output of `git log --graph --format=%s -5` may look like this:\n+\n+------------\n+*   Merge branch 'report-a-bug'\n+|\\\n+| * Add the feedback button\n+* | Merge branch 'refactor-button'\n+|\\ \\\n+| |/\n+| * Use the Button class for all buttons\n+| * Extract a generic Button class from the DownloadButton one\n+------------\n+\n+The developer might want to rebase those commits to a newer `master`\n+while keeping the branch topology, for example when the first topic\n+branch is expected to be integrated into `master` much earlier than the\n+second one, say, to resolve merge conflicts with changes to the\n+DownloadButton class that made it into `master`.\n+\n+This rebase can be performed using the `--rebase-merges` option.\n+It will generate a todo list looking like this:\n+\n+------------\n+label onto\n+\n+# Branch: refactor-button\n+reset onto\n+pick 123456 Extract a generic Button class from the DownloadButton one\n+pick 654321 Use the Button class for all buttons\n+label refactor-button\n+\n+# Branch: report-a-bug\n+reset refactor-button # Use the Button class for all buttons\n+pick abcdef Add the feedback button\n+label report-a-bug\n+\n+reset onto\n+merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n+merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n+------------\n+\n+In contrast to a regular interactive rebase, there are `label`, `reset`\n+and `merge` commands in addition to `pick` ones.\n+\n+The `label` command associates a label with the current HEAD when that\n+command is executed. These labels are created as worktree-local refs\n+(`refs/rewritten/<label>`) that will be deleted when the rebase\n+finishes. That way, rebase operations in multiple worktrees linked to\n+the same repository do not interfere with one another. If the `label`\n+command fails, it is rescheduled immediately, with a helpful message how\n+to proceed.\n+\n+The `reset` command resets the HEAD, index and worktree to the specified\n+revision. It is isimilar to an `exec git reset --hard <label>`, but\n+refuses to overwrite untracked files. If the `reset` command fails, it is\n+rescheduled immediately, with a helpful message how to edit the todo list\n+(this typically happens when a `reset` command was inserted into the todo\n+list manually and contains a typo).\n+\n+The `merge` command will merge the specified revision into whatever is\n+HEAD at that time. With `-C <original-commit>`, the commit message of\n+the specified merge commit will be used. When the `-C` is changed to\n+a lower-case `-c`, the message will be opened in an editor after a\n+successful merge so that the user can edit the message.\n+\n+If a `merge` command fails for any reason other than merge conflicts (i.e.\n+when the merge operation did not even start), it is rescheduled immediately.\n+\n+At this time, the `merge` command will *always* use the `recursive`\n+merge strategy, with no way to choose a different one. To work around\n+this, an `exec` command can be used to call `git merge` explicitly,\n+using the fact that the labels are worktree-local refs (the ref\n+`refs/rewritten/onto` would correspond to the label `onto`, for example).\n+\n+Note: the first command (`label onto`) labels the revision onto which\n+the commits are rebased; The name `onto` is just a convention, as a nod\n+to the `--onto` option.\n+\n+It is also possible to introduce completely new merge commits from scratch\n+by adding a command of the form `merge <merge-head>`. This form will\n+generate a tentative commit message and always open an editor to let the\n+user edit it. This can be useful e.g. when a topic branch turns out to\n+address more than a single concern and wants to be split into two or\n+even more topic branches. Consider this todo list:\n+\n+------------\n+pick 192837 Switch from GNU Makefiles to CMake\n+pick 5a6c7e Document the switch to CMake\n+pick 918273 Fix detection of OpenSSL in CMake\n+pick afbecd http: add support for TLS v1.3\n+pick fdbaec Fix detection of cURL in CMake on Windows\n+------------\n+\n+The one commit in this list that is not related to CMake may very well\n+have been motivated by working on fixing all those bugs introduced by\n+switching to CMake, but it addresses a different concern. To split this\n+branch into two topic branches, the todo list could be edited like this:\n+\n+------------\n+label onto\n+\n+pick afbecd http: add support for TLS v1.3\n+label tlsv1.3\n+\n+reset onto\n+pick 192837 Switch from GNU Makefiles to CMake\n+pick 918273 Fix detection of OpenSSL in CMake\n+pick fdbaec Fix detection of cURL in CMake on Windows\n+pick 5a6c7e Document the switch to CMake\n+label cmake\n+\n+reset onto\n+merge tlsv1.3\n+merge cmake\n+------------\n+\n BUGS\n ----\n The todo list presented by `--preserve-merges --interactive` does not\n-- \n2.17.0.windows.1.15.gaa56ade3205\n"},{"id":"345368","messageId":"b05915e6-2259-5dee-69d8-90a5a524662d@talktalk.net","threadId":"47635","inReplyTo":"7360a072f6fdd276d30839613434329b645e2cce.1524306546.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-21T15:56:23Z","receivedAt":"2018-04-21T15:56:31Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 21/04/18 11:33, Johannes Schindelin wrote:\n> This patch is part of the effort to reimplement `--preserve-merges` with\n> a substantially improved design, a design that has been developed in the\n> Git for Windows project to maintain the dozens of Windows-specific patch\n> series on top of upstream Git.\n> \n> The previous patch implemented the `label` and `reset` commands to label\n> commits and to reset to labeled commits. This patch adds the `merge`\n> command, with the following syntax:\n\nThe two patches seem to have been fused together in this series.\n\nIf the reset command fails because it would overwrite untracked files it \nsays\n\nerror: Untracked working tree file 'b' would be overwritten by merge.\n\nFollowed by the hint to edit the todo file. Saying 'merge' rather \n'reset' is possibly confusing to users. Perhaps it could call \nsetup_unpack_trees_porcelain(), though that would need to be extended to \nhandle 'reset'. Also it currently refuses to overwrite ignored files \nwhich is either annoying or safe depending on one's point of view.\n\nBest Wishes\n\nPhillip\n\n> \n> \tmerge [-C <commit>] <rev> # <oneline>\n> \n> The <commit> parameter in this instance is the *original* merge commit,\n> whose author and message will be used for the merge commit that is about\n> to be created.\n> \n> The <rev> parameter refers to the (possibly rewritten) revision to\n> merge. Let's see an example of a todo list:\n> \n> \tlabel onto\n> \n> \t# Branch abc\n> \treset onto\n> \tpick deadbeef Hello, world!\n> \tlabel abc\n> \n> \treset onto\n> \tpick cafecafe And now for something completely different\n> \tmerge -C baaabaaa abc # Merge the branch 'abc' into master\n> \n> To edit the merge commit's message (a \"reword\" for merges, if you will),\n> use `-c` (lower-case) instead of `-C`; this convention was borrowed from\n> `git commit` that also supports `-c` and `-C` with similar meanings.\n> \n> To create *new* merges, i.e. without copying the commit message from an\n> existing commit, simply omit the `-C <commit>` parameter (which will\n> open an editor for the merge message):\n> \n> \tmerge abc\n> \n> This comes in handy when splitting a branch into two or more branches.\n> \n> Note: this patch only adds support for recursive merges, to keep things\n> simple. Support for octopus merges will be added later in a separate\n> patch series, support for merges using strategies other than the\n> recursive merge is left for the future.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>   git-rebase--interactive.sh |   6 +\n>   sequencer.c                | 407 ++++++++++++++++++++++++++++++++++++-\n>   2 files changed, 406 insertions(+), 7 deletions(-)\n> \n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index e1b865f43f2..ccd5254d1c9 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -162,6 +162,12 @@ s, squash <commit> = use commit, but meld into previous commit\n>   f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n>   x, exec <commit> = run command (the rest of the line) using shell\n>   d, drop <commit> = remove commit\n> +l, label <label> = label current HEAD with a name\n> +t, reset <label> = reset HEAD to a label\n> +m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n> +.       create a merge commit using the original merge commit's\n> +.       message (or the oneline, if no original merge commit was\n> +.       specified). Use -c <commit> to reword the commit message.\n>   \n>   These lines can be re-ordered; they are executed from top to bottom.\n>   \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 01443e0f245..35fcacbdf0f 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -23,6 +23,8 @@\n>   #include \"hashmap.h\"\n>   #include \"notes-utils.h\"\n>   #include \"sigchain.h\"\n> +#include \"unpack-trees.h\"\n> +#include \"worktree.h\"\n>   \n>   #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>   \n> @@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n>   static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n>   static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n>   \t\"rebase-merge/rewritten-pending\")\n> +\n> +/*\n> + * The path of the file listing refs that need to be deleted after the rebase\n> + * finishes. This is used by the `label` command to record the need for cleanup.\n> + */\n> +static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n> +\n>   /*\n>    * The following files are written by git-rebase just after parsing the\n>    * command-line (and are only consumed, not modified, by the sequencer).\n> @@ -244,18 +253,34 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n>   \n>   int sequencer_remove_state(struct replay_opts *opts)\n>   {\n> -\tstruct strbuf dir = STRBUF_INIT;\n> +\tstruct strbuf buf = STRBUF_INIT;\n>   \tint i;\n>   \n> +\tif (is_rebase_i(opts) &&\n> +\t    strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n> +\t\tchar *p = buf.buf;\n> +\t\twhile (*p) {\n> +\t\t\tchar *eol = strchr(p, '\\n');\n> +\t\t\tif (eol)\n> +\t\t\t\t*eol = '\\0';\n> +\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n> +\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n> +\t\t\tif (!eol)\n> +\t\t\t\tbreak;\n> +\t\t\tp = eol + 1;\n> +\t\t}\n> +\t}\n> +\n>   \tfree(opts->gpg_sign);\n>   \tfree(opts->strategy);\n>   \tfor (i = 0; i < opts->xopts_nr; i++)\n>   \t\tfree(opts->xopts[i]);\n>   \tfree(opts->xopts);\n>   \n> -\tstrbuf_addstr(&dir, get_dir(opts));\n> -\tremove_dir_recursively(&dir, 0);\n> -\tstrbuf_release(&dir);\n> +\tstrbuf_reset(&buf);\n> +\tstrbuf_addstr(&buf, get_dir(opts));\n> +\tremove_dir_recursively(&buf, 0);\n> +\tstrbuf_release(&buf);\n>   \n>   \treturn 0;\n>   }\n> @@ -1279,6 +1304,9 @@ enum todo_command {\n>   \tTODO_SQUASH,\n>   \t/* commands that do something else than handling a single commit */\n>   \tTODO_EXEC,\n> +\tTODO_LABEL,\n> +\tTODO_RESET,\n> +\tTODO_MERGE,\n>   \t/* commands that do nothing but are counted for reporting progress */\n>   \tTODO_NOOP,\n>   \tTODO_DROP,\n> @@ -1297,6 +1325,9 @@ static struct {\n>   \t{ 'f', \"fixup\" },\n>   \t{ 's', \"squash\" },\n>   \t{ 'x', \"exec\" },\n> +\t{ 'l', \"label\" },\n> +\t{ 't', \"reset\" },\n> +\t{ 'm', \"merge\" },\n>   \t{ 0,   \"noop\" },\n>   \t{ 'd', \"drop\" },\n>   \t{ 0,   NULL }\n> @@ -1724,9 +1755,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)\n>   \treturn 0;\n>   }\n>   \n> +enum todo_item_flags {\n> +\tTODO_EDIT_MERGE_MSG = 1\n> +};\n> +\n>   struct todo_item {\n>   \tenum todo_command command;\n>   \tstruct commit *commit;\n> +\tunsigned int flags;\n>   \tconst char *arg;\n>   \tint arg_len;\n>   \tsize_t offset_in_buf;\n> @@ -1761,6 +1797,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>   \tchar *end_of_object_name;\n>   \tint i, saved, status, padding;\n>   \n> +\titem->flags = 0;\n> +\n>   \t/* left-trim */\n>   \tbol += strspn(bol, \" \\t\");\n>   \n> @@ -1802,13 +1840,29 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n>   \t\treturn error(_(\"missing arguments for %s\"),\n>   \t\t\t     command_to_string(item->command));\n>   \n> -\tif (item->command == TODO_EXEC) {\n> +\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n> +\t    item->command == TODO_RESET) {\n>   \t\titem->commit = NULL;\n>   \t\titem->arg = bol;\n>   \t\titem->arg_len = (int)(eol - bol);\n>   \t\treturn 0;\n>   \t}\n>   \n> +\tif (item->command == TODO_MERGE) {\n> +\t\tif (skip_prefix(bol, \"-C\", &bol))\n> +\t\t\tbol += strspn(bol, \" \\t\");\n> +\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n> +\t\t\tbol += strspn(bol, \" \\t\");\n> +\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n> +\t\t} else {\n> +\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n> +\t\t\titem->commit = NULL;\n> +\t\t\titem->arg = bol;\n> +\t\t\titem->arg_len = (int)(eol - bol);\n> +\t\t\treturn 0;\n> +\t\t}\n> +\t}\n> +\n>   \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n>   \tsaved = *end_of_object_name;\n>   \t*end_of_object_name = '\\0';\n> @@ -2465,6 +2519,305 @@ static int do_exec(const char *command_line)\n>   \treturn status;\n>   }\n>   \n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> +\tva_list ap;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tint fd = hold_lock_file_for_update(&lock, filename,\n> +\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\n> +\tif (fd < 0)\n> +\t\treturn -1;\n> +\n> +\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n> +\t\terror_errno(_(\"could not read '%s'\"), filename);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn -1;\n> +\t}\n> +\tstrbuf_complete(&buf, '\\n');\n> +\tva_start(ap, fmt);\n> +\tstrbuf_vaddf(&buf, fmt, ap);\n> +\tva_end(ap);\n> +\n> +\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n> +\t\terror_errno(_(\"could not write to '%s'\"), filename);\n> +\t\tstrbuf_release(&buf);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn -1;\n> +\t}\n> +\tif (commit_lock_file(&lock) < 0) {\n> +\t\tstrbuf_release(&buf);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n> +\t}\n> +\n> +\tstrbuf_release(&buf);\n> +\treturn 0;\n> +}\n> +\n> +static int do_label(const char *name, int len)\n> +{\n> +\tstruct ref_store *refs = get_main_ref_store();\n> +\tstruct ref_transaction *transaction;\n> +\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n> +\tstruct strbuf msg = STRBUF_INIT;\n> +\tint ret = 0;\n> +\tstruct object_id head_oid;\n> +\n> +\tif (len == 1 && *name == '#')\n> +\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n> +\n> +\ttransaction = ref_store_transaction_begin(refs, &err);\n> +\tif (!transaction) {\n> +\t\terror(\"%s\", err.buf);\n> +\t\tret = -1;\n> +\t} else if (get_oid(\"HEAD\", &head_oid)) {\n> +\t\terror(_(\"could not read HEAD\"));\n> +\t\tret = -1;\n> +\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n> +\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n> +\t\t   ref_transaction_commit(transaction, &err)) {\n> +\t\terror(\"%s\", err.buf);\n> +\t\tret = -1;\n> +\t}\n> +\tref_transaction_free(transaction);\n> +\tstrbuf_release(&err);\n> +\tstrbuf_release(&msg);\n> +\n> +\tif (!ret)\n> +\t\tret = safe_append(rebase_path_refs_to_delete(),\n> +\t\t\t\t  \"%s\\n\", ref_name.buf);\n> +\tstrbuf_release(&ref_name);\n> +\n> +\treturn ret;\n> +}\n> +\n> +static const char *reflog_message(struct replay_opts *opts,\n> +\tconst char *sub_action, const char *fmt, ...);\n> +\n> +static int do_reset(const char *name, int len, struct replay_opts *opts)\n> +{\n> +\tstruct strbuf ref_name = STRBUF_INIT;\n> +\tstruct object_id oid;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tstruct tree_desc desc;\n> +\tstruct tree *tree;\n> +\tstruct unpack_trees_options unpack_tree_opts;\n> +\tint ret = 0, i;\n> +\n> +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> +\t\treturn -1;\n> +\n> +\t/* Determine the length of the label */\n> +\tfor (i = 0; i < len; i++)\n> +\t\tif (isspace(name[i]))\n> +\t\t\tlen = i;\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> +\tif (get_oid(ref_name.buf, &oid) &&\n> +\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> +\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n> +\t\trollback_lock_file(&lock);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n> +\tunpack_tree_opts.head_idx = 1;\n> +\tunpack_tree_opts.src_index = &the_index;\n> +\tunpack_tree_opts.dst_index = &the_index;\n> +\tunpack_tree_opts.fn = oneway_merge;\n> +\tunpack_tree_opts.merge = 1;\n> +\tunpack_tree_opts.update = 1;\n> +\n> +\tif (read_cache_unmerged()) {\n> +\t\trollback_lock_file(&lock);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn error_resolve_conflict(_(action_name(opts)));\n> +\t}\n> +\n> +\tif (!fill_tree_descriptor(&desc, &oid)) {\n> +\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n> +\t\trollback_lock_file(&lock);\n> +\t\tfree((void *)desc.buffer);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n> +\t\trollback_lock_file(&lock);\n> +\t\tfree((void *)desc.buffer);\n> +\t\tstrbuf_release(&ref_name);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttree = parse_tree_indirect(&oid);\n> +\tprime_cache_tree(&the_index, tree);\n> +\n> +\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n> +\t\tret = error(_(\"could not write index\"));\n> +\tfree((void *)desc.buffer);\n> +\n> +\tif (!ret)\n> +\t\tret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n> +\t\t\t\t\t\tlen, name), \"HEAD\", &oid,\n> +\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n> +\n> +\tstrbuf_release(&ref_name);\n> +\treturn ret;\n> +}\n> +\n> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n> +\t\t    int flags, struct replay_opts *opts)\n> +{\n> +\tint run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n> +\t\tEDIT_MSG | VERIFY_MSG : 0;\n> +\tstruct strbuf ref_name = STRBUF_INIT;\n> +\tstruct commit *head_commit, *merge_commit, *i;\n> +\tstruct commit_list *bases, *j, *reversed = NULL;\n> +\tstruct merge_options o;\n> +\tint merge_arg_len, oneline_offset, ret;\n> +\tstatic struct lock_file lock;\n> +\tconst char *p;\n> +\n> +\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {\n> +\t\tret = -1;\n> +\t\tgoto leave_merge;\n> +\t}\n> +\n> +\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n> +\tif (!head_commit) {\n> +\t\tret = error(_(\"cannot merge without a current revision\"));\n> +\t\tgoto leave_merge;\n> +\t}\n> +\n> +\toneline_offset = arg_len;\n> +\tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n> +\tp = arg + merge_arg_len;\n> +\tp += strspn(p, \" \\t\\n\");\n> +\tif (*p == '#' && (!p[1] || isspace(p[1]))) {\n> +\t\tp += 1 + strspn(p + 1, \" \\t\\n\");\n> +\t\toneline_offset = p - arg;\n> +\t} else if (p - arg < arg_len)\n> +\t\tBUG(\"octopus merges are not supported yet: '%s'\", p);\n> +\n> +\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n> +\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> +\tif (!merge_commit) {\n> +\t\t/* fall back to non-rewritten ref or commit */\n> +\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n> +\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> +\t}\n> +\n> +\tif (!merge_commit) {\n> +\t\tret = error(_(\"could not resolve '%s'\"), ref_name.buf);\n> +\t\tgoto leave_merge;\n> +\t}\n> +\n> +\tif (commit) {\n> +\t\tconst char *message = get_commit_buffer(commit, NULL);\n> +\t\tconst char *body;\n> +\t\tint len;\n> +\n> +\t\tif (!message) {\n> +\t\t\tret = error(_(\"could not get commit message of '%s'\"),\n> +\t\t\t\t    oid_to_hex(&commit->object.oid));\n> +\t\t\tgoto leave_merge;\n> +\t\t}\n> +\t\twrite_author_script(message);\n> +\t\tfind_commit_subject(message, &body);\n> +\t\tlen = strlen(body);\n> +\t\tret = write_message(body, len, git_path_merge_msg(), 0);\n> +\t\tunuse_commit_buffer(commit, message);\n> +\t\tif (ret) {\n> +\t\t\terror_errno(_(\"could not write '%s'\"),\n> +\t\t\t\t    git_path_merge_msg());\n> +\t\t\tgoto leave_merge;\n> +\t\t}\n> +\t} else {\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tint len;\n> +\n> +\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n> +\t\twrite_author_script(buf.buf);\n> +\t\tstrbuf_reset(&buf);\n> +\n> +\t\tif (oneline_offset < arg_len) {\n> +\t\t\tp = arg + oneline_offset;\n> +\t\t\tlen = arg_len - oneline_offset;\n> +\t\t} else {\n> +\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n> +\t\t\t\t    merge_arg_len, arg);\n> +\t\t\tp = buf.buf;\n> +\t\t\tlen = buf.len;\n> +\t\t}\n> +\n> +\t\tret = write_message(p, len, git_path_merge_msg(), 0);\n> +\t\tstrbuf_release(&buf);\n> +\t\tif (ret) {\n> +\t\t\terror_errno(_(\"could not write '%s'\"),\n> +\t\t\t\t    git_path_merge_msg());\n> +\t\t\tgoto leave_merge;\n> +\t\t}\n> +\t}\n> +\n> +\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n> +\t\t      git_path_merge_head(), 0);\n> +\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n> +\n> +\tbases = get_merge_bases(head_commit, merge_commit);\n> +\tfor (j = bases; j; j = j->next)\n> +\t\tcommit_list_insert(j->item, &reversed);\n> +\tfree_commit_list(bases);\n> +\n> +\tread_cache();\n> +\tinit_merge_options(&o);\n> +\to.branch1 = \"HEAD\";\n> +\to.branch2 = ref_name.buf;\n> +\to.buffer_output = 2;\n> +\n> +\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n> +\tif (ret <= 0)\n> +\t\tfputs(o.obuf.buf, stdout);\n> +\tstrbuf_release(&o.obuf);\n> +\tif (ret < 0) {\n> +\t\terror(_(\"could not even attempt to merge '%.*s'\"),\n> +\t\t      merge_arg_len, arg);\n> +\t\tgoto leave_merge;\n> +\t}\n> +\t/*\n> +\t * The return value of merge_recursive() is 1 on clean, and 0 on\n> +\t * unclean merge.\n> +\t *\n> +\t * Let's reverse that, so that do_merge() returns 0 upon success and\n> +\t * 1 upon failed merge (keeping the return value -1 for the cases where\n> +\t * we will want to reschedule the `merge` command).\n> +\t */\n> +\tret = !ret;\n> +\n> +\tif (active_cache_changed &&\n> +\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n> +\t\tret = error(_(\"merge: Unable to write new index file\"));\n> +\t\tgoto leave_merge;\n> +\t}\n> +\n> +\trollback_lock_file(&lock);\n> +\tif (ret)\n> +\t\trerere(opts->allow_rerere_auto);\n> +\telse\n> +\t\tret = run_git_commit(git_path_merge_msg(), opts,\n> +\t\t\t\t     run_commit_flags);\n> +\n> +leave_merge:\n> +\tstrbuf_release(&ref_name);\n> +\trollback_lock_file(&lock);\n> +\treturn ret;\n> +}\n> +\n>   static int is_final_fixup(struct todo_list *todo_list)\n>   {\n>   \tint i = todo_list->current;\n> @@ -2568,7 +2921,7 @@ N_(\"Could not execute the todo command\\n\"\n>   \n>   static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>   {\n> -\tint res = 0;\n> +\tint res = 0, reschedule = 0;\n>   \n>   \tsetenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n>   \tif (opts->allow_ff)\n> @@ -2639,7 +2992,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>   \t\t\t\t\tintend_to_amend();\n>   \t\t\t\treturn error_failed_squash(item->commit, opts,\n>   \t\t\t\t\titem->arg_len, item->arg);\n> -\t\t\t} else if (res && is_rebase_i(opts))\n> +\t\t\t} else if (res && is_rebase_i(opts) && item->commit)\n>   \t\t\t\treturn res | error_with_patch(item->commit,\n>   \t\t\t\t\titem->arg, item->arg_len, opts, res,\n>   \t\t\t\t\titem->command == TODO_REWORD);\n> @@ -2665,9 +3018,41 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n>   \t\t\t\t/* `current` will be incremented below */\n>   \t\t\t\ttodo_list->current = -1;\n>   \t\t\t}\n> +\t\t} else if (item->command == TODO_LABEL) {\n> +\t\t\tif ((res = do_label(item->arg, item->arg_len)))\n> +\t\t\t\treschedule = 1;\n> +\t\t} else if (item->command == TODO_RESET) {\n> +\t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n> +\t\t\t\treschedule = 1;\n> +\t\t} else if (item->command == TODO_MERGE) {\n> +\t\t\tif ((res = do_merge(item->commit,\n> +\t\t\t\t\t    item->arg, item->arg_len,\n> +\t\t\t\t\t    item->flags, opts)) < 0)\n> +\t\t\t\treschedule = 1;\n> +\t\t\telse if (res > 0)\n> +\t\t\t\t/* failed with merge conflicts */\n> +\t\t\t\treturn error_with_patch(item->commit,\n> +\t\t\t\t\t\t\titem->arg,\n> +\t\t\t\t\t\t\titem->arg_len, opts,\n> +\t\t\t\t\t\t\tres, 0);\n>   \t\t} else if (!is_noop(item->command))\n>   \t\t\treturn error(_(\"unknown command %d\"), item->command);\n>   \n> +\t\tif (reschedule) {\n> +\t\t\tadvise(_(rescheduled_advice),\n> +\t\t\t       get_item_line_length(todo_list,\n> +\t\t\t\t\t\t    todo_list->current),\n> +\t\t\t       get_item_line(todo_list, todo_list->current));\n> +\t\t\ttodo_list->current--;\n> +\t\t\tif (save_todo(todo_list, opts))\n> +\t\t\t\treturn -1;\n> +\t\t\tif (item->commit)\n> +\t\t\t\treturn error_with_patch(item->commit,\n> +\t\t\t\t\t\t\titem->arg,\n> +\t\t\t\t\t\t\titem->arg_len, opts,\n> +\t\t\t\t\t\t\tres, 0);\n> +\t\t}\n> +\n>   \t\ttodo_list->current++;\n>   \t\tif (res)\n>   \t\t\treturn res;\n> @@ -3147,8 +3532,16 @@ int transform_todos(unsigned flags)\n>   \t\t\t\t\t  short_commit_name(item->commit) :\n>   \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n>   \n> +\t\t\tif (item->command == TODO_MERGE) {\n> +\t\t\t\tif (item->flags & TODO_EDIT_MERGE_MSG)\n> +\t\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n> +\t\t\t\telse\n> +\t\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n> +\t\t\t}\n> +\n>   \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n>   \t\t}\n> +\n>   \t\t/* add all the rest */\n>   \t\tif (!item->arg_len)\n>   \t\t\tstrbuf_addch(&buf, '\\n');\n> \n\n"},{"id":"345386","messageId":"9270DF86352B4E3493AADA159E7FFE33@PhilipOakley","threadId":"47635","inReplyTo":"7360a072f6fdd276d30839613434329b645e2cce.1524306546.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-22T12:01:13Z","receivedAt":"2018-04-22T12:01:30Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> This patch is part of the effort to reimplement `--preserve-merges` with\n> a substantially improved design, a design that has been developed in the\n> Git for Windows project to maintain the dozens of Windows-specific patch\n> series on top of upstream Git.\n>\n> The previous patch implemented the `label` and `reset` commands to label\n\nThe previous patch was [Patch 05/16] git-rebase--interactive: clarify\narguments, so this statement doesn't appear to be true. Has a patch been\nmissed or re-ordered? Or should it be simply \"This patch implements\" ?\nLikewise the patch subject would be updated.\n\n> commits and to reset to labeled commits. This patch adds the `merge`\n\ns/adds/also adds/ ?\n\n> command, with the following syntax:\n>\n> merge [-C <commit>] <rev> # <oneline>\n>\n> The <commit> parameter in this instance is the *original* merge commit,\n> whose author and message will be used for the merge commit that is about\n> to be created.\n>\n> The <rev> parameter refers to the (possibly rewritten) revision to\n> merge. Let's see an example of a todo list:\n>\nThe example ought to also note that `label onto` is to\n`# label current HEAD with a name`, seeing as this is the first occurance.\nIt may be obvious in retrospect, but not at first reading.\n\n> label onto\n>\n> # Branch abc\n> reset onto\n\nIs this reset strictly necessary. We are already there @head.\n\n> pick deadbeef Hello, world!\n> label abc\n>\n> reset onto\n> pick cafecafe And now for something completely different\n> merge -C baaabaaa abc # Merge the branch 'abc' into master\n>\n> To edit the merge commit's message (a \"reword\" for merges, if you will),\n> use `-c` (lower-case) instead of `-C`; this convention was borrowed from\n> `git commit` that also supports `-c` and `-C` with similar meanings.\n>\n> To create *new* merges, i.e. without copying the commit message from an\n> existing commit, simply omit the `-C <commit>` parameter (which will\n> open an editor for the merge message):\n>\n> merge abc\n>\n> This comes in handy when splitting a branch into two or more branches.\n>\n> Note: this patch only adds support for recursive merges, to keep things\n> simple. Support for octopus merges will be added later in a separate\n> patch series, support for merges using strategies other than the\n> recursive merge is left for the future.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> git-rebase--interactive.sh |   6 +\n> sequencer.c                | 407 ++++++++++++++++++++++++++++++++++++-\n> 2 files changed, 406 insertions(+), 7 deletions(-)\n>\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index e1b865f43f2..ccd5254d1c9 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -162,6 +162,12 @@ s, squash <commit> = use commit, but meld into\n> previous commit\n> f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n> x, exec <commit> = run command (the rest of the line) using shell\n> d, drop <commit> = remove commit\n> +l, label <label> = label current HEAD with a name\n> +t, reset <label> = reset HEAD to a label\n> +m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n> +.       create a merge commit using the original merge commit's\n> +.       message (or the oneline, if no original merge commit was\n> +.       specified). Use -c <commit> to reword the commit message.\n>\n> These lines can be re-ordered; they are executed from top to bottom.\n> \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 01443e0f245..35fcacbdf0f 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -23,6 +23,8 @@\n> #include \"hashmap.h\"\n> #include \"notes-utils.h\"\n> #include \"sigchain.h\"\n> +#include \"unpack-trees.h\"\n> +#include \"worktree.h\"\n>\n> #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>\n> @@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha,\n> \"rebase-merge/stopped-sha\")\n> static GIT_PATH_FUNC(rebase_path_rewritten_list,\n> \"rebase-merge/rewritten-list\")\n> static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n>  \"rebase-merge/rewritten-pending\")\n> +\n> +/*\n> + * The path of the file listing refs that need to be deleted after the\n> rebase\n> + * finishes. This is used by the `label` command to record the need for\n> cleanup.\n> + */\n> +static GIT_PATH_FUNC(rebase_path_refs_to_delete,\n> \"rebase-merge/refs-to-delete\")\n> +\n> /*\n>  * The following files are written by git-rebase just after parsing the\n>  * command-line (and are only consumed, not modified, by the sequencer).\n> @@ -244,18 +253,34 @@ static const char *gpg_sign_opt_quoted(struct\n> replay_opts *opts)\n>\n> int sequencer_remove_state(struct replay_opts *opts)\n> {\n> - struct strbuf dir = STRBUF_INIT;\n> + struct strbuf buf = STRBUF_INIT;\n>  int i;\n>\n> + if (is_rebase_i(opts) &&\n> +     strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n> + char *p = buf.buf;\n> + while (*p) {\n> + char *eol = strchr(p, '\\n');\n> + if (eol)\n> + *eol = '\\0';\n> + if (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n> + warning(_(\"could not delete '%s'\"), p);\n> + if (!eol)\n> + break;\n> + p = eol + 1;\n> + }\n> + }\n> +\n>  free(opts->gpg_sign);\n>  free(opts->strategy);\n>  for (i = 0; i < opts->xopts_nr; i++)\n>  free(opts->xopts[i]);\n>  free(opts->xopts);\n>\n> - strbuf_addstr(&dir, get_dir(opts));\n> - remove_dir_recursively(&dir, 0);\n> - strbuf_release(&dir);\n> + strbuf_reset(&buf);\n> + strbuf_addstr(&buf, get_dir(opts));\n> + remove_dir_recursively(&buf, 0);\n> + strbuf_release(&buf);\n>\n>  return 0;\n> }\n> @@ -1279,6 +1304,9 @@ enum todo_command {\n>  TODO_SQUASH,\n>  /* commands that do something else than handling a single commit */\n>  TODO_EXEC,\n> + TODO_LABEL,\n> + TODO_RESET,\n> + TODO_MERGE,\n>  /* commands that do nothing but are counted for reporting progress */\n>  TODO_NOOP,\n>  TODO_DROP,\n> @@ -1297,6 +1325,9 @@ static struct {\n>  { 'f', \"fixup\" },\n>  { 's', \"squash\" },\n>  { 'x', \"exec\" },\n> + { 'l', \"label\" },\n> + { 't', \"reset\" },\n> + { 'm', \"merge\" },\n>  { 0,   \"noop\" },\n>  { 'd', \"drop\" },\n>  { 0,   NULL }\n> @@ -1724,9 +1755,14 @@ static int read_and_refresh_cache(struct\n> replay_opts *opts)\n>  return 0;\n> }\n>\n> +enum todo_item_flags {\n> + TODO_EDIT_MERGE_MSG = 1\n> +};\n> +\n> struct todo_item {\n>  enum todo_command command;\n>  struct commit *commit;\n> + unsigned int flags;\n>  const char *arg;\n>  int arg_len;\n>  size_t offset_in_buf;\n> @@ -1761,6 +1797,8 @@ static int parse_insn_line(struct todo_item *item,\n> const char *bol, char *eol)\n>  char *end_of_object_name;\n>  int i, saved, status, padding;\n>\n> + item->flags = 0;\n> +\n>  /* left-trim */\n>  bol += strspn(bol, \" \\t\");\n>\n> @@ -1802,13 +1840,29 @@ static int parse_insn_line(struct todo_item *item,\n> const char *bol, char *eol)\n>  return error(_(\"missing arguments for %s\"),\n>       command_to_string(item->command));\n>\n> - if (item->command == TODO_EXEC) {\n> + if (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n> +     item->command == TODO_RESET) {\n>  item->commit = NULL;\n>  item->arg = bol;\n>  item->arg_len = (int)(eol - bol);\n>  return 0;\n>  }\n>\n> + if (item->command == TODO_MERGE) {\n> + if (skip_prefix(bol, \"-C\", &bol))\n> + bol += strspn(bol, \" \\t\");\n> + else if (skip_prefix(bol, \"-c\", &bol)) {\n> + bol += strspn(bol, \" \\t\");\n> + item->flags |= TODO_EDIT_MERGE_MSG;\n> + } else {\n> + item->flags |= TODO_EDIT_MERGE_MSG;\n> + item->commit = NULL;\n> + item->arg = bol;\n> + item->arg_len = (int)(eol - bol);\n> + return 0;\n> + }\n> + }\n> +\n>  end_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n>  saved = *end_of_object_name;\n>  *end_of_object_name = '\\0';\n> @@ -2465,6 +2519,305 @@ static int do_exec(const char *command_line)\n>  return status;\n> }\n>\n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> + va_list ap;\n> + struct lock_file lock = LOCK_INIT;\n> + int fd = hold_lock_file_for_update(&lock, filename,\n> +    LOCK_REPORT_ON_ERROR);\n> + struct strbuf buf = STRBUF_INIT;\n> +\n> + if (fd < 0)\n> + return -1;\n> +\n> + if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n> + error_errno(_(\"could not read '%s'\"), filename);\n> + rollback_lock_file(&lock);\n> + return -1;\n> + }\n> + strbuf_complete(&buf, '\\n');\n> + va_start(ap, fmt);\n> + strbuf_vaddf(&buf, fmt, ap);\n> + va_end(ap);\n> +\n> + if (write_in_full(fd, buf.buf, buf.len) < 0) {\n> + error_errno(_(\"could not write to '%s'\"), filename);\n> + strbuf_release(&buf);\n> + rollback_lock_file(&lock);\n> + return -1;\n> + }\n> + if (commit_lock_file(&lock) < 0) {\n> + strbuf_release(&buf);\n> + rollback_lock_file(&lock);\n> + return error(_(\"failed to finalize '%s'\"), filename);\n> + }\n> +\n> + strbuf_release(&buf);\n> + return 0;\n> +}\n> +\n> +static int do_label(const char *name, int len)\n> +{\n> + struct ref_store *refs = get_main_ref_store();\n> + struct ref_transaction *transaction;\n> + struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n> + struct strbuf msg = STRBUF_INIT;\n> + int ret = 0;\n> + struct object_id head_oid;\n> +\n> + if (len == 1 && *name == '#')\n> + return error(\"Illegal label name: '%.*s'\", len, name);\n> +\n> + strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> + strbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n> +\n> + transaction = ref_store_transaction_begin(refs, &err);\n> + if (!transaction) {\n> + error(\"%s\", err.buf);\n> + ret = -1;\n> + } else if (get_oid(\"HEAD\", &head_oid)) {\n> + error(_(\"could not read HEAD\"));\n> + ret = -1;\n> + } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n> +   NULL, 0, msg.buf, &err) < 0 ||\n> +    ref_transaction_commit(transaction, &err)) {\n> + error(\"%s\", err.buf);\n> + ret = -1;\n> + }\n> + ref_transaction_free(transaction);\n> + strbuf_release(&err);\n> + strbuf_release(&msg);\n> +\n> + if (!ret)\n> + ret = safe_append(rebase_path_refs_to_delete(),\n> +   \"%s\\n\", ref_name.buf);\n> + strbuf_release(&ref_name);\n> +\n> + return ret;\n> +}\n> +\n> +static const char *reflog_message(struct replay_opts *opts,\n> + const char *sub_action, const char *fmt, ...);\n> +\n> +static int do_reset(const char *name, int len, struct replay_opts *opts)\n> +{\n> + struct strbuf ref_name = STRBUF_INIT;\n> + struct object_id oid;\n> + struct lock_file lock = LOCK_INIT;\n> + struct tree_desc desc;\n> + struct tree *tree;\n> + struct unpack_trees_options unpack_tree_opts;\n> + int ret = 0, i;\n> +\n> + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> + return -1;\n> +\n> + /* Determine the length of the label */\n> + for (i = 0; i < len; i++)\n> + if (isspace(name[i]))\n> + len = i;\n> +\n> + strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> + if (get_oid(ref_name.buf, &oid) &&\n> +     get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> + error(_(\"could not read '%s'\"), ref_name.buf);\n> + rollback_lock_file(&lock);\n> + strbuf_release(&ref_name);\n> + return -1;\n> + }\n> +\n> + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n> + unpack_tree_opts.head_idx = 1;\n> + unpack_tree_opts.src_index = &the_index;\n> + unpack_tree_opts.dst_index = &the_index;\n> + unpack_tree_opts.fn = oneway_merge;\n> + unpack_tree_opts.merge = 1;\n> + unpack_tree_opts.update = 1;\n> +\n> + if (read_cache_unmerged()) {\n> + rollback_lock_file(&lock);\n> + strbuf_release(&ref_name);\n> + return error_resolve_conflict(_(action_name(opts)));\n> + }\n> +\n> + if (!fill_tree_descriptor(&desc, &oid)) {\n> + error(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n> + rollback_lock_file(&lock);\n> + free((void *)desc.buffer);\n> + strbuf_release(&ref_name);\n> + return -1;\n> + }\n> +\n> + if (unpack_trees(1, &desc, &unpack_tree_opts)) {\n> + rollback_lock_file(&lock);\n> + free((void *)desc.buffer);\n> + strbuf_release(&ref_name);\n> + return -1;\n> + }\n> +\n> + tree = parse_tree_indirect(&oid);\n> + prime_cache_tree(&the_index, tree);\n> +\n> + if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n> + ret = error(_(\"could not write index\"));\n> + free((void *)desc.buffer);\n> +\n> + if (!ret)\n> + ret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n> + len, name), \"HEAD\", &oid,\n> + NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n> +\n> + strbuf_release(&ref_name);\n> + return ret;\n> +}\n> +\n> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n> +     int flags, struct replay_opts *opts)\n> +{\n> + int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n> + EDIT_MSG | VERIFY_MSG : 0;\n> + struct strbuf ref_name = STRBUF_INIT;\n> + struct commit *head_commit, *merge_commit, *i;\n> + struct commit_list *bases, *j, *reversed = NULL;\n> + struct merge_options o;\n> + int merge_arg_len, oneline_offset, ret;\n> + static struct lock_file lock;\n> + const char *p;\n> +\n> + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {\n> + ret = -1;\n> + goto leave_merge;\n> + }\n> +\n> + head_commit = lookup_commit_reference_by_name(\"HEAD\");\n> + if (!head_commit) {\n> + ret = error(_(\"cannot merge without a current revision\"));\n> + goto leave_merge;\n> + }\n> +\n> + oneline_offset = arg_len;\n> + merge_arg_len = strcspn(arg, \" \\t\\n\");\n> + p = arg + merge_arg_len;\n> + p += strspn(p, \" \\t\\n\");\n> + if (*p == '#' && (!p[1] || isspace(p[1]))) {\n> + p += 1 + strspn(p + 1, \" \\t\\n\");\n> + oneline_offset = p - arg;\n> + } else if (p - arg < arg_len)\n> + BUG(\"octopus merges are not supported yet: '%s'\", p);\n> +\n> + strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n> + merge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> + if (!merge_commit) {\n> + /* fall back to non-rewritten ref or commit */\n> + strbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n> + merge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> + }\n> +\n> + if (!merge_commit) {\n> + ret = error(_(\"could not resolve '%s'\"), ref_name.buf);\n> + goto leave_merge;\n> + }\n> +\n> + if (commit) {\n> + const char *message = get_commit_buffer(commit, NULL);\n> + const char *body;\n> + int len;\n> +\n> + if (!message) {\n> + ret = error(_(\"could not get commit message of '%s'\"),\n> +     oid_to_hex(&commit->object.oid));\n> + goto leave_merge;\n> + }\n> + write_author_script(message);\n> + find_commit_subject(message, &body);\n> + len = strlen(body);\n> + ret = write_message(body, len, git_path_merge_msg(), 0);\n> + unuse_commit_buffer(commit, message);\n> + if (ret) {\n> + error_errno(_(\"could not write '%s'\"),\n> +     git_path_merge_msg());\n> + goto leave_merge;\n> + }\n> + } else {\n> + struct strbuf buf = STRBUF_INIT;\n> + int len;\n> +\n> + strbuf_addf(&buf, \"author %s\", git_author_info(0));\n> + write_author_script(buf.buf);\n> + strbuf_reset(&buf);\n> +\n> + if (oneline_offset < arg_len) {\n> + p = arg + oneline_offset;\n> + len = arg_len - oneline_offset;\n> + } else {\n> + strbuf_addf(&buf, \"Merge branch '%.*s'\",\n> +     merge_arg_len, arg);\n> + p = buf.buf;\n> + len = buf.len;\n> + }\n> +\n> + ret = write_message(p, len, git_path_merge_msg(), 0);\n> + strbuf_release(&buf);\n> + if (ret) {\n> + error_errno(_(\"could not write '%s'\"),\n> +     git_path_merge_msg());\n> + goto leave_merge;\n> + }\n> + }\n> +\n> + write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n> +       git_path_merge_head(), 0);\n> + write_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n> +\n> + bases = get_merge_bases(head_commit, merge_commit);\n> + for (j = bases; j; j = j->next)\n> + commit_list_insert(j->item, &reversed);\n> + free_commit_list(bases);\n> +\n> + read_cache();\n> + init_merge_options(&o);\n> + o.branch1 = \"HEAD\";\n> + o.branch2 = ref_name.buf;\n> + o.buffer_output = 2;\n> +\n> + ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n> + if (ret <= 0)\n> + fputs(o.obuf.buf, stdout);\n> + strbuf_release(&o.obuf);\n> + if (ret < 0) {\n> + error(_(\"could not even attempt to merge '%.*s'\"),\n> +       merge_arg_len, arg);\n> + goto leave_merge;\n> + }\n> + /*\n> + * The return value of merge_recursive() is 1 on clean, and 0 on\n> + * unclean merge.\n> + *\n> + * Let's reverse that, so that do_merge() returns 0 upon success and\n> + * 1 upon failed merge (keeping the return value -1 for the cases where\n> + * we will want to reschedule the `merge` command).\n> + */\n> + ret = !ret;\n> +\n> + if (active_cache_changed &&\n> +     write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n> + ret = error(_(\"merge: Unable to write new index file\"));\n> + goto leave_merge;\n> + }\n> +\n> + rollback_lock_file(&lock);\n> + if (ret)\n> + rerere(opts->allow_rerere_auto);\n> + else\n> + ret = run_git_commit(git_path_merge_msg(), opts,\n> +      run_commit_flags);\n> +\n> +leave_merge:\n> + strbuf_release(&ref_name);\n> + rollback_lock_file(&lock);\n> + return ret;\n> +}\n> +\n> static int is_final_fixup(struct todo_list *todo_list)\n> {\n>  int i = todo_list->current;\n> @@ -2568,7 +2921,7 @@ N_(\"Could not execute the todo command\\n\"\n>\n> static int pick_commits(struct todo_list *todo_list, struct replay_opts\n> *opts)\n> {\n> - int res = 0;\n> + int res = 0, reschedule = 0;\n>\n>  setenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n>  if (opts->allow_ff)\n> @@ -2639,7 +2992,7 @@ static int pick_commits(struct todo_list *todo_list,\n> struct replay_opts *opts)\n>  intend_to_amend();\n>  return error_failed_squash(item->commit, opts,\n>  item->arg_len, item->arg);\n> - } else if (res && is_rebase_i(opts))\n> + } else if (res && is_rebase_i(opts) && item->commit)\n>  return res | error_with_patch(item->commit,\n>  item->arg, item->arg_len, opts, res,\n>  item->command == TODO_REWORD);\n> @@ -2665,9 +3018,41 @@ static int pick_commits(struct todo_list\n> *todo_list, struct replay_opts *opts)\n>  /* `current` will be incremented below */\n>  todo_list->current = -1;\n>  }\n> + } else if (item->command == TODO_LABEL) {\n> + if ((res = do_label(item->arg, item->arg_len)))\n> + reschedule = 1;\n> + } else if (item->command == TODO_RESET) {\n> + if ((res = do_reset(item->arg, item->arg_len, opts)))\n> + reschedule = 1;\n> + } else if (item->command == TODO_MERGE) {\n> + if ((res = do_merge(item->commit,\n> +     item->arg, item->arg_len,\n> +     item->flags, opts)) < 0)\n> + reschedule = 1;\n> + else if (res > 0)\n> + /* failed with merge conflicts */\n> + return error_with_patch(item->commit,\n> + item->arg,\n> + item->arg_len, opts,\n> + res, 0);\n>  } else if (!is_noop(item->command))\n>  return error(_(\"unknown command %d\"), item->command);\n>\n> + if (reschedule) {\n> + advise(_(rescheduled_advice),\n> +        get_item_line_length(todo_list,\n> +     todo_list->current),\n> +        get_item_line(todo_list, todo_list->current));\n> + todo_list->current--;\n> + if (save_todo(todo_list, opts))\n> + return -1;\n> + if (item->commit)\n> + return error_with_patch(item->commit,\n> + item->arg,\n> + item->arg_len, opts,\n> + res, 0);\n> + }\n> +\n>  todo_list->current++;\n>  if (res)\n>  return res;\n> @@ -3147,8 +3532,16 @@ int transform_todos(unsigned flags)\n>    short_commit_name(item->commit) :\n>    oid_to_hex(&item->commit->object.oid);\n>\n> + if (item->command == TODO_MERGE) {\n> + if (item->flags & TODO_EDIT_MERGE_MSG)\n> + strbuf_addstr(&buf, \" -c\");\n> + else\n> + strbuf_addstr(&buf, \" -C\");\n> + }\n> +\n>  strbuf_addf(&buf, \" %s\", oid);\n>  }\n> +\n>  /* add all the rest */\n>  if (!item->arg_len)\n>  strbuf_addch(&buf, '\\n');\n> -- \n> 2.17.0.windows.1.15.gaa56ade3205\n>\n>\n>\n\n"},{"id":"345389","messageId":"0023E61D311849B68FECAE76613839C4@PhilipOakley","threadId":"47635","inReplyTo":"ff71f636d5171359d94097423b8e9e340ab2152a.1524306546.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v8 08/16] rebase-helper --make-script: introduce a flag to rebase merges","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-22T13:42:28Z","receivedAt":"2018-04-22T13:42:33Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n\nSorry for the very late in the series comments..\n\n> The sequencer just learned new commands intended to recreate branch\n> structure (similar in spirit to --preserve-merges, but with a\n> substantially less-broken design).\n>\n> Let's allow the rebase--helper to generate todo lists making use of\n> these commands, triggered by the new --rebase-merges option. For a\n> commit topology like this (where the HEAD points to C):\n>\n> - A - B - C\n>     \\   /\n>       D\n>\n> the generated todo list would look like this:\n>\n> # branch D\n> pick 0123 A\n> label branch-point\n> pick 1234 D\n> label D\n>\n> reset branch-point\n> pick 2345 B\n> merge -C 3456 D # C\n>\n> To keep things simple, we first only implement support for merge commits\n> with exactly two parents, leaving support for octopus merges to a later\n> patch series.\n>\nFor the first time reader this (below) isn't as obvious as may be thought.\nmaybe we should be a little more explicit here.\n\n> As a special, hard-coded label, all merge-rebasing todo lists start with\n> the command `label onto`\n\n.. which labels the start point head with the name 'onto' ...\n\nMaybe even:\n\"All merge-rebasing todo lists start with, as a convenience, a hard-coded\n`label onto` line which will label the start point's head\" ...\n\n>                so that we can later always refer to the revision\n> onto which everything is rebased.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> builtin/rebase--helper.c |   4 +-\n> sequencer.c              | 351 ++++++++++++++++++++++++++++++++++++++-\n> sequencer.h              |   1 +\n> 3 files changed, 353 insertions(+), 3 deletions(-)\n>\n> diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\n> index ad074705bb5..781782e7272 100644\n> --- a/builtin/rebase--helper.c\n> +++ b/builtin/rebase--helper.c\n> @@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[]\n> = {\n> int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n> {\n>  struct replay_opts opts = REPLAY_OPTS_INIT;\n> - unsigned flags = 0, keep_empty = 0;\n> + unsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n>  int abbreviate_commands = 0;\n>  enum {\n>  CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n> @@ -24,6 +24,7 @@ int cmd_rebase__helper(int argc, const char **argv,\n> const char *prefix)\n>  OPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n>  OPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n>  N_(\"allow commits with empty messages\")),\n> + OPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge\n> commits\")),\n>  OPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n>  CONTINUE),\n>  OPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n> @@ -57,6 +58,7 @@ int cmd_rebase__helper(int argc, const char **argv,\n> const char *prefix)\n>\n>  flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n>  flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n> + flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n>  flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n>\n>  if (command == CONTINUE && argc == 1)\n> diff --git a/sequencer.c b/sequencer.c\n> index 5944d3a34eb..1e17a11ca32 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -25,6 +25,8 @@\n> #include \"sigchain.h\"\n> #include \"unpack-trees.h\"\n> #include \"worktree.h\"\n> +#include \"oidmap.h\"\n> +#include \"oidset.h\"\n>\n> #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>\n> @@ -3436,6 +3438,343 @@ void append_signoff(struct strbuf *msgbuf, int\n> ignore_footer, unsigned flag)\n>  strbuf_release(&sob);\n> }\n>\n> +struct labels_entry {\n> + struct hashmap_entry entry;\n> + char label[FLEX_ARRAY];\n> +};\n> +\n> +static int labels_cmp(const void *fndata, const struct labels_entry *a,\n> +       const struct labels_entry *b, const void *key)\n> +{\n> + return key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n> +}\n> +\n> +struct string_entry {\n> + struct oidmap_entry entry;\n> + char string[FLEX_ARRAY];\n> +};\n> +\n> +struct label_state {\n> + struct oidmap commit2label;\n> + struct hashmap labels;\n> + struct strbuf buf;\n> +};\n> +\n> +static const char *label_oid(struct object_id *oid, const char *label,\n> +      struct label_state *state)\n> +{\n> + struct labels_entry *labels_entry;\n> + struct string_entry *string_entry;\n> + struct object_id dummy;\n> + size_t len;\n> + int i;\n> +\n> + string_entry = oidmap_get(&state->commit2label, oid);\n> + if (string_entry)\n> + return string_entry->string;\n> +\n> + /*\n> + * For \"uninteresting\" commits, i.e. commits that are not to be\n> + * rebased, and which can therefore not be labeled, we use a unique\n> + * abbreviation of the commit name. This is slightly more complicated\n> + * than calling find_unique_abbrev() because we also need to make\n> + * sure that the abbreviation does not conflict with any other\n> + * label.\n> + *\n> + * We disallow \"interesting\" commits to be labeled by a string that\n> + * is a valid full-length hash, to ensure that we always can find an\n> + * abbreviation for any uninteresting commit's names that does not\n> + * clash with any other label.\n> + */\n> + if (!label) {\n> + char *p;\n> +\n> + strbuf_reset(&state->buf);\n> + strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n> + label = p = state->buf.buf;\n> +\n> + find_unique_abbrev_r(p, oid, default_abbrev);\n> +\n> + /*\n> + * We may need to extend the abbreviated hash so that there is\n> + * no conflicting label.\n> + */\n> + if (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n> + size_t i = strlen(p) + 1;\n> +\n> + oid_to_hex_r(p, oid);\n> + for (; i < GIT_SHA1_HEXSZ; i++) {\n> + char save = p[i];\n> + p[i] = '\\0';\n> + if (!hashmap_get_from_hash(&state->labels,\n> +    strihash(p), p))\n> + break;\n> + p[i] = save;\n> + }\n> + }\n> + } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n> +     !get_oid_hex(label, &dummy)) ||\n> +    (len == 1 && *label == '#') ||\n> +    hashmap_get_from_hash(&state->labels,\n> + strihash(label), label)) {\n> + /*\n> + * If the label already exists, or if the label is a valid full\n> + * OID, or the label is a '#' (which we use as a separator\n> + * between merge heads and oneline), we append a dash and a\n> + * number to make it unique.\n> + */\n> + struct strbuf *buf = &state->buf;\n> +\n> + strbuf_reset(buf);\n> + strbuf_add(buf, label, len);\n> +\n> + for (i = 2; ; i++) {\n> + strbuf_setlen(buf, len);\n> + strbuf_addf(buf, \"-%d\", i);\n> + if (!hashmap_get_from_hash(&state->labels,\n> +    strihash(buf->buf),\n> +    buf->buf))\n> + break;\n> + }\n> +\n> + label = buf->buf;\n> + }\n> +\n> + FLEX_ALLOC_STR(labels_entry, label, label);\n> + hashmap_entry_init(labels_entry, strihash(label));\n> + hashmap_add(&state->labels, labels_entry);\n> +\n> + FLEX_ALLOC_STR(string_entry, string, label);\n> + oidcpy(&string_entry->entry.oid, oid);\n> + oidmap_put(&state->commit2label, string_entry);\n> +\n> + return string_entry->string;\n> +}\n> +\n> +static int make_script_with_merges(struct pretty_print_context *pp,\n> +    struct rev_info *revs, FILE *out,\n> +    unsigned flags)\n> +{\n> + int keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n> + struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n> + struct strbuf label = STRBUF_INIT;\n> + struct commit_list *commits = NULL, **tail = &commits, *iter;\n> + struct commit_list *tips = NULL, **tips_tail = &tips;\n> + struct commit *commit;\n> + struct oidmap commit2todo = OIDMAP_INIT;\n> + struct string_entry *entry;\n> + struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n> + shown = OIDSET_INIT;\n> + struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n> +\n> + int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n> + const char *cmd_pick = abbr ? \"p\" : \"pick\",\n> + *cmd_label = abbr ? \"l\" : \"label\",\n> + *cmd_reset = abbr ? \"t\" : \"reset\",\n> + *cmd_merge = abbr ? \"m\" : \"merge\";\n> +\n> + oidmap_init(&commit2todo, 0);\n> + oidmap_init(&state.commit2label, 0);\n> + hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n> + strbuf_init(&state.buf, 32);\n> +\n> + if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n> + struct object_id *oid = &revs->cmdline.rev[0].item->oid;\n> + FLEX_ALLOC_STR(entry, string, \"onto\");\n> + oidcpy(&entry->entry.oid, oid);\n> + oidmap_put(&state.commit2label, entry);\n> + }\n> +\n> + /*\n> + * First phase:\n> + * - get onelines for all commits\n> + * - gather all branch tips (i.e. 2nd or later parents of merges)\n> + * - label all branch tips\n> + */\n> + while ((commit = get_revision(revs))) {\n> + struct commit_list *to_merge;\n> + int is_octopus;\n> + const char *p1, *p2;\n> + struct object_id *oid;\n> + int is_empty;\n> +\n> + tail = &commit_list_insert(commit, tail)->next;\n> + oidset_insert(&interesting, &commit->object.oid);\n> +\n> + is_empty = is_original_commit_empty(commit);\n> + if (!is_empty && (commit->object.flags & PATCHSAME))\n> + continue;\n> +\n> + strbuf_reset(&oneline);\n> + pretty_print_commit(pp, commit, &oneline);\n> +\n> + to_merge = commit->parents ? commit->parents->next : NULL;\n> + if (!to_merge) {\n> + /* non-merge commit: easy case */\n> + strbuf_reset(&buf);\n> + if (!keep_empty && is_empty)\n> + strbuf_addf(&buf, \"%c \", comment_line_char);\n> + strbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n> +     oid_to_hex(&commit->object.oid),\n> +     oneline.buf);\n> +\n> + FLEX_ALLOC_STR(entry, string, buf.buf);\n> + oidcpy(&entry->entry.oid, &commit->object.oid);\n> + oidmap_put(&commit2todo, entry);\n> +\n> + continue;\n> + }\n> +\n> + is_octopus = to_merge && to_merge->next;\n> +\n> + if (is_octopus)\n> + BUG(\"Octopus merges not yet supported\");\n> +\n> + /* Create a label */\n> + strbuf_reset(&label);\n> + if (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n> +     (p1 = strchr(p1, '\\'')) &&\n> +     (p2 = strchr(++p1, '\\'')))\n> + strbuf_add(&label, p1, p2 - p1);\n> + else if (skip_prefix(oneline.buf, \"Merge pull request \",\n> +      &p1) &&\n> + (p1 = strstr(p1, \" from \")))\n> + strbuf_addstr(&label, p1 + strlen(\" from \"));\n> + else\n> + strbuf_addbuf(&label, &oneline);\n> +\n> + for (p1 = label.buf; *p1; p1++)\n> + if (isspace(*p1))\n> + *(char *)p1 = '-';\n> +\n> + strbuf_reset(&buf);\n> + strbuf_addf(&buf, \"%s -C %s\",\n> +     cmd_merge, oid_to_hex(&commit->object.oid));\n> +\n> + /* label the tip of merged branch */\n> + oid = &to_merge->item->object.oid;\n> + strbuf_addch(&buf, ' ');\n> +\n> + if (!oidset_contains(&interesting, oid))\n> + strbuf_addstr(&buf, label_oid(oid, NULL, &state));\n> + else {\n> + tips_tail = &commit_list_insert(to_merge->item,\n> + tips_tail)->next;\n> +\n> + strbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n> + }\n> + strbuf_addf(&buf, \" # %s\", oneline.buf);\n> +\n> + FLEX_ALLOC_STR(entry, string, buf.buf);\n> + oidcpy(&entry->entry.oid, &commit->object.oid);\n> + oidmap_put(&commit2todo, entry);\n> + }\n> +\n> + /*\n> + * Second phase:\n> + * - label branch points\n> + * - add HEAD to the branch tips\n> + */\n> + for (iter = commits; iter; iter = iter->next) {\n> + struct commit_list *parent = iter->item->parents;\n> + for (; parent; parent = parent->next) {\n> + struct object_id *oid = &parent->item->object.oid;\n> + if (!oidset_contains(&interesting, oid))\n> + continue;\n> + if (!oidset_contains(&child_seen, oid))\n> + oidset_insert(&child_seen, oid);\n> + else\n> + label_oid(oid, \"branch-point\", &state);\n> + }\n> +\n> + /* Add HEAD as implict \"tip of branch\" */\n> + if (!iter->next)\n> + tips_tail = &commit_list_insert(iter->item,\n> + tips_tail)->next;\n> + }\n> +\n> + /*\n> + * Third phase: output the todo list. This is a bit tricky, as we\n> + * want to avoid jumping back and forth between revisions. To\n> + * accomplish that goal, we walk backwards from the branch tips,\n> + * gathering commits not yet shown, reversing the list on the fly,\n> + * then outputting that list (labeling revisions as needed).\n> + */\n> + fprintf(out, \"%s onto\\n\", cmd_label);\n> + for (iter = tips; iter; iter = iter->next) {\n> + struct commit_list *list = NULL, *iter2;\n> +\n> + commit = iter->item;\n> + if (oidset_contains(&shown, &commit->object.oid))\n> + continue;\n> + entry = oidmap_get(&state.commit2label, &commit->object.oid);\n> +\n> + if (entry)\n> + fprintf(out, \"\\n# Branch %s\\n\", entry->string);\n> + else\n> + fprintf(out, \"\\n\");\n> +\n> + while (oidset_contains(&interesting, &commit->object.oid) &&\n> +        !oidset_contains(&shown, &commit->object.oid)) {\n> + commit_list_insert(commit, &list);\n> + if (!commit->parents) {\n> + commit = NULL;\n> + break;\n> + }\n> + commit = commit->parents->item;\n> + }\n> +\n> + if (!commit)\n> + fprintf(out, \"%s onto\\n\", cmd_reset);\n> + else {\n> + const char *to = NULL;\n> +\n> + entry = oidmap_get(&state.commit2label,\n> +    &commit->object.oid);\n> + if (entry)\n> + to = entry->string;\n> +\n> + if (!to || !strcmp(to, \"onto\"))\n> + fprintf(out, \"%s onto\\n\", cmd_reset);\n> + else {\n> + strbuf_reset(&oneline);\n> + pretty_print_commit(pp, commit, &oneline);\n> + fprintf(out, \"%s %s # %s\\n\",\n> + cmd_reset, to, oneline.buf);\n> + }\n> + }\n> +\n> + for (iter2 = list; iter2; iter2 = iter2->next) {\n> + struct object_id *oid = &iter2->item->object.oid;\n> + entry = oidmap_get(&commit2todo, oid);\n> + /* only show if not already upstream */\n> + if (entry)\n> + fprintf(out, \"%s\\n\", entry->string);\n> + entry = oidmap_get(&state.commit2label, oid);\n> + if (entry)\n> + fprintf(out, \"%s %s\\n\",\n> + cmd_label, entry->string);\n> + oidset_insert(&shown, oid);\n> + }\n> +\n> + free_commit_list(list);\n> + }\n> +\n> + free_commit_list(commits);\n> + free_commit_list(tips);\n> +\n> + strbuf_release(&label);\n> + strbuf_release(&oneline);\n> + strbuf_release(&buf);\n> +\n> + oidmap_free(&commit2todo, 1);\n> + oidmap_free(&state.commit2label, 1);\n> + hashmap_free(&state.labels, 1);\n> + strbuf_release(&state.buf);\n> +\n> + return 0;\n> +}\n> +\n> int sequencer_make_script(FILE *out, int argc, const char **argv,\n>    unsigned flags)\n> {\n> @@ -3446,11 +3785,16 @@ int sequencer_make_script(FILE *out, int argc,\n> const char **argv,\n>  struct commit *commit;\n>  int keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n>  const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n> + int rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n>\n>  init_revisions(&revs, NULL);\n>  revs.verbose_header = 1;\n> - revs.max_parents = 1;\n> - revs.cherry_pick = 1;\n> + if (rebase_merges)\n> + revs.cherry_mark = 1;\n> + else {\n> + revs.max_parents = 1;\n> + revs.cherry_pick = 1;\n> + }\n>  revs.limited = 1;\n>  revs.reverse = 1;\n>  revs.right_only = 1;\n> @@ -3474,6 +3818,9 @@ int sequencer_make_script(FILE *out, int argc, const\n> char **argv,\n>  if (prepare_revision_walk(&revs) < 0)\n>  return error(_(\"make_script: error preparing revisions\"));\n>\n> + if (rebase_merges)\n> + return make_script_with_merges(&pp, &revs, out, flags);\n> +\n>  while ((commit = get_revision(&revs))) {\n>  strbuf_reset(&buf);\n>  if (!keep_empty && is_original_commit_empty(commit))\n> diff --git a/sequencer.h b/sequencer.h\n> index e45b178dfc4..6bc4da17243 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -59,6 +59,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n> #define TODO_LIST_KEEP_EMPTY (1U << 0)\n> #define TODO_LIST_SHORTEN_IDS (1U << 1)\n> #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n> +#define TODO_LIST_REBASE_MERGES (1U << 3)\n> int sequencer_make_script(FILE *out, int argc, const char **argv,\n>    unsigned flags);\n>\n> -- \n> 2.17.0.windows.1.15.gaa56ade3205\n>\n>\n>\n\n"},{"id":"345391","messageId":"83137354138143FD962AF49D37F93E9E@PhilipOakley","threadId":"47635","inReplyTo":"0c92bdd1829328544269722cbbd3edcd169bb148.1524306547.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v8 09/16] rebase: introduce the --rebase-merges option","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-22T14:37:45Z","receivedAt":"2018-04-22T14:37:52Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> Once upon a time, this here developer thought: wouldn't it be nice if,\n> say, Git for Windows' patches on top of core Git could be represented as\n> a thicket of branches, and be rebased on top of core Git in order to\n> maintain a cherry-pick'able set of patch series?\n>\n> The original attempt to answer this was: git rebase --preserve-merges.\n>\n> However, that experiment was never intended as an interactive option,\n> and it only piggy-backed on git rebase --interactive because that\n> command's implementation looked already very, very familiar: it was\n> designed by the same person who designed --preserve-merges: yours truly.\n>\n> Some time later, some other developer (I am looking at you, Andreas!\n> ;-)) decided that it would be a good idea to allow --preserve-merges to\n> be combined with --interactive (with caveats!) and the Git maintainer\n> (well, the interim Git maintainer during Junio's absence, that is)\n> agreed, and that is when the glamor of the --preserve-merges design\n> started to fall apart rather quickly and unglamorously.\n>\n> The reason? In --preserve-merges mode, the parents of a merge commit (or\n> for that matter, of *any* commit) were not stated explicitly, but were\n> *implied* by the commit name passed to the `pick` command.\n>\nAside: I think this para should be extracted to the --preserve-merges \ndocumentation to highlight what it does / why it is 'wrong' (not what would \nbe expected in some case). It may also need to discuss the (figurative) \nCousins vs. Siblings distinction [merge of branches external, or internal, \nto the rebase.\n\n\"In --preserve-merges, the commit being selected for merging is implied by \nthe commit name  passed to the `pick` command (i.e. of the original merge \ncommit), not that of the rebased version of that parent.\"\n\nA similar issue occurs with (figuratively) '--ancestry-path --first parent' \nsearches which lacks the alternate '--lead parent' post-walk selection. [1]. \nI don't think there is a dot notation to select the merge cousins, nor merge \nsiblings either A.,B ? (that's dot-comma ;-)\n\n> This made it impossible, for example, to reorder commits. Not to mention\n> to flatten the branch topology or, deity forbid, to split topic branches\n> into two.\n>\n> Alas, these shortcomings also prevented that mode (whose original\n> purpose was to serve Git for Windows' needs, with the additional hope\n> that it may be useful to others, too) from serving Git for Windows'\n> needs.\n>\n> Five years later, when it became really untenable to have one unwieldy,\n> big hodge-podge patch series of partly related, partly unrelated patches\n> in Git for Windows that was rebased onto core Git's tags from time to\n> time (earning the undeserved wrath of the developer of the ill-fated\n> git-remote-hg series that first obsoleted Git for Windows' competing\n> approach, only to be abandoned without maintainer later) was really\n> untenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\n> piggy-backing on top of the interactive rebase, that would first\n> determine the branch topology of the patches to be rebased, create a\n> pseudo todo list for further editing, transform the result into a real\n> todo list (making heavy use of the `exec` command to \"implement\" the\n> missing todo list commands) and finally recreate the patch series on\n> top of the new base commit.\n>\n> That was in 2013. And it took about three weeks to come up with the\n> design and implement it as an out-of-tree script. Needless to say, the\n> implementation needed quite a few years to stabilize, all the while the\n> design itself proved itself sound.\n>\n> With this patch, the goodness of the Git garden shears comes to `git\n> rebase -i` itself. Passing the `--rebase-merges` option will generate\n> a todo list that can be understood readily, and where it is obvious\n> how to reorder commits. New branches can be introduced by inserting\n> `label` commands and calling `merge <label>`. And once this mode will\n> have become stable and universally accepted, we can deprecate the design\n> mistake that was `--preserve-merges`.\n>\n> Link *1*:\n> https://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\n> Link *2*:\n> https://github.com/git-for-windows/build-extra/blob/master/shears.sh\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> Documentation/git-rebase.txt           |  20 ++-\n> contrib/completion/git-completion.bash |   2 +-\n> git-rebase--interactive.sh             |   1 +\n> git-rebase.sh                          |   6 +\n> t/t3430-rebase-merges.sh               | 179 +++++++++++++++++++++++++\n> 5 files changed, 206 insertions(+), 2 deletions(-)\n> create mode 100755 t/t3430-rebase-merges.sh\n>\n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 3277ca14327..34e0f6a69c1 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -378,6 +378,23 @@ The commit list format can be changed by setting the \n> configuration option\n> rebase.instructionFormat.  A customized instruction format will \n> automatically\n> have the long commit hash prepended to the format.\n>\n> +-r::\n> +--rebase-merges::\n> + By default, a rebase will simply drop merge commits and only rebase\n> + the non-merge commits. With this option, it will try to preserve\n> + the branching structure within the commits that are to be rebased,\n> + by recreating the merge commits. If a merge commit resolved any merge\n> + or contained manual amendments, then they will have to be re-applied\n> + manually.\n> ++\n> +This mode is similar in spirit to `--preserve-merges`, but in contrast to\n> +that option works well in interactive rebases: commits can be reordered,\n> +inserted and dropped at will.\n> ++\n> +It is currently only possible to recreate the merge commits using the\n> +`recursive` merge strategy; Different merge strategies can be used only \n> via\n> +explicit `exec git merge -s <strategy> [...]` commands.\n> +\n> -p::\n> --preserve-merges::\n>  Recreate merge commits instead of flattening the history by replaying\n> @@ -780,7 +797,8 @@ BUGS\n> The todo list presented by `--preserve-merges --interactive` does not\n> represent the topology of the revision graph.  Editing commits and\n> rewording their commit messages should work fine, but attempts to\n> -reorder commits tend to produce counterintuitive results.\n> +reorder commits tend to produce counterintuitive results. Use\n> +`--rebase-merges` in such scenarios instead.\n>\n> For example, an attempt to rearrange\n> ------------\n> diff --git a/contrib/completion/git-completion.bash \n> b/contrib/completion/git-completion.bash\n> index a7570739454..d4c0a995c39 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1949,7 +1949,7 @@ _git_rebase ()\n>  --*)\n>  __gitcomp \"\n>  --onto --merge --strategy --interactive\n> - --preserve-merges --stat --no-stat\n> + --rebase-merges --preserve-merges --stat --no-stat\n>  --committer-date-is-author-date --ignore-date\n>  --ignore-whitespace --whitespace=\n>  --autosquash --no-autosquash\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index ccd5254d1c9..7a3daf3e40c 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -970,6 +970,7 @@ git_rebase__interactive () {\n>  init_revisions_and_shortrevisions\n>\n>  git rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n> + ${rebase_merges:+--rebase-merges} \\\n>  $revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n>  die \"$(gettext \"Could not generate todo list\")\"\n>\n> diff --git a/git-rebase.sh b/git-rebase.sh\n> index fb64ee1fe42..a64460fd25a 100755\n> --- a/git-rebase.sh\n> +++ b/git-rebase.sh\n> @@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n> autostash          automatically stash/stash pop before and after\n> fork-point         use 'merge-base --fork-point' to refine upstream\n> onto=!             rebase onto given branch instead of upstream\n> +r,rebase-merges!   try to rebase merges instead of skipping them\n> p,preserve-merges! try to recreate merges instead of ignoring them\n> s,strategy=!       use the given merge strategy\n> no-ff!             cherry-pick all commits, even if unchanged\n> @@ -88,6 +89,7 @@ type=\n> state_dir=\n> # One of {'', continue, skip, abort}, as parsed from command line\n> action=\n> +rebase_merges=\n> preserve_merges=\n> autosquash=\n> keep_empty=\n> @@ -270,6 +272,10 @@ do\n>  --allow-empty-message)\n>  allow_empty_message=--allow-empty-message\n>  ;;\n> + --rebase-merges)\n> + rebase_merges=t\n> + test -z \"$interactive_rebase\" && interactive_rebase=implied\n> + ;;\n>  --preserve-merges)\n>  preserve_merges=t\n>  test -z \"$interactive_rebase\" && interactive_rebase=implied\n> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n> new file mode 100755\n> index 00000000000..5f0febb9970\n> --- /dev/null\n> +++ b/t/t3430-rebase-merges.sh\n> @@ -0,0 +1,179 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2018 Johannes E. Schindelin\n> +#\n> +\n> +test_description='git rebase -i --rebase-merges\n> +\n> +This test runs git rebase \"interactively\", retaining the branch structure \n> by\n> +recreating merge commits.\n> +\n> +Initial setup:\n> +\n> +    -- B --                   (first)\n> +   /       \\\n> + A - C - D - E - H            (master)\n> +       \\       /\n> +         F - G                (second)\n> +'\n> +. ./test-lib.sh\n> +. \"$TEST_DIRECTORY\"/lib-rebase.sh\n> +\n> +test_cmp_graph () {\n> + cat >expect &&\n> + git log --graph --boundary --format=%s \"$@\" >output &&\n> + sed \"s/ *$//\" <output >output.trimmed &&\n> + test_cmp expect output.trimmed\n> +}\n> +\n> +test_expect_success 'setup' '\n> + write_script replace-editor.sh <<-\\EOF &&\n> + mv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n> + cp script-from-scratch \"$1\"\n> + EOF\n> +\n> + test_commit A &&\n> + git checkout -b first &&\n> + test_commit B &&\n> + git checkout master &&\n> + test_commit C &&\n> + test_commit D &&\n> + git merge --no-commit B &&\n> + test_tick &&\n> + git commit -m E &&\n> + git tag -m E E &&\n> + git checkout -b second C &&\n> + test_commit F &&\n> + test_commit G &&\n> + git checkout master &&\n> + git merge --no-commit G &&\n> + test_tick &&\n> + git commit -m H &&\n> + git tag -m H H\n> +'\n> +\n> +test_expect_success 'create completely different structure' '\n> + cat >script-from-scratch <<-\\EOF &&\n> + label onto\n> +\n> + # onebranch\n> + pick G\n> + pick D\n> + label onebranch\n> +\n> + # second\n> + reset onto\n> + pick B\n> + label second\n> +\n> + reset onto\n> + merge -C H second\n> + merge onebranch # Merge the topic branch '\\''onebranch'\\''\n> + EOF\n> + test_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n> + test_tick &&\n> + git rebase -i -r A &&\n> + test_cmp_graph <<-\\EOF\n> + *   Merge the topic branch '\\''onebranch'\\''\n> + |\\\n> + | * D\n> + | * G\n> + * |   H\n> + |\\ \\\n> + | |/\n> + |/|\n> + | * B\n> + |/\n> + * A\n> + EOF\n> +'\n> +\n> +test_expect_success 'generate correct todo list' '\n> + cat >expect <<-\\EOF &&\n> + label onto\n> +\n> + reset onto\n> + pick d9df450 B\n> + label E\n> +\n> + reset onto\n> + pick 5dee784 C\n> + label branch-point\n> + pick ca2c861 F\n> + pick 088b00a G\n> + label H\n> +\n> + reset branch-point # C\n> + pick 12bd07b D\n> + merge -C 2051b56 E # E\n> + merge -C 233d48a H # H\n> +\n> + EOF\n> +\n> + grep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n> + test_cmp expect output\n> +'\n> +\n> +test_expect_success '`reset` refuses to overwrite untracked files' '\n> + git checkout -b refuse-to-reset &&\n> + test_commit dont-overwrite-untracked &&\n> + git checkout @{-1} &&\n> + : >dont-overwrite-untracked.t &&\n> + echo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n> + test_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n> + test_must_fail git rebase -r HEAD &&\n> + git rebase --abort\n> +'\n> +\n> +test_expect_success 'failed `merge` writes patch (may be rescheduled, \n> too)' '\n> + test_when_finished \"test_might_fail git rebase --abort\" &&\n> + git checkout -b conflicting-merge A &&\n> +\n> + : fail because of conflicting untracked file &&\n> + >G.t &&\n> + echo \"merge -C H G\" >script-from-scratch &&\n> + test_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n> + test_tick &&\n> + test_must_fail git rebase -ir HEAD &&\n> + grep \"^merge -C .* G$\" .git/rebase-merge/done &&\n> + grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n> + test_path_is_file .git/rebase-merge/patch &&\n> +\n> + : fail because of merge conflict &&\n> + rm G.t .git/rebase-merge/patch &&\n> + git reset --hard &&\n> + test_commit conflicting-G G.t not-G conflicting-G &&\n> + test_must_fail git rebase --continue &&\n> + ! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n> + test_path_is_file .git/rebase-merge/patch\n> +'\n> +\n> +test_expect_success 'with a branch tip that was cherry-picked already' '\n> + git checkout -b already-upstream master &&\n> + base=\"$(git rev-parse --verify HEAD)\" &&\n> +\n> + test_commit A1 &&\n> + test_commit A2 &&\n> + git reset --hard $base &&\n> + test_commit B1 &&\n> + test_tick &&\n> + git merge -m \"Merge branch A\" A2 &&\n> +\n> + git checkout -b upstream-with-a2 $base &&\n> + test_tick &&\n> + git cherry-pick A2 &&\n> +\n> + git checkout already-upstream &&\n> + test_tick &&\n> + git rebase -i -r upstream-with-a2 &&\n> + test_cmp_graph upstream-with-a2.. <<-\\EOF\n> + *   Merge branch A\n> + |\\\n> + | * A1\n> + * | B1\n> + |/\n> + o A2\n> + EOF\n> +'\n> +\n> +test_done\n> -- \n> 2.17.0.windows.1.15.gaa56ade3205\n>\n>\n>\n[1]https://public-inbox.org/git/2FA1998250474E76A386B82AD635E56A@PhilipOakley/ \n\n"},{"id":"345393","messageId":"E8BADD438B3F4D20B33BCC2CD92BC004@PhilipOakley","threadId":"47635","inReplyTo":"0c92bdd1829328544269722cbbd3edcd169bb148.1524306547.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v8 09/16] rebase: introduce the --rebase-merges option","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-22T14:15:27Z","receivedAt":"2018-04-22T14:49:28Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> Once upon a time, this here developer thought: wouldn't it be nice if,\n> say, Git for Windows' patches on top of core Git could be represented as\n> a thicket of branches, and be rebased on top of core Git in order to\n> maintain a cherry-pick'able set of patch series?\n>\n> The original attempt to answer this was: git rebase --preserve-merges.\n>\n> However, that experiment was never intended as an interactive option,\n> and it only piggy-backed on git rebase --interactive because that\n> command's implementation looked already very, very familiar: it was\n> designed by the same person who designed --preserve-merges: yours truly.\n>\n> Some time later, some other developer (I am looking at you, Andreas!\n> ;-)) decided that it would be a good idea to allow --preserve-merges to\n> be combined with --interactive (with caveats!) and the Git maintainer\n> (well, the interim Git maintainer during Junio's absence, that is)\n> agreed, and that is when the glamor of the --preserve-merges design\n> started to fall apart rather quickly and unglamorously.\n>\n> The reason? In --preserve-merges mode, the parents of a merge commit (or\n> for that matter, of *any* commit) were not stated explicitly, but were\n> *implied* by the commit name passed to the `pick` command.\n>\n> This made it impossible, for example, to reorder commits. Not to mention\n> to flatten the branch topology or, deity forbid, to split topic branches\n\nAside: The idea of a \"flattened\" topology is, to my mind, not actually\ndefined though may be understood by devs working in the area. Hopefully it's\ngoing away as a term, though the new 'cousins' will need clarification\n(there's no dot notation for that area of topology).\n\n> into two.\n>\n> Alas, these shortcomings also prevented that mode (whose original\n> purpose was to serve Git for Windows' needs, with the additional hope\n> that it may be useful to others, too) from serving Git for Windows'\n> needs.\n>\n> Five years later, when it became really untenable to have one unwieldy,\n> big hodge-podge patch series of partly related, partly unrelated patches\n> in Git for Windows that was rebased onto core Git's tags from time to\n> time (earning the undeserved wrath of the developer of the ill-fated\n> git-remote-hg series that first obsoleted Git for Windows' competing\n> approach, only to be abandoned without maintainer later) was really\n> untenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\n> piggy-backing on top of the interactive rebase, that would first\n> determine the branch topology of the patches to be rebased, create a\n> pseudo todo list for further editing, transform the result into a real\n> todo list (making heavy use of the `exec` command to \"implement\" the\n> missing todo list commands) and finally recreate the patch series on\n> top of the new base commit.\n>\n> That was in 2013. And it took about three weeks to come up with the\n> design and implement it as an out-of-tree script. Needless to say, the\n> implementation needed quite a few years to stabilize, all the while the\n> design itself proved itself sound.\n>\n> With this patch, the goodness of the Git garden shears comes to `git\n> rebase -i` itself. Passing the `--rebase-merges` option will generate\n> a todo list that can be understood readily, and where it is obvious\n> how to reorder commits. New branches can be introduced by inserting\n> `label` commands and calling `merge <label>`. And once this mode will\n> have become stable and universally accepted, we can deprecate the design\n> mistake that was `--preserve-merges`.\n>\n> Link *1*:\n> https://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\n> Link *2*:\n> https://github.com/git-for-windows/build-extra/blob/master/shears.sh\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> Documentation/git-rebase.txt           |  20 ++-\n> contrib/completion/git-completion.bash |   2 +-\n> git-rebase--interactive.sh             |   1 +\n> git-rebase.sh                          |   6 +\n> t/t3430-rebase-merges.sh               | 179 +++++++++++++++++++++++++\n> 5 files changed, 206 insertions(+), 2 deletions(-)\n> create mode 100755 t/t3430-rebase-merges.sh\n>\n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 3277ca14327..34e0f6a69c1 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -378,6 +378,23 @@ The commit list format can be changed by setting the\n> configuration option\n> rebase.instructionFormat.  A customized instruction format will\n> automatically\n> have the long commit hash prepended to the format.\n>\n> +-r::\n> +--rebase-merges::\n> + By default, a rebase will simply drop merge commits and only rebase\n> + the non-merge commits. With this option, it will try to preserve\n> + the branching structure within the commits that are to be rebased,\n> + by recreating the merge commits. If a merge commit resolved any merge\n> + or contained manual amendments, then they will have to be re-applied\n> + manually.\n> ++\n> +This mode is similar in spirit to `--preserve-merges`, but in contrast to\n> +that option works well in interactive rebases: commits can be reordered,\n> +inserted and dropped at will.\n> ++\n> +It is currently only possible to recreate the merge commits using the\n> +`recursive` merge strategy; Different merge strategies can be used only\n> via\n> +explicit `exec git merge -s <strategy> [...]` commands.\n> +\n> -p::\n> --preserve-merges::\n>  Recreate merge commits instead of flattening the history by replaying\n\nFlatten is here in the context lines but its just a blunt statement that 'it\nis what it is'...\n\n> @@ -780,7 +797,8 @@ BUGS\n> The todo list presented by `--preserve-merges --interactive` does not\n> represent the topology of the revision graph.  Editing commits and\n> rewording their commit messages should work fine, but attempts to\n> -reorder commits tend to produce counterintuitive results.\n> +reorder commits tend to produce counterintuitive results. Use\n> +`--rebase-merges` in such scenarios instead.\n>\n> For example, an attempt to rearrange\n> ------------\n> diff --git a/contrib/completion/git-completion.bash\n> b/contrib/completion/git-completion.bash\n> index a7570739454..d4c0a995c39 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1949,7 +1949,7 @@ _git_rebase ()\n>  --*)\n>  __gitcomp \"\n>  --onto --merge --strategy --interactive\n> - --preserve-merges --stat --no-stat\n> + --rebase-merges --preserve-merges --stat --no-stat\n>  --committer-date-is-author-date --ignore-date\n>  --ignore-whitespace --whitespace=\n>  --autosquash --no-autosquash\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index ccd5254d1c9..7a3daf3e40c 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -970,6 +970,7 @@ git_rebase__interactive () {\n>  init_revisions_and_shortrevisions\n>\n>  git rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n> + ${rebase_merges:+--rebase-merges} \\\n>  $revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n>  die \"$(gettext \"Could not generate todo list\")\"\n>\n> diff --git a/git-rebase.sh b/git-rebase.sh\n> index fb64ee1fe42..a64460fd25a 100755\n> --- a/git-rebase.sh\n> +++ b/git-rebase.sh\n> @@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n> autostash          automatically stash/stash pop before and after\n> fork-point         use 'merge-base --fork-point' to refine upstream\n> onto=!             rebase onto given branch instead of upstream\n> +r,rebase-merges!   try to rebase merges instead of skipping them\n> p,preserve-merges! try to recreate merges instead of ignoring them\n> s,strategy=!       use the given merge strategy\n> no-ff!             cherry-pick all commits, even if unchanged\n> @@ -88,6 +89,7 @@ type=\n> state_dir=\n> # One of {'', continue, skip, abort}, as parsed from command line\n> action=\n> +rebase_merges=\n> preserve_merges=\n> autosquash=\n> keep_empty=\n> @@ -270,6 +272,10 @@ do\n>  --allow-empty-message)\n>  allow_empty_message=--allow-empty-message\n>  ;;\n> + --rebase-merges)\n> + rebase_merges=t\n> + test -z \"$interactive_rebase\" && interactive_rebase=implied\n> + ;;\n>  --preserve-merges)\n>  preserve_merges=t\n>  test -z \"$interactive_rebase\" && interactive_rebase=implied\n> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n> new file mode 100755\n> index 00000000000..5f0febb9970\n> --- /dev/null\n> +++ b/t/t3430-rebase-merges.sh\n> @@ -0,0 +1,179 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2018 Johannes E. Schindelin\n> +#\n> +\n> +test_description='git rebase -i --rebase-merges\n> +\n> +This test runs git rebase \"interactively\", retaining the branch structure\n> by\n> +recreating merge commits.\n> +\n> +Initial setup:\n> +\n> +    -- B --                   (first)\n> +   /       \\\n> + A - C - D - E - H            (master)\n> +       \\       /\n> +         F - G                (second)\n> +'\n> +. ./test-lib.sh\n> +. \"$TEST_DIRECTORY\"/lib-rebase.sh\n> +\n> +test_cmp_graph () {\n> + cat >expect &&\n> + git log --graph --boundary --format=%s \"$@\" >output &&\n> + sed \"s/ *$//\" <output >output.trimmed &&\n> + test_cmp expect output.trimmed\n> +}\n> +\n> +test_expect_success 'setup' '\n> + write_script replace-editor.sh <<-\\EOF &&\n> + mv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n> + cp script-from-scratch \"$1\"\n> + EOF\n> +\n> + test_commit A &&\n> + git checkout -b first &&\n> + test_commit B &&\n> + git checkout master &&\n> + test_commit C &&\n> + test_commit D &&\n> + git merge --no-commit B &&\n> + test_tick &&\n> + git commit -m E &&\n> + git tag -m E E &&\n> + git checkout -b second C &&\n> + test_commit F &&\n> + test_commit G &&\n> + git checkout master &&\n> + git merge --no-commit G &&\n> + test_tick &&\n> + git commit -m H &&\n> + git tag -m H H\n> +'\n> +\n> +test_expect_success 'create completely different structure' '\n> + cat >script-from-scratch <<-\\EOF &&\n> + label onto\n> +\n> + # onebranch\n> + pick G\n> + pick D\n> + label onebranch\n> +\n> + # second\n> + reset onto\n> + pick B\n> + label second\n> +\n> + reset onto\n> + merge -C H second\n> + merge onebranch # Merge the topic branch '\\''onebranch'\\''\n> + EOF\n> + test_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n> + test_tick &&\n> + git rebase -i -r A &&\n> + test_cmp_graph <<-\\EOF\n> + *   Merge the topic branch '\\''onebranch'\\''\n> + |\\\n> + | * D\n> + | * G\n> + * |   H\n> + |\\ \\\n> + | |/\n> + |/|\n> + | * B\n> + |/\n> + * A\n> + EOF\n> +'\n> +\n> +test_expect_success 'generate correct todo list' '\n> + cat >expect <<-\\EOF &&\n> + label onto\n> +\n> + reset onto\n> + pick d9df450 B\n> + label E\n> +\n> + reset onto\n> + pick 5dee784 C\n> + label branch-point\n> + pick ca2c861 F\n> + pick 088b00a G\n> + label H\n> +\n> + reset branch-point # C\n> + pick 12bd07b D\n> + merge -C 2051b56 E # E\n> + merge -C 233d48a H # H\n> +\n> + EOF\n> +\n> + grep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n> + test_cmp expect output\n> +'\n> +\n> +test_expect_success '`reset` refuses to overwrite untracked files' '\n> + git checkout -b refuse-to-reset &&\n> + test_commit dont-overwrite-untracked &&\n> + git checkout @{-1} &&\n> + : >dont-overwrite-untracked.t &&\n> + echo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n> + test_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n> + test_must_fail git rebase -r HEAD &&\n> + git rebase --abort\n> +'\n> +\n> +test_expect_success 'failed `merge` writes patch (may be rescheduled,\n> too)' '\n> + test_when_finished \"test_might_fail git rebase --abort\" &&\n> + git checkout -b conflicting-merge A &&\n> +\n> + : fail because of conflicting untracked file &&\n> + >G.t &&\n> + echo \"merge -C H G\" >script-from-scratch &&\n> + test_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n> + test_tick &&\n> + test_must_fail git rebase -ir HEAD &&\n> + grep \"^merge -C .* G$\" .git/rebase-merge/done &&\n> + grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n> + test_path_is_file .git/rebase-merge/patch &&\n> +\n> + : fail because of merge conflict &&\n> + rm G.t .git/rebase-merge/patch &&\n> + git reset --hard &&\n> + test_commit conflicting-G G.t not-G conflicting-G &&\n> + test_must_fail git rebase --continue &&\n> + ! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n> + test_path_is_file .git/rebase-merge/patch\n> +'\n> +\n> +test_expect_success 'with a branch tip that was cherry-picked already' '\n> + git checkout -b already-upstream master &&\n> + base=\"$(git rev-parse --verify HEAD)\" &&\n> +\n> + test_commit A1 &&\n> + test_commit A2 &&\n> + git reset --hard $base &&\n> + test_commit B1 &&\n> + test_tick &&\n> + git merge -m \"Merge branch A\" A2 &&\n> +\n> + git checkout -b upstream-with-a2 $base &&\n> + test_tick &&\n> + git cherry-pick A2 &&\n> +\n> + git checkout already-upstream &&\n> + test_tick &&\n> + git rebase -i -r upstream-with-a2 &&\n> + test_cmp_graph upstream-with-a2.. <<-\\EOF\n> + *   Merge branch A\n> + |\\\n> + | * A1\n> + * | B1\n> + |/\n> + o A2\n> + EOF\n> +'\n> +\n> +test_done\n> -- \n> 2.17.0.windows.1.15.gaa56ade3205\n>\n>\n>\n\n"},{"id":"345398","messageId":"57A18662264741D1A6CD3A10C8749175@PhilipOakley","threadId":"47635","inReplyTo":"7360a072f6fdd276d30839613434329b645e2cce.1524306546.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-22T13:55:54Z","receivedAt":"2018-04-22T16:11:21Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> This patch is part of the effort to reimplement `--preserve-merges` with\n> a substantially improved design, a design that has been developed in the\n> Git for Windows project to maintain the dozens of Windows-specific patch\n> series on top of upstream Git.\n>\n> The previous patch implemented the `label` and `reset` commands to label\n\nThe previous patch was [Patch 05/16] git-rebase--interactive: clarify \narguments, so this statement doesn't appear to be true. Has a patch been \nmissed or re-ordered? Or should it be simply \"This patch implements\" ? \nLikewise the patch subject would be updated.\n\n> commits and to reset to labeled commits. This patch adds the `merge`\n\ns/adds/also adds/ ?\n\n> command, with the following syntax:\n>\n> merge [-C <commit>] <rev> # <oneline>\n>\n> The <commit> parameter in this instance is the *original* merge commit,\n> whose author and message will be used for the merge commit that is about\n> to be created.\n>\n> The <rev> parameter refers to the (possibly rewritten) revision to\n> merge. Let's see an example of a todo list:\n>\nThe example ought to also note that `label onto` is to\n`# label current HEAD with a name`, seeing as this is the first occurance.\nIt may be obvious in retrospect, but not at first reading.\n\n> label onto\n>\n> # Branch abc\n> reset onto\n\nIs this reset strictly necessary. We are already there @head.\n\n> pick deadbeef Hello, world!\n> label abc\n>\n> reset onto\n> pick cafecafe And now for something completely different\n> merge -C baaabaaa abc # Merge the branch 'abc' into master\n>\n> To edit the merge commit's message (a \"reword\" for merges, if you will),\n> use `-c` (lower-case) instead of `-C`; this convention was borrowed from\n> `git commit` that also supports `-c` and `-C` with similar meanings.\n>\n> To create *new* merges, i.e. without copying the commit message from an\n> existing commit, simply omit the `-C <commit>` parameter (which will\n> open an editor for the merge message):\n>\n> merge abc\n>\n> This comes in handy when splitting a branch into two or more branches.\n>\n> Note: this patch only adds support for recursive merges, to keep things\n> simple. Support for octopus merges will be added later in a separate\n> patch series, support for merges using strategies other than the\n> recursive merge is left for the future.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> git-rebase--interactive.sh |   6 +\n> sequencer.c                | 407 ++++++++++++++++++++++++++++++++++++-\n> 2 files changed, 406 insertions(+), 7 deletions(-)\n>\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index e1b865f43f2..ccd5254d1c9 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -162,6 +162,12 @@ s, squash <commit> = use commit, but meld into \n> previous commit\n> f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n> x, exec <commit> = run command (the rest of the line) using shell\n> d, drop <commit> = remove commit\n> +l, label <label> = label current HEAD with a name\n> +t, reset <label> = reset HEAD to a label\n> +m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n> +.       create a merge commit using the original merge commit's\n> +.       message (or the oneline, if no original merge commit was\n> +.       specified). Use -c <commit> to reword the commit message.\n>\n> These lines can be re-ordered; they are executed from top to bottom.\n> \" | git stripspace --comment-lines >>\"$todo\"\n> diff --git a/sequencer.c b/sequencer.c\n> index 01443e0f245..35fcacbdf0f 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -23,6 +23,8 @@\n> #include \"hashmap.h\"\n> #include \"notes-utils.h\"\n> #include \"sigchain.h\"\n> +#include \"unpack-trees.h\"\n> +#include \"worktree.h\"\n>\n> #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>\n> @@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \n> \"rebase-merge/stopped-sha\")\n> static GIT_PATH_FUNC(rebase_path_rewritten_list, \n> \"rebase-merge/rewritten-list\")\n> static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n>  \"rebase-merge/rewritten-pending\")\n> +\n> +/*\n> + * The path of the file listing refs that need to be deleted after the \n> rebase\n> + * finishes. This is used by the `label` command to record the need for \n> cleanup.\n> + */\n> +static GIT_PATH_FUNC(rebase_path_refs_to_delete, \n> \"rebase-merge/refs-to-delete\")\n> +\n> /*\n>  * The following files are written by git-rebase just after parsing the\n>  * command-line (and are only consumed, not modified, by the sequencer).\n> @@ -244,18 +253,34 @@ static const char *gpg_sign_opt_quoted(struct \n> replay_opts *opts)\n>\n> int sequencer_remove_state(struct replay_opts *opts)\n> {\n> - struct strbuf dir = STRBUF_INIT;\n> + struct strbuf buf = STRBUF_INIT;\n>  int i;\n>\n> + if (is_rebase_i(opts) &&\n> +     strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n> + char *p = buf.buf;\n> + while (*p) {\n> + char *eol = strchr(p, '\\n');\n> + if (eol)\n> + *eol = '\\0';\n> + if (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n> + warning(_(\"could not delete '%s'\"), p);\n> + if (!eol)\n> + break;\n> + p = eol + 1;\n> + }\n> + }\n> +\n>  free(opts->gpg_sign);\n>  free(opts->strategy);\n>  for (i = 0; i < opts->xopts_nr; i++)\n>  free(opts->xopts[i]);\n>  free(opts->xopts);\n>\n> - strbuf_addstr(&dir, get_dir(opts));\n> - remove_dir_recursively(&dir, 0);\n> - strbuf_release(&dir);\n> + strbuf_reset(&buf);\n> + strbuf_addstr(&buf, get_dir(opts));\n> + remove_dir_recursively(&buf, 0);\n> + strbuf_release(&buf);\n>\n>  return 0;\n> }\n> @@ -1279,6 +1304,9 @@ enum todo_command {\n>  TODO_SQUASH,\n>  /* commands that do something else than handling a single commit */\n>  TODO_EXEC,\n> + TODO_LABEL,\n> + TODO_RESET,\n> + TODO_MERGE,\n>  /* commands that do nothing but are counted for reporting progress */\n>  TODO_NOOP,\n>  TODO_DROP,\n> @@ -1297,6 +1325,9 @@ static struct {\n>  { 'f', \"fixup\" },\n>  { 's', \"squash\" },\n>  { 'x', \"exec\" },\n> + { 'l', \"label\" },\n> + { 't', \"reset\" },\n> + { 'm', \"merge\" },\n>  { 0,   \"noop\" },\n>  { 'd', \"drop\" },\n>  { 0,   NULL }\n> @@ -1724,9 +1755,14 @@ static int read_and_refresh_cache(struct \n> replay_opts *opts)\n>  return 0;\n> }\n>\n> +enum todo_item_flags {\n> + TODO_EDIT_MERGE_MSG = 1\n> +};\n> +\n> struct todo_item {\n>  enum todo_command command;\n>  struct commit *commit;\n> + unsigned int flags;\n>  const char *arg;\n>  int arg_len;\n>  size_t offset_in_buf;\n> @@ -1761,6 +1797,8 @@ static int parse_insn_line(struct todo_item *item, \n> const char *bol, char *eol)\n>  char *end_of_object_name;\n>  int i, saved, status, padding;\n>\n> + item->flags = 0;\n> +\n>  /* left-trim */\n>  bol += strspn(bol, \" \\t\");\n>\n> @@ -1802,13 +1840,29 @@ static int parse_insn_line(struct todo_item *item, \n> const char *bol, char *eol)\n>  return error(_(\"missing arguments for %s\"),\n>       command_to_string(item->command));\n>\n> - if (item->command == TODO_EXEC) {\n> + if (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n> +     item->command == TODO_RESET) {\n>  item->commit = NULL;\n>  item->arg = bol;\n>  item->arg_len = (int)(eol - bol);\n>  return 0;\n>  }\n>\n> + if (item->command == TODO_MERGE) {\n> + if (skip_prefix(bol, \"-C\", &bol))\n> + bol += strspn(bol, \" \\t\");\n> + else if (skip_prefix(bol, \"-c\", &bol)) {\n> + bol += strspn(bol, \" \\t\");\n> + item->flags |= TODO_EDIT_MERGE_MSG;\n> + } else {\n> + item->flags |= TODO_EDIT_MERGE_MSG;\n> + item->commit = NULL;\n> + item->arg = bol;\n> + item->arg_len = (int)(eol - bol);\n> + return 0;\n> + }\n> + }\n> +\n>  end_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n>  saved = *end_of_object_name;\n>  *end_of_object_name = '\\0';\n> @@ -2465,6 +2519,305 @@ static int do_exec(const char *command_line)\n>  return status;\n> }\n>\n> +static int safe_append(const char *filename, const char *fmt, ...)\n> +{\n> + va_list ap;\n> + struct lock_file lock = LOCK_INIT;\n> + int fd = hold_lock_file_for_update(&lock, filename,\n> +    LOCK_REPORT_ON_ERROR);\n> + struct strbuf buf = STRBUF_INIT;\n> +\n> + if (fd < 0)\n> + return -1;\n> +\n> + if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n> + error_errno(_(\"could not read '%s'\"), filename);\n> + rollback_lock_file(&lock);\n> + return -1;\n> + }\n> + strbuf_complete(&buf, '\\n');\n> + va_start(ap, fmt);\n> + strbuf_vaddf(&buf, fmt, ap);\n> + va_end(ap);\n> +\n> + if (write_in_full(fd, buf.buf, buf.len) < 0) {\n> + error_errno(_(\"could not write to '%s'\"), filename);\n> + strbuf_release(&buf);\n> + rollback_lock_file(&lock);\n> + return -1;\n> + }\n> + if (commit_lock_file(&lock) < 0) {\n> + strbuf_release(&buf);\n> + rollback_lock_file(&lock);\n> + return error(_(\"failed to finalize '%s'\"), filename);\n> + }\n> +\n> + strbuf_release(&buf);\n> + return 0;\n> +}\n> +\n> +static int do_label(const char *name, int len)\n> +{\n> + struct ref_store *refs = get_main_ref_store();\n> + struct ref_transaction *transaction;\n> + struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n> + struct strbuf msg = STRBUF_INIT;\n> + int ret = 0;\n> + struct object_id head_oid;\n> +\n> + if (len == 1 && *name == '#')\n> + return error(\"Illegal label name: '%.*s'\", len, name);\n> +\n> + strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> + strbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n> +\n> + transaction = ref_store_transaction_begin(refs, &err);\n> + if (!transaction) {\n> + error(\"%s\", err.buf);\n> + ret = -1;\n> + } else if (get_oid(\"HEAD\", &head_oid)) {\n> + error(_(\"could not read HEAD\"));\n> + ret = -1;\n> + } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n> +   NULL, 0, msg.buf, &err) < 0 ||\n> +    ref_transaction_commit(transaction, &err)) {\n> + error(\"%s\", err.buf);\n> + ret = -1;\n> + }\n> + ref_transaction_free(transaction);\n> + strbuf_release(&err);\n> + strbuf_release(&msg);\n> +\n> + if (!ret)\n> + ret = safe_append(rebase_path_refs_to_delete(),\n> +   \"%s\\n\", ref_name.buf);\n> + strbuf_release(&ref_name);\n> +\n> + return ret;\n> +}\n> +\n> +static const char *reflog_message(struct replay_opts *opts,\n> + const char *sub_action, const char *fmt, ...);\n> +\n> +static int do_reset(const char *name, int len, struct replay_opts *opts)\n> +{\n> + struct strbuf ref_name = STRBUF_INIT;\n> + struct object_id oid;\n> + struct lock_file lock = LOCK_INIT;\n> + struct tree_desc desc;\n> + struct tree *tree;\n> + struct unpack_trees_options unpack_tree_opts;\n> + int ret = 0, i;\n> +\n> + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n> + return -1;\n> +\n> + /* Determine the length of the label */\n> + for (i = 0; i < len; i++)\n> + if (isspace(name[i]))\n> + len = i;\n> +\n> + strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n> + if (get_oid(ref_name.buf, &oid) &&\n> +     get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n> + error(_(\"could not read '%s'\"), ref_name.buf);\n> + rollback_lock_file(&lock);\n> + strbuf_release(&ref_name);\n> + return -1;\n> + }\n> +\n> + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n> + unpack_tree_opts.head_idx = 1;\n> + unpack_tree_opts.src_index = &the_index;\n> + unpack_tree_opts.dst_index = &the_index;\n> + unpack_tree_opts.fn = oneway_merge;\n> + unpack_tree_opts.merge = 1;\n> + unpack_tree_opts.update = 1;\n> +\n> + if (read_cache_unmerged()) {\n> + rollback_lock_file(&lock);\n> + strbuf_release(&ref_name);\n> + return error_resolve_conflict(_(action_name(opts)));\n> + }\n> +\n> + if (!fill_tree_descriptor(&desc, &oid)) {\n> + error(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n> + rollback_lock_file(&lock);\n> + free((void *)desc.buffer);\n> + strbuf_release(&ref_name);\n> + return -1;\n> + }\n> +\n> + if (unpack_trees(1, &desc, &unpack_tree_opts)) {\n> + rollback_lock_file(&lock);\n> + free((void *)desc.buffer);\n> + strbuf_release(&ref_name);\n> + return -1;\n> + }\n> +\n> + tree = parse_tree_indirect(&oid);\n> + prime_cache_tree(&the_index, tree);\n> +\n> + if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n> + ret = error(_(\"could not write index\"));\n> + free((void *)desc.buffer);\n> +\n> + if (!ret)\n> + ret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n> + len, name), \"HEAD\", &oid,\n> + NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n> +\n> + strbuf_release(&ref_name);\n> + return ret;\n> +}\n> +\n> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n> +     int flags, struct replay_opts *opts)\n> +{\n> + int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n> + EDIT_MSG | VERIFY_MSG : 0;\n> + struct strbuf ref_name = STRBUF_INIT;\n> + struct commit *head_commit, *merge_commit, *i;\n> + struct commit_list *bases, *j, *reversed = NULL;\n> + struct merge_options o;\n> + int merge_arg_len, oneline_offset, ret;\n> + static struct lock_file lock;\n> + const char *p;\n> +\n> + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {\n> + ret = -1;\n> + goto leave_merge;\n> + }\n> +\n> + head_commit = lookup_commit_reference_by_name(\"HEAD\");\n> + if (!head_commit) {\n> + ret = error(_(\"cannot merge without a current revision\"));\n> + goto leave_merge;\n> + }\n> +\n> + oneline_offset = arg_len;\n> + merge_arg_len = strcspn(arg, \" \\t\\n\");\n> + p = arg + merge_arg_len;\n> + p += strspn(p, \" \\t\\n\");\n> + if (*p == '#' && (!p[1] || isspace(p[1]))) {\n> + p += 1 + strspn(p + 1, \" \\t\\n\");\n> + oneline_offset = p - arg;\n> + } else if (p - arg < arg_len)\n> + BUG(\"octopus merges are not supported yet: '%s'\", p);\n> +\n> + strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n> + merge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> + if (!merge_commit) {\n> + /* fall back to non-rewritten ref or commit */\n> + strbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n> + merge_commit = lookup_commit_reference_by_name(ref_name.buf);\n> + }\n> +\n> + if (!merge_commit) {\n> + ret = error(_(\"could not resolve '%s'\"), ref_name.buf);\n> + goto leave_merge;\n> + }\n> +\n> + if (commit) {\n> + const char *message = get_commit_buffer(commit, NULL);\n> + const char *body;\n> + int len;\n> +\n> + if (!message) {\n> + ret = error(_(\"could not get commit message of '%s'\"),\n> +     oid_to_hex(&commit->object.oid));\n> + goto leave_merge;\n> + }\n> + write_author_script(message);\n> + find_commit_subject(message, &body);\n> + len = strlen(body);\n> + ret = write_message(body, len, git_path_merge_msg(), 0);\n> + unuse_commit_buffer(commit, message);\n> + if (ret) {\n> + error_errno(_(\"could not write '%s'\"),\n> +     git_path_merge_msg());\n> + goto leave_merge;\n> + }\n> + } else {\n> + struct strbuf buf = STRBUF_INIT;\n> + int len;\n> +\n> + strbuf_addf(&buf, \"author %s\", git_author_info(0));\n> + write_author_script(buf.buf);\n> + strbuf_reset(&buf);\n> +\n> + if (oneline_offset < arg_len) {\n> + p = arg + oneline_offset;\n> + len = arg_len - oneline_offset;\n> + } else {\n> + strbuf_addf(&buf, \"Merge branch '%.*s'\",\n> +     merge_arg_len, arg);\n> + p = buf.buf;\n> + len = buf.len;\n> + }\n> +\n> + ret = write_message(p, len, git_path_merge_msg(), 0);\n> + strbuf_release(&buf);\n> + if (ret) {\n> + error_errno(_(\"could not write '%s'\"),\n> +     git_path_merge_msg());\n> + goto leave_merge;\n> + }\n> + }\n> +\n> + write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n> +       git_path_merge_head(), 0);\n> + write_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n> +\n> + bases = get_merge_bases(head_commit, merge_commit);\n> + for (j = bases; j; j = j->next)\n> + commit_list_insert(j->item, &reversed);\n> + free_commit_list(bases);\n> +\n> + read_cache();\n> + init_merge_options(&o);\n> + o.branch1 = \"HEAD\";\n> + o.branch2 = ref_name.buf;\n> + o.buffer_output = 2;\n> +\n> + ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n> + if (ret <= 0)\n> + fputs(o.obuf.buf, stdout);\n> + strbuf_release(&o.obuf);\n> + if (ret < 0) {\n> + error(_(\"could not even attempt to merge '%.*s'\"),\n> +       merge_arg_len, arg);\n> + goto leave_merge;\n> + }\n> + /*\n> + * The return value of merge_recursive() is 1 on clean, and 0 on\n> + * unclean merge.\n> + *\n> + * Let's reverse that, so that do_merge() returns 0 upon success and\n> + * 1 upon failed merge (keeping the return value -1 for the cases where\n> + * we will want to reschedule the `merge` command).\n> + */\n> + ret = !ret;\n> +\n> + if (active_cache_changed &&\n> +     write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n> + ret = error(_(\"merge: Unable to write new index file\"));\n> + goto leave_merge;\n> + }\n> +\n> + rollback_lock_file(&lock);\n> + if (ret)\n> + rerere(opts->allow_rerere_auto);\n> + else\n> + ret = run_git_commit(git_path_merge_msg(), opts,\n> +      run_commit_flags);\n> +\n> +leave_merge:\n> + strbuf_release(&ref_name);\n> + rollback_lock_file(&lock);\n> + return ret;\n> +}\n> +\n> static int is_final_fixup(struct todo_list *todo_list)\n> {\n>  int i = todo_list->current;\n> @@ -2568,7 +2921,7 @@ N_(\"Could not execute the todo command\\n\"\n>\n> static int pick_commits(struct todo_list *todo_list, struct replay_opts \n> *opts)\n> {\n> - int res = 0;\n> + int res = 0, reschedule = 0;\n>\n>  setenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n>  if (opts->allow_ff)\n> @@ -2639,7 +2992,7 @@ static int pick_commits(struct todo_list *todo_list, \n> struct replay_opts *opts)\n>  intend_to_amend();\n>  return error_failed_squash(item->commit, opts,\n>  item->arg_len, item->arg);\n> - } else if (res && is_rebase_i(opts))\n> + } else if (res && is_rebase_i(opts) && item->commit)\n>  return res | error_with_patch(item->commit,\n>  item->arg, item->arg_len, opts, res,\n>  item->command == TODO_REWORD);\n> @@ -2665,9 +3018,41 @@ static int pick_commits(struct todo_list \n> *todo_list, struct replay_opts *opts)\n>  /* `current` will be incremented below */\n>  todo_list->current = -1;\n>  }\n> + } else if (item->command == TODO_LABEL) {\n> + if ((res = do_label(item->arg, item->arg_len)))\n> + reschedule = 1;\n> + } else if (item->command == TODO_RESET) {\n> + if ((res = do_reset(item->arg, item->arg_len, opts)))\n> + reschedule = 1;\n> + } else if (item->command == TODO_MERGE) {\n> + if ((res = do_merge(item->commit,\n> +     item->arg, item->arg_len,\n> +     item->flags, opts)) < 0)\n> + reschedule = 1;\n> + else if (res > 0)\n> + /* failed with merge conflicts */\n> + return error_with_patch(item->commit,\n> + item->arg,\n> + item->arg_len, opts,\n> + res, 0);\n>  } else if (!is_noop(item->command))\n>  return error(_(\"unknown command %d\"), item->command);\n>\n> + if (reschedule) {\n> + advise(_(rescheduled_advice),\n> +        get_item_line_length(todo_list,\n> +     todo_list->current),\n> +        get_item_line(todo_list, todo_list->current));\n> + todo_list->current--;\n> + if (save_todo(todo_list, opts))\n> + return -1;\n> + if (item->commit)\n> + return error_with_patch(item->commit,\n> + item->arg,\n> + item->arg_len, opts,\n> + res, 0);\n> + }\n> +\n>  todo_list->current++;\n>  if (res)\n>  return res;\n> @@ -3147,8 +3532,16 @@ int transform_todos(unsigned flags)\n>    short_commit_name(item->commit) :\n>    oid_to_hex(&item->commit->object.oid);\n>\n> + if (item->command == TODO_MERGE) {\n> + if (item->flags & TODO_EDIT_MERGE_MSG)\n> + strbuf_addstr(&buf, \" -c\");\n> + else\n> + strbuf_addstr(&buf, \" -C\");\n> + }\n> +\n>  strbuf_addf(&buf, \" %s\", oid);\n>  }\n> +\n>  /* add all the rest */\n>  if (!item->arg_len)\n>  strbuf_addch(&buf, '\\n');\n> -- \n> 2.17.0.windows.1.15.gaa56ade3205\n>\n>\n> \n\n"},{"id":"345401","messageId":"0b0555fb-9fec-cd9d-f2f2-cecb51412128@talktalk.net","threadId":"47635","inReplyTo":"b05915e6-2259-5dee-69d8-90a5a524662d@talktalk.net","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-22T17:17:21Z","receivedAt":"2018-04-22T17:17:29Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 21/04/18 16:56, Phillip Wood wrote:\n> On 21/04/18 11:33, Johannes Schindelin wrote:\n>> This patch is part of the effort to reimplement `--preserve-merges` with\n>> a substantially improved design, a design that has been developed in the\n>> Git for Windows project to maintain the dozens of Windows-specific patch\n>> series on top of upstream Git.\n>>\n>> The previous patch implemented the `label` and `reset` commands to label\n>> commits and to reset to labeled commits. This patch adds the `merge`\n>> command, with the following syntax:\n> \n> The two patches seem to have been fused together in this series.\n> \n> If the reset command fails because it would overwrite untracked files it\n> says\n> \n> error: Untracked working tree file 'b' would be overwritten by merge.\n> \n> Followed by the hint to edit the todo file. Saying 'merge' rather\n> 'reset' is possibly confusing to users. Perhaps it could call\n> setup_unpack_trees_porcelain(), though that would need to be extended to\n> handle 'reset'.\n\n\n> Also it currently refuses to overwrite ignored files\n> which is either annoying or safe depending on one's point of view.\n\nLooking at the existing code this is consistent with (most) of the rest\nof the sequencer. The code to fast-forward commits will overwrite\nignored files, and I think the initial checkout will as well but the\nrest (picking commits and the new merge command) will not.\n\n> Best Wishes\n> \n> Phillip\n> \n>>\n>>     merge [-C <commit>] <rev> # <oneline>\n>>\n>> The <commit> parameter in this instance is the *original* merge commit,\n>> whose author and message will be used for the merge commit that is about\n>> to be created.\n>>\n>> The <rev> parameter refers to the (possibly rewritten) revision to\n>> merge. Let's see an example of a todo list:\n>>\n>>     label onto\n>>\n>>     # Branch abc\n>>     reset onto\n>>     pick deadbeef Hello, world!\n>>     label abc\n>>\n>>     reset onto\n>>     pick cafecafe And now for something completely different\n>>     merge -C baaabaaa abc # Merge the branch 'abc' into master\n>>\n>> To edit the merge commit's message (a \"reword\" for merges, if you will),\n>> use `-c` (lower-case) instead of `-C`; this convention was borrowed from\n>> `git commit` that also supports `-c` and `-C` with similar meanings.\n>>\n>> To create *new* merges, i.e. without copying the commit message from an\n>> existing commit, simply omit the `-C <commit>` parameter (which will\n>> open an editor for the merge message):\n>>\n>>     merge abc\n>>\n>> This comes in handy when splitting a branch into two or more branches.\n>>\n>> Note: this patch only adds support for recursive merges, to keep things\n>> simple. Support for octopus merges will be added later in a separate\n>> patch series, support for merges using strategies other than the\n>> recursive merge is left for the future.\n>>\n>> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>> ---\n>>   git-rebase--interactive.sh |   6 +\n>>   sequencer.c                | 407 ++++++++++++++++++++++++++++++++++++-\n>>   2 files changed, 406 insertions(+), 7 deletions(-)\n>>\n>> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n>> index e1b865f43f2..ccd5254d1c9 100644\n>> --- a/git-rebase--interactive.sh\n>> +++ b/git-rebase--interactive.sh\n>> @@ -162,6 +162,12 @@ s, squash <commit> = use commit, but meld into\n>> previous commit\n>>   f, fixup <commit> = like \\\"squash\\\", but discard this commit's log\n>> message\n>>   x, exec <commit> = run command (the rest of the line) using shell\n>>   d, drop <commit> = remove commit\n>> +l, label <label> = label current HEAD with a name\n>> +t, reset <label> = reset HEAD to a label\n>> +m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n>> +.       create a merge commit using the original merge commit's\n>> +.       message (or the oneline, if no original merge commit was\n>> +.       specified). Use -c <commit> to reword the commit message.\n>>     These lines can be re-ordered; they are executed from top to bottom.\n>>   \" | git stripspace --comment-lines >>\"$todo\"\n>> diff --git a/sequencer.c b/sequencer.c\n>> index 01443e0f245..35fcacbdf0f 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -23,6 +23,8 @@\n>>   #include \"hashmap.h\"\n>>   #include \"notes-utils.h\"\n>>   #include \"sigchain.h\"\n>> +#include \"unpack-trees.h\"\n>> +#include \"worktree.h\"\n>>     #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>>   @@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha,\n>> \"rebase-merge/stopped-sha\")\n>>   static GIT_PATH_FUNC(rebase_path_rewritten_list,\n>> \"rebase-merge/rewritten-list\")\n>>   static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n>>       \"rebase-merge/rewritten-pending\")\n>> +\n>> +/*\n>> + * The path of the file listing refs that need to be deleted after\n>> the rebase\n>> + * finishes. This is used by the `label` command to record the need\n>> for cleanup.\n>> + */\n>> +static GIT_PATH_FUNC(rebase_path_refs_to_delete,\n>> \"rebase-merge/refs-to-delete\")\n>> +\n>>   /*\n>>    * The following files are written by git-rebase just after parsing the\n>>    * command-line (and are only consumed, not modified, by the\n>> sequencer).\n>> @@ -244,18 +253,34 @@ static const char *gpg_sign_opt_quoted(struct\n>> replay_opts *opts)\n>>     int sequencer_remove_state(struct replay_opts *opts)\n>>   {\n>> -    struct strbuf dir = STRBUF_INIT;\n>> +    struct strbuf buf = STRBUF_INIT;\n>>       int i;\n>>   +    if (is_rebase_i(opts) &&\n>> +        strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n>> +        char *p = buf.buf;\n>> +        while (*p) {\n>> +            char *eol = strchr(p, '\\n');\n>> +            if (eol)\n>> +                *eol = '\\0';\n>> +            if (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n>> +                warning(_(\"could not delete '%s'\"), p);\n>> +            if (!eol)\n>> +                break;\n>> +            p = eol + 1;\n>> +        }\n>> +    }\n>> +\n>>       free(opts->gpg_sign);\n>>       free(opts->strategy);\n>>       for (i = 0; i < opts->xopts_nr; i++)\n>>           free(opts->xopts[i]);\n>>       free(opts->xopts);\n>>   -    strbuf_addstr(&dir, get_dir(opts));\n>> -    remove_dir_recursively(&dir, 0);\n>> -    strbuf_release(&dir);\n>> +    strbuf_reset(&buf);\n>> +    strbuf_addstr(&buf, get_dir(opts));\n>> +    remove_dir_recursively(&buf, 0);\n>> +    strbuf_release(&buf);\n>>         return 0;\n>>   }\n>> @@ -1279,6 +1304,9 @@ enum todo_command {\n>>       TODO_SQUASH,\n>>       /* commands that do something else than handling a single commit */\n>>       TODO_EXEC,\n>> +    TODO_LABEL,\n>> +    TODO_RESET,\n>> +    TODO_MERGE,\n>>       /* commands that do nothing but are counted for reporting\n>> progress */\n>>       TODO_NOOP,\n>>       TODO_DROP,\n>> @@ -1297,6 +1325,9 @@ static struct {\n>>       { 'f', \"fixup\" },\n>>       { 's', \"squash\" },\n>>       { 'x', \"exec\" },\n>> +    { 'l', \"label\" },\n>> +    { 't', \"reset\" },\n>> +    { 'm', \"merge\" },\n>>       { 0,   \"noop\" },\n>>       { 'd', \"drop\" },\n>>       { 0,   NULL }\n>> @@ -1724,9 +1755,14 @@ static int read_and_refresh_cache(struct\n>> replay_opts *opts)\n>>       return 0;\n>>   }\n>>   +enum todo_item_flags {\n>> +    TODO_EDIT_MERGE_MSG = 1\n>> +};\n>> +\n>>   struct todo_item {\n>>       enum todo_command command;\n>>       struct commit *commit;\n>> +    unsigned int flags;\n>>       const char *arg;\n>>       int arg_len;\n>>       size_t offset_in_buf;\n>> @@ -1761,6 +1797,8 @@ static int parse_insn_line(struct todo_item\n>> *item, const char *bol, char *eol)\n>>       char *end_of_object_name;\n>>       int i, saved, status, padding;\n>>   +    item->flags = 0;\n>> +\n>>       /* left-trim */\n>>       bol += strspn(bol, \" \\t\");\n>>   @@ -1802,13 +1840,29 @@ static int parse_insn_line(struct todo_item\n>> *item, const char *bol, char *eol)\n>>           return error(_(\"missing arguments for %s\"),\n>>                    command_to_string(item->command));\n>>   -    if (item->command == TODO_EXEC) {\n>> +    if (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n>> +        item->command == TODO_RESET) {\n>>           item->commit = NULL;\n>>           item->arg = bol;\n>>           item->arg_len = (int)(eol - bol);\n>>           return 0;\n>>       }\n>>   +    if (item->command == TODO_MERGE) {\n>> +        if (skip_prefix(bol, \"-C\", &bol))\n>> +            bol += strspn(bol, \" \\t\");\n>> +        else if (skip_prefix(bol, \"-c\", &bol)) {\n>> +            bol += strspn(bol, \" \\t\");\n>> +            item->flags |= TODO_EDIT_MERGE_MSG;\n>> +        } else {\n>> +            item->flags |= TODO_EDIT_MERGE_MSG;\n>> +            item->commit = NULL;\n>> +            item->arg = bol;\n>> +            item->arg_len = (int)(eol - bol);\n>> +            return 0;\n>> +        }\n>> +    }\n>> +\n>>       end_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n>>       saved = *end_of_object_name;\n>>       *end_of_object_name = '\\0';\n>> @@ -2465,6 +2519,305 @@ static int do_exec(const char *command_line)\n>>       return status;\n>>   }\n>>   +static int safe_append(const char *filename, const char *fmt, ...)\n>> +{\n>> +    va_list ap;\n>> +    struct lock_file lock = LOCK_INIT;\n>> +    int fd = hold_lock_file_for_update(&lock, filename,\n>> +                       LOCK_REPORT_ON_ERROR);\n>> +    struct strbuf buf = STRBUF_INIT;\n>> +\n>> +    if (fd < 0)\n>> +        return -1;\n>> +\n>> +    if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n>> +        error_errno(_(\"could not read '%s'\"), filename);\n>> +        rollback_lock_file(&lock);\n>> +        return -1;\n>> +    }\n>> +    strbuf_complete(&buf, '\\n');\n>> +    va_start(ap, fmt);\n>> +    strbuf_vaddf(&buf, fmt, ap);\n>> +    va_end(ap);\n>> +\n>> +    if (write_in_full(fd, buf.buf, buf.len) < 0) {\n>> +        error_errno(_(\"could not write to '%s'\"), filename);\n>> +        strbuf_release(&buf);\n>> +        rollback_lock_file(&lock);\n>> +        return -1;\n>> +    }\n>> +    if (commit_lock_file(&lock) < 0) {\n>> +        strbuf_release(&buf);\n>> +        rollback_lock_file(&lock);\n>> +        return error(_(\"failed to finalize '%s'\"), filename);\n>> +    }\n>> +\n>> +    strbuf_release(&buf);\n>> +    return 0;\n>> +}\n>> +\n>> +static int do_label(const char *name, int len)\n>> +{\n>> +    struct ref_store *refs = get_main_ref_store();\n>> +    struct ref_transaction *transaction;\n>> +    struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n>> +    struct strbuf msg = STRBUF_INIT;\n>> +    int ret = 0;\n>> +    struct object_id head_oid;\n>> +\n>> +    if (len == 1 && *name == '#')\n>> +        return error(\"Illegal label name: '%.*s'\", len, name);\n>> +\n>> +    strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n>> +    strbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n>> +\n>> +    transaction = ref_store_transaction_begin(refs, &err);\n>> +    if (!transaction) {\n>> +        error(\"%s\", err.buf);\n>> +        ret = -1;\n>> +    } else if (get_oid(\"HEAD\", &head_oid)) {\n>> +        error(_(\"could not read HEAD\"));\n>> +        ret = -1;\n>> +    } else if (ref_transaction_update(transaction, ref_name.buf,\n>> &head_oid,\n>> +                      NULL, 0, msg.buf, &err) < 0 ||\n>> +           ref_transaction_commit(transaction, &err)) {\n>> +        error(\"%s\", err.buf);\n>> +        ret = -1;\n>> +    }\n>> +    ref_transaction_free(transaction);\n>> +    strbuf_release(&err);\n>> +    strbuf_release(&msg);\n>> +\n>> +    if (!ret)\n>> +        ret = safe_append(rebase_path_refs_to_delete(),\n>> +                  \"%s\\n\", ref_name.buf);\n>> +    strbuf_release(&ref_name);\n>> +\n>> +    return ret;\n>> +}\n>> +\n>> +static const char *reflog_message(struct replay_opts *opts,\n>> +    const char *sub_action, const char *fmt, ...);\n>> +\n>> +static int do_reset(const char *name, int len, struct replay_opts *opts)\n>> +{\n>> +    struct strbuf ref_name = STRBUF_INIT;\n>> +    struct object_id oid;\n>> +    struct lock_file lock = LOCK_INIT;\n>> +    struct tree_desc desc;\n>> +    struct tree *tree;\n>> +    struct unpack_trees_options unpack_tree_opts;\n>> +    int ret = 0, i;\n>> +\n>> +    if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n>> +        return -1;\n>> +\n>> +    /* Determine the length of the label */\n>> +    for (i = 0; i < len; i++)\n>> +        if (isspace(name[i]))\n>> +            len = i;\n>> +\n>> +    strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n>> +    if (get_oid(ref_name.buf, &oid) &&\n>> +        get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n>> +        error(_(\"could not read '%s'\"), ref_name.buf);\n>> +        rollback_lock_file(&lock);\n>> +        strbuf_release(&ref_name);\n>> +        return -1;\n>> +    }\n>> +\n>> +    memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n>> +    unpack_tree_opts.head_idx = 1;\n>> +    unpack_tree_opts.src_index = &the_index;\n>> +    unpack_tree_opts.dst_index = &the_index;\n>> +    unpack_tree_opts.fn = oneway_merge;\n>> +    unpack_tree_opts.merge = 1;\n>> +    unpack_tree_opts.update = 1;\n>> +\n>> +    if (read_cache_unmerged()) {\n>> +        rollback_lock_file(&lock);\n>> +        strbuf_release(&ref_name);\n>> +        return error_resolve_conflict(_(action_name(opts)));\n>> +    }\n>> +\n>> +    if (!fill_tree_descriptor(&desc, &oid)) {\n>> +        error(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n>> +        rollback_lock_file(&lock);\n>> +        free((void *)desc.buffer);\n>> +        strbuf_release(&ref_name);\n>> +        return -1;\n>> +    }\n>> +\n>> +    if (unpack_trees(1, &desc, &unpack_tree_opts)) {\n>> +        rollback_lock_file(&lock);\n>> +        free((void *)desc.buffer);\n>> +        strbuf_release(&ref_name);\n>> +        return -1;\n>> +    }\n>> +\n>> +    tree = parse_tree_indirect(&oid);\n>> +    prime_cache_tree(&the_index, tree);\n>> +\n>> +    if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n>> +        ret = error(_(\"could not write index\"));\n>> +    free((void *)desc.buffer);\n>> +\n>> +    if (!ret)\n>> +        ret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n>> +                        len, name), \"HEAD\", &oid,\n>> +                 NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n>> +\n>> +    strbuf_release(&ref_name);\n>> +    return ret;\n>> +}\n>> +\n>> +static int do_merge(struct commit *commit, const char *arg, int arg_len,\n>> +            int flags, struct replay_opts *opts)\n>> +{\n>> +    int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n>> +        EDIT_MSG | VERIFY_MSG : 0;\n>> +    struct strbuf ref_name = STRBUF_INIT;\n>> +    struct commit *head_commit, *merge_commit, *i;\n>> +    struct commit_list *bases, *j, *reversed = NULL;\n>> +    struct merge_options o;\n>> +    int merge_arg_len, oneline_offset, ret;\n>> +    static struct lock_file lock;\n>> +    const char *p;\n>> +\n>> +    if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {\n>> +        ret = -1;\n>> +        goto leave_merge;\n>> +    }\n>> +\n>> +    head_commit = lookup_commit_reference_by_name(\"HEAD\");\n>> +    if (!head_commit) {\n>> +        ret = error(_(\"cannot merge without a current revision\"));\n>> +        goto leave_merge;\n>> +    }\n>> +\n>> +    oneline_offset = arg_len;\n>> +    merge_arg_len = strcspn(arg, \" \\t\\n\");\n>> +    p = arg + merge_arg_len;\n>> +    p += strspn(p, \" \\t\\n\");\n>> +    if (*p == '#' && (!p[1] || isspace(p[1]))) {\n>> +        p += 1 + strspn(p + 1, \" \\t\\n\");\n>> +        oneline_offset = p - arg;\n>> +    } else if (p - arg < arg_len)\n>> +        BUG(\"octopus merges are not supported yet: '%s'\", p);\n>> +\n>> +    strbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n>> +    merge_commit = lookup_commit_reference_by_name(ref_name.buf);\n>> +    if (!merge_commit) {\n>> +        /* fall back to non-rewritten ref or commit */\n>> +        strbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n>> +        merge_commit = lookup_commit_reference_by_name(ref_name.buf);\n>> +    }\n>> +\n>> +    if (!merge_commit) {\n>> +        ret = error(_(\"could not resolve '%s'\"), ref_name.buf);\n>> +        goto leave_merge;\n>> +    }\n>> +\n>> +    if (commit) {\n>> +        const char *message = get_commit_buffer(commit, NULL);\n>> +        const char *body;\n>> +        int len;\n>> +\n>> +        if (!message) {\n>> +            ret = error(_(\"could not get commit message of '%s'\"),\n>> +                    oid_to_hex(&commit->object.oid));\n>> +            goto leave_merge;\n>> +        }\n>> +        write_author_script(message);\n>> +        find_commit_subject(message, &body);\n>> +        len = strlen(body);\n>> +        ret = write_message(body, len, git_path_merge_msg(), 0);\n>> +        unuse_commit_buffer(commit, message);\n>> +        if (ret) {\n>> +            error_errno(_(\"could not write '%s'\"),\n>> +                    git_path_merge_msg());\n>> +            goto leave_merge;\n>> +        }\n>> +    } else {\n>> +        struct strbuf buf = STRBUF_INIT;\n>> +        int len;\n>> +\n>> +        strbuf_addf(&buf, \"author %s\", git_author_info(0));\n>> +        write_author_script(buf.buf);\n>> +        strbuf_reset(&buf);\n>> +\n>> +        if (oneline_offset < arg_len) {\n>> +            p = arg + oneline_offset;\n>> +            len = arg_len - oneline_offset;\n>> +        } else {\n>> +            strbuf_addf(&buf, \"Merge branch '%.*s'\",\n>> +                    merge_arg_len, arg);\n>> +            p = buf.buf;\n>> +            len = buf.len;\n>> +        }\n>> +\n>> +        ret = write_message(p, len, git_path_merge_msg(), 0);\n>> +        strbuf_release(&buf);\n>> +        if (ret) {\n>> +            error_errno(_(\"could not write '%s'\"),\n>> +                    git_path_merge_msg());\n>> +            goto leave_merge;\n>> +        }\n>> +    }\n>> +\n>> +    write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n>> +              git_path_merge_head(), 0);\n>> +    write_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n>> +\n>> +    bases = get_merge_bases(head_commit, merge_commit);\n>> +    for (j = bases; j; j = j->next)\n>> +        commit_list_insert(j->item, &reversed);\n>> +    free_commit_list(bases);\n>> +\n>> +    read_cache();\n>> +    init_merge_options(&o);\n>> +    o.branch1 = \"HEAD\";\n>> +    o.branch2 = ref_name.buf;\n>> +    o.buffer_output = 2;\n>> +\n>> +    ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n>> +    if (ret <= 0)\n>> +        fputs(o.obuf.buf, stdout);\n>> +    strbuf_release(&o.obuf);\n>> +    if (ret < 0) {\n>> +        error(_(\"could not even attempt to merge '%.*s'\"),\n>> +              merge_arg_len, arg);\n>> +        goto leave_merge;\n>> +    }\n>> +    /*\n>> +     * The return value of merge_recursive() is 1 on clean, and 0 on\n>> +     * unclean merge.\n>> +     *\n>> +     * Let's reverse that, so that do_merge() returns 0 upon success and\n>> +     * 1 upon failed merge (keeping the return value -1 for the cases\n>> where\n>> +     * we will want to reschedule the `merge` command).\n>> +     */\n>> +    ret = !ret;\n>> +\n>> +    if (active_cache_changed &&\n>> +        write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n>> +        ret = error(_(\"merge: Unable to write new index file\"));\n>> +        goto leave_merge;\n>> +    }\n>> +\n>> +    rollback_lock_file(&lock);\n>> +    if (ret)\n>> +        rerere(opts->allow_rerere_auto);\n>> +    else\n>> +        ret = run_git_commit(git_path_merge_msg(), opts,\n>> +                     run_commit_flags);\n>> +\n>> +leave_merge:\n>> +    strbuf_release(&ref_name);\n>> +    rollback_lock_file(&lock);\n>> +    return ret;\n>> +}\n>> +\n>>   static int is_final_fixup(struct todo_list *todo_list)\n>>   {\n>>       int i = todo_list->current;\n>> @@ -2568,7 +2921,7 @@ N_(\"Could not execute the todo command\\n\"\n>>     static int pick_commits(struct todo_list *todo_list, struct\n>> replay_opts *opts)\n>>   {\n>> -    int res = 0;\n>> +    int res = 0, reschedule = 0;\n>>         setenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n>>       if (opts->allow_ff)\n>> @@ -2639,7 +2992,7 @@ static int pick_commits(struct todo_list\n>> *todo_list, struct replay_opts *opts)\n>>                       intend_to_amend();\n>>                   return error_failed_squash(item->commit, opts,\n>>                       item->arg_len, item->arg);\n>> -            } else if (res && is_rebase_i(opts))\n>> +            } else if (res && is_rebase_i(opts) && item->commit)\n>>                   return res | error_with_patch(item->commit,\n>>                       item->arg, item->arg_len, opts, res,\n>>                       item->command == TODO_REWORD);\n>> @@ -2665,9 +3018,41 @@ static int pick_commits(struct todo_list\n>> *todo_list, struct replay_opts *opts)\n>>                   /* `current` will be incremented below */\n>>                   todo_list->current = -1;\n>>               }\n>> +        } else if (item->command == TODO_LABEL) {\n>> +            if ((res = do_label(item->arg, item->arg_len)))\n>> +                reschedule = 1;\n>> +        } else if (item->command == TODO_RESET) {\n>> +            if ((res = do_reset(item->arg, item->arg_len, opts)))\n>> +                reschedule = 1;\n>> +        } else if (item->command == TODO_MERGE) {\n>> +            if ((res = do_merge(item->commit,\n>> +                        item->arg, item->arg_len,\n>> +                        item->flags, opts)) < 0)\n>> +                reschedule = 1;\n>> +            else if (res > 0)\n>> +                /* failed with merge conflicts */\n>> +                return error_with_patch(item->commit,\n>> +                            item->arg,\n>> +                            item->arg_len, opts,\n>> +                            res, 0);\n>>           } else if (!is_noop(item->command))\n>>               return error(_(\"unknown command %d\"), item->command);\n>>   +        if (reschedule) {\n>> +            advise(_(rescheduled_advice),\n>> +                   get_item_line_length(todo_list,\n>> +                            todo_list->current),\n>> +                   get_item_line(todo_list, todo_list->current));\n>> +            todo_list->current--;\n>> +            if (save_todo(todo_list, opts))\n>> +                return -1;\n>> +            if (item->commit)\n>> +                return error_with_patch(item->commit,\n>> +                            item->arg,\n>> +                            item->arg_len, opts,\n>> +                            res, 0);\n>> +        }\n>> +\n>>           todo_list->current++;\n>>           if (res)\n>>               return res;\n>> @@ -3147,8 +3532,16 @@ int transform_todos(unsigned flags)\n>>                         short_commit_name(item->commit) :\n>>                         oid_to_hex(&item->commit->object.oid);\n>>   +            if (item->command == TODO_MERGE) {\n>> +                if (item->flags & TODO_EDIT_MERGE_MSG)\n>> +                    strbuf_addstr(&buf, \" -c\");\n>> +                else\n>> +                    strbuf_addstr(&buf, \" -C\");\n>> +            }\n>> +\n>>               strbuf_addf(&buf, \" %s\", oid);\n>>           }\n>> +\n>>           /* add all the rest */\n>>           if (!item->arg_len)\n>>               strbuf_addch(&buf, '\\n');\n>>\n> \n\n"},{"id":"345454","messageId":"nycvar.QRO.7.76.6.1804231401130.15174@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"9270DF86352B4E3493AADA159E7FFE33@PhilipOakley","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-23T12:03:27Z","receivedAt":"2018-04-23T12:04:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Sun, 22 Apr 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> > This patch is part of the effort to reimplement `--preserve-merges` with\n> > a substantially improved design, a design that has been developed in the\n> > Git for Windows project to maintain the dozens of Windows-specific patch\n> > series on top of upstream Git.\n> >\n> > The previous patch implemented the `label` and `reset` commands to label\n> \n> The previous patch was [Patch 05/16] git-rebase--interactive: clarify\n> arguments, so this statement doesn't appear to be true. Has a patch been\n> missed or re-ordered? Or should it be simply \"This patch implements\" ?\n> Likewise the patch subject would be updated.\n\nAs Phillip guessed correctly, it was a mistaken `git commit --amend`.\n\n> > commits and to reset to labeled commits. This patch adds the `merge`\n> \n> s/adds/also adds/ ?\n\nNo, as I really want to keep those two commits separate. I disentangled\nthem.\n\n> > command, with the following syntax:\n> >\n> > merge [-C <commit>] <rev> # <oneline>\n> >\n> > The <commit> parameter in this instance is the *original* merge commit,\n> > whose author and message will be used for the merge commit that is about\n> > to be created.\n> >\n> > The <rev> parameter refers to the (possibly rewritten) revision to\n> > merge. Let's see an example of a todo list:\n> >\n> The example ought to also note that `label onto` is to\n> `# label current HEAD with a name`, seeing as this is the first occurance.\n> It may be obvious in retrospect, but not at first reading.\n\nI added some sentence to describe what `label onto` does and why.\n\n> > label onto\n> >\n> > # Branch abc\n> > reset onto\n> \n> Is this reset strictly necessary. We are already there @head.\n\nNo, this is not strictly necessary, but\n\n- it makes it easier to auto-generate (otherwise you would have to keep\n  track of the \"current HEAD\" while generating that todo list, and\n\n- if I keep the `reset onto` there, then it is *a lot* easier to reorder\n  topic branches.\n\nCiao,\nDscho\n"},{"id":"345455","messageId":"nycvar.QRO.7.76.6.1804231405230.15174@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"b05915e6-2259-5dee-69d8-90a5a524662d@talktalk.net","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-23T12:20:42Z","receivedAt":"2018-04-23T12:21:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Sat, 21 Apr 2018, Phillip Wood wrote:\n\n> On 21/04/18 11:33, Johannes Schindelin wrote:\n> > This patch is part of the effort to reimplement `--preserve-merges` with\n> > a substantially improved design, a design that has been developed in the\n> > Git for Windows project to maintain the dozens of Windows-specific patch\n> > series on top of upstream Git.\n> > \n> > The previous patch implemented the `label` and `reset` commands to label\n> > commits and to reset to labeled commits. This patch adds the `merge`\n> > command, with the following syntax:\n> \n> The two patches seem to have been fused together in this series.\n\nIndeed. I have yet to investigate further how that happened, it could be a\nbug in my series after all.\n\n> If the reset command fails because it would overwrite untracked files it\n> says\n> \n> error: Untracked working tree file 'b' would be overwritten by merge.\n> \n> Followed by the hint to edit the todo file. Saying 'merge' rather 'reset' is\n> possibly confusing to users. Perhaps it could call\n> setup_unpack_trees_porcelain(), though that would need to be extended to\n> handle 'reset'.\n\nYes, and it changes global state :-(\n\nMaybe we can leave it as-is for now? After all, it should be clear to the\nuser what is happening. The most important part is the \"Untracked working\ntree file\"...\n\n> Also it currently refuses to overwrite ignored files which is either\n> annoying or safe depending on one's point of view.\n\nLet me think about that. My gut feeling says: if it is easy to do, then\nlet's nuke ignored files, but keep untracked files. But I do not think\nthat the unpack-trees machinery was taught to know about .gitignore...\n\nSeeing as `label` and `reset` really are mostly about revisions we see\nalong the lines, I think that the common case will *not* overwrite any\nuntracked files, ever. You would have to use `reset` on a\nnot-previously-seen commit and/or add `exec` commands designed to\ninterfere with the `reset`.\n\nSo I tend to want to not bother with discerning between untracked and\nignored files here.\n\nCiao,\nDscho\n"},{"id":"345456","messageId":"nycvar.QRO.7.76.6.1804231421330.15174@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"0b0555fb-9fec-cd9d-f2f2-cecb51412128@talktalk.net","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-23T12:22:26Z","receivedAt":"2018-04-23T12:22:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Sun, 22 Apr 2018, Phillip Wood wrote:\n\n> On 21/04/18 16:56, Phillip Wood wrote:\n> > On 21/04/18 11:33, Johannes Schindelin wrote:\n> >> This patch is part of the effort to reimplement `--preserve-merges` with\n> >> a substantially improved design, a design that has been developed in the\n> >> Git for Windows project to maintain the dozens of Windows-specific patch\n> >> series on top of upstream Git.\n> >>\n> >> The previous patch implemented the `label` and `reset` commands to label\n> >> commits and to reset to labeled commits. This patch adds the `merge`\n> >> command, with the following syntax:\n> > \n> > The two patches seem to have been fused together in this series.\n> > \n> > If the reset command fails because it would overwrite untracked files it\n> > says\n> > \n> > error: Untracked working tree file 'b' would be overwritten by merge.\n> > \n> > Followed by the hint to edit the todo file. Saying 'merge' rather\n> > 'reset' is possibly confusing to users. Perhaps it could call\n> > setup_unpack_trees_porcelain(), though that would need to be extended to\n> > handle 'reset'.\n> \n> \n> > Also it currently refuses to overwrite ignored files\n> > which is either annoying or safe depending on one's point of view.\n> \n> Looking at the existing code this is consistent with (most) of the rest\n> of the sequencer. The code to fast-forward commits will overwrite\n> ignored files, and I think the initial checkout will as well but the\n> rest (picking commits and the new merge command) will not.\n\nI never thought about that... but then, I never came close to encountering\nsuch an issue, as I do not typically turn ignored files into tracked ones\n;-)\n\nCiao,\nDscho\n"},{"id":"345473","messageId":"ec58f482-ffde-1959-ff4a-9b128905ccb0@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804231405230.15174@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-23T15:54:00Z","receivedAt":"2018-04-23T15:54:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 23/04/18 13:20, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Sat, 21 Apr 2018, Phillip Wood wrote:\n> \n>> On 21/04/18 11:33, Johannes Schindelin wrote:\n>>> This patch is part of the effort to reimplement `--preserve-merges` with\n>>> a substantially improved design, a design that has been developed in the\n>>> Git for Windows project to maintain the dozens of Windows-specific patch\n>>> series on top of upstream Git.\n>>>\n>>> The previous patch implemented the `label` and `reset` commands to label\n>>> commits and to reset to labeled commits. This patch adds the `merge`\n>>> command, with the following syntax:\n>>\n>> The two patches seem to have been fused together in this series.\n> \n> Indeed. I have yet to investigate further how that happened, it could be a\n> bug in my series after all.\n> \n>> If the reset command fails because it would overwrite untracked files it\n>> says\n>>\n>> error: Untracked working tree file 'b' would be overwritten by merge.\n>>\n>> Followed by the hint to edit the todo file. Saying 'merge' rather 'reset' is\n>> possibly confusing to users. Perhaps it could call\n>> setup_unpack_trees_porcelain(), though that would need to be extended to\n>> handle 'reset'.\n> \n> Yes, and it changes global state :-(\n> \n> Maybe we can leave it as-is for now? After all, it should be clear to the\n> user what is happening. The most important part is the \"Untracked working\n> tree file\"...\n\nI'm fine with leaving it, I've might get round to doing a small series \nto clean things up slightly in a few weeks. At the moment \nsetup_unpack_trees_porcelain() leaks memory as it is called for each \nmerge and allocates new strings each time. It would also be nice if the \nerror messages reflected the command, so it said 'cherry-pick', 'revert' \nor 'reset' rather than 'merge'\n\n>> Also it currently refuses to overwrite ignored files which is either\n>> annoying or safe depending on one's point of view.\n> \n> Let me think about that. My gut feeling says: if it is easy to do, then\n> let's nuke ignored files, but keep untracked files. But I do not think\n> that the unpack-trees machinery was taught to know about .gitignore...\n> \n> Seeing as `label` and `reset` really are mostly about revisions we see\n> along the lines, I think that the common case will *not* overwrite any\n> untracked files, ever. You would have to use `reset` on a\n> not-previously-seen commit and/or add `exec` commands designed to\n> interfere with the `reset`.\n> \n> So I tend to want to not bother with discerning between untracked and\n> ignored files here.\n\nI don't think it's a pressing concern. In the past I once had a patch \nseries that removed some tracked files in favor of having the build \nsystem generate them and added them to .gitignore. Each time I rebased I \nhad to manually remove them at some stage which was annoying but that is \nquite a rare occurrence.\n\nBest Wishes\n\nPhillip\n\n\n> Ciao,\n> Dscho\n> \n\n"},{"id":"345493","messageId":"CAGZ79kbK7j0njrZ8-d_Da6+XyH_RVRhBu=hcpzr7EFsOCSFJVQ@mail.gmail.com","threadId":"47635","inReplyTo":"cover.1524296064.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 0/4] rebase -i: avoid stale \"# This is a combination of\" in commit messages","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-23T18:11:05Z","receivedAt":"2018-04-23T18:11:11Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Sat, Apr 21, 2018 at 12:34 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> Eric Sunshine pointed out that I had such a commit message in\n> https://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\n> and I went on a hunt to figure out how the heck this happened.\n>\n> Turns out that if there is a fixup/squash chain where the *last* command\n> fails with merge conflicts, and we either --skip ahead or resolve the\n> conflict to a clean tree and then --continue, our code does not do a\n> final cleanup.\n>\n> Contrary to my initial gut feeling, this bug was not introduced by my\n> rewrite in C of the core parts of rebase -i, but it looks to me as if\n> that bug was with us for a very long time (at least the --skip part).\n>\n> The developer (read: user of rebase -i) in me says that we would want to\n> fast-track this, but the author of rebase -i in me says that we should\n> be cautious and cook this in `next` for a while.\n\nI looked through the patches again and think this series is good to go.\n\nThanks,\nStefan\n"},{"id":"345505","messageId":"12fa9a1f-9dc2-9108-132f-48b75ae5a8d3@talktalk.net","threadId":"47635","inReplyTo":"CAGZ79kbK7j0njrZ8-d_Da6+XyH_RVRhBu=hcpzr7EFsOCSFJVQ@mail.gmail.com","subject":"Re: [PATCH v3 0/4] rebase -i: avoid stale \"# This is a combinationof\" in commit messages","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-23T19:50:43Z","receivedAt":"2018-04-23T19:50:50Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 23/04/18 19:11, Stefan Beller wrote:\n> \n> On Sat, Apr 21, 2018 at 12:34 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n>> Eric Sunshine pointed out that I had such a commit message in\n>> https://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\n>> and I went on a hunt to figure out how the heck this happened.\n>>\n>> Turns out that if there is a fixup/squash chain where the *last* command\n>> fails with merge conflicts, and we either --skip ahead or resolve the\n>> conflict to a clean tree and then --continue, our code does not do a\n>> final cleanup.\n>>\n>> Contrary to my initial gut feeling, this bug was not introduced by my\n>> rewrite in C of the core parts of rebase -i, but it looks to me as if\n>> that bug was with us for a very long time (at least the --skip part).\n>>\n>> The developer (read: user of rebase -i) in me says that we would want to\n>> fast-track this, but the author of rebase -i in me says that we should\n>> be cautious and cook this in `next` for a while.\n> \n> I looked through the patches again and think this series is good to go.\n\nI've just realized I commented on an outdated version as the new version \nwas posted there rather than as a reply to v1. I've just looked through \nit and I'm not sure it addresses the unnecessary editing of the commit \nmessage of the previous commit if a single squash command is skipped as \noutlined in \nhttps://public-inbox.org/git/b6512eae-e214-9699-4d69-77117a0daec3@talktalk.net/\n\nBest Wishes\n\nPhillip\n\n> Thanks,\n> Stefan\n> \n"},{"id":"345509","messageId":"0E6803122A5241F98148087A045993BC@PhilipOakley","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804231401130.15174@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-23T20:34:35Z","receivedAt":"2018-04-23T20:34:39Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <Johannes.Schindelin@gmx.de> : Monday, April 23, \n2018 1:03 PM\nSubject: Re: [PATCH v8 06/16] sequencer: introduce the `merge` command\n\n\n> Hi Philip,\n>\n[...]\n>\n>> > label onto\n>> >\n>> > # Branch abc\n>> > reset onto\n>>\n>> Is this reset strictly necessary. We are already there @head.\n>\n> No, this is not strictly necessary, but\n\nI've realised my misunderstanding. I was thinking this (and others) was \nequivalent to\n\n$  git reset <thatHead'onto'> # maybe even --hard,\n\ni.e. affecting the worktree\n\nrather that just being a movement of the Head rev (though I may be having \nbrain fade here regarding untracked files etc..)\n\n>\n> - it makes it easier to auto-generate (otherwise you would have to keep\n>  track of the \"current HEAD\" while generating that todo list, and\n>\n> - if I keep the `reset onto` there, then it is *a lot* easier to reorder\n>  topic branches.\n>\n> Ciao,\n> Dscho\n>\nThanks\n\nPhilip \n\n"},{"id":"345592","messageId":"xmqq4lk18ka5.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"cover.1524296064.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 0/4] rebase -i: avoid stale \"# This is a combination of\" in commit messages","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-24T01:28:50Z","receivedAt":"2018-04-24T01:28:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Eric Sunshine pointed out that I had such a commit message in\n> https://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\n> and I went on a hunt to figure out how the heck this happened.\n>\n> Turns out that if there is a fixup/squash chain where the *last* command\n> fails with merge conflicts, and we either --skip ahead or resolve the\n> conflict to a clean tree and then --continue, our code does not do a\n> final cleanup.\n>\n> Contrary to my initial gut feeling, this bug was not introduced by my\n> rewrite in C of the core parts of rebase -i, but it looks to me as if\n> that bug was with us for a very long time (at least the --skip part).\n>\n> The developer (read: user of rebase -i) in me says that we would want to\n> fast-track this, but the author of rebase -i in me says that we should\n> be cautious and cook this in `next` for a while.\n>\n> Fixes since v2 (thanks, Stefan!):\n>\n> - Fixed commit message of 2/4: \"Thisis\" -> \"This is\".\n>\n> - Reinstated the order where the `message-squash` file is renamed to\n>   `message` first, and only if that succeeded, we delete the\n>   `message-fixup` file.\n>\n> base-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\n\nThis round looks reasonable (the last one was already so, though\n;-).  As this is not a recent regression, however, I think we would\nwant to have it regardless of recent updates to rebase-i that is\nhappening on the 'next down to master' front.\n\nI've queued this round using base-commit of d32eb83c (\"Git 2.16.3\",\n2018-03-22), the same base as the previous round, for that reason.\n\nMerging it to the tip of 'master' and applying these patches\ndirectly on top of 'master' result in identical trees, of course\n(otherwise we wouldn't be able to maintain the stable releases and\nmake forward progress on the 'master' front at the same time ;-).\n\n"},{"id":"345608","messageId":"xmqq1sf56vvb.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"E8BADD438B3F4D20B33BCC2CD92BC004@PhilipOakley","subject":"Re: [PATCH v8 09/16] rebase: introduce the --rebase-merges option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-24T05:01:28Z","receivedAt":"2018-04-24T05:01:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Philip Oakley\" <philipoakley@iee.org> writes:\n\n>> +-r::\n>> +--rebase-merges::\n>> + By default, a rebase will simply drop merge commits and only rebase\n>> + the non-merge commits. With this option, it will try to preserve\n>> + the branching structure within the commits that are to be rebased,\n>> + by recreating the merge commits. If a merge commit resolved any merge\n>> + or contained manual amendments, then they will have to be re-applied\n>> + manually.\n>> ++\n>> +This mode is similar in spirit to `--preserve-merges`, but in contrast to\n>> +that option works well in interactive rebases: commits can be reordered,\n>> +inserted and dropped at will.\n>> ++\n>> +It is currently only possible to recreate the merge commits using the\n>> +`recursive` merge strategy; Different merge strategies can be used only\n>> via\n>> +explicit `exec git merge -s <strategy> [...]` commands.\n>> +\n>> -p::\n>> --preserve-merges::\n>>  Recreate merge commits instead of flattening the history by replaying\n>\n> Flatten is here in the context lines but its just a blunt statement that 'it\n> is what it is'...\n\nThe first paragraph that explains --rebase-merges talks about what\nhappens when the option is not given, and says \"drop merge commits\nand only rebase the non-merge commits\", which is not incorrect\nper-se but does not make it explicit how the resulting topology\nlooks like.  I think it is easier to understand if it mentioned\n\"flattening\" as well.  If flatten is not the word you want, perhaps\n\"make it linear\" or something like that?\n"},{"id":"345617","messageId":"cover.1524545557.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"ec58f482-ffde-1959-ff4a-9b128905ccb0@talktalk.net","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-04-24T05:13:37Z","receivedAt":"2018-04-24T05:14:02Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"Hi Phillip,\n\nOn 23 April 2018 at 17:54, Phillip Wood <phillip.wood@talktalk.net> wrote:\n> I'm fine with leaving it, I've might get round to doing a small series to\n> clean things up slightly in a few weeks. At the moment\n> setup_unpack_trees_porcelain() leaks memory as it is called for each merge\n> and allocates new strings each time. It would also be nice if the error\n> messages reflected the command, so it said 'cherry-pick', 'revert' or\n> 'reset' rather than 'merge'\n\nThis is a small patch series to introduce and use\n`clear_unpack_trees_porcelain()`. Since Elijah is doing substantial\nrewrites to one of the users of `setup_unpack_trees_porcelain()` [1], I\nthink we should hold off on these for now to avoid a quite evil merge.\n(I haven't studied the details enough to be confident, but I think the\ncalls to `setup_...()` and `clear_...()` might need to be moved up the\ncall-chain.)\n\nI'm posting this so we don't repeat each other's work. If you feel like\npicking these up and running with them, go ahead. Otherwise, I will try\nto get them in as soon as Elijah's series lands. I'll keep you posted.\n\n[1] https://public-inbox.org/git/CAN0heSquJboMMgay+5XomqXCGoHtXxf1mJBmY_L7y+AA4eG0KA@mail.gmail.com/#t\n\nMartin\n\nMartin Ågren (2):\n  merge: setup `opts` later in `checkout_fast_forward()`\n  unpack_trees_options: free messages when done\n\n unpack-trees.h     |  5 +++++\n builtin/checkout.c |  1 +\n merge-recursive.c  |  1 +\n merge.c            | 35 ++++++++++++++++++++---------------\n unpack-trees.c     | 11 +++++++++++\n 5 files changed, 38 insertions(+), 15 deletions(-)\n\n-- \n2.17.0\n\n"},{"id":"345618","messageId":"f140d2795b9dce8b805cd2ebaf076742978ab8ae.1524545557.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1524545557.git.martin.agren@gmail.com","subject":"[PATCH 1/2] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-04-24T05:13:38Z","receivedAt":"2018-04-24T05:14:10Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"After we initialize the various fields in `opts` but before we actually\nuse them, we might return early. Move the initialization further down,\nto immediately before we use `opts`.\n\nThis limits the scope of `opts` and will help a subsequent commit fix a\nmemory leak without having to worry about those early returns.\n\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n merge.c | 32 +++++++++++++++++---------------\n 1 file changed, 17 insertions(+), 15 deletions(-)\n\ndiff --git a/merge.c b/merge.c\nindex f06a4773d4..f123658e58 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -94,8 +94,24 @@ int checkout_fast_forward(const struct object_id *head,\n \t\treturn -1;\n \n \tmemset(&trees, 0, sizeof(trees));\n-\tmemset(&opts, 0, sizeof(opts));\n \tmemset(&t, 0, sizeof(t));\n+\n+\ttrees[nr_trees] = parse_tree_indirect(head);\n+\tif (!trees[nr_trees++]) {\n+\t\trollback_lock_file(&lock_file);\n+\t\treturn -1;\n+\t}\n+\ttrees[nr_trees] = parse_tree_indirect(remote);\n+\tif (!trees[nr_trees++]) {\n+\t\trollback_lock_file(&lock_file);\n+\t\treturn -1;\n+\t}\n+\tfor (i = 0; i < nr_trees; i++) {\n+\t\tparse_tree(trees[i]);\n+\t\tinit_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n+\t}\n+\n+\tmemset(&opts, 0, sizeof(opts));\n \tif (overwrite_ignore) {\n \t\tmemset(&dir, 0, sizeof(dir));\n \t\tdir.flags |= DIR_SHOW_IGNORED;\n@@ -112,20 +128,6 @@ int checkout_fast_forward(const struct object_id *head,\n \topts.fn = twoway_merge;\n \tsetup_unpack_trees_porcelain(&opts, \"merge\");\n \n-\ttrees[nr_trees] = parse_tree_indirect(head);\n-\tif (!trees[nr_trees++]) {\n-\t\trollback_lock_file(&lock_file);\n-\t\treturn -1;\n-\t}\n-\ttrees[nr_trees] = parse_tree_indirect(remote);\n-\tif (!trees[nr_trees++]) {\n-\t\trollback_lock_file(&lock_file);\n-\t\treturn -1;\n-\t}\n-\tfor (i = 0; i < nr_trees; i++) {\n-\t\tparse_tree(trees[i]);\n-\t\tinit_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n-\t}\n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n \t\treturn -1;\n-- \n2.17.0\n\n"},{"id":"345619","messageId":"5cfb43fad88cfb14e77bb0febdac297b78500dc0.1524545557.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1524545557.git.martin.agren@gmail.com","subject":"[PATCH 2/2] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-04-24T05:13:39Z","receivedAt":"2018-04-24T05:14:19Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"The strings allocated in `setup_unpack_trees_porcelain()` are never\nfreed. Provide a function `clear_unpack_trees_porcelain()` to do so and\ncall it in the functions which use `setup_unpack_trees_porcelain()`.\n\nIn all current callers, the pointers are about to go out of scope, so we\ndo not need to set them to NULL. Let's do so anyway so that a future\ncaller or restructured code doesn't suddenly start accessing dangling\npointers.\n\nNote that we only take responsibility for the memory allocated in\n`setup_unpack_trees_porcelain()` and not any other members of the\n`struct unpack_trees_options`.\n\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n unpack-trees.h     |  5 +++++\n builtin/checkout.c |  1 +\n merge-recursive.c  |  1 +\n merge.c            |  3 +++\n unpack-trees.c     | 11 +++++++++++\n 5 files changed, 21 insertions(+)\n\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 6c48117b84..8c56cf0150 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -32,6 +32,11 @@ enum unpack_trees_error_types {\n void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t\t  const char *cmd);\n \n+/*\n+ * Frees resources allocated by setup_unpack_trees_porcelain().\n+ */\n+extern void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);\n+\n struct unpack_trees_options {\n \tunsigned int reset,\n \t\t     merge,\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b49b582071..5cebe170fc 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -526,6 +526,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tinit_tree_desc(&trees[1], tree->buffer, tree->size);\n \n \t\tret = unpack_trees(2, trees, &topts);\n+\t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n \t\t\t/*\n \t\t\t * Unpack couldn't do a trivial merge; either\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0c0d48624d..8229b91e2f 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -301,6 +301,7 @@ static int git_merge_trees(int index_only,\n \tinit_tree_desc_from_tree(t+2, merge);\n \n \trc = unpack_trees(3, t, &opts);\n+\tclear_unpack_trees_porcelain(&opts);\n \tcache_tree_free(&active_cache_tree);\n \treturn rc;\n }\ndiff --git a/merge.c b/merge.c\nindex f123658e58..b433291d0c 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -130,8 +130,11 @@ int checkout_fast_forward(const struct object_id *head,\n \n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n+\t\tclear_unpack_trees_porcelain(&opts);\n \t\treturn -1;\n \t}\n+\tclear_unpack_trees_porcelain(&opts);\n+\n \tif (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex e73745051e..4c76a29241 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -179,6 +179,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\topts->unpack_rejects[i].strdup_strings = 1;\n }\n \n+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n+{\n+\tchar **msgs = (char **)opts->msgs;\n+\n+\tfree(msgs[ERROR_WOULD_OVERWRITE]);\n+\tfree(msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED]);\n+\tfree(msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN]);\n+\n+\tmemset(opts->msgs, 0, sizeof(opts->msgs));\n+}\n+\n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n \t\t\t unsigned int set, unsigned int clear)\n {\n-- \n2.17.0\n\n"},{"id":"345623","messageId":"CA+P7+xqP8b1i4-C242de2_P9dFsZ03pcRGo45scyfK2ohd-ykw@mail.gmail.com","threadId":"47635","inReplyTo":"f140d2795b9dce8b805cd2ebaf076742978ab8ae.1524545557.git.martin.agren@gmail.com","subject":"Re: [PATCH 1/2] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-04-24T06:20:51Z","receivedAt":"2018-04-24T06:21:16Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Mon, Apr 23, 2018 at 10:13 PM, Martin Ågren <martin.agren@gmail.com> wrote:\n> After we initialize the various fields in `opts` but before we actually\n> use them, we might return early. Move the initialization further down,\n> to immediately before we use `opts`.\n>\n> This limits the scope of `opts` and will help a subsequent commit fix a\n> memory leak without having to worry about those early returns.\n>\n> Signed-off-by: Martin Ågren <martin.agren@gmail.com>\n> ---\n>  merge.c | 32 +++++++++++++++++---------------\n>  1 file changed, 17 insertions(+), 15 deletions(-)\n>\n> diff --git a/merge.c b/merge.c\n> index f06a4773d4..f123658e58 100644\n> --- a/merge.c\n> +++ b/merge.c\n> @@ -94,8 +94,24 @@ int checkout_fast_forward(const struct object_id *head,\n>                 return -1;\n>\n>         memset(&trees, 0, sizeof(trees));\n> -       memset(&opts, 0, sizeof(opts));\n>         memset(&t, 0, sizeof(t));\n> +\n> +       trees[nr_trees] = parse_tree_indirect(head);\n> +       if (!trees[nr_trees++]) {\n> +               rollback_lock_file(&lock_file);\n> +               return -1;\n> +       }\n> +       trees[nr_trees] = parse_tree_indirect(remote);\n> +       if (!trees[nr_trees++]) {\n> +               rollback_lock_file(&lock_file);\n> +               return -1;\n> +       }\n> +       for (i = 0; i < nr_trees; i++) {\n> +               parse_tree(trees[i]);\n> +               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n> +       }\n> +\n> +       memset(&opts, 0, sizeof(opts));\n>         if (overwrite_ignore) {\n>                 memset(&dir, 0, sizeof(dir));\n\nI'm guessing the diff algorithm simply found that this was a more\ncompact representation of the change? It's a bit confusing when your\ndescription indicates you \"moved\" some code down, but it looks like\nyou moved code up.\n\nThanks,\nJake\n\n>                 dir.flags |= DIR_SHOW_IGNORED;\n> @@ -112,20 +128,6 @@ int checkout_fast_forward(const struct object_id *head,\n>         opts.fn = twoway_merge;\n>         setup_unpack_trees_porcelain(&opts, \"merge\");\n>\n> -       trees[nr_trees] = parse_tree_indirect(head);\n> -       if (!trees[nr_trees++]) {\n> -               rollback_lock_file(&lock_file);\n> -               return -1;\n> -       }\n> -       trees[nr_trees] = parse_tree_indirect(remote);\n> -       if (!trees[nr_trees++]) {\n> -               rollback_lock_file(&lock_file);\n> -               return -1;\n> -       }\n> -       for (i = 0; i < nr_trees; i++) {\n> -               parse_tree(trees[i]);\n> -               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n> -       }\n>         if (unpack_trees(nr_trees, t, &opts)) {\n>                 rollback_lock_file(&lock_file);\n>                 return -1;\n> --\n> 2.17.0\n>\n"},{"id":"345626","messageId":"nycvar.QRO.7.76.6.1804241006230.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"0E6803122A5241F98148087A045993BC@PhilipOakley","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T08:11:37Z","receivedAt":"2018-04-24T08:12:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Mon, 23 Apr 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <Johannes.Schindelin@gmx.de> : Monday, April 23,\n> 2018 1:03 PM\n> Subject: Re: [PATCH v8 06/16] sequencer: introduce the `merge` command\n> \n> [...]\n> >\n> > > > label onto\n> > > >\n> > > > # Branch abc\n> > > > reset onto\n> > >\n> > > Is this reset strictly necessary. We are already there @head.\n> >\n> > No, this is not strictly necessary, but\n> \n> I've realised my misunderstanding. I was thinking this (and others) was\n> equivalent to\n> \n> $  git reset <thatHead'onto'> # maybe even --hard,\n> \n> i.e. affecting the worktree\n\nOh, but it *is* affecting the worktree. In this case, since we label HEAD\nand then immediately reset to the label, there is just nothing to change.\n\nConsider this example, though:\n\n\tlabel onto\n\n\t# Branch: from-philip\n\treset onto\n\tpick abcdef something\n\tlabel from-philip\n\n\t# Branch: with-love\n\treset onto\n\tpick 012345 else\n\tlabel with-love\n\n\treset onto\n\tmerge -C 98765 from-philip\n\tmerge -C 43210 with-love\n\nOnly in the first instance is the `reset onto` a no-op, an incidental one.\nAfter picking `something` and labeling the result as `from-philip`,\nthough, the next `reset onto` really resets the worktree.\n\n> rather that just being a movement of the Head rev (though I may be having\n> brain fade here regarding untracked files etc..)\n\nThe current way of doing things does not allow the `reset` to overwrite\nuntracked, nor ignored files (I think, I only verified the former, not the\nlatter).\n\nBut yeah, it is not just a movement of HEAD. It does reset the worktree,\nalthough quite a bit more gently (and safely) than `git reset --hard`. In\nthat respect, this patch series is a drastic improvement over the Git\ngarden shears (which is the shell script I use in Git for Windows which\ninspired this here patch series).\n\nCiao,\nDscho\n"},{"id":"345627","messageId":"nycvar.QRO.7.76.6.1804241015470.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"cover.1524545557.git.martin.agren@gmail.com","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T08:22:35Z","receivedAt":"2018-04-24T08:22:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Martin,\n\nOn Tue, 24 Apr 2018, Martin Ågren wrote:\n\n> On 23 April 2018 at 17:54, Phillip Wood <phillip.wood@talktalk.net> wrote:\n> > I'm fine with leaving it, I've might get round to doing a small series to\n> > clean things up slightly in a few weeks. At the moment\n> > setup_unpack_trees_porcelain() leaks memory as it is called for each merge\n> > and allocates new strings each time. It would also be nice if the error\n> > messages reflected the command, so it said 'cherry-pick', 'revert' or\n> > 'reset' rather than 'merge'\n> \n> This is a small patch series to introduce and use\n> `clear_unpack_trees_porcelain()`.\n\nGreat. Now I have no excuse but must change the sequencer code to output\n\"reset\" instead of \"merge\" ;-)\n\nSeriously speaking again: thank you for those patches. This is truly\nexciting! I mean, we all touch the same code and move it forward, and\nsomehow it all works out.\n\nCiao,\nDscho"},{"id":"345628","messageId":"nycvar.QRO.7.76.6.1804241032030.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"0023E61D311849B68FECAE76613839C4@PhilipOakley","subject":"Re: [PATCH v8 08/16] rebase-helper --make-script: introduce a flag to rebase merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T08:33:13Z","receivedAt":"2018-04-24T08:33:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Sun, 22 Apr 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> \n> Sorry for the very late in the series comments..\n> \n> > The sequencer just learned new commands intended to recreate branch\n> > structure (similar in spirit to --preserve-merges, but with a\n> > substantially less-broken design).\n> >\n> > Let's allow the rebase--helper to generate todo lists making use of\n> > these commands, triggered by the new --rebase-merges option. For a\n> > commit topology like this (where the HEAD points to C):\n> >\n> > - A - B - C\n> >     \\   /\n> >       D\n> >\n> > the generated todo list would look like this:\n> >\n> > # branch D\n> > pick 0123 A\n> > label branch-point\n> > pick 1234 D\n> > label D\n> >\n> > reset branch-point\n> > pick 2345 B\n> > merge -C 3456 D # C\n> >\n> > To keep things simple, we first only implement support for merge commits\n> > with exactly two parents, leaving support for octopus merges to a later\n> > patch series.\n> >\n> For the first time reader this (below) isn't as obvious as may be thought.\n> maybe we should be a little more explicit here.\n> \n> > As a special, hard-coded label, all merge-rebasing todo lists start with\n> > the command `label onto`\n> \n> .. which labels the start point head with the name 'onto' ...\n> \n> Maybe even:\n> \"All merge-rebasing todo lists start with, as a convenience, a hard-coded\n> `label onto` line which will label the start point's head\" ...\n\nI changed it to\n\n    All merge-rebasing todo lists start with a hard-coded `label onto` line.\n    This makes it convenient to refer later on to the revision onto which\n    everything is rebased, e.g. as starting point for branches other than\n    the very first one.\n\nCiao,\nDscho\n"},{"id":"345629","messageId":"nycvar.QRO.7.76.6.1804241037300.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"E8BADD438B3F4D20B33BCC2CD92BC004@PhilipOakley","subject":"Re: [PATCH v8 09/16] rebase: introduce the --rebase-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T08:40:35Z","receivedAt":"2018-04-24T08:40:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Sun, 22 Apr 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> > Once upon a time, this here developer thought: wouldn't it be nice if,\n> > say, Git for Windows' patches on top of core Git could be represented as\n> > a thicket of branches, and be rebased on top of core Git in order to\n> > maintain a cherry-pick'able set of patch series?\n> >\n> > The original attempt to answer this was: git rebase --preserve-merges.\n> >\n> > However, that experiment was never intended as an interactive option,\n> > and it only piggy-backed on git rebase --interactive because that\n> > command's implementation looked already very, very familiar: it was\n> > designed by the same person who designed --preserve-merges: yours truly.\n> >\n> > Some time later, some other developer (I am looking at you, Andreas!\n> > ;-)) decided that it would be a good idea to allow --preserve-merges to\n> > be combined with --interactive (with caveats!) and the Git maintainer\n> > (well, the interim Git maintainer during Junio's absence, that is)\n> > agreed, and that is when the glamor of the --preserve-merges design\n> > started to fall apart rather quickly and unglamorously.\n> >\n> > The reason? In --preserve-merges mode, the parents of a merge commit (or\n> > for that matter, of *any* commit) were not stated explicitly, but were\n> > *implied* by the commit name passed to the `pick` command.\n> >\n> > This made it impossible, for example, to reorder commits. Not to mention\n> > to flatten the branch topology or, deity forbid, to split topic branches\n> \n> Aside: The idea of a \"flattened\" topology is, to my mind, not actually\n> defined though may be understood by devs working in the area. Hopefully it's\n> going away as a term, though the new 'cousins' will need clarification\n> (there's no dot notation for that area of topology).\n\nRight. The point is not actually to talk about \"flattening\" branches. The\npoint is to talk about the flexibility one might expect in an\n*interactive* rebase, a flexibility notably lacking from the\n--preserve-merges mode.\n\nSo I changed it to\n\n    This made it impossible, for example, to reorder commits. Not to mention\n    to move commits between branches or, deity forbid, to split topic branches\n    into two.\n\n> > into two.\n> >\n> > Alas, these shortcomings also prevented that mode (whose original\n> > purpose was to serve Git for Windows' needs, with the additional hope\n> > that it may be useful to others, too) from serving Git for Windows'\n> > needs.\n>\n> [... please feel free to save readers time by culling quoted text that\n> is irrelevant to your reply...]\n>\n> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> > index 3277ca14327..34e0f6a69c1 100644\n> > --- a/Documentation/git-rebase.txt\n> > +++ b/Documentation/git-rebase.txt\n> > @@ -378,6 +378,23 @@ The commit list format can be changed by setting the\n> > configuration option\n> > rebase.instructionFormat.  A customized instruction format will\n> > automatically\n> > have the long commit hash prepended to the format.\n> >\n> > +-r::\n> > +--rebase-merges::\n> > + By default, a rebase will simply drop merge commits and only rebase\n> > + the non-merge commits. With this option, it will try to preserve\n> > + the branching structure within the commits that are to be rebased,\n> > + by recreating the merge commits. If a merge commit resolved any merge\n> > + or contained manual amendments, then they will have to be re-applied\n> > + manually.\n> > ++\n> > +This mode is similar in spirit to `--preserve-merges`, but in contrast to\n> > +that option works well in interactive rebases: commits can be reordered,\n> > +inserted and dropped at will.\n> > ++\n> > +It is currently only possible to recreate the merge commits using the\n> > +`recursive` merge strategy; Different merge strategies can be used only\n> > via\n> > +explicit `exec git merge -s <strategy> [...]` commands.\n> > +\n> > -p::\n> > --preserve-merges::\n> >  Recreate merge commits instead of flattening the history by replaying\n> \n> Flatten is here in the context lines but its just a blunt statement that 'it\n> is what it is'...\n\nCorrect. This is where that way of expressing things came from.\n\nI will *not* fix the documentation of `--preserve-merges`, though, as I\nhope it can be instead retired soon enough.\n\nCiao,\nDscho\n"},{"id":"345630","messageId":"nycvar.QRO.7.76.6.1804241042400.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"xmqq1sf56vvb.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v8 09/16] rebase: introduce the --rebase-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T09:03:45Z","receivedAt":"2018-04-24T09:04:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 24 Apr 2018, Junio C Hamano wrote:\n\n> \"Philip Oakley\" <philipoakley@iee.org> writes:\n> \n> >> +-r::\n> >> +--rebase-merges::\n> >> + By default, a rebase will simply drop merge commits and only rebase\n> >> + the non-merge commits. With this option, it will try to preserve\n> >> + the branching structure within the commits that are to be rebased,\n> >> + by recreating the merge commits. If a merge commit resolved any merge\n\nIt is funny how nobody caught the missing \"conflicts\" in \"If a merge\ncommit resolved any merge [conflicts]\"...\n\n> >> + or contained manual amendments, then they will have to be re-applied\n> >> + manually.\n> >> ++\n> >> +This mode is similar in spirit to `--preserve-merges`, but in contrast to\n> >> +that option works well in interactive rebases: commits can be reordered,\n> >> +inserted and dropped at will.\n> >> ++\n> >> +It is currently only possible to recreate the merge commits using the\n> >> +`recursive` merge strategy; Different merge strategies can be used only\n> >> via\n> >> +explicit `exec git merge -s <strategy> [...]` commands.\n> >> +\n> >> -p::\n> >> --preserve-merges::\n> >>  Recreate merge commits instead of flattening the history by replaying\n> >\n> > Flatten is here in the context lines but its just a blunt statement that 'it\n> > is what it is'...\n> \n> The first paragraph that explains --rebase-merges talks about what\n> happens when the option is not given, and says \"drop merge commits\n> and only rebase the non-merge commits\", which is not incorrect\n> per-se but does not make it explicit how the resulting topology\n> looks like.\n\nCorrect. And it would be the wrong place to describe in detail what a\nrebase *without --rebase-merges* does, right?\n\n> I think it is easier to understand if it mentioned \"flattening\" as well.\n> If flatten is not the word you want, perhaps \"make it linear\" or\n> something like that?\n\nI fear that we all here are way too deeply in \"Git think\". If I ask a\nrandom Git user what it means to \"make commits linear\", I am sure I would\nget only puzzled, nervous looks as a response.\n\nSo I am rather certain that the suggested wording is something I want to\navoid.\n\nBesides, we really should expect the reader to know a little about the way\nthe interactive rebase works by the time they read the explanation of\n`--rebase-merges`.\n\nTherefore, I think I can sidestep the entire thing by saying this instead:\n\n-r::\n--rebase-merges::\n        By default, a rebase will simply drop merge commits from the todo\n        list, and put the rebased commits into a single, linear branch.\n        With `--rebase-merges`, the rebase will instead try to preserve\n        the branching structure within the commits that are to be rebased,\n        by recreating the merge commits. Any resolved merge conflicts or\n        manual amendments in these merge commits will have to be\n        resolved/re-applied manually.\n\nThank you for helping me improve the documentation part of this patch,\nwhich I think is really, really important,\nDscho\n"},{"id":"345631","messageId":"CAN0heSqo43YkHa-1e7jaZqxktenonj3HZFrF=AKYR4+3ciZ2Sg@mail.gmail.com","threadId":"47635","inReplyTo":"CA+P7+xqP8b1i4-C242de2_P9dFsZ03pcRGo45scyfK2ohd-ykw@mail.gmail.com","subject":"Re: [PATCH 1/2] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-04-24T09:36:34Z","receivedAt":"2018-04-24T09:36:39Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 24 April 2018 at 08:20, Jacob Keller <jacob.keller@gmail.com> wrote:\n> I'm guessing the diff algorithm simply found that this was a more\n> compact representation of the change? It's a bit confusing when your\n> description indicates you \"moved\" some code down, but it looks like\n> you moved code up.\n\nAgreed. I'll play with --anchored and other magic stuff to see if I can\nimprove this. Or I could instead try to sell this patch as \"move some\nother stuff out of the way\" ;-) That seems a bit less direct though.\n\nThanks\nMartin\n"},{"id":"345640","messageId":"nycvar.QRO.7.76.6.1804241105170.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"83137354138143FD962AF49D37F93E9E@PhilipOakley","subject":"Re: [PATCH v8 09/16] rebase: introduce the --rebase-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T10:52:40Z","receivedAt":"2018-04-24T10:52:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Sun, 22 Apr 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> > Once upon a time, this here developer thought: wouldn't it be nice if,\n> > say, Git for Windows' patches on top of core Git could be represented as\n> > a thicket of branches, and be rebased on top of core Git in order to\n> > maintain a cherry-pick'able set of patch series?\n> >\n> > The original attempt to answer this was: git rebase --preserve-merges.\n> >\n> > However, that experiment was never intended as an interactive option,\n> > and it only piggy-backed on git rebase --interactive because that\n> > command's implementation looked already very, very familiar: it was\n> > designed by the same person who designed --preserve-merges: yours truly.\n> >\n> > Some time later, some other developer (I am looking at you, Andreas!\n> > ;-)) decided that it would be a good idea to allow --preserve-merges to\n> > be combined with --interactive (with caveats!) and the Git maintainer\n> > (well, the interim Git maintainer during Junio's absence, that is)\n> > agreed, and that is when the glamor of the --preserve-merges design\n> > started to fall apart rather quickly and unglamorously.\n> >\n> > The reason? In --preserve-merges mode, the parents of a merge commit (or\n> > for that matter, of *any* commit) were not stated explicitly, but were\n> > *implied* by the commit name passed to the `pick` command.\n> >\n> Aside: I think this para should be extracted to the --preserve-merges\n> documentation to highlight what it does / why it is 'wrong' (not what would be\n> expected in some case). It may also need to discuss the (figurative) Cousins\n> vs. Siblings distinction [merge of branches external, or internal, to the\n> rebase.\n\nQuite honestly, I'd much rather spend time improving --rebase-merges than\nimproving --preserve-merges documentation. In my mind, the latter is\npretty useless, especially once the former lands in an official Git\nversion.\n\nOf course, feel free to disagree with me by sending a patch to improve the\ndocumentation of --preserve-merges ;-)\n\n> \"In --preserve-merges, the commit being selected for merging is implied by the\n> commit name  passed to the `pick` command (i.e. of the original merge commit),\n> not that of the rebased version of that parent.\"\n\nIt is much, much worse:\n\n\tIn --preserve-merges, no commit can change its ancestry. Every\n\trebased commit's parents will be the rebased original parents.\n\nOr some such. But really, why bother describing something *that* broken?\nWhy not work toward a solution that makes that broken option obsolete?\nLike, say, --rebase-merges? ;-)\n\n> A similar issue occurs with (figuratively) '--ancestry-path --first parent'\n> searches which lacks the alternate '--lead parent' post-walk selection. [1]. I\n> don't think there is a dot notation to select the merge cousins, nor merge\n> siblings either A.,B ? (that's dot-comma ;-)\n\nI actually had missed `--ancestry-path`... I should probably use it in the\ndescription of the \"cousins\".\n\n> [... lots of quoted text...]\n\nCould I ask you to make it easier for me by cutting quoted text that is\nirrelevant to your reply? The way I read mails forces me to scroll down\n(sometimes on a phone) all the way to the end, just to find that that time\nwas spent in vain.\n\nThanks,\nDscho\n\n\n"},{"id":"345641","messageId":"nycvar.QRO.7.76.6.1804241329310.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"47635","inReplyTo":"CAN0heSqo43YkHa-1e7jaZqxktenonj3HZFrF=AKYR4+3ciZ2Sg@mail.gmail.com","subject":"Re: [PATCH 1/2] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T11:30:20Z","receivedAt":"2018-04-24T11:30:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Martin,\n\nOn Tue, 24 Apr 2018, Martin Ågren wrote:\n\n> On 24 April 2018 at 08:20, Jacob Keller <jacob.keller@gmail.com> wrote:\n> > I'm guessing the diff algorithm simply found that this was a more\n> > compact representation of the change? It's a bit confusing when your\n> > description indicates you \"moved\" some code down, but it looks like\n> > you moved code up.\n> \n> Agreed. I'll play with --anchored and other magic stuff to see if I can\n> improve this. Or I could instead try to sell this patch as \"move some\n> other stuff out of the way\" ;-) That seems a bit less direct though.\n\nOr you could add a remark to the commit message along the lines \"best\nviewed with `--anchored=...`\". This is what I would do ;-)\n\nCiao,\nDscho"},{"id":"345648","messageId":"20180424162939.20956-1-newren@gmail.com","threadId":"47635","inReplyTo":"5cfb43fad88cfb14e77bb0febdac297b78500dc0.1524545557.git.martin.agren@gmail.com","subject":"Re: [PATCH 2/2] unpack_trees_options: free messages when done","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-04-24T16:29:39Z","receivedAt":"2018-04-24T16:30:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Apr 23, 2018 at 10:13 PM, Martin Ågren <martin.agren@gmail.com> wrote:\n> The strings allocated in `setup_unpack_trees_porcelain()` are never\n> freed. Provide a function `clear_unpack_trees_porcelain()` to do so and\n> call it in the functions which use `setup_unpack_trees_porcelain()`.\n\nThis is awesome; thanks.\n\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 0c0d48624d..8229b91e2f 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -301,6 +301,7 @@ static int git_merge_trees(int index_only,\n>         init_tree_desc_from_tree(t+2, merge);\n>\n>         rc = unpack_trees(3, t, &opts);\n> +       clear_unpack_trees_porcelain(&opts);\n>         cache_tree_free(&active_cache_tree);\n>         return rc;\n\nYeah, this could result in an evil merge.  In my series, I want to\ncontinue to be able to call verify_uptodate() from unpack_trees.c in order\nto check if files affected by renames are dirty and we need to avoid\noverwriting them.  That can trigger error messages, so they need to not be\nfreed until later.  So, instead, I'd like to see something like the below\n(built on top of my series):\n\n-- >8 --\n\n---\n merge-recursive.c | 25 ++++++++++++-------------\n 1 file changed, 12 insertions(+), 13 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f2cbad4f10..3491a27bf2 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -337,10 +337,10 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n \tinit_tree_desc(desc, tree->buffer, tree->size);\n }\n \n-static int git_merge_trees(struct merge_options *o,\n-\t\t\t   struct tree *common,\n-\t\t\t   struct tree *head,\n-\t\t\t   struct tree *merge)\n+static int unpack_trees_start(struct merge_options *o,\n+\t\t\t      struct tree *common,\n+\t\t\t      struct tree *head,\n+\t\t\t      struct tree *merge)\n {\n \tint rc;\n \tstruct tree_desc t[3];\n@@ -378,6 +378,12 @@ static int git_merge_trees(struct merge_options *o,\n \treturn rc;\n }\n \n+static void unpack_trees_finish(struct merge_options *o)\n+{\n+\tdiscard_index(&o->orig_index);\n+\tclear_unpack_trees_porcelain(&o->unpack_opts);\n+}\n+\n struct tree *write_tree_from_memory(struct merge_options *o)\n {\n \tstruct tree *result = NULL;\n@@ -3079,7 +3085,7 @@ int merge_trees(struct merge_options *o,\n \t\treturn 1;\n \t}\n \n-\tcode = git_merge_trees(o, common, head, merge);\n+\tcode = unpack_trees_start(o, common, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(o, 4) || o->call_depth)\n@@ -3144,14 +3150,7 @@ int merge_trees(struct merge_options *o,\n \telse\n \t\tclean = 1;\n \n-\t/* Free the extra index left from git_merge_trees() */\n-\t/*\n-\t * FIXME: Need to also free data allocated by\n-\t * setup_unpack_trees_porcelain() tucked away in o->unpack_opts.msgs,\n-\t * but the problem is that only half of it refers to dynamically\n-\t * allocated data, while the other half points at static strings.\n-\t */\n-\tdiscard_index(&o->orig_index);\n+\tunpack_trees_finish(o);\n \n \tif (o->call_depth && !(*result = write_tree_from_memory(o)))\n \t\treturn -1;\n-- \n2.17.0.295.g791b7256b2.dirty\n\n"},{"id":"345672","messageId":"2494D111781E4353B9522D5FFA3D8F0E@PhilipOakley","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804241006230.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v8 06/16] sequencer: introduce the `merge` command","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-24T19:41:38Z","receivedAt":"2018-04-24T19:41:47Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <Johannes.Schindelin@gmx.de>\n> On Mon, 23 Apr 2018, Philip Oakley wrote:\n>\n>> From: \"Johannes Schindelin\" <Johannes.Schindelin@gmx.de> : Monday, April \n>> 23,\n>> 2018 1:03 PM\n>> Subject: Re: [PATCH v8 06/16] sequencer: introduce the `merge` command\n>>\n>> [...]\n>> >\n>> > > > label onto\n>> > > >\n>> > > > # Branch abc\n>> > > > reset onto\n>> > >\n>> > > Is this reset strictly necessary. We are already there @head.\n>> >\n>> > No, this is not strictly necessary, but\n>>\n>> I've realised my misunderstanding. I was thinking this (and others) was\n>> equivalent to\n>>\n>> $  git reset <thatHead'onto'> # maybe even --hard,\n>>\n>> i.e. affecting the worktree\n>\n> Oh, but it *is* affecting the worktree. In this case, since we label HEAD\n> and then immediately reset to the label, there is just nothing to change.\n>\n> Consider this example, though:\n>\n> label onto\n>\n> # Branch: from-philip\n> reset onto\n> pick abcdef something\n> label from-philip\n>\n> # Branch: with-love\n> reset onto\n> pick 012345 else\n> label with-love\n>\n> reset onto\n> merge -C 98765 from-philip\n> merge -C 43210 with-love\n>\n> Only in the first instance is the `reset onto` a no-op, an incidental one.\n> After picking `something` and labeling the result as `from-philip`,\n> though, the next `reset onto` really resets the worktree.\n>\n>> rather that just being a movement of the Head rev (though I may be having\n>> brain fade here regarding untracked files etc..)\n>\n> The current way of doing things does not allow the `reset` to overwrite\n> untracked, nor ignored files (I think, I only verified the former, not the\n> latter).\n>\n> But yeah, it is not just a movement of HEAD. It does reset the worktree,\n> although quite a bit more gently (and safely) than `git reset --hard`. In\n> that respect, this patch series is a drastic improvement over the Git\n> garden shears (which is the shell script I use in Git for Windows which\n> inspired this here patch series).\n>\nthanks for clarifying. Yes my reasoning  was a total brain fade ... Along \nwith the fact that it's a soft/safe/gentle reset.\n--\nPhilip \n\n"},{"id":"345761","messageId":"cover.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524306546.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 00/17] rebase -i: offer to recreate commit topology by rebasing merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:08Z","receivedAt":"2018-04-25T12:28:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Junio, I think this is now ready for `next`. Thank you for your patience\nand help with this.\n\nOnce upon a time, I dreamed of an interactive rebase that would not\nlinearize all patches and drop all merge commits, but instead recreate\nthe commit topology faithfully.\n\nMy original attempt was --preserve-merges, but that design was so\nlimited that I did not even enable it in interactive mode.\n\nSubsequently, it *was* enabled in interactive mode, with the predictable\nconsequences: as the --preserve-merges design does not allow for\nspecifying the parents of merge commits explicitly, all the new commits'\nparents are defined *implicitly* by the previous commit history, and\nhence it is *not possible to even reorder commits*.\n\nThis design flaw cannot be fixed. Not without a complete re-design, at\nleast. This patch series offers such a re-design.\n\nThink of --rebase-merges as \"--preserve-merges done right\". It\nintroduces new verbs for the todo list, `label`, `reset` and `merge`.\nFor a commit topology like this:\n\n            A - B - C\n              \\   /\n                D\n\nthe generated todo list would look like this:\n\n            # branch D\n            pick 0123 A\n            label branch-point\n            pick 1234 D\n            label D\n\n            reset branch-point\n            pick 2345 B\n            merge -C 3456 D # C\n\nThere are more patches in the pipeline, based on this patch series, but\nleft for later in the interest of reviewable patch series: one mini\nseries to use the sequencer even for `git rebase -i --root`, and another\none to add support for octopus merges to --rebase-merges. And then one\nto allow for rebasing merge commits in a smarter way (this one will need\na bit more work, though, as it can result in very complicated, nested\nmerge conflicts *very* easily).\n\nChanges since v8:\n\n- Disentangled the patch introducing `label`/`reset` from the one\n  introducing `merge` again (this was one stupid, tired `git commit\n  --amend` too many).\n\n- Augmented the commit message of \"introduce the `merge` command\" to\n  describe what the `label onto` is all about.\n\n- Fixed the error message when `reset` would overwrite untracked files to\n  actually say that a \"reset\" failed (not a \"merge\").\n\n- Clarified the rationale for `label onto` in the commit message of\n  \"rebase-helper --make-script: introduce a flag to rebase merges\".\n\n- Edited the description of `--rebase-merges` heavily, for clarity, in\n  \"rebase: introduce the --rebase-merges option\".\n\n- Edited the commit message of (and the documentation introduced by) \" rebase\n  -i: introduce --rebase-merges=[no-]rebase-cousins\" for clarity (also\n  mentioning the `--ancestry-path` option).\n\n- When run_git_commit() fails after a successful merge, we now take pains\n  not to reschedule the `merge` command.\n\n- Rebased the patch series on top of current `master`, i.e. both\n  `pw/rebase-keep-empty-fixes` and `pw/rebase-signoff`, to resolve merge\n  conflicts myself.\n\n\nJohannes Schindelin (15):\n  sequencer: avoid using errno clobbered by rollback_lock_file()\n  sequencer: make rearrange_squash() a bit more obvious\n  sequencer: refactor how original todo list lines are accessed\n  sequencer: offer helpful advice when a command was rescheduled\n  sequencer: introduce new commands to reset the revision\n  sequencer: introduce the `merge` command\n  sequencer: fast-forward `merge` commands, if possible\n  rebase-helper --make-script: introduce a flag to rebase merges\n  rebase: introduce the --rebase-merges option\n  sequencer: make refs generated by the `label` command worktree-local\n  sequencer: handle post-rewrite for merge commands\n  rebase --rebase-merges: avoid \"empty merges\"\n  pull: accept --rebase=merges to recreate the branch topology\n  rebase -i: introduce --rebase-merges=[no-]rebase-cousins\n  rebase -i --rebase-merges: add a section to the man page\n\nPhillip Wood (1):\n  rebase --rebase-merges: add test for --keep-empty\n\nStefan Beller (1):\n  git-rebase--interactive: clarify arguments\n\n Documentation/config.txt               |   8 +\n Documentation/git-pull.txt             |   6 +-\n Documentation/git-rebase.txt           | 163 ++++-\n builtin/pull.c                         |  14 +-\n builtin/rebase--helper.c               |  13 +-\n builtin/remote.c                       |  18 +-\n contrib/completion/git-completion.bash |   4 +-\n git-rebase--interactive.sh             |  22 +-\n git-rebase.sh                          |  16 +\n refs.c                                 |   3 +-\n sequencer.c                            | 892 ++++++++++++++++++++++++-\n sequencer.h                            |   7 +\n t/t3421-rebase-topology-linear.sh      |   1 +\n t/t3430-rebase-merges.sh               | 244 +++++++\n 14 files changed, 1352 insertions(+), 59 deletions(-)\n create mode 100755 t/t3430-rebase-merges.sh\n\n\nbase-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d\nPublished-As: https://github.com/dscho/git/releases/tag/recreate-merges-v9\nFetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v9\n\nInterdiff vs v8:\n diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n index e691b93e920..bd5ecff980e 100644\n --- a/Documentation/git-rebase.txt\n +++ b/Documentation/git-rebase.txt\n @@ -381,21 +381,24 @@ have the long commit hash prepended to the format.\n  \n  -r::\n  --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n -\tBy default, a rebase will simply drop merge commits and only rebase\n -\tthe non-merge commits. With this option, it will try to preserve\n +\tBy default, a rebase will simply drop merge commits from the todo\n +\tlist, and put the rebased commits into a single, linear branch.\n +\tWith `--rebase-merges`, the rebase will instead try to preserve\n  \tthe branching structure within the commits that are to be rebased,\n -\tby recreating the merge commits. If a merge commit resolved any merge\n -\tor contained manual amendments, then they will have to be re-applied\n -\tmanually.\n +\tby recreating the merge commits. Any resolved merge conflicts or\n +\tmanual amendments in these merge commits will have to be\n +\tresolved/re-applied manually.\n  +\n  By default, or when `no-rebase-cousins` was specified, commits which do not\n -have `<upstream>` as direct ancestor will keep their original branch point.\n -If the `rebase-cousins` mode is turned on, such commits are instead rebased\n +have `<upstream>` as direct ancestor will keep their original branch point,\n +i.e. commits that would be excluded by gitlink:git-log[1]'s\n +`--ancestry-path` option will keep their original ancestry by default. If\n +the `rebase-cousins` mode is turned on, such commits are instead rebased\n  onto `<upstream>` (or `<onto>`, if specified).\n  +\n -This mode is similar in spirit to `--preserve-merges`, but in contrast to\n -that option works well in interactive rebases: commits can be reordered,\n -inserted and dropped at will.\n +The `--rebase-merges` mode is similar in spirit to `--preserve-merges`, but\n +in contrast to that option works well in interactive rebases: commits can be\n +reordered, inserted and dropped at will.\n  +\n  It is currently only possible to recreate the merge commits using the\n  `recursive` merge strategy; Different merge strategies can be used only via\n diff --git a/sequencer.c b/sequencer.c\n index b5715f69450..e2f83942843 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -2635,6 +2635,7 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n  \t}\n  \n  \tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n +\tsetup_unpack_trees_porcelain(&unpack_tree_opts, \"reset\");\n  \tunpack_tree_opts.head_idx = 1;\n  \tunpack_tree_opts.src_index = &the_index;\n  \tunpack_tree_opts.dst_index = &the_index;\n @@ -2855,7 +2856,12 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n  \tif (ret)\n  \t\trerere(opts->allow_rerere_auto);\n  \telse\n -\t\tret = run_git_commit(git_path_merge_msg(), opts,\n +\t\t/*\n +\t\t * In case of problems, we now want to return a positive\n +\t\t * value (a negative one would indicate that the `merge`\n +\t\t * command needs to be rescheduled).\n +\t\t */\n +\t\tret = !!run_git_commit(git_path_merge_msg(), opts,\n  \t\t\t\t     run_commit_flags);\n  \n  leave_merge:\n @@ -3809,12 +3815,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n  \n  \tinit_revisions(&revs, NULL);\n  \trevs.verbose_header = 1;\n -\tif (rebase_merges)\n -\t\trevs.cherry_mark = 1;\n -\telse {\n +\tif (!rebase_merges)\n  \t\trevs.max_parents = 1;\n -\t\trevs.cherry_pick = 1;\n -\t}\n +\trevs.cherry_mark = 1;\n  \trevs.limited = 1;\n  \trevs.reverse = 1;\n  \trevs.right_only = 1;\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n"},{"id":"345762","messageId":"a328071912bc0ef35f1f57748ae8cc03f41dd67f.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 01/17] sequencer: avoid using errno clobbered by rollback_lock_file()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:17Z","receivedAt":"2018-04-25T12:28:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"As pointed out in a review of the `--rebase-merges` patch series,\n`rollback_lock_file()` clobbers errno. Therefore, we have to report the\nerror message that uses errno before calling said function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 10 ++++++----\n 1 file changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 5e3a50fafc9..674e26bf826 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -346,12 +346,14 @@ static int write_message(const void *buf, size_t len, const char *filename,\n \tif (msg_fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), filename);\n \tif (write_in_full(msg_fd, buf, len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (append_eol && write(msg_fd, \"\\n\", 1) < 0) {\n+\t\terror_errno(_(\"could not write eol to '%s'\"), filename);\n \t\trollback_lock_file(&msg_file);\n-\t\treturn error_errno(_(\"could not write eol to '%s'\"), filename);\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&msg_file) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), filename);\n@@ -2125,9 +2127,9 @@ static int save_head(const char *head)\n \twritten = write_in_full(fd, buf.buf, buf.len);\n \tstrbuf_release(&buf);\n \tif (written < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), git_path_head_file());\n \t\trollback_lock_file(&head_lock);\n-\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t   git_path_head_file());\n+\t\treturn -1;\n \t}\n \tif (commit_lock_file(&head_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), git_path_head_file());\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345763","messageId":"a555a3b1d1c2360bf44c7b859bef4ef21c1e32ca.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 02/17] sequencer: make rearrange_squash() a bit more obvious","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:25Z","receivedAt":"2018-04-25T12:28:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"There are some commands that have to be skipped from rearranging by virtue\nof not handling any commits.\n\nHowever, the logic was not quite obvious: it skipped commands based on\ntheir position in the enum todo_command.\n\nInstead, let's make it explicit that we skip all commands that do not\nhandle any commit. With one exception: the `drop` command, because it,\nwell, drops the commit and is therefore not eligible to rearranging.\n\nNote: this is a bit academic at the moment because the only time we call\n`rearrange_squash()` is directly after generating the todo list, when we\nhave nothing but `pick` commands anyway.\n\nHowever, the upcoming `merge` command *will* want to be handled by that\nfunction, and it *can* handle commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 674e26bf826..c131e39fa93 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3403,7 +3403,7 @@ int rearrange_squash(void)\n \t\tstruct subject2item_entry *entry;\n \n \t\tnext[i] = tail[i] = -1;\n-\t\tif (item->command >= TODO_EXEC) {\n+\t\tif (!item->commit || item->command == TODO_DROP) {\n \t\t\tsubjects[i] = NULL;\n \t\t\tcontinue;\n \t\t}\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345764","messageId":"eb63a8dc3d698d0ee513b957fc775c6396b3a0d8.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 03/17] sequencer: refactor how original todo list lines are accessed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:29Z","receivedAt":"2018-04-25T12:28:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Previously, we did a lot of arithmetic gymnastics to get at the line in\nthe todo list (as stored in todo_list.buf). This might have been fast,\nbut only in terms of execution speed, not in terms of developer time.\n\nLet's refactor this to make it a lot easier to read, and hence to\nreason about the correctness of the code. It is not performance-critical\ncode anyway.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 60 ++++++++++++++++++++++++++++++++---------------------\n 1 file changed, 36 insertions(+), 24 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex c131e39fa93..eac1c341c1c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1871,6 +1871,23 @@ static int count_commands(struct todo_list *todo_list)\n \treturn count;\n }\n \n+static int get_item_line_offset(struct todo_list *todo_list, int index)\n+{\n+\treturn index < todo_list->nr ?\n+\t\ttodo_list->items[index].offset_in_buf : todo_list->buf.len;\n+}\n+\n+static const char *get_item_line(struct todo_list *todo_list, int index)\n+{\n+\treturn todo_list->buf.buf + get_item_line_offset(todo_list, index);\n+}\n+\n+static int get_item_line_length(struct todo_list *todo_list, int index)\n+{\n+\treturn get_item_line_offset(todo_list, index + 1)\n+\t\t-  get_item_line_offset(todo_list, index);\n+}\n+\n static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)\n {\n \tint fd;\n@@ -2250,29 +2267,27 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n \tif (fd < 0)\n \t\treturn error_errno(_(\"could not lock '%s'\"), todo_path);\n-\toffset = next < todo_list->nr ?\n-\t\ttodo_list->items[next].offset_in_buf : todo_list->buf.len;\n+\toffset = get_item_line_offset(todo_list, next);\n \tif (write_in_full(fd, todo_list->buf.buf + offset,\n \t\t\ttodo_list->buf.len - offset) < 0)\n \t\treturn error_errno(_(\"could not write to '%s'\"), todo_path);\n \tif (commit_lock_file(&todo_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n \n-\tif (is_rebase_i(opts)) {\n-\t\tconst char *done_path = rebase_path_done();\n-\t\tint fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);\n-\t\tint prev_offset = !next ? 0 :\n-\t\t\ttodo_list->items[next - 1].offset_in_buf;\n+\tif (is_rebase_i(opts) && next > 0) {\n+\t\tconst char *done = rebase_path_done();\n+\t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n+\t\tint ret = 0;\n \n-\t\tif (fd >= 0 && offset > prev_offset &&\n-\t\t    write_in_full(fd, todo_list->buf.buf + prev_offset,\n-\t\t\t\t  offset - prev_offset) < 0) {\n-\t\t\tclose(fd);\n-\t\t\treturn error_errno(_(\"could not write to '%s'\"),\n-\t\t\t\t\t   done_path);\n-\t\t}\n-\t\tif (fd >= 0)\n-\t\t\tclose(fd);\n+\t\tif (fd < 0)\n+\t\t\treturn 0;\n+\t\tif (write_in_full(fd, get_item_line(todo_list, next - 1),\n+\t\t\t\t  get_item_line_length(todo_list, next - 1))\n+\t\t    < 0)\n+\t\t\tret = error_errno(_(\"could not write to '%s'\"), done);\n+\t\tif (close(fd) < 0)\n+\t\t\tret = error_errno(_(\"failed to finalize '%s'\"), done);\n+\t\treturn ret;\n \t}\n \treturn 0;\n }\n@@ -3307,8 +3322,7 @@ int skip_unnecessary_picks(void)\n \t\toid = &item->commit->object.oid;\n \t}\n \tif (i > 0) {\n-\t\tint offset = i < todo_list.nr ?\n-\t\t\ttodo_list.items[i].offset_in_buf : todo_list.buf.len;\n+\t\tint offset = get_item_line_offset(&todo_list, i);\n \t\tconst char *done_path = rebase_path_done();\n \n \t\tfd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);\n@@ -3488,12 +3502,10 @@ int rearrange_squash(void)\n \t\t\t\tcontinue;\n \n \t\t\twhile (cur >= 0) {\n-\t\t\t\tint offset = todo_list.items[cur].offset_in_buf;\n-\t\t\t\tint end_offset = cur + 1 < todo_list.nr ?\n-\t\t\t\t\ttodo_list.items[cur + 1].offset_in_buf :\n-\t\t\t\t\ttodo_list.buf.len;\n-\t\t\t\tchar *bol = todo_list.buf.buf + offset;\n-\t\t\t\tchar *eol = todo_list.buf.buf + end_offset;\n+\t\t\t\tconst char *bol =\n+\t\t\t\t\tget_item_line(&todo_list, cur);\n+\t\t\t\tconst char *eol =\n+\t\t\t\t\tget_item_line(&todo_list, cur + 1);\n \n \t\t\t\t/* replace 'pick', by 'fixup' or 'squash' */\n \t\t\t\tcommand = todo_list.items[cur].command;\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345765","messageId":"06c48bfcfa79b925af339fef04a29bdb0b216f9e.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 04/17] sequencer: offer helpful advice when a command was rescheduled","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:33Z","receivedAt":"2018-04-25T12:28:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Previously, we did that just magically, and potentially left some users\nquite puzzled. Let's err on the safe side instead, telling the user what\nis happening, and how they are supposed to continue.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 16 ++++++++++++++++\n 1 file changed, 16 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex eac1c341c1c..f9c1ddb5385 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2561,6 +2561,17 @@ static const char *reflog_message(struct replay_opts *opts,\n \treturn buf.buf;\n }\n \n+static const char rescheduled_advice[] =\n+N_(\"Could not execute the todo command\\n\"\n+\"\\n\"\n+\"    %.*s\"\n+\"\\n\"\n+\"It has been rescheduled; To edit the command before continuing, please\\n\"\n+\"edit the todo list first:\\n\"\n+\"\\n\"\n+\"    git rebase --edit-todo\\n\"\n+\"    git rebase --continue\\n\");\n+\n static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n {\n \tint res = 0;\n@@ -2606,6 +2617,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t\topts, is_final_fixup(todo_list));\n \t\t\tif (is_rebase_i(opts) && res < 0) {\n \t\t\t\t/* Reschedule */\n+\t\t\t\tadvise(_(rescheduled_advice),\n+\t\t\t\t       get_item_line_length(todo_list,\n+\t\t\t\t\t\t\t    todo_list->current),\n+\t\t\t\t       get_item_line(todo_list,\n+\t\t\t\t\t\t     todo_list->current));\n \t\t\t\ttodo_list->current--;\n \t\t\t\tif (save_todo(todo_list, opts))\n \t\t\t\t\treturn -1;\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345766","messageId":"32ec29cc643fbe3809e7dd603adc64c26c9280e6.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 05/17] git-rebase--interactive: clarify arguments","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:45Z","receivedAt":"2018-04-25T12:28:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Stefan Beller <stefanbeller@gmail.com>\n\nUp to now each command took a commit as its first argument and ignored\nthe rest of the line (usually the subject of the commit)\n\nNow that we are about to introduce commands that take different\narguments, clarify each command by giving the argument list.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 9947e6265fe..a60df2ee5a0 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -155,13 +155,13 @@ reschedule_last_action () {\n append_todo_help () {\n \tgettext \"\n Commands:\n-p, pick = use commit\n-r, reword = use commit, but edit the commit message\n-e, edit = use commit, but stop for amending\n-s, squash = use commit, but meld into previous commit\n-f, fixup = like \\\"squash\\\", but discard this commit's log message\n-x, exec = run command (the rest of the line) using shell\n-d, drop = remove commit\n+p, pick <commit> = use commit\n+r, reword <commit> = use commit, but edit the commit message\n+e, edit <commit> = use commit, but stop for amending\n+s, squash <commit> = use commit, but meld into previous commit\n+f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n+x, exec <commit> = run command (the rest of the line) using shell\n+d, drop <commit> = remove commit\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345767","messageId":"548acb39f06d2569893159076cecca1daa954eb1.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 06/17] sequencer: introduce new commands to reset the revision","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:47Z","receivedAt":"2018-04-25T12:29:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the upcoming commits, we will teach the sequencer to rebase merges.\nThis will be done in a very different way from the unfortunate design of\n`git rebase --preserve-merges` (which does not allow for reordering\ncommits, or changing the branch topology).\n\nThe main idea is to introduce new todo list commands, to support\nlabeling the current revision with a given name, resetting the current\nrevision to a previous state, and  merging labeled revisions.\n\nThis idea was developed in Git for Windows' Git garden shears (that are\nused to maintain Git for Windows' \"thicket of branches\" on top of\nupstream Git), and this patch is part of the effort to make it available\nto a wider audience, as well as to make the entire process more robust\n(by implementing it in a safe and portable language rather than a Unix\nshell script).\n\nThis commit implements the commands to label, and to reset to, given\nrevisions. The syntax is:\n\n\tlabel <name>\n\treset <name>\n\nInternally, the `label <name>` command creates the ref\n`refs/rewritten/<name>`. This makes it possible to work with the labeled\nrevisions interactively, or in a scripted fashion (e.g. via the todo\nlist command `exec`).\n\nThese temporary refs are removed upon sequencer_remove_state(), so that\neven a `git rebase --abort` cleans them up.\n\nWe disallow '#' as label because that character will be used as separator\nin the upcoming `merge` command.\n\nLater in this patch series, we will mark the `refs/rewritten/` refs as\nworktree-local, to allow for interactive rebases to be run in parallel in\nworktrees linked to the same repository.\n\nAs typos happen, a failed `label` or `reset` command will be rescheduled\nimmediately. As the previous code to reschedule a command is embedded\ndeeply in the pick/fixup/squash code path, we simply duplicate the few\nlines. This will allow us to extend the new code path easily for the\nupcoming `merge` command.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   2 +\n sequencer.c                | 213 +++++++++++++++++++++++++++++++++++--\n 2 files changed, 208 insertions(+), 7 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex a60df2ee5a0..d6e8958dae4 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit\n f, fixup <commit> = like \\\"squash\\\", but discard this commit's log message\n x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n+l, label <label> = label current HEAD with a name\n+t, reset <label> = reset HEAD to a label\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex f9c1ddb5385..c9655edffa5 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -23,6 +23,8 @@\n #include \"hashmap.h\"\n #include \"notes-utils.h\"\n #include \"sigchain.h\"\n+#include \"unpack-trees.h\"\n+#include \"worktree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n static GIT_PATH_FUNC(rebase_path_rewritten_list, \"rebase-merge/rewritten-list\")\n static GIT_PATH_FUNC(rebase_path_rewritten_pending,\n \t\"rebase-merge/rewritten-pending\")\n+\n+/*\n+ * The path of the file listing refs that need to be deleted after the rebase\n+ * finishes. This is used by the `label` command to record the need for cleanup.\n+ */\n+static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line (and are only consumed, not modified, by the sequencer).\n@@ -245,18 +254,34 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)\n \n int sequencer_remove_state(struct replay_opts *opts)\n {\n-\tstruct strbuf dir = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i;\n \n+\tif (is_rebase_i(opts) &&\n+\t    strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {\n+\t\tchar *p = buf.buf;\n+\t\twhile (*p) {\n+\t\t\tchar *eol = strchr(p, '\\n');\n+\t\t\tif (eol)\n+\t\t\t\t*eol = '\\0';\n+\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0)\n+\t\t\t\twarning(_(\"could not delete '%s'\"), p);\n+\t\t\tif (!eol)\n+\t\t\t\tbreak;\n+\t\t\tp = eol + 1;\n+\t\t}\n+\t}\n+\n \tfree(opts->gpg_sign);\n \tfree(opts->strategy);\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n \tfree(opts->xopts);\n \n-\tstrbuf_addstr(&dir, get_dir(opts));\n-\tremove_dir_recursively(&dir, 0);\n-\tstrbuf_release(&dir);\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, get_dir(opts));\n+\tremove_dir_recursively(&buf, 0);\n+\tstrbuf_release(&buf);\n \n \treturn 0;\n }\n@@ -1280,6 +1305,8 @@ enum todo_command {\n \tTODO_SQUASH,\n \t/* commands that do something else than handling a single commit */\n \tTODO_EXEC,\n+\tTODO_LABEL,\n+\tTODO_RESET,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1298,6 +1325,8 @@ static struct {\n \t{ 'f', \"fixup\" },\n \t{ 's', \"squash\" },\n \t{ 'x', \"exec\" },\n+\t{ 'l', \"label\" },\n+\t{ 't', \"reset\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1803,7 +1832,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn error(_(\"missing arguments for %s\"),\n \t\t\t     command_to_string(item->command));\n \n-\tif (item->command == TODO_EXEC) {\n+\tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n+\t    item->command == TODO_RESET) {\n \t\titem->commit = NULL;\n \t\titem->arg = bol;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -2471,6 +2501,159 @@ static int do_exec(const char *command_line)\n \treturn status;\n }\n \n+static int safe_append(const char *filename, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t   LOCK_REPORT_ON_ERROR);\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (fd < 0)\n+\t\treturn -1;\n+\n+\tif (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {\n+\t\terror_errno(_(\"could not read '%s'\"), filename);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tstrbuf_complete(&buf, '\\n');\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&buf, fmt, ap);\n+\tva_end(ap);\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) < 0) {\n+\t\terror_errno(_(\"could not write to '%s'\"), filename);\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn -1;\n+\t}\n+\tif (commit_lock_file(&lock) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\trollback_lock_file(&lock);\n+\t\treturn error(_(\"failed to finalize '%s'\"), filename);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\n+\n+static int do_label(const char *name, int len)\n+{\n+\tstruct ref_store *refs = get_main_ref_store();\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id head_oid;\n+\n+\tif (len == 1 && *name == '#')\n+\t\treturn error(\"Illegal label name: '%.*s'\", len, name);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t} else if (get_oid(\"HEAD\", &head_oid)) {\n+\t\terror(_(\"could not read HEAD\"));\n+\t\tret = -1;\n+\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n+\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t   ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&msg);\n+\n+\tif (!ret)\n+\t\tret = safe_append(rebase_path_refs_to_delete(),\n+\t\t\t\t  \"%s\\n\", ref_name.buf);\n+\tstrbuf_release(&ref_name);\n+\n+\treturn ret;\n+}\n+\n+static const char *reflog_message(struct replay_opts *opts,\n+\tconst char *sub_action, const char *fmt, ...);\n+\n+static int do_reset(const char *name, int len, struct replay_opts *opts)\n+{\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc desc;\n+\tstruct tree *tree;\n+\tstruct unpack_trees_options unpack_tree_opts;\n+\tint ret = 0, i;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)\n+\t\treturn -1;\n+\n+\t/* Determine the length of the label */\n+\tfor (i = 0; i < len; i++)\n+\t\tif (isspace(name[i]))\n+\t\t\tlen = i;\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n+\tif (get_oid(ref_name.buf, &oid) &&\n+\t    get_oid(ref_name.buf + strlen(\"refs/rewritten/\"), &oid)) {\n+\t\terror(_(\"could not read '%s'\"), ref_name.buf);\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n+\tsetup_unpack_trees_porcelain(&unpack_tree_opts, \"reset\");\n+\tunpack_tree_opts.head_idx = 1;\n+\tunpack_tree_opts.src_index = &the_index;\n+\tunpack_tree_opts.dst_index = &the_index;\n+\tunpack_tree_opts.fn = oneway_merge;\n+\tunpack_tree_opts.merge = 1;\n+\tunpack_tree_opts.update = 1;\n+\n+\tif (read_cache_unmerged()) {\n+\t\trollback_lock_file(&lock);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn error_resolve_conflict(_(action_name(opts)));\n+\t}\n+\n+\tif (!fill_tree_descriptor(&desc, &oid)) {\n+\t\terror(_(\"failed to find tree of %s\"), oid_to_hex(&oid));\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\tif (unpack_trees(1, &desc, &unpack_tree_opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\tfree((void *)desc.buffer);\n+\t\tstrbuf_release(&ref_name);\n+\t\treturn -1;\n+\t}\n+\n+\ttree = parse_tree_indirect(&oid);\n+\tprime_cache_tree(&the_index, tree);\n+\n+\tif (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)\n+\t\tret = error(_(\"could not write index\"));\n+\tfree((void *)desc.buffer);\n+\n+\tif (!ret)\n+\t\tret = update_ref(reflog_message(opts, \"reset\", \"'%.*s'\",\n+\t\t\t\t\t\tlen, name), \"HEAD\", &oid,\n+\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\n+\tstrbuf_release(&ref_name);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2574,7 +2757,7 @@ N_(\"Could not execute the todo command\\n\"\n \n static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n {\n-\tint res = 0;\n+\tint res = 0, reschedule = 0;\n \n \tsetenv(GIT_REFLOG_ACTION, action_name(opts), 0);\n \tif (opts->allow_ff)\n@@ -2645,7 +2828,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t\tintend_to_amend();\n \t\t\t\treturn error_failed_squash(item->commit, opts,\n \t\t\t\t\titem->arg_len, item->arg);\n-\t\t\t} else if (res && is_rebase_i(opts))\n+\t\t\t} else if (res && is_rebase_i(opts) && item->commit)\n \t\t\t\treturn res | error_with_patch(item->commit,\n \t\t\t\t\titem->arg, item->arg_len, opts, res,\n \t\t\t\t\titem->command == TODO_REWORD);\n@@ -2671,9 +2854,25 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t/* `current` will be incremented below */\n \t\t\t\ttodo_list->current = -1;\n \t\t\t}\n+\t\t} else if (item->command == TODO_LABEL) {\n+\t\t\tif ((res = do_label(item->arg, item->arg_len)))\n+\t\t\t\treschedule = 1;\n+\t\t} else if (item->command == TODO_RESET) {\n+\t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n+\t\t\t\treschedule = 1;\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n+\t\tif (reschedule) {\n+\t\t\tadvise(_(rescheduled_advice),\n+\t\t\t       get_item_line_length(todo_list,\n+\t\t\t\t\t\t    todo_list->current),\n+\t\t\t       get_item_line(todo_list, todo_list->current));\n+\t\t\ttodo_list->current--;\n+\t\t\tif (save_todo(todo_list, opts))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n \t\ttodo_list->current++;\n \t\tif (res)\n \t\t\treturn res;\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345768","messageId":"c9539e37946802b74ec23ba4b76e64f31e3f8964.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 07/17] sequencer: introduce the `merge` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:54Z","receivedAt":"2018-04-25T12:29:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch is part of the effort to reimplement `--preserve-merges` with\na substantially improved design, a design that has been developed in the\nGit for Windows project to maintain the dozens of Windows-specific patch\nseries on top of upstream Git.\n\nThe previous patch implemented the `label` and `reset` commands to label\ncommits and to reset to labeled commits. This patch adds the `merge`\ncommand, with the following syntax:\n\n\tmerge [-C <commit>] <rev> # <oneline>\n\nThe <commit> parameter in this instance is the *original* merge commit,\nwhose author and message will be used for the merge commit that is about\nto be created.\n\nThe <rev> parameter refers to the (possibly rewritten) revision to\nmerge. Let's see an example of a todo list (the initial `label onto`\ncommand is an auto-generated convenience so that the label `onto` can be\nused to refer to the revision onto which we rebase):\n\n\tlabel onto\n\n\t# Branch abc\n\treset onto\n\tpick deadbeef Hello, world!\n\tlabel abc\n\n\treset onto\n\tpick cafecafe And now for something completely different\n\tmerge -C baaabaaa abc # Merge the branch 'abc' into master\n\nTo edit the merge commit's message (a \"reword\" for merges, if you will),\nuse `-c` (lower-case) instead of `-C`; this convention was borrowed from\n`git commit` that also supports `-c` and `-C` with similar meanings.\n\nTo create *new* merges, i.e. without copying the commit message from an\nexisting commit, simply omit the `-C <commit>` parameter (which will\nopen an editor for the merge message):\n\n\tmerge abc\n\nThis comes in handy when splitting a branch into two or more branches.\n\nNote: this patch only adds support for recursive merges, to keep things\nsimple. Support for octopus merges will be added later in a separate\npatch series, support for merges using strategies other than the\nrecursive merge is left for the future.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git-rebase--interactive.sh |   4 +\n sequencer.c                | 200 +++++++++++++++++++++++++++++++++++++\n 2 files changed, 204 insertions(+)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex d6e8958dae4..acb4bfd3fc8 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -164,6 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell\n d, drop <commit> = remove commit\n l, label <label> = label current HEAD with a name\n t, reset <label> = reset HEAD to a label\n+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n+.       create a merge commit using the original merge commit's\n+.       message (or the oneline, if no original merge commit was\n+.       specified). Use -c <commit> to reword the commit message.\n \n These lines can be re-ordered; they are executed from top to bottom.\n \" | git stripspace --comment-lines >>\"$todo\"\ndiff --git a/sequencer.c b/sequencer.c\nindex c9655edffa5..94f4831a0c3 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1307,6 +1307,7 @@ enum todo_command {\n \tTODO_EXEC,\n \tTODO_LABEL,\n \tTODO_RESET,\n+\tTODO_MERGE,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n@@ -1327,6 +1328,7 @@ static struct {\n \t{ 'x', \"exec\" },\n \t{ 'l', \"label\" },\n \t{ 't', \"reset\" },\n+\t{ 'm', \"merge\" },\n \t{ 0,   \"noop\" },\n \t{ 'd', \"drop\" },\n \t{ 0,   NULL }\n@@ -1754,9 +1756,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)\n \treturn 0;\n }\n \n+enum todo_item_flags {\n+\tTODO_EDIT_MERGE_MSG = 1\n+};\n+\n struct todo_item {\n \tenum todo_command command;\n \tstruct commit *commit;\n+\tunsigned int flags;\n \tconst char *arg;\n \tint arg_len;\n \tsize_t offset_in_buf;\n@@ -1791,6 +1798,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \tchar *end_of_object_name;\n \tint i, saved, status, padding;\n \n+\titem->flags = 0;\n+\n \t/* left-trim */\n \tbol += strspn(bol, \" \\t\");\n \n@@ -1840,6 +1849,21 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)\n \t\treturn 0;\n \t}\n \n+\tif (item->command == TODO_MERGE) {\n+\t\tif (skip_prefix(bol, \"-C\", &bol))\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\telse if (skip_prefix(bol, \"-c\", &bol)) {\n+\t\t\tbol += strspn(bol, \" \\t\");\n+\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n+\t\t} else {\n+\t\t\titem->flags |= TODO_EDIT_MERGE_MSG;\n+\t\t\titem->commit = NULL;\n+\t\t\titem->arg = bol;\n+\t\t\titem->arg_len = (int)(eol - bol);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n \tend_of_object_name = (char *) bol + strcspn(bol, \" \\t\\n\");\n \tsaved = *end_of_object_name;\n \t*end_of_object_name = '\\0';\n@@ -2654,6 +2678,158 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n \treturn ret;\n }\n \n+static int do_merge(struct commit *commit, const char *arg, int arg_len,\n+\t\t    int flags, struct replay_opts *opts)\n+{\n+\tint run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?\n+\t\tEDIT_MSG | VERIFY_MSG : 0;\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct commit *head_commit, *merge_commit, *i;\n+\tstruct commit_list *bases, *j, *reversed = NULL;\n+\tstruct merge_options o;\n+\tint merge_arg_len, oneline_offset, ret;\n+\tstatic struct lock_file lock;\n+\tconst char *p;\n+\n+\tif (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {\n+\t\tret = -1;\n+\t\tgoto leave_merge;\n+\t}\n+\n+\thead_commit = lookup_commit_reference_by_name(\"HEAD\");\n+\tif (!head_commit) {\n+\t\tret = error(_(\"cannot merge without a current revision\"));\n+\t\tgoto leave_merge;\n+\t}\n+\n+\toneline_offset = arg_len;\n+\tmerge_arg_len = strcspn(arg, \" \\t\\n\");\n+\tp = arg + merge_arg_len;\n+\tp += strspn(p, \" \\t\\n\");\n+\tif (*p == '#' && (!p[1] || isspace(p[1]))) {\n+\t\tp += 1 + strspn(p + 1, \" \\t\\n\");\n+\t\toneline_offset = p - arg;\n+\t} else if (p - arg < arg_len)\n+\t\tBUG(\"octopus merges are not supported yet: '%s'\", p);\n+\n+\tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", merge_arg_len, arg);\n+\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\tif (!merge_commit) {\n+\t\t/* fall back to non-rewritten ref or commit */\n+\t\tstrbuf_splice(&ref_name, 0, strlen(\"refs/rewritten/\"), \"\", 0);\n+\t\tmerge_commit = lookup_commit_reference_by_name(ref_name.buf);\n+\t}\n+\n+\tif (!merge_commit) {\n+\t\tret = error(_(\"could not resolve '%s'\"), ref_name.buf);\n+\t\tgoto leave_merge;\n+\t}\n+\n+\tif (commit) {\n+\t\tconst char *message = get_commit_buffer(commit, NULL);\n+\t\tconst char *body;\n+\t\tint len;\n+\n+\t\tif (!message) {\n+\t\t\tret = error(_(\"could not get commit message of '%s'\"),\n+\t\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t\twrite_author_script(message);\n+\t\tfind_commit_subject(message, &body);\n+\t\tlen = strlen(body);\n+\t\tret = write_message(body, len, git_path_merge_msg(), 0);\n+\t\tunuse_commit_buffer(commit, message);\n+\t\tif (ret) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t} else {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tstrbuf_addf(&buf, \"author %s\", git_author_info(0));\n+\t\twrite_author_script(buf.buf);\n+\t\tstrbuf_reset(&buf);\n+\n+\t\tif (oneline_offset < arg_len) {\n+\t\t\tp = arg + oneline_offset;\n+\t\t\tlen = arg_len - oneline_offset;\n+\t\t} else {\n+\t\t\tstrbuf_addf(&buf, \"Merge branch '%.*s'\",\n+\t\t\t\t    merge_arg_len, arg);\n+\t\t\tp = buf.buf;\n+\t\t\tlen = buf.len;\n+\t\t}\n+\n+\t\tret = write_message(p, len, git_path_merge_msg(), 0);\n+\t\tstrbuf_release(&buf);\n+\t\tif (ret) {\n+\t\t\terror_errno(_(\"could not write '%s'\"),\n+\t\t\t\t    git_path_merge_msg());\n+\t\t\tgoto leave_merge;\n+\t\t}\n+\t}\n+\n+\twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n+\t\t      git_path_merge_head(), 0);\n+\twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n+\n+\tbases = get_merge_bases(head_commit, merge_commit);\n+\tfor (j = bases; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &reversed);\n+\tfree_commit_list(bases);\n+\n+\tread_cache();\n+\tinit_merge_options(&o);\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = ref_name.buf;\n+\to.buffer_output = 2;\n+\n+\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (ret <= 0)\n+\t\tfputs(o.obuf.buf, stdout);\n+\tstrbuf_release(&o.obuf);\n+\tif (ret < 0) {\n+\t\terror(_(\"could not even attempt to merge '%.*s'\"),\n+\t\t      merge_arg_len, arg);\n+\t\tgoto leave_merge;\n+\t}\n+\t/*\n+\t * The return value of merge_recursive() is 1 on clean, and 0 on\n+\t * unclean merge.\n+\t *\n+\t * Let's reverse that, so that do_merge() returns 0 upon success and\n+\t * 1 upon failed merge (keeping the return value -1 for the cases where\n+\t * we will want to reschedule the `merge` command).\n+\t */\n+\tret = !ret;\n+\n+\tif (active_cache_changed &&\n+\t    write_locked_index(&the_index, &lock, COMMIT_LOCK)) {\n+\t\tret = error(_(\"merge: Unable to write new index file\"));\n+\t\tgoto leave_merge;\n+\t}\n+\n+\trollback_lock_file(&lock);\n+\tif (ret)\n+\t\trerere(opts->allow_rerere_auto);\n+\telse\n+\t\t/*\n+\t\t * In case of problems, we now want to return a positive\n+\t\t * value (a negative one would indicate that the `merge`\n+\t\t * command needs to be rescheduled).\n+\t\t */\n+\t\tret = !!run_git_commit(git_path_merge_msg(), opts,\n+\t\t\t\t     run_commit_flags);\n+\n+leave_merge:\n+\tstrbuf_release(&ref_name);\n+\trollback_lock_file(&lock);\n+\treturn ret;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -2860,6 +3036,17 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t} else if (item->command == TODO_RESET) {\n \t\t\tif ((res = do_reset(item->arg, item->arg_len, opts)))\n \t\t\t\treschedule = 1;\n+\t\t} else if (item->command == TODO_MERGE) {\n+\t\t\tif ((res = do_merge(item->commit,\n+\t\t\t\t\t    item->arg, item->arg_len,\n+\t\t\t\t\t    item->flags, opts)) < 0)\n+\t\t\t\treschedule = 1;\n+\t\t\telse if (res > 0)\n+\t\t\t\t/* failed with merge conflicts */\n+\t\t\t\treturn error_with_patch(item->commit,\n+\t\t\t\t\t\t\titem->arg,\n+\t\t\t\t\t\t\titem->arg_len, opts,\n+\t\t\t\t\t\t\tres, 0);\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \n@@ -2871,6 +3058,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\ttodo_list->current--;\n \t\t\tif (save_todo(todo_list, opts))\n \t\t\t\treturn -1;\n+\t\t\tif (item->commit)\n+\t\t\t\treturn error_with_patch(item->commit,\n+\t\t\t\t\t\t\titem->arg,\n+\t\t\t\t\t\t\titem->arg_len, opts,\n+\t\t\t\t\t\t\tres, 0);\n \t\t}\n \n \t\ttodo_list->current++;\n@@ -3356,8 +3548,16 @@ int transform_todos(unsigned flags)\n \t\t\t\t\t  short_commit_name(item->commit) :\n \t\t\t\t\t  oid_to_hex(&item->commit->object.oid);\n \n+\t\t\tif (item->command == TODO_MERGE) {\n+\t\t\t\tif (item->flags & TODO_EDIT_MERGE_MSG)\n+\t\t\t\t\tstrbuf_addstr(&buf, \" -c\");\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addstr(&buf, \" -C\");\n+\t\t\t}\n+\n \t\t\tstrbuf_addf(&buf, \" %s\", oid);\n \t\t}\n+\n \t\t/* add all the rest */\n \t\tif (!item->arg_len)\n \t\t\tstrbuf_addch(&buf, '\\n');\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345769","messageId":"bc72c0e471dc441541417ebf7e64374fce6e884e.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 08/17] sequencer: fast-forward `merge` commands, if possible","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:28:56Z","receivedAt":"2018-04-25T12:29:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like with regular `pick` commands, if we are trying to rebase a\nmerge commit, we now test whether the parents of said commit match HEAD\nand the commits to be merged, and fast-forward if possible.\n\nThis is not only faster, but also avoids unnecessary proliferation of\nnew objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 33 ++++++++++++++++++++++++++++++++-\n 1 file changed, 32 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 94f4831a0c3..6722095655d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2687,7 +2687,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \tstruct commit *head_commit, *merge_commit, *i;\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct merge_options o;\n-\tint merge_arg_len, oneline_offset, ret;\n+\tint merge_arg_len, oneline_offset, can_fast_forward, ret;\n \tstatic struct lock_file lock;\n \tconst char *p;\n \n@@ -2772,6 +2772,37 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \t\t}\n \t}\n \n+\t/*\n+\t * If HEAD is not identical to the first parent of the original merge\n+\t * commit, we cannot fast-forward.\n+\t */\n+\tcan_fast_forward = opts->allow_ff && commit && commit->parents &&\n+\t\t!oidcmp(&commit->parents->item->object.oid,\n+\t\t\t&head_commit->object.oid);\n+\n+\t/*\n+\t * If the merge head is different from the original one, we cannot\n+\t * fast-forward.\n+\t */\n+\tif (can_fast_forward) {\n+\t\tstruct commit_list *second_parent = commit->parents->next;\n+\n+\t\tif (second_parent && !second_parent->next &&\n+\t\t    oidcmp(&merge_commit->object.oid,\n+\t\t\t   &second_parent->item->object.oid))\n+\t\t\tcan_fast_forward = 0;\n+\t}\n+\n+\tif (can_fast_forward && commit->parents->next &&\n+\t    !commit->parents->next->next &&\n+\t    !oidcmp(&commit->parents->next->item->object.oid,\n+\t\t    &merge_commit->object.oid)) {\n+\t\trollback_lock_file(&lock);\n+\t\tret = fast_forward_to(&commit->object.oid,\n+\t\t\t\t      &head_commit->object.oid, 0, opts);\n+\t\tgoto leave_merge;\n+\t}\n+\n \twrite_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,\n \t\t      git_path_merge_head(), 0);\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345770","messageId":"3f2b8cf277917b185d03507e5d4a3b3b7a80c4d1.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 09/17] rebase-helper --make-script: introduce a flag to rebase merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:03Z","receivedAt":"2018-04-25T12:29:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The sequencer just learned new commands intended to recreate branch\nstructure (similar in spirit to --preserve-merges, but with a\nsubstantially less-broken design).\n\nLet's allow the rebase--helper to generate todo lists making use of\nthese commands, triggered by the new --rebase-merges option. For a\ncommit topology like this (where the HEAD points to C):\n\n\t- A - B - C\n\t    \\   /\n\t      D\n\nthe generated todo list would look like this:\n\n\t# branch D\n\tpick 0123 A\n\tlabel branch-point\n\tpick 1234 D\n\tlabel D\n\n\treset branch-point\n\tpick 2345 B\n\tmerge -C 3456 D # C\n\nTo keep things simple, we first only implement support for merge commits\nwith exactly two parents, leaving support for octopus merges to a later\npatch series.\n\nAll merge-rebasing todo lists start with a hard-coded `label onto` line.\nThis makes it convenient to refer later on to the revision onto which\neverything is rebased, e.g. as starting point for branches other than\nthe very first one.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/rebase--helper.c |   4 +-\n sequencer.c              | 346 ++++++++++++++++++++++++++++++++++++++-\n sequencer.h              |   1 +\n 3 files changed, 349 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex ad074705bb5..781782e7272 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -12,7 +12,7 @@ static const char * const builtin_rebase_helper_usage[] = {\n int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n-\tunsigned flags = 0, keep_empty = 0;\n+\tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n \tint abbreviate_commands = 0;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n@@ -24,6 +24,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"keep-empty\", &keep_empty, N_(\"keep empty commits\")),\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n+\t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -57,6 +58,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n+\tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n \tif (command == CONTINUE && argc == 1)\ndiff --git a/sequencer.c b/sequencer.c\nindex 6722095655d..e9297122633 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -25,6 +25,8 @@\n #include \"sigchain.h\"\n #include \"unpack-trees.h\"\n #include \"worktree.h\"\n+#include \"oidmap.h\"\n+#include \"oidset.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -3448,6 +3450,343 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)\n \tstrbuf_release(&sob);\n }\n \n+struct labels_entry {\n+\tstruct hashmap_entry entry;\n+\tchar label[FLEX_ARRAY];\n+};\n+\n+static int labels_cmp(const void *fndata, const struct labels_entry *a,\n+\t\t      const struct labels_entry *b, const void *key)\n+{\n+\treturn key ? strcmp(a->label, key) : strcmp(a->label, b->label);\n+}\n+\n+struct string_entry {\n+\tstruct oidmap_entry entry;\n+\tchar string[FLEX_ARRAY];\n+};\n+\n+struct label_state {\n+\tstruct oidmap commit2label;\n+\tstruct hashmap labels;\n+\tstruct strbuf buf;\n+};\n+\n+static const char *label_oid(struct object_id *oid, const char *label,\n+\t\t\t     struct label_state *state)\n+{\n+\tstruct labels_entry *labels_entry;\n+\tstruct string_entry *string_entry;\n+\tstruct object_id dummy;\n+\tsize_t len;\n+\tint i;\n+\n+\tstring_entry = oidmap_get(&state->commit2label, oid);\n+\tif (string_entry)\n+\t\treturn string_entry->string;\n+\n+\t/*\n+\t * For \"uninteresting\" commits, i.e. commits that are not to be\n+\t * rebased, and which can therefore not be labeled, we use a unique\n+\t * abbreviation of the commit name. This is slightly more complicated\n+\t * than calling find_unique_abbrev() because we also need to make\n+\t * sure that the abbreviation does not conflict with any other\n+\t * label.\n+\t *\n+\t * We disallow \"interesting\" commits to be labeled by a string that\n+\t * is a valid full-length hash, to ensure that we always can find an\n+\t * abbreviation for any uninteresting commit's names that does not\n+\t * clash with any other label.\n+\t */\n+\tif (!label) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&state->buf);\n+\t\tstrbuf_grow(&state->buf, GIT_SHA1_HEXSZ);\n+\t\tlabel = p = state->buf.buf;\n+\n+\t\tfind_unique_abbrev_r(p, oid, default_abbrev);\n+\n+\t\t/*\n+\t\t * We may need to extend the abbreviated hash so that there is\n+\t\t * no conflicting label.\n+\t\t */\n+\t\tif (hashmap_get_from_hash(&state->labels, strihash(p), p)) {\n+\t\t\tsize_t i = strlen(p) + 1;\n+\n+\t\t\toid_to_hex_r(p, oid);\n+\t\t\tfor (; i < GIT_SHA1_HEXSZ; i++) {\n+\t\t\t\tchar save = p[i];\n+\t\t\t\tp[i] = '\\0';\n+\t\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t\t   strihash(p), p))\n+\t\t\t\t\tbreak;\n+\t\t\t\tp[i] = save;\n+\t\t\t}\n+\t\t}\n+\t} else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&\n+\t\t    !get_oid_hex(label, &dummy)) ||\n+\t\t   (len == 1 && *label == '#') ||\n+\t\t   hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t strihash(label), label)) {\n+\t\t/*\n+\t\t * If the label already exists, or if the label is a valid full\n+\t\t * OID, or the label is a '#' (which we use as a separator\n+\t\t * between merge heads and oneline), we append a dash and a\n+\t\t * number to make it unique.\n+\t\t */\n+\t\tstruct strbuf *buf = &state->buf;\n+\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, label, len);\n+\n+\t\tfor (i = 2; ; i++) {\n+\t\t\tstrbuf_setlen(buf, len);\n+\t\t\tstrbuf_addf(buf, \"-%d\", i);\n+\t\t\tif (!hashmap_get_from_hash(&state->labels,\n+\t\t\t\t\t\t   strihash(buf->buf),\n+\t\t\t\t\t\t   buf->buf))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tlabel = buf->buf;\n+\t}\n+\n+\tFLEX_ALLOC_STR(labels_entry, label, label);\n+\thashmap_entry_init(labels_entry, strihash(label));\n+\thashmap_add(&state->labels, labels_entry);\n+\n+\tFLEX_ALLOC_STR(string_entry, string, label);\n+\toidcpy(&string_entry->entry.oid, oid);\n+\toidmap_put(&state->commit2label, string_entry);\n+\n+\treturn string_entry->string;\n+}\n+\n+static int make_script_with_merges(struct pretty_print_context *pp,\n+\t\t\t\t   struct rev_info *revs, FILE *out,\n+\t\t\t\t   unsigned flags)\n+{\n+\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n+\tstruct strbuf label = STRBUF_INIT;\n+\tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n+\tstruct commit_list *tips = NULL, **tips_tail = &tips;\n+\tstruct commit *commit;\n+\tstruct oidmap commit2todo = OIDMAP_INIT;\n+\tstruct string_entry *entry;\n+\tstruct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,\n+\t\tshown = OIDSET_INIT;\n+\tstruct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };\n+\n+\tint abbr = flags & TODO_LIST_ABBREVIATE_CMDS;\n+\tconst char *cmd_pick = abbr ? \"p\" : \"pick\",\n+\t\t*cmd_label = abbr ? \"l\" : \"label\",\n+\t\t*cmd_reset = abbr ? \"t\" : \"reset\",\n+\t\t*cmd_merge = abbr ? \"m\" : \"merge\";\n+\n+\toidmap_init(&commit2todo, 0);\n+\toidmap_init(&state.commit2label, 0);\n+\thashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);\n+\tstrbuf_init(&state.buf, 32);\n+\n+\tif (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {\n+\t\tstruct object_id *oid = &revs->cmdline.rev[0].item->oid;\n+\t\tFLEX_ALLOC_STR(entry, string, \"onto\");\n+\t\toidcpy(&entry->entry.oid, oid);\n+\t\toidmap_put(&state.commit2label, entry);\n+\t}\n+\n+\t/*\n+\t * First phase:\n+\t * - get onelines for all commits\n+\t * - gather all branch tips (i.e. 2nd or later parents of merges)\n+\t * - label all branch tips\n+\t */\n+\twhile ((commit = get_revision(revs))) {\n+\t\tstruct commit_list *to_merge;\n+\t\tint is_octopus;\n+\t\tconst char *p1, *p2;\n+\t\tstruct object_id *oid;\n+\t\tint is_empty;\n+\n+\t\ttail = &commit_list_insert(commit, tail)->next;\n+\t\toidset_insert(&interesting, &commit->object.oid);\n+\n+\t\tis_empty = is_original_commit_empty(commit);\n+\t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&oneline);\n+\t\tpretty_print_commit(pp, commit, &oneline);\n+\n+\t\tto_merge = commit->parents ? commit->parents->next : NULL;\n+\t\tif (!to_merge) {\n+\t\t\t/* non-merge commit: easy case */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tif (!keep_empty && is_empty)\n+\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n+\t\t\t\t    oid_to_hex(&commit->object.oid),\n+\t\t\t\t    oneline.buf);\n+\n+\t\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\t\toidmap_put(&commit2todo, entry);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tis_octopus = to_merge && to_merge->next;\n+\n+\t\tif (is_octopus)\n+\t\t\tBUG(\"Octopus merges not yet supported\");\n+\n+\t\t/* Create a label */\n+\t\tstrbuf_reset(&label);\n+\t\tif (skip_prefix(oneline.buf, \"Merge \", &p1) &&\n+\t\t    (p1 = strchr(p1, '\\'')) &&\n+\t\t    (p2 = strchr(++p1, '\\'')))\n+\t\t\tstrbuf_add(&label, p1, p2 - p1);\n+\t\telse if (skip_prefix(oneline.buf, \"Merge pull request \",\n+\t\t\t\t     &p1) &&\n+\t\t\t (p1 = strstr(p1, \" from \")))\n+\t\t\tstrbuf_addstr(&label, p1 + strlen(\" from \"));\n+\t\telse\n+\t\t\tstrbuf_addbuf(&label, &oneline);\n+\n+\t\tfor (p1 = label.buf; *p1; p1++)\n+\t\t\tif (isspace(*p1))\n+\t\t\t\t*(char *)p1 = '-';\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s -C %s\",\n+\t\t\t    cmd_merge, oid_to_hex(&commit->object.oid));\n+\n+\t\t/* label the tip of merged branch */\n+\t\toid = &to_merge->item->object.oid;\n+\t\tstrbuf_addch(&buf, ' ');\n+\n+\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, NULL, &state));\n+\t\telse {\n+\t\t\ttips_tail = &commit_list_insert(to_merge->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\n+\t\t\tstrbuf_addstr(&buf, label_oid(oid, label.buf, &state));\n+\t\t}\n+\t\tstrbuf_addf(&buf, \" # %s\", oneline.buf);\n+\n+\t\tFLEX_ALLOC_STR(entry, string, buf.buf);\n+\t\toidcpy(&entry->entry.oid, &commit->object.oid);\n+\t\toidmap_put(&commit2todo, entry);\n+\t}\n+\n+\t/*\n+\t * Second phase:\n+\t * - label branch points\n+\t * - add HEAD to the branch tips\n+\t */\n+\tfor (iter = commits; iter; iter = iter->next) {\n+\t\tstruct commit_list *parent = iter->item->parents;\n+\t\tfor (; parent; parent = parent->next) {\n+\t\t\tstruct object_id *oid = &parent->item->object.oid;\n+\t\t\tif (!oidset_contains(&interesting, oid))\n+\t\t\t\tcontinue;\n+\t\t\tif (!oidset_contains(&child_seen, oid))\n+\t\t\t\toidset_insert(&child_seen, oid);\n+\t\t\telse\n+\t\t\t\tlabel_oid(oid, \"branch-point\", &state);\n+\t\t}\n+\n+\t\t/* Add HEAD as implict \"tip of branch\" */\n+\t\tif (!iter->next)\n+\t\t\ttips_tail = &commit_list_insert(iter->item,\n+\t\t\t\t\t\t\ttips_tail)->next;\n+\t}\n+\n+\t/*\n+\t * Third phase: output the todo list. This is a bit tricky, as we\n+\t * want to avoid jumping back and forth between revisions. To\n+\t * accomplish that goal, we walk backwards from the branch tips,\n+\t * gathering commits not yet shown, reversing the list on the fly,\n+\t * then outputting that list (labeling revisions as needed).\n+\t */\n+\tfprintf(out, \"%s onto\\n\", cmd_label);\n+\tfor (iter = tips; iter; iter = iter->next) {\n+\t\tstruct commit_list *list = NULL, *iter2;\n+\n+\t\tcommit = iter->item;\n+\t\tif (oidset_contains(&shown, &commit->object.oid))\n+\t\t\tcontinue;\n+\t\tentry = oidmap_get(&state.commit2label, &commit->object.oid);\n+\n+\t\tif (entry)\n+\t\t\tfprintf(out, \"\\n# Branch %s\\n\", entry->string);\n+\t\telse\n+\t\t\tfprintf(out, \"\\n\");\n+\n+\t\twhile (oidset_contains(&interesting, &commit->object.oid) &&\n+\t\t       !oidset_contains(&shown, &commit->object.oid)) {\n+\t\t\tcommit_list_insert(commit, &list);\n+\t\t\tif (!commit->parents) {\n+\t\t\t\tcommit = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tcommit = commit->parents->item;\n+\t\t}\n+\n+\t\tif (!commit)\n+\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\telse {\n+\t\t\tconst char *to = NULL;\n+\n+\t\t\tentry = oidmap_get(&state.commit2label,\n+\t\t\t\t\t   &commit->object.oid);\n+\t\t\tif (entry)\n+\t\t\t\tto = entry->string;\n+\n+\t\t\tif (!to || !strcmp(to, \"onto\"))\n+\t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\n+\t\t\telse {\n+\t\t\t\tstrbuf_reset(&oneline);\n+\t\t\t\tpretty_print_commit(pp, commit, &oneline);\n+\t\t\t\tfprintf(out, \"%s %s # %s\\n\",\n+\t\t\t\t\tcmd_reset, to, oneline.buf);\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (iter2 = list; iter2; iter2 = iter2->next) {\n+\t\t\tstruct object_id *oid = &iter2->item->object.oid;\n+\t\t\tentry = oidmap_get(&commit2todo, oid);\n+\t\t\t/* only show if not already upstream */\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s\\n\", entry->string);\n+\t\t\tentry = oidmap_get(&state.commit2label, oid);\n+\t\t\tif (entry)\n+\t\t\t\tfprintf(out, \"%s %s\\n\",\n+\t\t\t\t\tcmd_label, entry->string);\n+\t\t\toidset_insert(&shown, oid);\n+\t\t}\n+\n+\t\tfree_commit_list(list);\n+\t}\n+\n+\tfree_commit_list(commits);\n+\tfree_commit_list(tips);\n+\n+\tstrbuf_release(&label);\n+\tstrbuf_release(&oneline);\n+\tstrbuf_release(&buf);\n+\n+\toidmap_free(&commit2todo, 1);\n+\toidmap_free(&state.commit2label, 1);\n+\thashmap_free(&state.labels, 1);\n+\tstrbuf_release(&state.buf);\n+\n+\treturn 0;\n+}\n+\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags)\n {\n@@ -3458,10 +3797,12 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tstruct commit *commit;\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n+\tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n \tinit_revisions(&revs, NULL);\n \trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n+\tif (!rebase_merges)\n+\t\trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n \trevs.limited = 1;\n \trevs.reverse = 1;\n@@ -3486,6 +3827,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n \tif (prepare_revision_walk(&revs) < 0)\n \t\treturn error(_(\"make_script: error preparing revisions\"));\n \n+\tif (rebase_merges)\n+\t\treturn make_script_with_merges(&pp, &revs, out, flags);\n+\n \twhile ((commit = get_revision(&revs))) {\n \t\tint is_empty  = is_original_commit_empty(commit);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex e45b178dfc4..6bc4da17243 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -59,6 +59,7 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_KEEP_EMPTY (1U << 0)\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n+#define TODO_LIST_REBASE_MERGES (1U << 3)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345771","messageId":"922adf04f1cbb04e83f075cb99c6673d2fee5026.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 10/17] rebase: introduce the --rebase-merges option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:04Z","receivedAt":"2018-04-25T12:29:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Once upon a time, this here developer thought: wouldn't it be nice if,\nsay, Git for Windows' patches on top of core Git could be represented as\na thicket of branches, and be rebased on top of core Git in order to\nmaintain a cherry-pick'able set of patch series?\n\nThe original attempt to answer this was: git rebase --preserve-merges.\n\nHowever, that experiment was never intended as an interactive option,\nand it only piggy-backed on git rebase --interactive because that\ncommand's implementation looked already very, very familiar: it was\ndesigned by the same person who designed --preserve-merges: yours truly.\n\nSome time later, some other developer (I am looking at you, Andreas!\n;-)) decided that it would be a good idea to allow --preserve-merges to\nbe combined with --interactive (with caveats!) and the Git maintainer\n(well, the interim Git maintainer during Junio's absence, that is)\nagreed, and that is when the glamor of the --preserve-merges design\nstarted to fall apart rather quickly and unglamorously.\n\nThe reason? In --preserve-merges mode, the parents of a merge commit (or\nfor that matter, of *any* commit) were not stated explicitly, but were\n*implied* by the commit name passed to the `pick` command.\n\nThis made it impossible, for example, to reorder commits. Not to mention\nto move commits between branches or, deity forbid, to split topic branches\ninto two.\n\nAlas, these shortcomings also prevented that mode (whose original\npurpose was to serve Git for Windows' needs, with the additional hope\nthat it may be useful to others, too) from serving Git for Windows'\nneeds.\n\nFive years later, when it became really untenable to have one unwieldy,\nbig hodge-podge patch series of partly related, partly unrelated patches\nin Git for Windows that was rebased onto core Git's tags from time to\ntime (earning the undeserved wrath of the developer of the ill-fated\ngit-remote-hg series that first obsoleted Git for Windows' competing\napproach, only to be abandoned without maintainer later) was really\nuntenable, the \"Git garden shears\" were born [*1*/*2*]: a script,\npiggy-backing on top of the interactive rebase, that would first\ndetermine the branch topology of the patches to be rebased, create a\npseudo todo list for further editing, transform the result into a real\ntodo list (making heavy use of the `exec` command to \"implement\" the\nmissing todo list commands) and finally recreate the patch series on\ntop of the new base commit.\n\nThat was in 2013. And it took about three weeks to come up with the\ndesign and implement it as an out-of-tree script. Needless to say, the\nimplementation needed quite a few years to stabilize, all the while the\ndesign itself proved itself sound.\n\nWith this patch, the goodness of the Git garden shears comes to `git\nrebase -i` itself. Passing the `--rebase-merges` option will generate\na todo list that can be understood readily, and where it is obvious\nhow to reorder commits. New branches can be introduced by inserting\n`label` commands and calling `merge <label>`. And once this mode will\nhave become stable and universally accepted, we can deprecate the design\nmistake that was `--preserve-merges`.\n\nLink *1*:\nhttps://github.com/msysgit/msysgit/blob/master/share/msysGit/shears.sh\nLink *2*:\nhttps://github.com/git-for-windows/build-extra/blob/master/shears.sh\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt           |  21 ++-\n contrib/completion/git-completion.bash |   2 +-\n git-rebase--interactive.sh             |   1 +\n git-rebase.sh                          |   6 +\n t/t3430-rebase-merges.sh               | 179 +++++++++++++++++++++++++\n 5 files changed, 207 insertions(+), 2 deletions(-)\n create mode 100755 t/t3430-rebase-merges.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex dd852068b1d..7f1756f1eba 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -379,6 +379,24 @@ The commit list format can be changed by setting the configuration option\n rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n+-r::\n+--rebase-merges::\n+\tBy default, a rebase will simply drop merge commits from the todo\n+\tlist, and put the rebased commits into a single, linear branch.\n+\tWith `--rebase-merges`, the rebase will instead try to preserve\n+\tthe branching structure within the commits that are to be rebased,\n+\tby recreating the merge commits. Any resolved merge conflicts or\n+\tmanual amendments in these merge commits will have to be\n+\tresolved/re-applied manually.\n++\n+This mode is similar in spirit to `--preserve-merges`, but in contrast to\n+that option works well in interactive rebases: commits can be reordered,\n+inserted and dropped at will.\n++\n+It is currently only possible to recreate the merge commits using the\n+`recursive` merge strategy; Different merge strategies can be used only via\n+explicit `exec git merge -s <strategy> [...]` commands.\n+\n -p::\n --preserve-merges::\n \tRecreate merge commits instead of flattening the history by replaying\n@@ -781,7 +799,8 @@ BUGS\n The todo list presented by `--preserve-merges --interactive` does not\n represent the topology of the revision graph.  Editing commits and\n rewording their commit messages should work fine, but attempts to\n-reorder commits tend to produce counterintuitive results.\n+reorder commits tend to produce counterintuitive results. Use\n+`--rebase-merges` in such scenarios instead.\n \n For example, an attempt to rearrange\n ------------\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 01dd9ff07a2..e6469004099 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1944,7 +1944,7 @@ _git_rebase ()\n \t--*)\n \t\t__gitcomp \"\n \t\t\t--onto --merge --strategy --interactive\n-\t\t\t--preserve-merges --stat --no-stat\n+\t\t\t--rebase-merges --preserve-merges --stat --no-stat\n \t\t\t--committer-date-is-author-date --ignore-date\n \t\t\t--ignore-whitespace --whitespace=\n \t\t\t--autosquash --no-autosquash\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex acb4bfd3fc8..e29da634339 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -970,6 +970,7 @@ git_rebase__interactive () {\n \tinit_revisions_and_shortrevisions\n \n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n+\t\t${rebase_merges:+--rebase-merges} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n \ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex ded5de085a8..a553f969d11 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n+r,rebase-merges!   try to rebase merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -89,6 +90,7 @@ type=\n state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n+rebase_merges=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -280,6 +282,10 @@ do\n \t--no-keep-empty)\n \t\tkeep_empty=\n \t\t;;\n+\t--rebase-merges)\n+\t\trebase_merges=t\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nnew file mode 100755\nindex 00000000000..5f0febb9970\n--- /dev/null\n+++ b/t/t3430-rebase-merges.sh\n@@ -0,0 +1,179 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2018 Johannes E. Schindelin\n+#\n+\n+test_description='git rebase -i --rebase-merges\n+\n+This test runs git rebase \"interactively\", retaining the branch structure by\n+recreating merge commits.\n+\n+Initial setup:\n+\n+    -- B --                   (first)\n+   /       \\\n+ A - C - D - E - H            (master)\n+       \\       /\n+         F - G                (second)\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_cmp_graph () {\n+\tcat >expect &&\n+\tgit log --graph --boundary --format=%s \"$@\" >output &&\n+\tsed \"s/ *$//\" <output >output.trimmed &&\n+\ttest_cmp expect output.trimmed\n+}\n+\n+test_expect_success 'setup' '\n+\twrite_script replace-editor.sh <<-\\EOF &&\n+\tmv \"$1\" \"$(git rev-parse --git-path ORIGINAL-TODO)\"\n+\tcp script-from-scratch \"$1\"\n+\tEOF\n+\n+\ttest_commit A &&\n+\tgit checkout -b first &&\n+\ttest_commit B &&\n+\tgit checkout master &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit merge --no-commit B &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag -m E E &&\n+\tgit checkout -b second C &&\n+\ttest_commit F &&\n+\ttest_commit G &&\n+\tgit checkout master &&\n+\tgit merge --no-commit G &&\n+\ttest_tick &&\n+\tgit commit -m H &&\n+\tgit tag -m H H\n+'\n+\n+test_expect_success 'create completely different structure' '\n+\tcat >script-from-scratch <<-\\EOF &&\n+\tlabel onto\n+\n+\t# onebranch\n+\tpick G\n+\tpick D\n+\tlabel onebranch\n+\n+\t# second\n+\treset onto\n+\tpick B\n+\tlabel second\n+\n+\treset onto\n+\tmerge -C H second\n+\tmerge onebranch # Merge the topic branch '\\''onebranch'\\''\n+\tEOF\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\tgit rebase -i -r A &&\n+\ttest_cmp_graph <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t* |   H\n+\t|\\ \\\n+\t| |/\n+\t|/|\n+\t| * B\n+\t|/\n+\t* A\n+\tEOF\n+'\n+\n+test_expect_success 'generate correct todo list' '\n+\tcat >expect <<-\\EOF &&\n+\tlabel onto\n+\n+\treset onto\n+\tpick d9df450 B\n+\tlabel E\n+\n+\treset onto\n+\tpick 5dee784 C\n+\tlabel branch-point\n+\tpick ca2c861 F\n+\tpick 088b00a G\n+\tlabel H\n+\n+\treset branch-point # C\n+\tpick 12bd07b D\n+\tmerge -C 2051b56 E # E\n+\tmerge -C 233d48a H # H\n+\n+\tEOF\n+\n+\tgrep -v \"^#\" <.git/ORIGINAL-TODO >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success '`reset` refuses to overwrite untracked files' '\n+\tgit checkout -b refuse-to-reset &&\n+\ttest_commit dont-overwrite-untracked &&\n+\tgit checkout @{-1} &&\n+\t: >dont-overwrite-untracked.t &&\n+\techo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_must_fail git rebase -r HEAD &&\n+\tgit rebase --abort\n+'\n+\n+test_expect_success 'failed `merge` writes patch (may be rescheduled, too)' '\n+\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\tgit checkout -b conflicting-merge A &&\n+\n+\t: fail because of conflicting untracked file &&\n+\t>G.t &&\n+\techo \"merge -C H G\" >script-from-scratch &&\n+\ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\ttest_tick &&\n+\ttest_must_fail git rebase -ir HEAD &&\n+\tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n+\tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n+\ttest_path_is_file .git/rebase-merge/patch &&\n+\n+\t: fail because of merge conflict &&\n+\trm G.t .git/rebase-merge/patch &&\n+\tgit reset --hard &&\n+\ttest_commit conflicting-G G.t not-G conflicting-G &&\n+\ttest_must_fail git rebase --continue &&\n+\t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n+\ttest_path_is_file .git/rebase-merge/patch\n+'\n+\n+test_expect_success 'with a branch tip that was cherry-picked already' '\n+\tgit checkout -b already-upstream master &&\n+\tbase=\"$(git rev-parse --verify HEAD)\" &&\n+\n+\ttest_commit A1 &&\n+\ttest_commit A2 &&\n+\tgit reset --hard $base &&\n+\ttest_commit B1 &&\n+\ttest_tick &&\n+\tgit merge -m \"Merge branch A\" A2 &&\n+\n+\tgit checkout -b upstream-with-a2 $base &&\n+\ttest_tick &&\n+\tgit cherry-pick A2 &&\n+\n+\tgit checkout already-upstream &&\n+\ttest_tick &&\n+\tgit rebase -i -r upstream-with-a2 &&\n+\ttest_cmp_graph upstream-with-a2.. <<-\\EOF\n+\t*   Merge branch A\n+\t|\\\n+\t| * A1\n+\t* | B1\n+\t|/\n+\to A2\n+\tEOF\n+'\n+\n+test_done\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345772","messageId":"23a1e05c64edc54c15f6d22c5fd6120b3711fd2a.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 11/17] rebase --rebase-merges: add test for --keep-empty","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:14Z","receivedAt":"2018-04-25T12:29:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf there are empty commits on the left hand side of $upstream...HEAD\nthen the empty commits on the right hand side that we want to keep are\nbeing pruned.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n t/t3421-rebase-topology-linear.sh | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex b078f930462..e7438ad06ac 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -217,6 +217,7 @@ test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n test_run_rebase failure -p\n+test_run_rebase success --rebase-merges\n \n #       m\n #      /\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345773","messageId":"7db22d85599229df85b9f48025c7983d376651d2.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 12/17] sequencer: make refs generated by the `label` command worktree-local","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:16Z","receivedAt":"2018-04-25T12:29:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This allows for rebases to be run in parallel in separate worktrees\n(think: interrupted in the middle of one rebase, being asked to perform\na different rebase, adding a separate worktree just for that job).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n refs.c                   |  3 ++-\n t/t3430-rebase-merges.sh | 14 ++++++++++++++\n 2 files changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 8b7a77fe5ee..f61ec58d1df 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -600,7 +600,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)\n static int is_per_worktree_ref(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n+\t\tstarts_with(refname, \"refs/bisect/\") ||\n+\t\tstarts_with(refname, \"refs/rewritten/\");\n }\n \n static int is_pseudoref_syntax(const char *refname)\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 5f0febb9970..96853784ec0 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -176,4 +176,18 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'refs/rewritten/* is worktree-local' '\n+\tgit worktree add wt &&\n+\tcat >wt/script-from-scratch <<-\\EOF &&\n+\tlabel xyz\n+\texec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :\n+\texec git rev-parse --verify refs/rewritten/xyz >b\n+\tEOF\n+\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tgit -C wt rebase -i HEAD &&\n+\ttest_must_be_empty wt/a &&\n+\ttest_cmp_rev HEAD \"$(cat wt/b)\"\n+'\n+\n test_done\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345774","messageId":"ae8bea78692ba6e2fb82db8e1cbeee645a71fe04.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 13/17] sequencer: handle post-rewrite for merge commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:29Z","receivedAt":"2018-04-25T12:29:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"In the previous patches, we implemented the basic functionality of the\n`git rebase -i --rebase-merges` command, in particular the `merge`\ncommand to create merge commits in the sequencer.\n\nThe interactive rebase is a lot more these days, though, than a simple\ncherry-pick in a loop. For example, it calls the post-rewrite hook (if\nany) after rebasing with a mapping of the old->new commits.\n\nThis patch implements the post-rewrite handling for the `merge` command\nwe just introduced. The other commands that were added recently (`label`\nand `reset`) do not create new commits, therefore post-rewrite hooks do\nnot need to handle them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c              |  5 ++++-\n t/t3430-rebase-merges.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 29 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e9297122633..558efc1af6e 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3074,7 +3074,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n \t\t\t\t\t    item->arg, item->arg_len,\n \t\t\t\t\t    item->flags, opts)) < 0)\n \t\t\t\treschedule = 1;\n-\t\t\telse if (res > 0)\n+\t\t\telse if (item->commit)\n+\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t\t\t    peek_command(todo_list, 1));\n+\t\t\tif (res > 0)\n \t\t\t\t/* failed with merge conflicts */\n \t\t\t\treturn error_with_patch(item->commit,\n \t\t\t\t\t\t\titem->arg,\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 96853784ec0..e9c5dc1cd95 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -190,4 +190,29 @@ test_expect_success 'refs/rewritten/* is worktree-local' '\n \ttest_cmp_rev HEAD \"$(cat wt/b)\"\n '\n \n+test_expect_success 'post-rewrite hook and fixups work for merges' '\n+\tgit checkout -b post-rewrite &&\n+\ttest_commit same1 &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit same2 &&\n+\tgit merge -m \"to fix up\" same1 &&\n+\techo same old same old >same2.t &&\n+\ttest_tick &&\n+\tgit commit --fixup HEAD same2.t &&\n+\tfixup=\"$(git rev-parse HEAD)\" &&\n+\n+\tmkdir -p .git/hooks &&\n+\ttest_when_finished \"rm .git/hooks/post-rewrite\" &&\n+\techo \"cat >actual\" | write_script .git/hooks/post-rewrite &&\n+\n+\ttest_tick &&\n+\tgit rebase -i --autosquash -r HEAD^^^ &&\n+\tprintf \"%s %s\\n%s %s\\n%s %s\\n%s %s\\n\" >expect $(git rev-parse \\\n+\t\t$fixup^^2 HEAD^2 \\\n+\t\t$fixup^^ HEAD^ \\\n+\t\t$fixup^ HEAD \\\n+\t\t$fixup HEAD) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345775","messageId":"c2034fcd4cd8c161a4310167423ea530ad144f6b.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 15/17] pull: accept --rebase=merges to recreate the branch topology","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:38Z","receivedAt":"2018-04-25T12:29:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Similar to the `preserve` mode simply passing the `--preserve-merges`\noption to the `rebase` command, the `merges` mode simply passes the\n`--rebase-merges` option.\n\nThis will allow users to conveniently rebase non-trivial commit\ntopologies when pulling new commits, without flattening them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt               |  8 ++++++++\n Documentation/git-pull.txt             |  6 +++++-\n builtin/pull.c                         | 14 ++++++++++----\n builtin/remote.c                       | 18 ++++++++++++++----\n contrib/completion/git-completion.bash |  2 +-\n 5 files changed, 38 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 2659153cb37..d6bcb5dcb67 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1058,6 +1058,10 @@ branch.<name>.rebase::\n \t\"git pull\" is run. See \"pull.rebase\" for doing this in a non\n \tbranch-specific manner.\n +\n+When `merges`, pass the `--rebase-merges` option to 'git rebase'\n+so that the local merge commits are included in the rebase (see\n+linkgit:git-rebase[1] for details).\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\n@@ -2617,6 +2621,10 @@ pull.rebase::\n \tpull\" is run. See \"branch.<name>.rebase\" for setting this on a\n \tper-branch basis.\n +\n+When `merges`, pass the `--rebase-merges` option to 'git rebase'\n+so that the local merge commits are included in the rebase (see\n+linkgit:git-rebase[1] for details).\n++\n When preserve, also pass `--preserve-merges` along to 'git rebase'\n so that locally committed merge commits will not be flattened\n by running 'git pull'.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex ce05b7a5b13..4e0ad6fd8e0 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -101,13 +101,17 @@ Options related to merging\n include::merge-options.txt[]\n \n -r::\n---rebase[=false|true|preserve|interactive]::\n+--rebase[=false|true|merges|preserve|interactive]::\n \tWhen true, rebase the current branch on top of the upstream\n \tbranch after fetching. If there is a remote-tracking branch\n \tcorresponding to the upstream branch and the upstream branch\n \twas rebased since last fetched, the rebase uses that information\n \tto avoid rebasing non-local changes.\n +\n+When set to `merges`, rebase using `git rebase --rebase-merges` so that\n+the local merge commits are included in the rebase (see\n+linkgit:git-rebase[1] for details).\n++\n When set to preserve, rebase with the `--preserve-merges` option passed\n to `git rebase` so that locally created merge commits will not be flattened.\n +\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 71aac5005e0..c719a4f9d73 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,14 +27,16 @@ enum rebase_type {\n \tREBASE_FALSE = 0,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n+\tREBASE_MERGES,\n \tREBASE_INTERACTIVE\n };\n \n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n- * \"preserve\", returns REBASE_PRESERVE. If value is a invalid value, dies with\n- * a fatal error if fatal is true, otherwise returns REBASE_INVALID.\n+ * \"merges\", returns REBASE_MERGES. If value is \"preserve\", returns\n+ * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if\n+ * fatal is true, otherwise returns REBASE_INVALID.\n  */\n static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\tint fatal)\n@@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \t\treturn REBASE_TRUE;\n \telse if (!strcmp(value, \"preserve\"))\n \t\treturn REBASE_PRESERVE;\n+\telse if (!strcmp(value, \"merges\"))\n+\t\treturn REBASE_MERGES;\n \telse if (!strcmp(value, \"interactive\"))\n \t\treturn REBASE_INTERACTIVE;\n \n@@ -130,7 +134,7 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \t{ OPTION_CALLBACK, 'r', \"rebase\", &opt_rebase,\n-\t  \"false|true|preserve|interactive\",\n+\t  \"false|true|merges|preserve|interactive\",\n \t  N_(\"incorporate changes by rebasing rather than merging\"),\n \t  PARSE_OPT_OPTARG, parse_opt_rebase },\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n@@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head,\n \targv_push_verbosity(&args);\n \n \t/* Options passed to git-rebase */\n-\tif (opt_rebase == REBASE_PRESERVE)\n+\tif (opt_rebase == REBASE_MERGES)\n+\t\targv_array_push(&args, \"--rebase-merges\");\n+\telse if (opt_rebase == REBASE_PRESERVE)\n \t\targv_array_push(&args, \"--preserve-merges\");\n \telse if (opt_rebase == REBASE_INTERACTIVE)\n \t\targv_array_push(&args, \"--interactive\");\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 805ffc05cdb..45c9219e07a 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -245,7 +245,9 @@ static int add(int argc, const char **argv)\n struct branch_info {\n \tchar *remote_name;\n \tstruct string_list merge;\n-\tenum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;\n+\tenum {\n+\t\tNO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES\n+\t} rebase;\n };\n \n static struct string_list branch_list = STRING_LIST_INIT_NODUP;\n@@ -306,6 +308,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)\n \t\t\t\tinfo->rebase = v;\n \t\t\telse if (!strcmp(value, \"preserve\"))\n \t\t\t\tinfo->rebase = NORMAL_REBASE;\n+\t\t\telse if (!strcmp(value, \"merges\"))\n+\t\t\t\tinfo->rebase = REBASE_MERGES;\n \t\t\telse if (!strcmp(value, \"interactive\"))\n \t\t\t\tinfo->rebase = INTERACTIVE_REBASE;\n \t\t}\n@@ -963,9 +967,15 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)\n \n \tprintf(\"    %-*s \", show_info->width, item->string);\n \tif (branch_info->rebase) {\n-\t\tprintf_ln(branch_info->rebase == INTERACTIVE_REBASE\n-\t\t\t  ? _(\"rebases interactively onto remote %s\")\n-\t\t\t  : _(\"rebases onto remote %s\"), merge->items[0].string);\n+\t\tconst char *msg;\n+\t\tif (branch_info->rebase == INTERACTIVE_REBASE)\n+\t\t\tmsg = _(\"rebases interactively onto remote %s\");\n+\t\telse if (branch_info->rebase == REBASE_MERGES)\n+\t\t\tmsg = _(\"rebases interactively (with merges) onto \"\n+\t\t\t\t\"remote %s\");\n+\t\telse\n+\t\t\tmsg = _(\"rebases onto remote %s\");\n+\t\tprintf_ln(msg, merge->items[0].string);\n \t\treturn 0;\n \t} else if (show_info->any_rebase) {\n \t\tprintf_ln(_(\" merges with remote %s\"), merge->items[0].string);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e6469004099..7fe2e213d1d 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2115,7 +2115,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n-\t\t__gitcomp \"false true preserve interactive\"\n+\t\t__gitcomp \"false true merges preserve interactive\"\n \t\treturn\n \t\t;;\n \tremote.pushdefault)\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345776","messageId":"5824b4537d7879f492d3ac908b7a10db8976ad6a.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 16/17] rebase -i: introduce --rebase-merges=[no-]rebase-cousins","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:40Z","receivedAt":"2018-04-25T12:30:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When running `git rebase --rebase-merges` non-interactively with an\nancestor of HEAD as <upstream> (or leaving the todo list unmodified),\nwe would ideally recreate the exact same commits as before the rebase.\n\nHowever, if there are commits in the commit range <upstream>.. that do not\nhave <upstream> as direct ancestor (i.e. if `git log <upstream>..` would\nshow commits that are omitted by `git log --ancestry-path <upstream>..`),\nthis is currently not the case: we would turn them into commits that have\n<upstream> as direct ancestor.\n\nLet's illustrate that with a diagram:\n\n        C\n      /   \\\nA - B - E - F\n  \\   /\n    D\n\nCurrently, after running `git rebase -i --rebase-merges B`, the new branch\nstructure would be (pay particular attention to the commit `D`):\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n      \\    /\n        D'\n\nThis is not really preserving the branch topology from before! The\nreason is that the commit `D` does not have `B` as ancestor, and\ntherefore it gets rebased onto `B`.\n\nThis is unintuitive behavior. Even worse, when recreating branch\nstructure, most use cases would appear to want cousins *not* to be\nrebased onto the new base commit. For example, Git for Windows (the\nheaviest user of the Git garden shears, which served as the blueprint\nfor --rebase-merges) frequently merges branches from `next` early, and\nthese branches certainly do *not* want to be rebased. In the example\nabove, the desired outcome would look like this:\n\n       --- C' --\n      /         \\\nA - B ------ E' - F'\n  \\        /\n   -- D' --\n\nLet's introduce the term \"cousins\" for such commits (\"D\" in the\nexample), and let's not rebase them by default. For hypothetical\nuse cases where cousins *do* need to be rebased, `git rebase\n--rebase=merges=rebase-cousins` needs to be used.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt | 15 +++++++++++----\n builtin/rebase--helper.c     |  9 ++++++++-\n git-rebase--interactive.sh   |  1 +\n git-rebase.sh                | 12 +++++++++++-\n sequencer.c                  |  4 ++++\n sequencer.h                  |  6 ++++++\n t/t3430-rebase-merges.sh     | 18 ++++++++++++++++++\n 7 files changed, 59 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 7f1756f1eba..fe681d69281 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -380,7 +380,7 @@ rebase.instructionFormat.  A customized instruction format will automatically\n have the long commit hash prepended to the format.\n \n -r::\n---rebase-merges::\n+--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n \tBy default, a rebase will simply drop merge commits from the todo\n \tlist, and put the rebased commits into a single, linear branch.\n \tWith `--rebase-merges`, the rebase will instead try to preserve\n@@ -389,9 +389,16 @@ have the long commit hash prepended to the format.\n \tmanual amendments in these merge commits will have to be\n \tresolved/re-applied manually.\n +\n-This mode is similar in spirit to `--preserve-merges`, but in contrast to\n-that option works well in interactive rebases: commits can be reordered,\n-inserted and dropped at will.\n+By default, or when `no-rebase-cousins` was specified, commits which do not\n+have `<upstream>` as direct ancestor will keep their original branch point,\n+i.e. commits that would be excluded by gitlink:git-log[1]'s\n+`--ancestry-path` option will keep their original ancestry by default. If\n+the `rebase-cousins` mode is turned on, such commits are instead rebased\n+onto `<upstream>` (or `<onto>`, if specified).\n++\n+The `--rebase-merges` mode is similar in spirit to `--preserve-merges`, but\n+in contrast to that option works well in interactive rebases: commits can be\n+reordered, inserted and dropped at will.\n +\n It is currently only possible to recreate the merge commits using the\n `recursive` merge strategy; Different merge strategies can be used only via\ndiff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c\nindex 781782e7272..f7c2a5fdc81 100644\n--- a/builtin/rebase--helper.c\n+++ b/builtin/rebase--helper.c\n@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n {\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tunsigned flags = 0, keep_empty = 0, rebase_merges = 0;\n-\tint abbreviate_commands = 0;\n+\tint abbreviate_commands = 0, rebase_cousins = -1;\n \tenum {\n \t\tCONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,\n \t\tCHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,\n@@ -25,6 +25,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\tN_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"rebase-merges\", &rebase_merges, N_(\"rebase merge commits\")),\n+\t\tOPT_BOOL(0, \"rebase-cousins\", &rebase_cousins,\n+\t\t\t N_(\"keep original branch points of cousins\")),\n \t\tOPT_CMDMODE(0, \"continue\", &command, N_(\"continue rebase\"),\n \t\t\t\tCONTINUE),\n \t\tOPT_CMDMODE(0, \"abort\", &command, N_(\"abort rebase\"),\n@@ -59,8 +61,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)\n \tflags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n+\tflags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n \tflags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;\n \n+\tif (rebase_cousins >= 0 && !rebase_merges)\n+\t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n+\t\t\t  \"--rebase-merges\"));\n+\n \tif (command == CONTINUE && argc == 1)\n \t\treturn !!sequencer_continue(&opts);\n \tif (command == ABORT && argc == 1)\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex e29da634339..cbf44f86482 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -971,6 +971,7 @@ git_rebase__interactive () {\n \n \tgit rebase--helper --make-script ${keep_empty:+--keep-empty} \\\n \t\t${rebase_merges:+--rebase-merges} \\\n+\t\t${rebase_cousins:+--rebase-cousins} \\\n \t\t$revisions ${restrict_revision+^$restrict_revision} >\"$todo\" ||\n \tdie \"$(gettext \"Could not generate todo list\")\"\n \ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex a553f969d11..40be59ecc47 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat\n autostash          automatically stash/stash pop before and after\n fork-point         use 'merge-base --fork-point' to refine upstream\n onto=!             rebase onto given branch instead of upstream\n-r,rebase-merges!   try to rebase merges instead of skipping them\n+r,rebase-merges?   try to rebase merges instead of skipping them\n p,preserve-merges! try to recreate merges instead of ignoring them\n s,strategy=!       use the given merge strategy\n no-ff!             cherry-pick all commits, even if unchanged\n@@ -91,6 +91,7 @@ state_dir=\n # One of {'', continue, skip, abort}, as parsed from command line\n action=\n rebase_merges=\n+rebase_cousins=\n preserve_merges=\n autosquash=\n keep_empty=\n@@ -286,6 +287,15 @@ do\n \t\trebase_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n \t\t;;\n+\t--rebase-merges=*)\n+\t\trebase_merges=t\n+\t\tcase \"${1#*=}\" in\n+\t\trebase-cousins) rebase_cousins=t;;\n+\t\tno-rebase-cousins) rebase_cousins=;;\n+\t\t*) die \"Unknown mode: $1\";;\n+\t\tesac\n+\t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\n+\t\t;;\n \t--preserve-merges)\n \t\tpreserve_merges=t\n \t\ttest -z \"$interactive_rebase\" && interactive_rebase=implied\ndiff --git a/sequencer.c b/sequencer.c\nindex afa155c2829..e2f83942843 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3578,6 +3578,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   unsigned flags)\n {\n \tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n \tstruct strbuf label = STRBUF_INIT;\n \tstruct commit_list *commits = NULL, **tail = &commits, *iter;\n@@ -3755,6 +3756,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t\t   &commit->object.oid);\n \t\t\tif (entry)\n \t\t\t\tto = entry->string;\n+\t\t\telse if (!rebase_cousins)\n+\t\t\t\tto = label_oid(&commit->object.oid, NULL,\n+\t\t\t\t\t       &state);\n \n \t\t\tif (!to || !strcmp(to, \"onto\"))\n \t\t\t\tfprintf(out, \"%s onto\\n\", cmd_reset);\ndiff --git a/sequencer.h b/sequencer.h\nindex 6bc4da17243..d9570d92b11 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -60,6 +60,12 @@ int sequencer_remove_state(struct replay_opts *opts);\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\n+/*\n+ * When rebasing merges, commits that do have the base commit as ancestor\n+ * (\"cousins\") are *not* rebased onto the new base by default. If those\n+ * commits should be rebased onto the new base, this flag needs to be passed.\n+ */\n+#define TODO_LIST_REBASE_COUSINS (1U << 4)\n int sequencer_make_script(FILE *out, int argc, const char **argv,\n \t\t\t  unsigned flags);\n \ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 1628c8dcc20..3d4dfdf7bec 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -176,6 +176,24 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase cousins unless asked for' '\n+\tgit checkout -b cousins master &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase -r HEAD^ &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase --rebase-merges=rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345777","messageId":"169bc4f09bff564032b93e0df9ffcb181d174793.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 14/17] rebase --rebase-merges: avoid \"empty merges\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:31Z","receivedAt":"2018-04-25T12:30:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The `git merge` command does not allow merging commits that are already\nreachable from HEAD: `git merge HEAD^`, for example, will report that we\nare already up to date and not change a thing.\n\nIn an interactive rebase, such a merge could occur previously, e.g. when\ncompeting (or slightly modified) versions of a patch series were applied\nupstream, and the user had to `git rebase --skip` all of the local\ncommits, and the topic branch becomes \"empty\" as a consequence.\n\nLet's teach the todo command `merge` to behave the same as `git merge`.\n\nSeeing as it requires some low-level trickery to create such merges with\nGit's commands in the first place, we do not even have to bother to\nintroduce an option to force `merge` to create such merge commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c              | 7 +++++++\n t/t3430-rebase-merges.sh | 8 ++++++++\n 2 files changed, 15 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 558efc1af6e..afa155c2829 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2810,6 +2810,13 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n \twrite_message(\"no-ff\", 5, git_path_merge_mode(), 0);\n \n \tbases = get_merge_bases(head_commit, merge_commit);\n+\tif (bases && !oidcmp(&merge_commit->object.oid,\n+\t\t\t     &bases->item->object.oid)) {\n+\t\tret = 0;\n+\t\t/* skip merging an ancestor of HEAD */\n+\t\tgoto leave_merge;\n+\t}\n+\n \tfor (j = bases; j; j = j->next)\n \t\tcommit_list_insert(j->item, &reversed);\n \tfree_commit_list(bases);\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex e9c5dc1cd95..1628c8dcc20 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -215,4 +215,12 @@ test_expect_success 'post-rewrite hook and fixups work for merges' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'refuse to merge ancestors of HEAD' '\n+\techo \"merge HEAD^\" >script-from-scratch &&\n+\ttest_config -C wt sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n+\tbefore=\"$(git rev-parse HEAD)\" &&\n+\tgit rebase -i HEAD &&\n+\ttest_cmp_rev HEAD $before\n+'\n+\n test_done\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345778","messageId":"673fb9cb8b5c7d57cb560b6ade45e419c8dd09fc.1524659287.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"[PATCH v9 17/17] rebase -i --rebase-merges: add a section to the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:29:47Z","receivedAt":"2018-04-25T12:30:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The --rebase-merges mode is probably not half as intuitive to use as\nits inventor hopes, so let's document it some.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-rebase.txt | 135 +++++++++++++++++++++++++++++++++++\n 1 file changed, 135 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex fe681d69281..bd5ecff980e 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -403,6 +403,8 @@ reordered, inserted and dropped at will.\n It is currently only possible to recreate the merge commits using the\n `recursive` merge strategy; Different merge strategies can be used only via\n explicit `exec git merge -s <strategy> [...]` commands.\n++\n+See also REBASING MERGES below.\n \n -p::\n --preserve-merges::\n@@ -801,6 +803,139 @@ The ripple effect of a \"hard case\" recovery is especially bad:\n 'everyone' downstream from 'topic' will now have to perform a \"hard\n case\" recovery too!\n \n+REBASING MERGES\n+-----------------\n+\n+The interactive rebase command was originally designed to handle\n+individual patch series. As such, it makes sense to exclude merge\n+commits from the todo list, as the developer may have merged the\n+then-current `master` while working on the branch, only to rebase\n+all the commits onto `master` eventually (skipping the merge\n+commits).\n+\n+However, there are legitimate reasons why a developer may want to\n+recreate merge commits: to keep the branch structure (or \"commit\n+topology\") when working on multiple, inter-related branches.\n+\n+In the following example, the developer works on a topic branch that\n+refactors the way buttons are defined, and on another topic branch\n+that uses that refactoring to implement a \"Report a bug\" button. The\n+output of `git log --graph --format=%s -5` may look like this:\n+\n+------------\n+*   Merge branch 'report-a-bug'\n+|\\\n+| * Add the feedback button\n+* | Merge branch 'refactor-button'\n+|\\ \\\n+| |/\n+| * Use the Button class for all buttons\n+| * Extract a generic Button class from the DownloadButton one\n+------------\n+\n+The developer might want to rebase those commits to a newer `master`\n+while keeping the branch topology, for example when the first topic\n+branch is expected to be integrated into `master` much earlier than the\n+second one, say, to resolve merge conflicts with changes to the\n+DownloadButton class that made it into `master`.\n+\n+This rebase can be performed using the `--rebase-merges` option.\n+It will generate a todo list looking like this:\n+\n+------------\n+label onto\n+\n+# Branch: refactor-button\n+reset onto\n+pick 123456 Extract a generic Button class from the DownloadButton one\n+pick 654321 Use the Button class for all buttons\n+label refactor-button\n+\n+# Branch: report-a-bug\n+reset refactor-button # Use the Button class for all buttons\n+pick abcdef Add the feedback button\n+label report-a-bug\n+\n+reset onto\n+merge -C a1b2c3 refactor-button # Merge 'refactor-button'\n+merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'\n+------------\n+\n+In contrast to a regular interactive rebase, there are `label`, `reset`\n+and `merge` commands in addition to `pick` ones.\n+\n+The `label` command associates a label with the current HEAD when that\n+command is executed. These labels are created as worktree-local refs\n+(`refs/rewritten/<label>`) that will be deleted when the rebase\n+finishes. That way, rebase operations in multiple worktrees linked to\n+the same repository do not interfere with one another. If the `label`\n+command fails, it is rescheduled immediately, with a helpful message how\n+to proceed.\n+\n+The `reset` command resets the HEAD, index and worktree to the specified\n+revision. It is isimilar to an `exec git reset --hard <label>`, but\n+refuses to overwrite untracked files. If the `reset` command fails, it is\n+rescheduled immediately, with a helpful message how to edit the todo list\n+(this typically happens when a `reset` command was inserted into the todo\n+list manually and contains a typo).\n+\n+The `merge` command will merge the specified revision into whatever is\n+HEAD at that time. With `-C <original-commit>`, the commit message of\n+the specified merge commit will be used. When the `-C` is changed to\n+a lower-case `-c`, the message will be opened in an editor after a\n+successful merge so that the user can edit the message.\n+\n+If a `merge` command fails for any reason other than merge conflicts (i.e.\n+when the merge operation did not even start), it is rescheduled immediately.\n+\n+At this time, the `merge` command will *always* use the `recursive`\n+merge strategy, with no way to choose a different one. To work around\n+this, an `exec` command can be used to call `git merge` explicitly,\n+using the fact that the labels are worktree-local refs (the ref\n+`refs/rewritten/onto` would correspond to the label `onto`, for example).\n+\n+Note: the first command (`label onto`) labels the revision onto which\n+the commits are rebased; The name `onto` is just a convention, as a nod\n+to the `--onto` option.\n+\n+It is also possible to introduce completely new merge commits from scratch\n+by adding a command of the form `merge <merge-head>`. This form will\n+generate a tentative commit message and always open an editor to let the\n+user edit it. This can be useful e.g. when a topic branch turns out to\n+address more than a single concern and wants to be split into two or\n+even more topic branches. Consider this todo list:\n+\n+------------\n+pick 192837 Switch from GNU Makefiles to CMake\n+pick 5a6c7e Document the switch to CMake\n+pick 918273 Fix detection of OpenSSL in CMake\n+pick afbecd http: add support for TLS v1.3\n+pick fdbaec Fix detection of cURL in CMake on Windows\n+------------\n+\n+The one commit in this list that is not related to CMake may very well\n+have been motivated by working on fixing all those bugs introduced by\n+switching to CMake, but it addresses a different concern. To split this\n+branch into two topic branches, the todo list could be edited like this:\n+\n+------------\n+label onto\n+\n+pick afbecd http: add support for TLS v1.3\n+label tlsv1.3\n+\n+reset onto\n+pick 192837 Switch from GNU Makefiles to CMake\n+pick 918273 Fix detection of OpenSSL in CMake\n+pick fdbaec Fix detection of cURL in CMake on Windows\n+pick 5a6c7e Document the switch to CMake\n+label cmake\n+\n+reset onto\n+merge tlsv1.3\n+merge cmake\n+------------\n+\n BUGS\n ----\n The todo list presented by `--preserve-merges --interactive` does not\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n"},{"id":"345781","messageId":"nycvar.QRO.7.76.6.1804251442510.4978@tvgsbejvaqbjf.bet","threadId":"47635","inReplyTo":"12fa9a1f-9dc2-9108-132f-48b75ae5a8d3@talktalk.net","subject":"Re: [PATCH v3 0/4] rebase -i: avoid stale \"# This is a combinationof\" in commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T12:48:23Z","receivedAt":"2018-04-25T12:48:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Mon, 23 Apr 2018, Phillip Wood wrote:\n\n> On 23/04/18 19:11, Stefan Beller wrote:\n> > \n> > On Sat, Apr 21, 2018 at 12:34 AM, Johannes Schindelin\n> > <johannes.schindelin@gmx.de> wrote:\n> > > Eric Sunshine pointed out that I had such a commit message in\n> > > https://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\n> > > and I went on a hunt to figure out how the heck this happened.\n> > >\n> > > Turns out that if there is a fixup/squash chain where the *last* command\n> > > fails with merge conflicts, and we either --skip ahead or resolve the\n> > > conflict to a clean tree and then --continue, our code does not do a\n> > > final cleanup.\n> > >\n> > > Contrary to my initial gut feeling, this bug was not introduced by my\n> > > rewrite in C of the core parts of rebase -i, but it looks to me as if\n> > > that bug was with us for a very long time (at least the --skip part).\n> > >\n> > > The developer (read: user of rebase -i) in me says that we would want to\n> > > fast-track this, but the author of rebase -i in me says that we should\n> > > be cautious and cook this in `next` for a while.\n> > \n> > I looked through the patches again and think this series is good to go.\n> \n> I've just realized I commented on an outdated version as the new version was\n> posted there rather than as a reply to v1. I've just looked through it and I'm\n> not sure it addresses the unnecessary editing of the commit message of the\n> previous commit if a single squash command is skipped as outlined in\n> https://public-inbox.org/git/b6512eae-e214-9699-4d69-77117a0daec3@talktalk.net/\n\nI have not forgotten about this! I simply did not find the time yet, is\nall...\n\nThe patch series still has not been merged to `next`, but I plan on\nworking on your suggested changes as an add-on commit anyway. I am not\nquite sure yet how I want to handle the \"avoid running commit for the\nfirst fixup/squash in the series\" problem, but I think we will have to add\n*yet another* file that is written (in the \"we already have comments in\nthe commit message\" conditional block in error_failed_squash())...\n\nCiao,\nDscho\n"},{"id":"345824","messageId":"949b5749-d721-31ea-77a1-ec6406f3083a@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804251442510.4978@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 0/4] rebase -i: avoid stale \"# This is a combinationof\" in commit messages","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-25T17:09:27Z","receivedAt":"2018-04-25T17:09:35Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 25/04/18 13:48, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Mon, 23 Apr 2018, Phillip Wood wrote:\n> \n>> On 23/04/18 19:11, Stefan Beller wrote:\n>>>\n>>> On Sat, Apr 21, 2018 at 12:34 AM, Johannes Schindelin\n>>> <johannes.schindelin@gmx.de> wrote:\n>>>> Eric Sunshine pointed out that I had such a commit message in\n>>>> https://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\n>>>> and I went on a hunt to figure out how the heck this happened.\n>>>>\n>>>> Turns out that if there is a fixup/squash chain where the *last* command\n>>>> fails with merge conflicts, and we either --skip ahead or resolve the\n>>>> conflict to a clean tree and then --continue, our code does not do a\n>>>> final cleanup.\n>>>>\n>>>> Contrary to my initial gut feeling, this bug was not introduced by my\n>>>> rewrite in C of the core parts of rebase -i, but it looks to me as if\n>>>> that bug was with us for a very long time (at least the --skip part).\n>>>>\n>>>> The developer (read: user of rebase -i) in me says that we would want to\n>>>> fast-track this, but the author of rebase -i in me says that we should\n>>>> be cautious and cook this in `next` for a while.\n>>>\n>>> I looked through the patches again and think this series is good to go.\n>>\n>> I've just realized I commented on an outdated version as the new version was\n>> posted there rather than as a reply to v1. I've just looked through it and I'm\n>> not sure it addresses the unnecessary editing of the commit message of the\n>> previous commit if a single squash command is skipped as outlined in\n>> https://public-inbox.org/git/b6512eae-e214-9699-4d69-77117a0daec3@talktalk.net/\n> \n> I have not forgotten about this! I simply did not find the time yet, is\n> all...\n\nI wondered if that was the case but I wanted to check as I wasn't sure \nif you'd seen the original message as it was on an obsolete version of \nthe series\n\n> The patch series still has not been merged to `next`, but I plan on\n> working on your suggested changes as an add-on commit anyway. I am not\n> quite sure yet how I want to handle the \"avoid running commit for the\n> first fixup/squash in the series\" problem, but I think we will have to add\n> *yet another* file that is written (in the \"we already have comments in\n> the commit message\" conditional block in error_failed_squash())...\n\nI wonder if creating the file in update_squash_messages() rather than \nerror_failed_squash() would be a better approach as then it is easy to \nonly create rebase_path_amend_type() when there has already been a \nsquash or fixup. The file is removed in the loop that picks commits in \npick_commits() so it would be cleaned up at the beginning of the next \npick if it's not needed.\n\nBest Wishes\n\nPhillip\n\n> \n> Ciao,\n> Dscho\n> \n\n"},{"id":"345856","messageId":"xmqqmuxq39rz.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v9 00/17] rebase -i: offer to recreate commit topology by rebasing merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-26T03:51:28Z","receivedAt":"2018-04-26T03:51:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Changes since v8:\n>\n> - Disentangled the patch introducing `label`/`reset` from the one\n>   introducing `merge` again (this was one stupid, tired `git commit\n>   --amend` too many).\n>\n> - Augmented the commit message of \"introduce the `merge` command\" to\n>   describe what the `label onto` is all about.\n>\n> - Fixed the error message when `reset` would overwrite untracked files to\n>   actually say that a \"reset\" failed (not a \"merge\").\n>\n> - Clarified the rationale for `label onto` in the commit message of\n>   \"rebase-helper --make-script: introduce a flag to rebase merges\".\n>\n> - Edited the description of `--rebase-merges` heavily, for clarity, in\n>   \"rebase: introduce the --rebase-merges option\".\n>\n> - Edited the commit message of (and the documentation introduced by) \" rebase\n>   -i: introduce --rebase-merges=[no-]rebase-cousins\" for clarity (also\n>   mentioning the `--ancestry-path` option).\n>\n> - When run_git_commit() fails after a successful merge, we now take pains\n>   not to reschedule the `merge` command.\n>\n> - Rebased the patch series on top of current `master`, i.e. both\n>   `pw/rebase-keep-empty-fixes` and `pw/rebase-signoff`, to resolve merge\n>   conflicts myself.\n\nGood to see the last item, as this gave me a chance to make sure\nthat the conflict resolution I've been carrying matches how you\nwould have resolved as the original author.  Applying these on the\nold base (with minor conflict resolution) to match the old iteration\nand merging the result to the new base1f1cddd5 (\"The fourth batch\nfor 2.18\", 2018-04-25) resulted in the same tree as the tree that\nresults from applying these on top of the new base.\n\nThat was done only to validate the result of the past resolution\n(and also seeing the interdiff from the old iteration).  There is no\nreason to keep this series back-portable to older tip of 'master',\nso I'll queue the result of applying the patches to the new base.\n\n"},{"id":"345875","messageId":"xmqqvace1oya.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"xmqqmuxq39rz.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v9 00/17] rebase -i: offer to recreate commit topology by rebasing merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-26T06:06:37Z","receivedAt":"2018-04-26T06:06:44Z","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>> - Rebased the patch series on top of current `master`, i.e. both\n>>   `pw/rebase-keep-empty-fixes` and `pw/rebase-signoff`, to resolve merge\n>>   conflicts myself.\n>\n> Good to see the last item, as this gave me a chance to make sure\n> that the conflict resolution I've been carrying matches how you\n> would have resolved as the original author.  Applying these on the\n> old base (with minor conflict resolution) to match the old iteration\n> and merging the result to the new base1f1cddd5 (\"The fourth batch\n> for 2.18\", 2018-04-25) resulted in the same tree as the tree that\n> results from applying these on top of the new base.\n>\n> That was done only to validate the result of the past resolution\n> (and also seeing the interdiff from the old iteration).  There is no\n> reason to keep this series back-portable to older tip of 'master',\n> so I'll queue the result of applying the patches to the new base.\n\nBy the way, the rebasing made the topic textually merge cleanly to\nthe tip of 'pu' which made it slightly more cumbersome to deal with\na semantic conflict the topic has with another topic that modifies\nthe function signature of get_main_ref_store().  This topic adds a\nnew callsite in sequencer.c to this function.\n\nThe old base that forced the integrator to resolve conflicts in\nsequencer.c with some other topic, thanks to that exact textual\nconflicts, gave rerere a chance to record the adjustment for this\nsemantic conflict.\n\nNow because the series applied to new base does not have textual\nconflicts in sequencer.c when merged to 'pu', the adjustment for the\nsemantic conflict needs to be carried by a different mechanism.\n\n    Side note.  Do not take the above as a complaint.  Dealing with\n    interactions among various topics in flight while keeping them\n    as straight and clean topic is what I do.  It is a normal part\n    of running an active project.\n\nIt may be an interesting exercise to attempt to rebase tonight's\n'pu' onto something younger in 'pu', say 'pu~4', without changing\nanything in \"pu^2\" (which is the tip of this topic) and see how well\nthe merge recreation feature of this topic handles the evil merge.\n\nThe gist of the evil merge looks like this:\n\ndiff --cc sequencer.c\nindex a428fc7db7,e2f8394284..729cf05768\n--- a/sequencer.c\n+++ b/sequencer.c\n@@@ -2483,6 -2527,349 +2556,349 @@@ static int do_exec(const char *command_\n ...\n+ \n+ static int do_label(const char *name, int len)\n+ {\n -\tstruct ref_store *refs = get_main_ref_store();\n++\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+ \tstruct ref_transaction *transaction;\n+ \tstruct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;\n+ \tstruct strbuf msg = STRBUF_INIT;\n+...\n"},{"id":"345889","messageId":"nycvar.QRO.7.76.6.1804261135530.4978@tvgsbejvaqbjf.bet","threadId":"47635","inReplyTo":"949b5749-d721-31ea-77a1-ec6406f3083a@talktalk.net","subject":"Re: [PATCH v3 0/4] rebase -i: avoid stale \"# This is a combinationof\" in commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-26T09:51:25Z","receivedAt":"2018-04-26T09:51:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Wed, 25 Apr 2018, Phillip Wood wrote:\n\n> On 25/04/18 13:48, Johannes Schindelin wrote:\n> > \n> > On Mon, 23 Apr 2018, Phillip Wood wrote:\n> > \n> > > On 23/04/18 19:11, Stefan Beller wrote:\n> > > >\n> > > > On Sat, Apr 21, 2018 at 12:34 AM, Johannes Schindelin\n> > > > <johannes.schindelin@gmx.de> wrote:\n> > > > > Eric Sunshine pointed out that I had such a commit message in\n> > > > > https://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\n> > > > > and I went on a hunt to figure out how the heck this happened.\n> > > > >\n> > > > > Turns out that if there is a fixup/squash chain where the *last*\n> > > > > command fails with merge conflicts, and we either --skip ahead\n> > > > > or resolve the conflict to a clean tree and then --continue, our\n> > > > > code does not do a final cleanup.\n> > > > >\n> > > > > Contrary to my initial gut feeling, this bug was not introduced\n> > > > > by my rewrite in C of the core parts of rebase -i, but it looks\n> > > > > to me as if that bug was with us for a very long time (at least\n> > > > > the --skip part).\n> > > > >\n> > > > > The developer (read: user of rebase -i) in me says that we would\n> > > > > want to fast-track this, but the author of rebase -i in me says\n> > > > > that we should be cautious and cook this in `next` for a while.\n> > > >\n> > > > I looked through the patches again and think this series is good\n> > > > to go.\n> > >\n> > > I've just realized I commented on an outdated version as the new\n> > > version was posted there rather than as a reply to v1. I've just\n> > > looked through it and I'm not sure it addresses the unnecessary\n> > > editing of the commit message of the previous commit if a single\n> > > squash command is skipped as outlined in\n> > > https://public-inbox.org/git/b6512eae-e214-9699-4d69-77117a0daec3@talktalk.net/\n> > \n> > I have not forgotten about this! I simply did not find the time yet,\n> > is all...\n> \n> I wondered if that was the case but I wanted to check as I wasn't sure\n> if you'd seen the original message as it was on an obsolete version of\n> the series\n> \n> > The patch series still has not been merged to `next`, but I plan on\n> > working on your suggested changes as an add-on commit anyway. I am not\n> > quite sure yet how I want to handle the \"avoid running commit for the\n> > first fixup/squash in the series\" problem, but I think we will have to\n> > add *yet another* file that is written (in the \"we already have\n> > comments in the commit message\" conditional block in\n> > error_failed_squash())...\n> \n> I wonder if creating the file in update_squash_messages() rather than\n> error_failed_squash() would be a better approach as then it is easy to\n> only create rebase_path_amend_type() when there has already been a\n> squash or fixup.  The file is removed in the loop that picks commits in\n> pick_commits() so it would be cleaned up at the beginning of the next\n> pick if it's not needed.\n\nThat would be a good idea in general, but I think we have to take care of\nthe following scenario:\n\n\tpick\t<- succeeds\n\tsquash\t<- succeeds\n\tfixup\t<- fails, will be skipped\n\nIn this case, we do need to open the editor. But in this scenario, we do\nnot:\n\n\tpick\t<- succeeds\n\tfixup\t<- succeeds\n\tsquash\t<- fails, will be skipped\n\nIf we write the amend-type file in update_squash_messages(), we would\nwrite \"squash\" into it in both cases. My hope was to somehow avoid that.\n\nI just realized that the current iteration does not fulfill that goal, as\nthe message-fixup file would be long gone by the time\nerror_failed_squash() was called in the latter example.\n\nAlso, I realized something else: my previous work-around for the\nGETTEXT_POISON case (where I fail gently when a commit message does not\ncontain the \"This is a combination of #<count> commits\" count in ASCII)\nwould be much superior if it simply would not abuse the comment in the\ncommit message, but had a robust, non-l18ned way to count the fixup/squash\ncommits.\n\nMy current thinking is to reconcile both problems by shunning the\namend-type and instead just record the sequence of fixup/squash commits\nthat went into HEAD, in a new file, say, current-fixups.\n\nTo answer the question how many commit messages are combined, I then\nsimply need to count the lines in that file.\n\nTo answer the question whether a skipped fixup/squash requires the editor\nto be launched, I can simply look whether there is a \"squash\" line\n(ignoring the last line).\n\nOh, and I also forgot to test whether this is the \"final fixup\". If we are\nskipping a \"fixup\" in the middle of a chain, there is no need to clean the\ncommit message to begin with.\n\nThis will take a while... ;-)\n\nCiao,\nDscho\n"},{"id":"345891","messageId":"3e1d2e06-a415-24c8-ad6c-02ab707cc52e@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1804261135530.4978@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 0/4] rebase -i: avoid stale \"# This is a combinationof\" in commit messages","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-04-26T10:52:54Z","receivedAt":"2018-04-26T10:53:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 26/04/18 10:51, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Wed, 25 Apr 2018, Phillip Wood wrote:\n> \n>> On 25/04/18 13:48, Johannes Schindelin wrote:\n>>>\n>>> On Mon, 23 Apr 2018, Phillip Wood wrote:\n>>>\n>>>> On 23/04/18 19:11, Stefan Beller wrote:\n>>>>>\n>>>>> On Sat, Apr 21, 2018 at 12:34 AM, Johannes Schindelin\n>>>>> <johannes.schindelin@gmx.de> wrote:\n>>>>>> Eric Sunshine pointed out that I had such a commit message in\n>>>>>> https://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\n>>>>>> and I went on a hunt to figure out how the heck this happened.\n>>>>>>\n>>>>>> Turns out that if there is a fixup/squash chain where the *last*\n>>>>>> command fails with merge conflicts, and we either --skip ahead\n>>>>>> or resolve the conflict to a clean tree and then --continue, our\n>>>>>> code does not do a final cleanup.\n>>>>>>\n>>>>>> Contrary to my initial gut feeling, this bug was not introduced\n>>>>>> by my rewrite in C of the core parts of rebase -i, but it looks\n>>>>>> to me as if that bug was with us for a very long time (at least\n>>>>>> the --skip part).\n>>>>>>\n>>>>>> The developer (read: user of rebase -i) in me says that we would\n>>>>>> want to fast-track this, but the author of rebase -i in me says\n>>>>>> that we should be cautious and cook this in `next` for a while.\n>>>>>\n>>>>> I looked through the patches again and think this series is good\n>>>>> to go.\n>>>>\n>>>> I've just realized I commented on an outdated version as the new\n>>>> version was posted there rather than as a reply to v1. I've just\n>>>> looked through it and I'm not sure it addresses the unnecessary\n>>>> editing of the commit message of the previous commit if a single\n>>>> squash command is skipped as outlined in\n>>>> https://public-inbox.org/git/b6512eae-e214-9699-4d69-77117a0daec3@talktalk.net/\n>>>\n>>> I have not forgotten about this! I simply did not find the time yet,\n>>> is all...\n>>\n>> I wondered if that was the case but I wanted to check as I wasn't sure\n>> if you'd seen the original message as it was on an obsolete version of\n>> the series\n>>\n>>> The patch series still has not been merged to `next`, but I plan on\n>>> working on your suggested changes as an add-on commit anyway. I am not\n>>> quite sure yet how I want to handle the \"avoid running commit for the\n>>> first fixup/squash in the series\" problem, but I think we will have to\n>>> add *yet another* file that is written (in the \"we already have\n>>> comments in the commit message\" conditional block in\n>>> error_failed_squash())...\n>>\n>> I wonder if creating the file in update_squash_messages() rather than\n>> error_failed_squash() would be a better approach as then it is easy to\n>> only create rebase_path_amend_type() when there has already been a\n>> squash or fixup.  The file is removed in the loop that picks commits in\n>> pick_commits() so it would be cleaned up at the beginning of the next\n>> pick if it's not needed.\n> \n> That would be a good idea in general, but I think we have to take care of\n> the following scenario:\n> \n> \tpick\t<- succeeds\n> \tsquash\t<- succeeds\n> \tfixup\t<- fails, will be skipped\n> \n> In this case, we do need to open the editor. But in this scenario, we do\n> not:\n> \n> \tpick\t<- succeeds\n> \tfixup\t<- succeeds\n> \tsquash\t<- fails, will be skipped\n> \n> If we write the amend-type file in update_squash_messages(), we would\n> write \"squash\" into it in both cases. My hope was to somehow avoid that.\n\nGood point, I'd not thought of that\n\n> I just realized that the current iteration does not fulfill that goal, as\n> the message-fixup file would be long gone by the time\n> error_failed_squash() was called in the latter example.\n> \n> Also, I realized something else: my previous work-around for the\n> GETTEXT_POISON case (where I fail gently when a commit message does not\n> contain the \"This is a combination of #<count> commits\" count in ASCII)\n> would be much superior if it simply would not abuse the comment in the\n> commit message, but had a robust, non-l18ned way to count the fixup/squash\n> commits.\n> \n> My current thinking is to reconcile both problems by shunning the\n> amend-type and instead just record the sequence of fixup/squash commits\n> that went into HEAD, in a new file, say, current-fixups.\n> \n> To answer the question how many commit messages are combined, I then\n> simply need to count the lines in that file.\n> \n> To answer the question whether a skipped fixup/squash requires the editor\n> to be launched, I can simply look whether there is a \"squash\" line\n> (ignoring the last line).\n\nThat sounds like a good plan, keeping count of the fixup/squash without\nhaving to parse the last message is a good idea.\n\n> Oh, and I also forgot to test whether this is the \"final fixup\". If we are\n> skipping a \"fixup\" in the middle of a chain, there is no need to clean the\n> commit message to begin with.\n> \n> This will take a while... ;-)\n\nYes, it sounds like quite a bit of work, but it will be a very\nworthwhile improvement.\n\nThanks\n\nPhillip\n\n> Ciao,\n> Dscho\n> \n\n"},{"id":"345986","messageId":"cover.1524862093.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com","subject":"[PATCH v4 0/4] rebase -i: avoid stale \"# This is a combination of\" in commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T20:48:13Z","receivedAt":"2018-04-27T20:48:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Eric Sunshine pointed out that I had such a commit message in\nhttps://public-inbox.org/git/CAPig+cRrS0_nYJJY=O6cboV630sNQHPV5QGrQdD8MW-sYzNFGQ@mail.gmail.com/\nand I went on a hunt to figure out how the heck this happened.\n\nTurns out that if there is a fixup/squash chain where the *last* command\nfails with merge conflicts, and we either --skip ahead or resolve the\nconflict to a clean tree and then --continue, our code does not do a\nfinal cleanup.\n\nContrary to my initial gut feeling, this bug was not introduced by my\nrewrite in C of the core parts of rebase -i, but it looks to me as if\nthat bug was with us for a very long time (at least the --skip part).\n\nThe developer (read: user of rebase -i) in me says that we would want to\nfast-track this, but the author of rebase -i in me says that we should\nbe cautious and cook this in `next` for a while.\n\nFixes since v3 (thanks, Phillip, for the really fruitful discussion!):\n\n- We now avoid using the commit message prepared for the skipped\n  fixup/squash.\n\n- Replaced the \"rebase -i: Handle \"combination of <n> commits\" with\n  GETTEXT_POISON\" patch by a *real* fix instead of a work-around: Instead\n  of parsing the first line of the commit message and punting when it is\n  missing an ASCII-encoded number, we determine <n> separately\n  (independent from any localized text).\n\n- Fixed quite a couple more corner cases, using the `current-fixups`\n  file introduced for the GETTEXT_POISON fix:\n\n  * we only need to re-commit if this was the final fixup/squash in the\n    fixup/squash chain,\n\n  * we only need to commit interactively if there was *any* non-skipped\n    squash,\n\n  * if the fixup/squash chain continues, the <N> was incorrect in the\n    \"This is a combination of <N> commits\" comment in the intermediate\n    commit message (it included the now-skipped commits), and\n\n  * even if a filed fixup/squash in the middle of a fixup/squash chain\n    failed, and its merge conflicts were resolved and committed, the\n    \"This is a combination of <N> commits\" comment was incorrect: we\n    had already deleted message-fixup and message-squash, so the next\n    update_squash_message() would mistakenly assume that we were\n    starting afresh. Worse: if only fixup commands were remaining, but\n    there had been a squash command, we would retain the \"squash!\" line\n    in the commit message and not give the user a chance to clean things\n    up in the final fixup!\n\n\nJohannes Schindelin (4):\n  rebase -i: demonstrate bugs with fixup!/squash! commit messages\n  rebase -i: Handle \"combination of <n> commits\" with GETTEXT_POISON\n  sequencer: always commit without editing when asked for\n  rebase --skip: clean up commit message after a failed fixup/squash\n\n sequencer.c                | 193 ++++++++++++++++++++++++++++---------\n sequencer.h                |   6 +-\n t/t3418-rebase-continue.sh |  49 ++++++++++\n 3 files changed, 200 insertions(+), 48 deletions(-)\n\n\nbase-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d\nPublished-As: https://github.com/dscho/git/releases/tag/clean-msg-after-fixup-continue-v4\nFetch-It-Via: git fetch https://github.com/dscho/git clean-msg-after-fixup-continue-v4\n\nInterdiff vs v3:\n diff --git a/sequencer.c b/sequencer.c\n index e1efb0ebf31..cec180714ef 100644\n --- a/sequencer.c\n +++ b/sequencer.c\n @@ -74,13 +74,6 @@ static GIT_PATH_FUNC(rebase_path_message, \"rebase-merge/message\")\n   * previous commit and from the first squash/fixup commit are written\n   * to it. The commit message for each subsequent squash/fixup commit\n   * is appended to the file as it is processed.\n - *\n - * The first line of the file is of the form\n - *     # This is a combination of $count commits.\n - * where $count is the number of commits whose messages have been\n - * written to the file so far (including the initial \"pick\" commit).\n - * Each time that a commit message is processed, this line is read and\n - * updated. It is deleted just before the combined commit is made.\n   */\n  static GIT_PATH_FUNC(rebase_path_squash_msg, \"rebase-merge/message-squash\")\n  /*\n @@ -91,6 +84,11 @@ static GIT_PATH_FUNC(rebase_path_squash_msg, \"rebase-merge/message-squash\")\n   * commit without opening the editor.)\n   */\n  static GIT_PATH_FUNC(rebase_path_fixup_msg, \"rebase-merge/message-fixup\")\n +/*\n + * This file contains the list fixup/squash commands that have been\n + * accumulated into message-fixup or message-squash so far.\n + */\n +static GIT_PATH_FUNC(rebase_path_current_fixups, \"rebase-merge/current-fixups\")\n  /*\n   * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and\n   * GIT_AUTHOR_DATE that will be used for the commit that is currently\n @@ -106,13 +104,6 @@ static GIT_PATH_FUNC(rebase_path_author_script, \"rebase-merge/author-script\")\n   * command is processed, this file is deleted.\n   */\n  static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n -/*\n - * If there was a merge conflict in a fixup/squash series, we need to\n - * record the type so that a `git rebase --skip` can clean up the commit\n - * message as appropriate. This file will contain that type (`fixup` or\n - * `squash`), and not exist otherwise.\n - */\n -static GIT_PATH_FUNC(rebase_path_amend_type, \"rebase-merge/amend-type\")\n  /*\n   * When we stop at a given patch via the \"edit\" command, this file contains\n   * the abbreviated commit name of the corresponding patch.\n @@ -260,6 +251,7 @@ int sequencer_remove_state(struct replay_opts *opts)\n  \tfor (i = 0; i < opts->xopts_nr; i++)\n  \t\tfree(opts->xopts[i]);\n  \tfree(opts->xopts);\n +\tstrbuf_release(&opts->current_fixups);\n  \n  \tstrbuf_addstr(&dir, get_dir(opts));\n  \tremove_dir_recursively(&dir, 0);\n @@ -725,6 +717,8 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,\n  \t\targv_array_pushf(&cmd.args, \"-S%s\", opts->gpg_sign);\n  \tif (defmsg)\n  \t\targv_array_pushl(&cmd.args, \"-F\", defmsg, NULL);\n +\telse if (!(flags & EDIT_MSG))\n +\t\targv_array_pushl(&cmd.args, \"-C\", \"HEAD\", NULL);\n  \tif ((flags & CLEANUP_MSG))\n  \t\targv_array_push(&cmd.args, \"--cleanup=strip\");\n  \tif ((flags & EDIT_MSG))\n @@ -1336,33 +1330,23 @@ static int update_squash_messages(enum todo_command command,\n  \t\tstruct commit *commit, struct replay_opts *opts)\n  {\n  \tstruct strbuf buf = STRBUF_INIT;\n -\tint count, res;\n +\tint res;\n  \tconst char *message, *body;\n  \n -\tif (file_exists(rebase_path_squash_msg())) {\n +\tif (opts->current_fixup_count > 0) {\n  \t\tstruct strbuf header = STRBUF_INIT;\n -\t\tchar *eol, *p;\n +\t\tchar *eol;\n  \n -\t\tif (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)\n +\t\tif (strbuf_read_file(&buf, rebase_path_squash_msg(), 9) <= 0)\n  \t\t\treturn error(_(\"could not read '%s'\"),\n  \t\t\t\trebase_path_squash_msg());\n  \n -\t\tp = buf.buf + 1;\n -\t\teol = strchrnul(buf.buf, '\\n');\n -\t\tif (buf.buf[0] != comment_line_char ||\n -\t\t    (p += strcspn(p, \"0123456789\\n\")) == eol)\n -\t\t\tcount = -1;\n -\t\telse\n -\t\t\tcount = strtol(p, NULL, 10);\n +\t\teol = buf.buf[0] != comment_line_char ?\n +\t\t\tbuf.buf : strchrnul(buf.buf, '\\n');\n  \n  \t\tstrbuf_addf(&header, \"%c \", comment_line_char);\n -\t\tif (count < 1)\n -\t\t\tstrbuf_addf(&header, _(\"This is a combination of \"\n -\t\t\t\t\t       \"several commits.\"));\n -\t\telse\n -\t\t\tstrbuf_addf(&header,\n -\t\t\t\t    _(\"This is a combination of %d commits.\"),\n -\t\t\t\t    ++count);\n +\t\tstrbuf_addf(&header, _(\"This is a combination of %d commits.\"),\n +\t\t\t    opts->current_fixup_count + 2);\n  \t\tstrbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);\n  \t\tstrbuf_release(&header);\n  \t} else {\n @@ -1385,10 +1369,8 @@ static int update_squash_messages(enum todo_command command,\n  \t\t\t\t     rebase_path_fixup_msg());\n  \t\t}\n  \n -\t\tcount = 2;\n  \t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n -\t\tstrbuf_addf(&buf, _(\"This is a combination of %d commits.\"),\n -\t\t\t    count);\n +\t\tstrbuf_addf(&buf, _(\"This is a combination of %d commits.\"), 2);\n  \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n  \t\tstrbuf_addstr(&buf, _(\"This is the 1st commit message:\"));\n  \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n @@ -1405,22 +1387,14 @@ static int update_squash_messages(enum todo_command command,\n  \tif (command == TODO_SQUASH) {\n  \t\tunlink(rebase_path_fixup_msg());\n  \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n -\t\tif (count < 2)\n -\t\t\tstrbuf_addf(&buf, _(\"This is the next commit \"\n -\t\t\t\t\t    \"message:\"));\n -\t\telse\n -\t\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"),\n -\t\t\t\t    count);\n +\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"),\n +\t\t\t    ++opts->current_fixup_count);\n  \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n  \t\tstrbuf_addstr(&buf, body);\n  \t} else if (command == TODO_FIXUP) {\n  \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n -\t\tif (count < 2)\n -\t\t\tstrbuf_addf(&buf, _(\"The next commit message will be \"\n -\t\t\t\t\t    \"skipped:\"));\n -\t\telse\n -\t\t\tstrbuf_addf(&buf, _(\"The commit message #%d will be \"\n -\t\t\t\t\t    \"skipped:\"), count);\n +\t\tstrbuf_addf(&buf, _(\"The commit message #%d will be skipped:\"),\n +\t\t\t    ++opts->current_fixup_count);\n  \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n  \t\tstrbuf_add_commented_lines(&buf, body, strlen(body));\n  \t} else\n @@ -1429,6 +1403,17 @@ static int update_squash_messages(enum todo_command command,\n  \n  \tres = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);\n  \tstrbuf_release(&buf);\n +\n +\tif (!res) {\n +\t\tstrbuf_addf(&opts->current_fixups, \"%s%s %s\",\n +\t\t\t    opts->current_fixups.len ? \"\\n\" : \"\",\n +\t\t\t    command_to_string(command),\n +\t\t\t    oid_to_hex(&commit->object.oid));\n +\t\tres = write_message(opts->current_fixups.buf,\n +\t\t\t\t    opts->current_fixups.len,\n +\t\t\t\t    rebase_path_current_fixups(), 0);\n +\t}\n +\n  \treturn res;\n  }\n  \n @@ -1691,6 +1676,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,\n  \tif (!res && final_fixup) {\n  \t\tunlink(rebase_path_fixup_msg());\n  \t\tunlink(rebase_path_squash_msg());\n +\t\tunlink(rebase_path_current_fixups());\n +\t\tstrbuf_reset(&opts->current_fixups);\n +\t\topts->current_fixup_count = 0;\n  \t}\n  \n  leave:\n @@ -2067,6 +2055,16 @@ static int read_populate_opts(struct replay_opts *opts)\n  \t\tread_strategy_opts(opts, &buf);\n  \t\tstrbuf_release(&buf);\n  \n +\t\tif (read_oneliner(&opts->current_fixups,\n +\t\t\t\t  rebase_path_current_fixups(), 1)) {\n +\t\t\tconst char *p = opts->current_fixups.buf;\n +\t\t\topts->current_fixup_count = 1;\n +\t\t\twhile ((p = strchr(p, '\\n'))) {\n +\t\t\t\topts->current_fixup_count++;\n +\t\t\t\tp++;\n +\t\t\t}\n +\t\t}\n +\n  \t\treturn 0;\n  \t}\n  \n @@ -2413,21 +2411,9 @@ static int error_with_patch(struct commit *commit,\n  static int error_failed_squash(struct commit *commit,\n  \tstruct replay_opts *opts, int subject_len, const char *subject)\n  {\n -\tconst char *amend_type = \"squash\";\n -\n -\tif (rename(rebase_path_squash_msg(), rebase_path_message()))\n -\t\treturn error(_(\"could not rename '%s' to '%s'\"),\n +\tif (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))\n +\t\treturn error(_(\"could not copy '%s' to '%s'\"),\n  \t\t\trebase_path_squash_msg(), rebase_path_message());\n -\n -\tif (file_exists(rebase_path_fixup_msg())) {\n -\t\tunlink(rebase_path_fixup_msg());\n -\t\tamend_type = \"fixup\";\n -\t}\n -\tif (write_message(amend_type, strlen(amend_type),\n -\t\t       rebase_path_amend_type(), 0))\n -\t\treturn error(_(\"could not write '%s'\"),\n -\t\t\t     rebase_path_amend_type());\n -\n  \tunlink(git_path_merge_msg());\n  \tif (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))\n  \t\treturn error(_(\"could not copy '%s' to '%s'\"),\n @@ -2604,7 +2590,6 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)\n  \t\t\tunlink(rebase_path_author_script());\n  \t\t\tunlink(rebase_path_stopped_sha());\n  \t\t\tunlink(rebase_path_amend());\n -\t\t\tunlink(rebase_path_amend_type());\n  \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n  \t\t}\n  \t\tif (item->command <= TODO_SQUASH) {\n @@ -2794,9 +2779,11 @@ static int continue_single_pick(void)\n  \treturn run_command_v_opt(argv, RUN_GIT_CMD);\n  }\n  \n -static int commit_staged_changes(struct replay_opts *opts)\n +static int commit_staged_changes(struct replay_opts *opts,\n +\t\t\t\t struct todo_list *todo_list)\n  {\n -\tunsigned int flags = ALLOW_EMPTY | EDIT_MSG, is_fixup = 0, is_clean;\n +\tunsigned int flags = ALLOW_EMPTY | EDIT_MSG;\n +\tunsigned int final_fixup = 0, is_clean;\n  \n  \tif (has_unstaged_changes(1))\n  \t\treturn error(_(\"cannot rebase: You have unstaged changes.\"));\n @@ -2819,21 +2806,69 @@ static int commit_staged_changes(struct replay_opts *opts)\n  \t\t\t\t       \"working tree. Please, commit them\\n\"\n  \t\t\t\t       \"first and then run 'git rebase \"\n  \t\t\t\t       \"--continue' again.\"));\n -\t\tif (is_clean && !oidcmp(&head, &to_amend)) {\n -\t\t\tstrbuf_reset(&rev);\n +\t\t/*\n +\t\t * When skipping a failed fixup/squash, we need to edit the\n +\t\t * commit message, the current fixup list and count, and if it\n +\t\t * was the last fixup/squash in the chain, we need to clean up\n +\t\t * the commit message and if there was a squash, let the user\n +\t\t * edit it.\n +\t\t */\n +\t\tif (is_clean && !oidcmp(&head, &to_amend) &&\n +\t\t    opts->current_fixup_count > 0 &&\n +\t\t    file_exists(rebase_path_stopped_sha())) {\n +\t\t\tconst char *p = opts->current_fixups.buf;\n +\t\t\tint len = opts->current_fixups.len;\n +\n +\t\t\topts->current_fixup_count--;\n +\t\t\tif (!len)\n +\t\t\t\tBUG(\"Incorrect current_fixups:\\n%s\", p);\n +\t\t\twhile (len && p[len - 1] != '\\n')\n +\t\t\t\tlen--;\n +\t\t\tstrbuf_setlen(&opts->current_fixups, len);\n +\t\t\tif (write_message(p, len, rebase_path_current_fixups(),\n +\t\t\t\t\t  0) < 0)\n +\t\t\t\treturn error(_(\"could not write file: '%s'\"),\n +\t\t\t\t\t     rebase_path_current_fixups());\n +\n  \t\t\t/*\n -\t\t\t * Clean tree, but we may need to finalize a\n -\t\t\t * fixup/squash chain. A failed fixup/squash leaves the\n -\t\t\t * file amend-type in rebase-merge/; It is okay if that\n -\t\t\t * file is missing, in which case there is no such\n -\t\t\t * chain to finalize.\n +\t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n +\t\t\t * commit message is already correct, no need to commit\n +\t\t\t * it again.\n +\t\t\t *\n +\t\t\t * Only if it is the final command in the fixup/squash\n +\t\t\t * chain, and only if the chain is longer than a single\n +\t\t\t * fixup/squash command (which was just skipped), do we\n +\t\t\t * actually need to re-commit with a cleaned up commit\n +\t\t\t * message.\n  \t\t\t */\n -\t\t\tread_oneliner(&rev, rebase_path_amend_type(), 0);\n -\t\t\tif (!strcmp(\"squash\", rev.buf))\n -\t\t\t\tis_fixup = TODO_SQUASH;\n -\t\t\telse if (!strcmp(\"fixup\", rev.buf)) {\n -\t\t\t\tis_fixup = TODO_FIXUP;\n -\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n +\t\t\tif (opts->current_fixup_count > 0 &&\n +\t\t\t    !is_fixup(peek_command(todo_list, 0))) {\n +\t\t\t\tfinal_fixup = 1;\n +\t\t\t\t/*\n +\t\t\t\t * If there was not a single \"squash\" in the\n +\t\t\t\t * chain, we only need to clean up the commit\n +\t\t\t\t * message, no need to bother the user with\n +\t\t\t\t * opening the commit message in the editor.\n +\t\t\t\t */\n +\t\t\t\tif (!starts_with(p, \"squash \") &&\n +\t\t\t\t    !strstr(p, \"\\nsquash \"))\n +\t\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n +\t\t\t} else if (is_fixup(peek_command(todo_list, 0))) {\n +\t\t\t\t/*\n +\t\t\t\t * We need to update the squash message to skip\n +\t\t\t\t * the latest commit message.\n +\t\t\t\t */\n +\t\t\t\tstruct commit *commit;\n +\t\t\t\tconst char *path = rebase_path_squash_msg();\n +\n +\t\t\t\tif (parse_head(&commit) ||\n +\t\t\t\t    !(p = get_commit_buffer(commit, NULL)) ||\n +\t\t\t\t    write_message(p, strlen(p), path, 0)) {\n +\t\t\t\t\tunuse_commit_buffer(commit, p);\n +\t\t\t\t\treturn error(_(\"could not write file: \"\n +\t\t\t\t\t\t       \"'%s'\"), path);\n +\t\t\t\t}\n +\t\t\t\tunuse_commit_buffer(commit, p);\n  \t\t\t}\n  \t\t}\n  \n @@ -2841,18 +2876,32 @@ static int commit_staged_changes(struct replay_opts *opts)\n  \t\tflags |= AMEND_MSG;\n  \t}\n  \n -\tif (is_clean && !is_fixup) {\n +\tif (is_clean) {\n  \t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n  \n  \t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n  \t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n -\t\treturn 0;\n +\t\tif (!final_fixup)\n +\t\t\treturn 0;\n  \t}\n  \n -\tif (run_git_commit(rebase_path_message(), opts, flags))\n +\tif (run_git_commit(final_fixup ? NULL : rebase_path_message(),\n +\t\t\t   opts, flags))\n  \t\treturn error(_(\"could not commit staged changes.\"));\n  \tunlink(rebase_path_amend());\n -\tunlink(rebase_path_amend_type());\n +\tif (final_fixup) {\n +\t\tunlink(rebase_path_fixup_msg());\n +\t\tunlink(rebase_path_squash_msg());\n +\t}\n +\tif (opts->current_fixup_count > 0) {\n +\t\t/*\n +\t\t * Whether final fixup or not, we just cleaned up the commit\n +\t\t * message...\n +\t\t */\n +\t\tunlink(rebase_path_current_fixups());\n +\t\tstrbuf_reset(&opts->current_fixups);\n +\t\topts->current_fixup_count = 0;\n +\t}\n  \treturn 0;\n  }\n  \n @@ -2864,14 +2913,16 @@ int sequencer_continue(struct replay_opts *opts)\n  \tif (read_and_refresh_cache(opts))\n  \t\treturn -1;\n  \n +\tif (read_populate_opts(opts))\n +\t\treturn -1;\n  \tif (is_rebase_i(opts)) {\n -\t\tif (commit_staged_changes(opts))\n +\t\tif ((res = read_populate_todo(&todo_list, opts)))\n +\t\t\tgoto release_todo_list;\n +\t\tif (commit_staged_changes(opts, &todo_list))\n  \t\t\treturn -1;\n  \t} else if (!file_exists(get_todo_path(opts)))\n  \t\treturn continue_single_pick();\n -\tif (read_populate_opts(opts))\n -\t\treturn -1;\n -\tif ((res = read_populate_todo(&todo_list, opts)))\n +\telse if ((res = read_populate_todo(&todo_list, opts)))\n  \t\tgoto release_todo_list;\n  \n  \tif (!is_rebase_i(opts)) {\n diff --git a/sequencer.h b/sequencer.h\n index e45b178dfc4..1898158c52d 100644\n --- a/sequencer.h\n +++ b/sequencer.h\n @@ -44,10 +44,14 @@ struct replay_opts {\n  \tchar **xopts;\n  \tsize_t xopts_nr, xopts_alloc;\n  \n +\t/* Used by fixup/squash */\n +\tstruct strbuf current_fixups;\n +\tint current_fixup_count;\n +\n  \t/* Only used by REPLAY_NONE */\n  \tstruct rev_info *revs;\n  };\n -#define REPLAY_OPTS_INIT { -1 }\n +#define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT }\n  \n  /* Call this to setup defaults before parsing command line options */\n  void sequencer_init_config(struct replay_opts *opts);\n diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n index 693f92409ec..03bf1b8a3b3 100755\n --- a/t/t3418-rebase-continue.sh\n +++ b/t/t3418-rebase-continue.sh\n @@ -95,19 +95,46 @@ test_expect_success '--skip after failed fixup cleans commit message' '\n  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n -\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n +\ttest_must_fail env FAKE_LINES=\"1 fixup 2 squash 4\" \\\n  \t\tgit rebase -i HEAD~4 &&\n  \n  \t: now there is a conflict, and comments in the commit message &&\n  \tgit show HEAD >out &&\n -\ttest_i18ngrep \"This is a combination of\" out &&\n +\tgrep \"fixup! wants-fixup\" out &&\n  \n  \t: skip and continue &&\n -\tgit rebase --skip &&\n +\techo \"cp \\\"\\$1\\\" .git/copy.txt\" | write_script copy-editor.sh &&\n +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n +\n +\t: the user should not have had to edit the commit message &&\n +\ttest_path_is_missing .git/copy.txt &&\n  \n  \t: now the comments in the commit message should have been cleaned up &&\n  \tgit show HEAD >out &&\n -\ttest_i18ngrep ! \"This is a combination of\" out\n +\t! grep \"fixup! wants-fixup\" out &&\n +\n +\t: now, let us ensure that \"squash\" is handled correctly &&\n +\tgit reset --hard wants-fixup-3 &&\n +\ttest_must_fail env FAKE_LINES=\"1 squash 4 squash 2 squash 4\" \\\n +\t\tgit rebase -i HEAD~4 &&\n +\n +\t: the first squash failed, but there are two more in the chain &&\n +\t(test_set_editor \"$PWD/copy-editor.sh\" &&\n +\t test_must_fail git rebase --skip) &&\n +\n +\t: not the final squash, no need to edit the commit message &&\n +\ttest_path_is_missing .git/copy.txt &&\n +\n +\t: The first squash was skipped, therefore: &&\n +\tgit show HEAD >out &&\n +\ttest_i18ngrep \"# This is a combination of 2 commits\" out &&\n +\n +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n +\tgit show HEAD >out &&\n +\ttest_i18ngrep ! \"# This is a combination\" out &&\n +\n +\t: Final squash failed, but there was still a squash &&\n +\ttest_i18ngrep \"# This is a combination of 2 commits\" .git/copy.txt\n  '\n  \n  test_expect_success 'setup rerere database' '\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n"},{"id":"345987","messageId":"49b9f6562e3fd32f1dab706de29057d536d3d2de.1524862093.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524862093.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 1/4] rebase -i: demonstrate bugs with fixup!/squash! commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T20:48:16Z","receivedAt":"2018-04-27T20:48:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When multiple fixup/squash commands are processed and the last one\ncauses merge conflicts and is skipped, we leave the \"This is a\ncombination of ...\" comments in the commit message.\n\nNoticed by Eric Sunshine.\n\nThis regression test also demonstrates that we rely on the localized\nversion of\n\n\t# This is a combination of <number> commits\n\nto contain the <number> in ASCII, which breaks under GETTEXT_POISON.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t3418-rebase-continue.sh | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 9214d0bb511..3874f187246 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -88,6 +88,28 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n \tgit rebase --continue\n '\n \n+test_expect_failure '--skip after failed fixup cleans commit message' '\n+\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\tgit checkout -b with-conflicting-fixup &&\n+\ttest_commit wants-fixup &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n+\ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n+\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n+\t\tgit rebase -i HEAD~4 &&\n+\n+\t: now there is a conflict, and comments in the commit message &&\n+\tgit show HEAD >out &&\n+\tgrep \"fixup! wants-fixup\" out &&\n+\n+\t: skip and continue &&\n+\tgit rebase --skip &&\n+\n+\t: now the comments in the commit message should have been cleaned up &&\n+\tgit show HEAD >out &&\n+\t! grep \"fixup! wants-fixup\" out\n+'\n+\n test_expect_success 'setup rerere database' '\n \trm -fr .git/rebase-* &&\n \tgit reset --hard commit-new-file-F3-on-topic-branch &&\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345988","messageId":"b4c41988ab591e5b67c78cca91c834ddfc8ded84.1524862093.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524862093.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 3/4] sequencer: always commit without editing when asked for","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T20:48:28Z","receivedAt":"2018-04-27T20:48:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Previously, we only called run_git_commit() without EDIT_MSG when we also\npassed in a default message.\n\nHowever, an upcoming caller will want to commit without EDIT_MSG and\n*without* a default message: to clean up fixup/squash comments in HEAD's\ncommit message.\n\nLet's prepare for that.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex d2e6f33023d..56166b0d6c7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -717,6 +717,8 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,\n \t\targv_array_pushf(&cmd.args, \"-S%s\", opts->gpg_sign);\n \tif (defmsg)\n \t\targv_array_pushl(&cmd.args, \"-F\", defmsg, NULL);\n+\telse if (!(flags & EDIT_MSG))\n+\t\targv_array_pushl(&cmd.args, \"-C\", \"HEAD\", NULL);\n \tif ((flags & CLEANUP_MSG))\n \t\targv_array_push(&cmd.args, \"--cleanup=strip\");\n \tif ((flags & EDIT_MSG))\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345989","messageId":"99624118cb9e68ac6b38c4abf344ad880b607bf4.1524862093.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524862093.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 2/4] rebase -i: Handle \"combination of <n> commits\" with GETTEXT_POISON","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T20:48:21Z","receivedAt":"2018-04-27T20:48:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We previously relied on the localized versions of\n\n\t# This is a combination of <N> commits\n\n(which we write into the commit messages during fixup/squash chains)\nto contain <N> encoded in ASCII.\n\nThis is not true in general, and certainly not true when compiled with\nGETTEXT_POISON=TryToKillMe, as demonstrated by the regression test we\njust introduced in t3418.\n\nSo let's decouple keeping track of the count from the (localized) commit\nmessages by introducing a new file called 'current-fixups' that keeps\ntrack of the current fixup/squash chain. This file contains a bit more\nthan just the count (it contains a list of \"fixup <commit>\"/\"squash\n<commit>\" lines). This is done on purpose, as it will come in handy for\na fix for the bug where `git rebase --skip` on a final fixup/squash will\nleave the commit message in limbo.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c | 78 ++++++++++++++++++++++++++++++-----------------------\n sequencer.h |  6 ++++-\n 2 files changed, 49 insertions(+), 35 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 5e3a50fafc9..d2e6f33023d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -74,13 +74,6 @@ static GIT_PATH_FUNC(rebase_path_message, \"rebase-merge/message\")\n  * previous commit and from the first squash/fixup commit are written\n  * to it. The commit message for each subsequent squash/fixup commit\n  * is appended to the file as it is processed.\n- *\n- * The first line of the file is of the form\n- *     # This is a combination of $count commits.\n- * where $count is the number of commits whose messages have been\n- * written to the file so far (including the initial \"pick\" commit).\n- * Each time that a commit message is processed, this line is read and\n- * updated. It is deleted just before the combined commit is made.\n  */\n static GIT_PATH_FUNC(rebase_path_squash_msg, \"rebase-merge/message-squash\")\n /*\n@@ -91,6 +84,11 @@ static GIT_PATH_FUNC(rebase_path_squash_msg, \"rebase-merge/message-squash\")\n  * commit without opening the editor.)\n  */\n static GIT_PATH_FUNC(rebase_path_fixup_msg, \"rebase-merge/message-fixup\")\n+/*\n+ * This file contains the list fixup/squash commands that have been\n+ * accumulated into message-fixup or message-squash so far.\n+ */\n+static GIT_PATH_FUNC(rebase_path_current_fixups, \"rebase-merge/current-fixups\")\n /*\n  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and\n  * GIT_AUTHOR_DATE that will be used for the commit that is currently\n@@ -253,6 +251,7 @@ int sequencer_remove_state(struct replay_opts *opts)\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n \tfree(opts->xopts);\n+\tstrbuf_release(&opts->current_fixups);\n \n \tstrbuf_addstr(&dir, get_dir(opts));\n \tremove_dir_recursively(&dir, 0);\n@@ -1329,34 +1328,23 @@ static int update_squash_messages(enum todo_command command,\n \t\tstruct commit *commit, struct replay_opts *opts)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tint count, res;\n+\tint res;\n \tconst char *message, *body;\n \n-\tif (file_exists(rebase_path_squash_msg())) {\n+\tif (opts->current_fixup_count > 0) {\n \t\tstruct strbuf header = STRBUF_INIT;\n-\t\tchar *eol, *p;\n+\t\tchar *eol;\n \n-\t\tif (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)\n+\t\tif (strbuf_read_file(&buf, rebase_path_squash_msg(), 9) <= 0)\n \t\t\treturn error(_(\"could not read '%s'\"),\n \t\t\t\trebase_path_squash_msg());\n \n-\t\tp = buf.buf + 1;\n-\t\teol = strchrnul(buf.buf, '\\n');\n-\t\tif (buf.buf[0] != comment_line_char ||\n-\t\t    (p += strcspn(p, \"0123456789\\n\")) == eol)\n-\t\t\treturn error(_(\"unexpected 1st line of squash message:\"\n-\t\t\t\t       \"\\n\\n\\t%.*s\"),\n-\t\t\t\t     (int)(eol - buf.buf), buf.buf);\n-\t\tcount = strtol(p, NULL, 10);\n-\n-\t\tif (count < 1)\n-\t\t\treturn error(_(\"invalid 1st line of squash message:\\n\"\n-\t\t\t\t       \"\\n\\t%.*s\"),\n-\t\t\t\t     (int)(eol - buf.buf), buf.buf);\n+\t\teol = buf.buf[0] != comment_line_char ?\n+\t\t\tbuf.buf : strchrnul(buf.buf, '\\n');\n \n \t\tstrbuf_addf(&header, \"%c \", comment_line_char);\n-\t\tstrbuf_addf(&header,\n-\t\t\t    _(\"This is a combination of %d commits.\"), ++count);\n+\t\tstrbuf_addf(&header, _(\"This is a combination of %d commits.\"),\n+\t\t\t    opts->current_fixup_count + 2);\n \t\tstrbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);\n \t\tstrbuf_release(&header);\n \t} else {\n@@ -1379,10 +1367,8 @@ static int update_squash_messages(enum todo_command command,\n \t\t\t\t     rebase_path_fixup_msg());\n \t\t}\n \n-\t\tcount = 2;\n \t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n-\t\tstrbuf_addf(&buf, _(\"This is a combination of %d commits.\"),\n-\t\t\t    count);\n+\t\tstrbuf_addf(&buf, _(\"This is a combination of %d commits.\"), 2);\n \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n \t\tstrbuf_addstr(&buf, _(\"This is the 1st commit message:\"));\n \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n@@ -1399,13 +1385,14 @@ static int update_squash_messages(enum todo_command command,\n \tif (command == TODO_SQUASH) {\n \t\tunlink(rebase_path_fixup_msg());\n \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n-\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"), count);\n+\t\tstrbuf_addf(&buf, _(\"This is the commit message #%d:\"),\n+\t\t\t    ++opts->current_fixup_count);\n \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\tstrbuf_addstr(&buf, body);\n \t} else if (command == TODO_FIXUP) {\n \t\tstrbuf_addf(&buf, \"\\n%c \", comment_line_char);\n \t\tstrbuf_addf(&buf, _(\"The commit message #%d will be skipped:\"),\n-\t\t\t    count);\n+\t\t\t    ++opts->current_fixup_count);\n \t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\tstrbuf_add_commented_lines(&buf, body, strlen(body));\n \t} else\n@@ -1414,6 +1401,17 @@ static int update_squash_messages(enum todo_command command,\n \n \tres = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);\n \tstrbuf_release(&buf);\n+\n+\tif (!res) {\n+\t\tstrbuf_addf(&opts->current_fixups, \"%s%s %s\",\n+\t\t\t    opts->current_fixups.len ? \"\\n\" : \"\",\n+\t\t\t    command_to_string(command),\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tres = write_message(opts->current_fixups.buf,\n+\t\t\t\t    opts->current_fixups.len,\n+\t\t\t\t    rebase_path_current_fixups(), 0);\n+\t}\n+\n \treturn res;\n }\n \n@@ -1676,6 +1674,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,\n \tif (!res && final_fixup) {\n \t\tunlink(rebase_path_fixup_msg());\n \t\tunlink(rebase_path_squash_msg());\n+\t\tunlink(rebase_path_current_fixups());\n+\t\tstrbuf_reset(&opts->current_fixups);\n+\t\topts->current_fixup_count = 0;\n \t}\n \n leave:\n@@ -2052,6 +2053,16 @@ static int read_populate_opts(struct replay_opts *opts)\n \t\tread_strategy_opts(opts, &buf);\n \t\tstrbuf_release(&buf);\n \n+\t\tif (read_oneliner(&opts->current_fixups,\n+\t\t\t\t  rebase_path_current_fixups(), 1)) {\n+\t\t\tconst char *p = opts->current_fixups.buf;\n+\t\t\topts->current_fixup_count = 1;\n+\t\t\twhile ((p = strchr(p, '\\n'))) {\n+\t\t\t\topts->current_fixup_count++;\n+\t\t\t\tp++;\n+\t\t\t}\n+\t\t}\n+\n \t\treturn 0;\n \t}\n \n@@ -2398,10 +2409,9 @@ static int error_with_patch(struct commit *commit,\n static int error_failed_squash(struct commit *commit,\n \tstruct replay_opts *opts, int subject_len, const char *subject)\n {\n-\tif (rename(rebase_path_squash_msg(), rebase_path_message()))\n-\t\treturn error(_(\"could not rename '%s' to '%s'\"),\n+\tif (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))\n+\t\treturn error(_(\"could not copy '%s' to '%s'\"),\n \t\t\trebase_path_squash_msg(), rebase_path_message());\n-\tunlink(rebase_path_fixup_msg());\n \tunlink(git_path_merge_msg());\n \tif (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))\n \t\treturn error(_(\"could not copy '%s' to '%s'\"),\ndiff --git a/sequencer.h b/sequencer.h\nindex e45b178dfc4..1898158c52d 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -44,10 +44,14 @@ struct replay_opts {\n \tchar **xopts;\n \tsize_t xopts_nr, xopts_alloc;\n \n+\t/* Used by fixup/squash */\n+\tstruct strbuf current_fixups;\n+\tint current_fixup_count;\n+\n \t/* Only used by REPLAY_NONE */\n \tstruct rev_info *revs;\n };\n-#define REPLAY_OPTS_INIT { -1 }\n+#define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT }\n \n /* Call this to setup defaults before parsing command line options */\n void sequencer_init_config(struct replay_opts *opts);\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345990","messageId":"08609b5f2d5bc7f3f6d010088db4b58695dfbd64.1524862093.git.johannes.schindelin@gmx.de","threadId":"47635","inReplyTo":"cover.1524862093.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T20:48:30Z","receivedAt":"2018-04-27T20:48:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"During a series of fixup/squash commands, the interactive rebase builds\nup a commit message with comments. This will be presented to the user in\nthe editor if at least one of those commands was a `squash`.\n\nIn any case, the commit message will be cleaned up eventually, removing\nall those intermediate comments, in the final step of such a\nfixup/squash chain.\n\nHowever, if the last fixup/squash command in such a chain fails with\nmerge conflicts, and if the user then decides to skip it (or resolve it\nto a clean worktree and then continue the rebase), the current code\nfails to clean up the commit message.\n\nThis commit fixes that behavior.\n\nThe fix is quite a bit more involved than meets the eye because it is\nnot only about the question whether we are `git rebase --skip`ing a\nfixup or squash. It is also about removing the skipped fixup/squash's\ncommit message from the accumulated commit message. And it is also about\nthe question whether we should let the user edit the final commit\nmessage or not (\"Was there a squash in the chain *that was not\nskipped*?\").\n\nFor example, in this case we will want to fix the commit message, but\nnot open it in an editor:\n\n\tpick\t<- succeeds\n\tfixup\t<- succeeds\n\tsquash\t<- fails, will be skipped\n\nThis is where the newly-introduced `current-fixups` file comes in real\nhandy. A quick look and we can determine whether there was a non-skipped\nsquash. We only need to make sure to keep it up to date with respect to\nskipped fixup/squash commands. As a bonus, we can even avoid committing\nunnecessarily, e.g. when there was only one fixup, and it failed, and\nwas skipped.\n\nTo fix only the bug where the final commit message was not cleaned up\nproperly, but without fixing the rest, would have been more complicated\nthan fixing it all in one go, hence this commit lumps together more than\na single concern.\n\nFor the same reason, this commit also adds a bit more to the existing\ntest case for the regression we just fixed.\n\nThe diff is best viewed with --color-moved.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sequencer.c                | 113 ++++++++++++++++++++++++++++++++-----\n t/t3418-rebase-continue.sh |  35 ++++++++++--\n 2 files changed, 131 insertions(+), 17 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 56166b0d6c7..cec180714ef 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2779,19 +2779,16 @@ static int continue_single_pick(void)\n \treturn run_command_v_opt(argv, RUN_GIT_CMD);\n }\n \n-static int commit_staged_changes(struct replay_opts *opts)\n+static int commit_staged_changes(struct replay_opts *opts,\n+\t\t\t\t struct todo_list *todo_list)\n {\n \tunsigned int flags = ALLOW_EMPTY | EDIT_MSG;\n+\tunsigned int final_fixup = 0, is_clean;\n \n \tif (has_unstaged_changes(1))\n \t\treturn error(_(\"cannot rebase: You have unstaged changes.\"));\n-\tif (!has_uncommitted_changes(0)) {\n-\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n \n-\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n-\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n-\t\treturn 0;\n-\t}\n+\tis_clean = !has_uncommitted_changes(0);\n \n \tif (file_exists(rebase_path_amend())) {\n \t\tstruct strbuf rev = STRBUF_INIT;\n@@ -2804,19 +2801,107 @@ static int commit_staged_changes(struct replay_opts *opts)\n \t\tif (get_oid_hex(rev.buf, &to_amend))\n \t\t\treturn error(_(\"invalid contents: '%s'\"),\n \t\t\t\trebase_path_amend());\n-\t\tif (oidcmp(&head, &to_amend))\n+\t\tif (!is_clean && oidcmp(&head, &to_amend))\n \t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n \t\t\t\t       \"working tree. Please, commit them\\n\"\n \t\t\t\t       \"first and then run 'git rebase \"\n \t\t\t\t       \"--continue' again.\"));\n+\t\t/*\n+\t\t * When skipping a failed fixup/squash, we need to edit the\n+\t\t * commit message, the current fixup list and count, and if it\n+\t\t * was the last fixup/squash in the chain, we need to clean up\n+\t\t * the commit message and if there was a squash, let the user\n+\t\t * edit it.\n+\t\t */\n+\t\tif (is_clean && !oidcmp(&head, &to_amend) &&\n+\t\t    opts->current_fixup_count > 0 &&\n+\t\t    file_exists(rebase_path_stopped_sha())) {\n+\t\t\tconst char *p = opts->current_fixups.buf;\n+\t\t\tint len = opts->current_fixups.len;\n+\n+\t\t\topts->current_fixup_count--;\n+\t\t\tif (!len)\n+\t\t\t\tBUG(\"Incorrect current_fixups:\\n%s\", p);\n+\t\t\twhile (len && p[len - 1] != '\\n')\n+\t\t\t\tlen--;\n+\t\t\tstrbuf_setlen(&opts->current_fixups, len);\n+\t\t\tif (write_message(p, len, rebase_path_current_fixups(),\n+\t\t\t\t\t  0) < 0)\n+\t\t\t\treturn error(_(\"could not write file: '%s'\"),\n+\t\t\t\t\t     rebase_path_current_fixups());\n+\n+\t\t\t/*\n+\t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n+\t\t\t * commit message is already correct, no need to commit\n+\t\t\t * it again.\n+\t\t\t *\n+\t\t\t * Only if it is the final command in the fixup/squash\n+\t\t\t * chain, and only if the chain is longer than a single\n+\t\t\t * fixup/squash command (which was just skipped), do we\n+\t\t\t * actually need to re-commit with a cleaned up commit\n+\t\t\t * message.\n+\t\t\t */\n+\t\t\tif (opts->current_fixup_count > 0 &&\n+\t\t\t    !is_fixup(peek_command(todo_list, 0))) {\n+\t\t\t\tfinal_fixup = 1;\n+\t\t\t\t/*\n+\t\t\t\t * If there was not a single \"squash\" in the\n+\t\t\t\t * chain, we only need to clean up the commit\n+\t\t\t\t * message, no need to bother the user with\n+\t\t\t\t * opening the commit message in the editor.\n+\t\t\t\t */\n+\t\t\t\tif (!starts_with(p, \"squash \") &&\n+\t\t\t\t    !strstr(p, \"\\nsquash \"))\n+\t\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n+\t\t\t} else if (is_fixup(peek_command(todo_list, 0))) {\n+\t\t\t\t/*\n+\t\t\t\t * We need to update the squash message to skip\n+\t\t\t\t * the latest commit message.\n+\t\t\t\t */\n+\t\t\t\tstruct commit *commit;\n+\t\t\t\tconst char *path = rebase_path_squash_msg();\n+\n+\t\t\t\tif (parse_head(&commit) ||\n+\t\t\t\t    !(p = get_commit_buffer(commit, NULL)) ||\n+\t\t\t\t    write_message(p, strlen(p), path, 0)) {\n+\t\t\t\t\tunuse_commit_buffer(commit, p);\n+\t\t\t\t\treturn error(_(\"could not write file: \"\n+\t\t\t\t\t\t       \"'%s'\"), path);\n+\t\t\t\t}\n+\t\t\t\tunuse_commit_buffer(commit, p);\n+\t\t\t}\n+\t\t}\n \n \t\tstrbuf_release(&rev);\n \t\tflags |= AMEND_MSG;\n \t}\n \n-\tif (run_git_commit(rebase_path_message(), opts, flags))\n+\tif (is_clean) {\n+\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n+\n+\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n+\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n+\t\tif (!final_fixup)\n+\t\t\treturn 0;\n+\t}\n+\n+\tif (run_git_commit(final_fixup ? NULL : rebase_path_message(),\n+\t\t\t   opts, flags))\n \t\treturn error(_(\"could not commit staged changes.\"));\n \tunlink(rebase_path_amend());\n+\tif (final_fixup) {\n+\t\tunlink(rebase_path_fixup_msg());\n+\t\tunlink(rebase_path_squash_msg());\n+\t}\n+\tif (opts->current_fixup_count > 0) {\n+\t\t/*\n+\t\t * Whether final fixup or not, we just cleaned up the commit\n+\t\t * message...\n+\t\t */\n+\t\tunlink(rebase_path_current_fixups());\n+\t\tstrbuf_reset(&opts->current_fixups);\n+\t\topts->current_fixup_count = 0;\n+\t}\n \treturn 0;\n }\n \n@@ -2828,14 +2913,16 @@ int sequencer_continue(struct replay_opts *opts)\n \tif (read_and_refresh_cache(opts))\n \t\treturn -1;\n \n+\tif (read_populate_opts(opts))\n+\t\treturn -1;\n \tif (is_rebase_i(opts)) {\n-\t\tif (commit_staged_changes(opts))\n+\t\tif ((res = read_populate_todo(&todo_list, opts)))\n+\t\t\tgoto release_todo_list;\n+\t\tif (commit_staged_changes(opts, &todo_list))\n \t\t\treturn -1;\n \t} else if (!file_exists(get_todo_path(opts)))\n \t\treturn continue_single_pick();\n-\tif (read_populate_opts(opts))\n-\t\treturn -1;\n-\tif ((res = read_populate_todo(&todo_list, opts)))\n+\telse if ((res = read_populate_todo(&todo_list, opts)))\n \t\tgoto release_todo_list;\n \n \tif (!is_rebase_i(opts)) {\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 3874f187246..03bf1b8a3b3 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -88,14 +88,14 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n \tgit rebase --continue\n '\n \n-test_expect_failure '--skip after failed fixup cleans commit message' '\n+test_expect_success '--skip after failed fixup cleans commit message' '\n \ttest_when_finished \"test_might_fail git rebase --abort\" &&\n \tgit checkout -b with-conflicting-fixup &&\n \ttest_commit wants-fixup &&\n \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n-\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n+\ttest_must_fail env FAKE_LINES=\"1 fixup 2 squash 4\" \\\n \t\tgit rebase -i HEAD~4 &&\n \n \t: now there is a conflict, and comments in the commit message &&\n@@ -103,11 +103,38 @@ test_expect_failure '--skip after failed fixup cleans commit message' '\n \tgrep \"fixup! wants-fixup\" out &&\n \n \t: skip and continue &&\n-\tgit rebase --skip &&\n+\techo \"cp \\\"\\$1\\\" .git/copy.txt\" | write_script copy-editor.sh &&\n+\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n+\n+\t: the user should not have had to edit the commit message &&\n+\ttest_path_is_missing .git/copy.txt &&\n \n \t: now the comments in the commit message should have been cleaned up &&\n \tgit show HEAD >out &&\n-\t! grep \"fixup! wants-fixup\" out\n+\t! grep \"fixup! wants-fixup\" out &&\n+\n+\t: now, let us ensure that \"squash\" is handled correctly &&\n+\tgit reset --hard wants-fixup-3 &&\n+\ttest_must_fail env FAKE_LINES=\"1 squash 4 squash 2 squash 4\" \\\n+\t\tgit rebase -i HEAD~4 &&\n+\n+\t: the first squash failed, but there are two more in the chain &&\n+\t(test_set_editor \"$PWD/copy-editor.sh\" &&\n+\t test_must_fail git rebase --skip) &&\n+\n+\t: not the final squash, no need to edit the commit message &&\n+\ttest_path_is_missing .git/copy.txt &&\n+\n+\t: The first squash was skipped, therefore: &&\n+\tgit show HEAD >out &&\n+\ttest_i18ngrep \"# This is a combination of 2 commits\" out &&\n+\n+\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n+\tgit show HEAD >out &&\n+\ttest_i18ngrep ! \"# This is a combination\" out &&\n+\n+\t: Final squash failed, but there was still a squash &&\n+\ttest_i18ngrep \"# This is a combination of 2 commits\" .git/copy.txt\n '\n \n test_expect_success 'setup rerere database' '\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n"},{"id":"345995","messageId":"CAGZ79kYJ--qt5AdM3EX9vwMb_AwD6gtWFp7wt2KP4OKsdOYLGQ@mail.gmail.com","threadId":"47635","inReplyTo":"08609b5f2d5bc7f3f6d010088db4b58695dfbd64.1524862093.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-27T21:28:54Z","receivedAt":"2018-04-27T21:28:58Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Apr 27, 2018 at 1:48 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> During a series of fixup/squash commands, the interactive rebase builds\n> up a commit message with comments. This will be presented to the user in\n> the editor if at least one of those commands was a `squash`.\n\nThis sounds as if the whole series will be presented to the user, i.e.\n\n pick A\n squash B\n fixup C\n\nwould present A+B+C in the editor. I always assumed the sequencer\nto be linear, i.e. pick A+B, open editor and then fixup C into the\nprevious result?\n\nNo need to resend it reworded, I just realize that I never tested my\npotentially wrong assumption.\n\n> The diff is best viewed with --color-moved.\n\n... and web pages are \"best viewed with IE 6.0\" ;-)\n\nI found this so funny that I had to download the patches and actually\nlook at them\nusing the move detection only to find out that only very few lines are moved,\nas there are only very few deleted lines.\n"},{"id":"346030","messageId":"20180428113257.25425-1-martin.agren@gmail.com","threadId":"47635","inReplyTo":"20180424162939.20956-1-newren@gmail.com","subject":"Re: [PATCH 2/2] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-04-28T11:32:57Z","receivedAt":"2018-04-28T11:34:04Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"From: Elijah Newren <newren@gmail.com>\n\nHi Elijah,\n\n[Since this is leaving the topic of rename-detection in favour of \nleak-plugging, I'm shortening the cc-list a bit.]\n\n> So, instead, I'd like to see something like the below\n> (built on top of my series):\n\nThanks a lot. I now have the below patch in my tree as a preparatory\npart of a three-patch series on top of your series. Since the gist of\nthis patch is entirely your creation, is it ok if I place your Author:\nand Signed-off-by: on it? Credit where credit is due.\n\nAs you noted elsewhere [1], Ben is also working in this area. I'd be\nperfectly happy to sit on these patches until both of your contributions\ncome through to master.\n\n[1] https://public-inbox.org/git/CABPp-BFh=gL6RnbST2bgtynkij1Z5TMgAr1Via5_VyteF5eBMg@mail.gmail.com/\n\nMartin\n\n-->8--\nSubject: merge-recursive: provide pair of `unpack_trees_{start,finish}()`\n\nRename `git_merge_trees()` to `unpack_trees_start()` and extract the\ncall to `discard_index()` into a new function `unpack_trees_finish()`.\nAs a result, these are called early resp. late in `merge_trees()`,\nmaking the resource handling clearer. The next commit will expand on\nthat, teaching `..._finish()` to free more memory. (So rather than\nmoving the TODO-comment, just drop it, since it will be addressed soon\nenough.)\n\nAlso call `..._finish()` when `merge_trees()` returns early.\n---\n merge-recursive.c | 29 +++++++++++++++--------------\n 1 file changed, 15 insertions(+), 14 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1de8dc1c53..e64102004a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -337,10 +337,10 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n \tinit_tree_desc(desc, tree->buffer, tree->size);\n }\n \n-static int git_merge_trees(struct merge_options *o,\n-\t\t\t   struct tree *common,\n-\t\t\t   struct tree *head,\n-\t\t\t   struct tree *merge)\n+static int unpack_trees_start(struct merge_options *o,\n+\t\t\t      struct tree *common,\n+\t\t\t      struct tree *head,\n+\t\t\t      struct tree *merge)\n {\n \tint rc;\n \tstruct tree_desc t[3];\n@@ -378,6 +378,11 @@ static int git_merge_trees(struct merge_options *o,\n \treturn rc;\n }\n \n+static void unpack_trees_finish(struct merge_options *o)\n+{\n+\tdiscard_index(&o->orig_index);\n+}\n+\n struct tree *write_tree_from_memory(struct merge_options *o)\n {\n \tstruct tree *result = NULL;\n@@ -3079,13 +3084,14 @@ int merge_trees(struct merge_options *o,\n \t\treturn 1;\n \t}\n \n-\tcode = git_merge_trees(o, common, head, merge);\n+\tcode = unpack_trees_start(o, common, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(o, 4) || o->call_depth)\n \t\t\terr(o, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n+\t\tunpack_trees_finish(o);\n \t\treturn -1;\n \t}\n \n@@ -3138,20 +3144,15 @@ int merge_trees(struct merge_options *o,\n \n \t\thashmap_free(&o->current_file_dir_set, 1);\n \n-\t\tif (clean < 0)\n+\t\tif (clean < 0) {\n+\t\t\tunpack_trees_finish(o);\n \t\t\treturn clean;\n+\t\t}\n \t}\n \telse\n \t\tclean = 1;\n \n-\t/* Free the extra index left from git_merge_trees() */\n-\t/*\n-\t * FIXME: Need to also data allocated by setup_unpack_trees_porcelain()\n-\t * tucked away in o->unpack_opts.msgs, but the problem is that only\n-\t * half of it refers to dynamically allocated data, while the other\n-\t * half points at static strings.\n-\t */\n-\tdiscard_index(&o->orig_index);\n+\tunpack_trees_finish(o);\n \n \tif (o->call_depth && !(*result = write_tree_from_memory(o)))\n \t\treturn -1;\n-- \n2.17.0\n\n"},{"id":"346034","messageId":"nycvar.QRO.7.76.6.1804281428070.79@tvgsbejvaqbjf.bet","threadId":"47635","inReplyTo":"20180428113257.25425-1-martin.agren@gmail.com","subject":"Re: [PATCH 2/2] unpack_trees_options: free messages when done","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T12:30:26Z","receivedAt":"2018-04-28T12:30:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Martin,\n\nOn Sat, 28 Apr 2018, Martin Ågren wrote:\n\n> -->8--\n> Subject: merge-recursive: provide pair of `unpack_trees_{start,finish}()`\n> \n> Rename `git_merge_trees()` to `unpack_trees_start()` and extract the\n> call to `discard_index()` into a new function `unpack_trees_finish()`.\n> As a result, these are called early resp. late in `merge_trees()`,\n> making the resource handling clearer. The next commit will expand on\n> that, teaching `..._finish()` to free more memory. (So rather than\n> moving the TODO-comment, just drop it, since it will be addressed soon\n> enough.)\n> \n> Also call `..._finish()` when `merge_trees()` returns early.\n\nLooks good! It is missing a Signed-off-by: line, and you probably want to\nstart a new thread that also includes the \"next commit\", but other than\nthat it is pretty nice and ready for contributing, methinks.\n\nCiao,\nDscho"},{"id":"346036","messageId":"nycvar.QRO.7.76.6.1804281500080.79@tvgsbejvaqbjf.bet","threadId":"47635","inReplyTo":"CAGZ79kYJ--qt5AdM3EX9vwMb_AwD6gtWFp7wt2KP4OKsdOYLGQ@mail.gmail.com","subject":"Re: [PATCH v4 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T13:05:19Z","receivedAt":"2018-04-28T13:05:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Fri, 27 Apr 2018, Stefan Beller wrote:\n\n> On Fri, Apr 27, 2018 at 1:48 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > During a series of fixup/squash commands, the interactive rebase builds\n> > up a commit message with comments. This will be presented to the user in\n> > the editor if at least one of those commands was a `squash`.\n> \n> This sounds as if the whole series will be presented to the user, i.e.\n> \n>  pick A\n>  squash B\n>  fixup C\n> \n> would present A+B+C in the editor.\n\nAnd that is indeed the case. The commit message would look something like\nthis:\n\n\t# This is a combination of 3 commits.\n\t# This is commit message #1:\n\n\tHello Stefan\n\n\tThis is A.\n\n\t# This is commit message #2:\n\n\tsquash! A\n\n\tMe again, Stefan. I am here to be squashed.\n\n\t# The commit message #3 will be skipped:\n\t#\n\t# fixup! A\n\n> I always assumed the sequencer to be linear, i.e. pick A+B, open editor\n> and then fixup C into the previous result?\n\nNope.\n\n> No need to resend it reworded, I just realize that I never tested my\n> potentially wrong assumption.\n\nNo worries, you learned something today.\n\n> > The diff is best viewed with --color-moved.\n> \n> ... and web pages are \"best viewed with IE 6.0\" ;-)\n\nThat is what I had in mind writing that.\n\n> I found this so funny that I had to download the patches and actually\n> look at them using the move detection only to find out that only very\n> few lines are moved, as there are only very few deleted lines.\n\nI agree that the current iteration is no longer such obvious a move. I had\nto add tons of stuff to fix the extra issues I found while working on v4.\n\nBut still, I found it super-helpful to see that the code was actually\nmoved, and where, because I essentially had to break up the nice sequence\nof \"is it clean? Yes? Then nothing to be done! No? Is HEAD to be amended?\nYes? No?\" and basically build a matrix what to do in all combinations of\n\"clean? Amend HEAD?\"\n\nCiao,\nDscho\n"},{"id":"346051","messageId":"CABPp-BHEz4aLrGwd0OMDgMYLsRgtkgqGi-T1Bz5RYg7059MS+A@mail.gmail.com","threadId":"47635","inReplyTo":"20180428113257.25425-1-martin.agren@gmail.com","subject":"Re: [PATCH 2/2] unpack_trees_options: free messages when done","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-04-28T20:56:04Z","receivedAt":"2018-04-28T20:56:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Martin,\n\nOn Sat, Apr 28, 2018 at 4:32 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n> From: Elijah Newren <newren@gmail.com>\n>\n> Hi Elijah,\n>\n> [Since this is leaving the topic of rename-detection in favour of\n> leak-plugging, I'm shortening the cc-list a bit.]\n>\n>> So, instead, I'd like to see something like the below\n>> (built on top of my series):\n>\n> Thanks a lot. I now have the below patch in my tree as a preparatory\n> part of a three-patch series on top of your series. Since the gist of\n> this patch is entirely your creation, is it ok if I place your Author:\n> and Signed-off-by: on it? Credit where credit is due.\n\nSure, I'm fine with either that or an Original-patch-by attribution.\n\nAnyway, thanks for fleshing it out with the commit message and\nhandling the early return cases.  And for tackling the\nsetup_unpack_trees_porcelain() memory leak.\n"},{"id":"346777","messageId":"4bd08893-52b4-3a01-d546-4884e2762670@talktalk.net","threadId":"47635","inReplyTo":"08609b5f2d5bc7f3f6d010088db4b58695dfbd64.1524862093.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-05-06T17:50:15Z","receivedAt":"2018-05-06T17:50:21Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Johannes, sorry it's taken me a while to look at this. I think it\nmostly makes sense to me, the code is well documented. I've got one\ncomment below\n\nOn 27/04/18 21:48, Johannes Schindelin wrote:\n> \n> During a series of fixup/squash commands, the interactive rebase builds\n> up a commit message with comments. This will be presented to the user in\n> the editor if at least one of those commands was a `squash`.\n> \n> In any case, the commit message will be cleaned up eventually, removing\n> all those intermediate comments, in the final step of such a\n> fixup/squash chain.\n> \n> However, if the last fixup/squash command in such a chain fails with\n> merge conflicts, and if the user then decides to skip it (or resolve it\n> to a clean worktree and then continue the rebase), the current code\n> fails to clean up the commit message.\n> \n> This commit fixes that behavior.\n> \n> The fix is quite a bit more involved than meets the eye because it is\n> not only about the question whether we are `git rebase --skip`ing a\n> fixup or squash. It is also about removing the skipped fixup/squash's\n> commit message from the accumulated commit message. And it is also about\n> the question whether we should let the user edit the final commit\n> message or not (\"Was there a squash in the chain *that was not\n> skipped*?\").\n> \n> For example, in this case we will want to fix the commit message, but\n> not open it in an editor:\n> \n> \tpick\t<- succeeds\n> \tfixup\t<- succeeds\n> \tsquash\t<- fails, will be skipped\n> \n> This is where the newly-introduced `current-fixups` file comes in real\n> handy. A quick look and we can determine whether there was a non-skipped\n> squash. We only need to make sure to keep it up to date with respect to\n> skipped fixup/squash commands. As a bonus, we can even avoid committing\n> unnecessarily, e.g. when there was only one fixup, and it failed, and\n> was skipped.\n> \n> To fix only the bug where the final commit message was not cleaned up\n> properly, but without fixing the rest, would have been more complicated\n> than fixing it all in one go, hence this commit lumps together more than\n> a single concern.\n> \n> For the same reason, this commit also adds a bit more to the existing\n> test case for the regression we just fixed.\n> \n> The diff is best viewed with --color-moved.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  sequencer.c                | 113 ++++++++++++++++++++++++++++++++-----\n>  t/t3418-rebase-continue.sh |  35 ++++++++++--\n>  2 files changed, 131 insertions(+), 17 deletions(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index 56166b0d6c7..cec180714ef 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -2779,19 +2779,16 @@ static int continue_single_pick(void)\n>  \treturn run_command_v_opt(argv, RUN_GIT_CMD);\n>  }\n>  \n> -static int commit_staged_changes(struct replay_opts *opts)\n> +static int commit_staged_changes(struct replay_opts *opts,\n> +\t\t\t\t struct todo_list *todo_list)\n>  {\n>  \tunsigned int flags = ALLOW_EMPTY | EDIT_MSG;\n> +\tunsigned int final_fixup = 0, is_clean;\n>  \n>  \tif (has_unstaged_changes(1))\n>  \t\treturn error(_(\"cannot rebase: You have unstaged changes.\"));\n> -\tif (!has_uncommitted_changes(0)) {\n> -\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n>  \n> -\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n> -\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n> -\t\treturn 0;\n> -\t}\n> +\tis_clean = !has_uncommitted_changes(0);\n>  \n>  \tif (file_exists(rebase_path_amend())) {\n>  \t\tstruct strbuf rev = STRBUF_INIT;\n> @@ -2804,19 +2801,107 @@ static int commit_staged_changes(struct replay_opts *opts)\n>  \t\tif (get_oid_hex(rev.buf, &to_amend))\n>  \t\t\treturn error(_(\"invalid contents: '%s'\"),\n>  \t\t\t\trebase_path_amend());\n> -\t\tif (oidcmp(&head, &to_amend))\n> +\t\tif (!is_clean && oidcmp(&head, &to_amend))\n>  \t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n>  \t\t\t\t       \"working tree. Please, commit them\\n\"\n>  \t\t\t\t       \"first and then run 'git rebase \"\n>  \t\t\t\t       \"--continue' again.\"));\n> +\t\t/*\n> +\t\t * When skipping a failed fixup/squash, we need to edit the\n> +\t\t * commit message, the current fixup list and count, and if it\n> +\t\t * was the last fixup/squash in the chain, we need to clean up\n> +\t\t * the commit message and if there was a squash, let the user\n> +\t\t * edit it.\n> +\t\t */\n> +\t\tif (is_clean && !oidcmp(&head, &to_amend) &&\n> +\t\t    opts->current_fixup_count > 0 &&\n> +\t\t    file_exists(rebase_path_stopped_sha())) {\n> +\t\t\tconst char *p = opts->current_fixups.buf;\n> +\t\t\tint len = opts->current_fixups.len;\n> +\n> +\t\t\topts->current_fixup_count--;\n> +\t\t\tif (!len)\n> +\t\t\t\tBUG(\"Incorrect current_fixups:\\n%s\", p);\n> +\t\t\twhile (len && p[len - 1] != '\\n')\n> +\t\t\t\tlen--;\n> +\t\t\tstrbuf_setlen(&opts->current_fixups, len);\n> +\t\t\tif (write_message(p, len, rebase_path_current_fixups(),\n> +\t\t\t\t\t  0) < 0)\n> +\t\t\t\treturn error(_(\"could not write file: '%s'\"),\n> +\t\t\t\t\t     rebase_path_current_fixups());\n> +\n> +\t\t\t/*\n> +\t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n> +\t\t\t * commit message is already correct, no need to commit\n> +\t\t\t * it again.\n> +\t\t\t *\n> +\t\t\t * Only if it is the final command in the fixup/squash\n> +\t\t\t * chain, and only if the chain is longer than a single\n> +\t\t\t * fixup/squash command (which was just skipped), do we\n> +\t\t\t * actually need to re-commit with a cleaned up commit\n> +\t\t\t * message.\n> +\t\t\t */\n> +\t\t\tif (opts->current_fixup_count > 0 &&\n> +\t\t\t    !is_fixup(peek_command(todo_list, 0))) {\n> +\t\t\t\tfinal_fixup = 1;\n> +\t\t\t\t/*\n> +\t\t\t\t * If there was not a single \"squash\" in the\n> +\t\t\t\t * chain, we only need to clean up the commit\n> +\t\t\t\t * message, no need to bother the user with\n> +\t\t\t\t * opening the commit message in the editor.\n> +\t\t\t\t */\n> +\t\t\t\tif (!starts_with(p, \"squash \") &&\n> +\t\t\t\t    !strstr(p, \"\\nsquash \"))\n> +\t\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n> +\t\t\t} else if (is_fixup(peek_command(todo_list, 0))) {\n> +\t\t\t\t/*\n> +\t\t\t\t * We need to update the squash message to skip\n> +\t\t\t\t * the latest commit message.\n> +\t\t\t\t */\n> +\t\t\t\tstruct commit *commit;\n> +\t\t\t\tconst char *path = rebase_path_squash_msg();\n> +\n> +\t\t\t\tif (parse_head(&commit) ||\n> +\t\t\t\t    !(p = get_commit_buffer(commit, NULL)) ||\n> +\t\t\t\t    write_message(p, strlen(p), path, 0)) {\n> +\t\t\t\t\tunuse_commit_buffer(commit, p);\n> +\t\t\t\t\treturn error(_(\"could not write file: \"\n> +\t\t\t\t\t\t       \"'%s'\"), path);\n> +\t\t\t\t}\n\nI think it should probably recreate the fixup message as well. If there\nis a sequence\n\npick commit\nfixup a\nfixup b\nfixup c\n\nand 'fixup b' gets skipped then when 'fixup c' is applied the user will\nbe prompted to edit the message unless rebase_path_fixup_msg() exists.\n\nBest Wishes\n\nPhillip\n\n> +\t\t\t\tunuse_commit_buffer(commit, p);\n> +\t\t\t}\n> +\t\t}\n>  \n>  \t\tstrbuf_release(&rev);\n>  \t\tflags |= AMEND_MSG;\n>  \t}\n>  \n> -\tif (run_git_commit(rebase_path_message(), opts, flags))\n> +\tif (is_clean) {\n> +\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n> +\n> +\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n> +\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n> +\t\tif (!final_fixup)\n> +\t\t\treturn 0;\n> +\t}\n> +\n> +\tif (run_git_commit(final_fixup ? NULL : rebase_path_message(),\n> +\t\t\t   opts, flags))\n>  \t\treturn error(_(\"could not commit staged changes.\"));\n>  \tunlink(rebase_path_amend());\n> +\tif (final_fixup) {\n> +\t\tunlink(rebase_path_fixup_msg());\n> +\t\tunlink(rebase_path_squash_msg());\n> +\t}\n> +\tif (opts->current_fixup_count > 0) {\n> +\t\t/*\n> +\t\t * Whether final fixup or not, we just cleaned up the commit\n> +\t\t * message...\n> +\t\t */\n> +\t\tunlink(rebase_path_current_fixups());\n> +\t\tstrbuf_reset(&opts->current_fixups);\n> +\t\topts->current_fixup_count = 0;\n> +\t}\n>  \treturn 0;\n>  }\n>  \n> @@ -2828,14 +2913,16 @@ int sequencer_continue(struct replay_opts *opts)\n>  \tif (read_and_refresh_cache(opts))\n>  \t\treturn -1;\n>  \n> +\tif (read_populate_opts(opts))\n> +\t\treturn -1;\n>  \tif (is_rebase_i(opts)) {\n> -\t\tif (commit_staged_changes(opts))\n> +\t\tif ((res = read_populate_todo(&todo_list, opts)))\n> +\t\t\tgoto release_todo_list;\n> +\t\tif (commit_staged_changes(opts, &todo_list))\n>  \t\t\treturn -1;\n>  \t} else if (!file_exists(get_todo_path(opts)))\n>  \t\treturn continue_single_pick();\n> -\tif (read_populate_opts(opts))\n> -\t\treturn -1;\n> -\tif ((res = read_populate_todo(&todo_list, opts)))\n> +\telse if ((res = read_populate_todo(&todo_list, opts)))\n>  \t\tgoto release_todo_list;\n>  \n>  \tif (!is_rebase_i(opts)) {\n> diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n> index 3874f187246..03bf1b8a3b3 100755\n> --- a/t/t3418-rebase-continue.sh\n> +++ b/t/t3418-rebase-continue.sh\n> @@ -88,14 +88,14 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n>  \tgit rebase --continue\n>  '\n>  \n> -test_expect_failure '--skip after failed fixup cleans commit message' '\n> +test_expect_success '--skip after failed fixup cleans commit message' '\n>  \ttest_when_finished \"test_might_fail git rebase --abort\" &&\n>  \tgit checkout -b with-conflicting-fixup &&\n>  \ttest_commit wants-fixup &&\n>  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n>  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n>  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n> -\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n> +\ttest_must_fail env FAKE_LINES=\"1 fixup 2 squash 4\" \\\n>  \t\tgit rebase -i HEAD~4 &&\n>  \n>  \t: now there is a conflict, and comments in the commit message &&\n> @@ -103,11 +103,38 @@ test_expect_failure '--skip after failed fixup cleans commit message' '\n>  \tgrep \"fixup! wants-fixup\" out &&\n>  \n>  \t: skip and continue &&\n> -\tgit rebase --skip &&\n> +\techo \"cp \\\"\\$1\\\" .git/copy.txt\" | write_script copy-editor.sh &&\n> +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n> +\n> +\t: the user should not have had to edit the commit message &&\n> +\ttest_path_is_missing .git/copy.txt &&\n>  \n>  \t: now the comments in the commit message should have been cleaned up &&\n>  \tgit show HEAD >out &&\n> -\t! grep \"fixup! wants-fixup\" out\n> +\t! grep \"fixup! wants-fixup\" out &&\n> +\n> +\t: now, let us ensure that \"squash\" is handled correctly &&\n> +\tgit reset --hard wants-fixup-3 &&\n> +\ttest_must_fail env FAKE_LINES=\"1 squash 4 squash 2 squash 4\" \\\n> +\t\tgit rebase -i HEAD~4 &&\n> +\n> +\t: the first squash failed, but there are two more in the chain &&\n> +\t(test_set_editor \"$PWD/copy-editor.sh\" &&\n> +\t test_must_fail git rebase --skip) &&\n> +\n> +\t: not the final squash, no need to edit the commit message &&\n> +\ttest_path_is_missing .git/copy.txt &&\n> +\n> +\t: The first squash was skipped, therefore: &&\n> +\tgit show HEAD >out &&\n> +\ttest_i18ngrep \"# This is a combination of 2 commits\" out &&\n> +\n> +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n> +\tgit show HEAD >out &&\n> +\ttest_i18ngrep ! \"# This is a combination\" out &&\n> +\n> +\t: Final squash failed, but there was still a squash &&\n> +\ttest_i18ngrep \"# This is a combination of 2 commits\" .git/copy.txt\n>  '\n>  \n>  test_expect_success 'setup rerere database' '\n> \n\n"},{"id":"347062","messageId":"6c5655c6-2b2a-67f5-e09f-1da65fc73e9a@talktalk.net","threadId":"47635","inReplyTo":"4bd08893-52b4-3a01-d546-4884e2762670@talktalk.net","subject":"Re: [PATCH v4 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-05-09T10:50:25Z","receivedAt":"2018-05-09T10:50:35Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 06/05/18 18:50, Phillip Wood wrote:\n> Hi Johannes, sorry it's taken me a while to look at this. I think it\n> mostly makes sense to me, the code is well documented. I've got one\n> comment below\n> \n> On 27/04/18 21:48, Johannes Schindelin wrote:\n>>\n>> During a series of fixup/squash commands, the interactive rebase builds\n>> up a commit message with comments. This will be presented to the user in\n>> the editor if at least one of those commands was a `squash`.\n>>\n>> In any case, the commit message will be cleaned up eventually, removing\n>> all those intermediate comments, in the final step of such a\n>> fixup/squash chain.\n>>\n>> However, if the last fixup/squash command in such a chain fails with\n>> merge conflicts, and if the user then decides to skip it (or resolve it\n>> to a clean worktree and then continue the rebase), the current code\n>> fails to clean up the commit message.\n>>\n>> This commit fixes that behavior.\n>>\n>> The fix is quite a bit more involved than meets the eye because it is\n>> not only about the question whether we are `git rebase --skip`ing a\n>> fixup or squash. It is also about removing the skipped fixup/squash's\n>> commit message from the accumulated commit message. And it is also about\n>> the question whether we should let the user edit the final commit\n>> message or not (\"Was there a squash in the chain *that was not\n>> skipped*?\").\n>>\n>> For example, in this case we will want to fix the commit message, but\n>> not open it in an editor:\n>>\n>> \tpick\t<- succeeds\n>> \tfixup\t<- succeeds\n>> \tsquash\t<- fails, will be skipped\n>>\n>> This is where the newly-introduced `current-fixups` file comes in real\n>> handy. A quick look and we can determine whether there was a non-skipped\n>> squash. We only need to make sure to keep it up to date with respect to\n>> skipped fixup/squash commands. As a bonus, we can even avoid committing\n>> unnecessarily, e.g. when there was only one fixup, and it failed, and\n>> was skipped.\n>>\n>> To fix only the bug where the final commit message was not cleaned up\n>> properly, but without fixing the rest, would have been more complicated\n>> than fixing it all in one go, hence this commit lumps together more than\n>> a single concern.\n>>\n>> For the same reason, this commit also adds a bit more to the existing\n>> test case for the regression we just fixed.\n>>\n>> The diff is best viewed with --color-moved.\n>>\n>> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>> ---\n>>   sequencer.c                | 113 ++++++++++++++++++++++++++++++++-----\n>>   t/t3418-rebase-continue.sh |  35 ++++++++++--\n>>   2 files changed, 131 insertions(+), 17 deletions(-)\n>>\n>> diff --git a/sequencer.c b/sequencer.c\n>> index 56166b0d6c7..cec180714ef 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -2779,19 +2779,16 @@ static int continue_single_pick(void)\n>>   \treturn run_command_v_opt(argv, RUN_GIT_CMD);\n>>   }\n>>   \n>> -static int commit_staged_changes(struct replay_opts *opts)\n>> +static int commit_staged_changes(struct replay_opts *opts,\n>> +\t\t\t\t struct todo_list *todo_list)\n>>   {\n>>   \tunsigned int flags = ALLOW_EMPTY | EDIT_MSG;\n>> +\tunsigned int final_fixup = 0, is_clean;\n>>   \n>>   \tif (has_unstaged_changes(1))\n>>   \t\treturn error(_(\"cannot rebase: You have unstaged changes.\"));\n>> -\tif (!has_uncommitted_changes(0)) {\n>> -\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n>>   \n>> -\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n>> -\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n>> -\t\treturn 0;\n>> -\t}\n>> +\tis_clean = !has_uncommitted_changes(0);\n>>   \n>>   \tif (file_exists(rebase_path_amend())) {\n>>   \t\tstruct strbuf rev = STRBUF_INIT;\n>> @@ -2804,19 +2801,107 @@ static int commit_staged_changes(struct replay_opts *opts)\n>>   \t\tif (get_oid_hex(rev.buf, &to_amend))\n>>   \t\t\treturn error(_(\"invalid contents: '%s'\"),\n>>   \t\t\t\trebase_path_amend());\n>> -\t\tif (oidcmp(&head, &to_amend))\n>> +\t\tif (!is_clean && oidcmp(&head, &to_amend))\n>>   \t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n>>   \t\t\t\t       \"working tree. Please, commit them\\n\"\n>>   \t\t\t\t       \"first and then run 'git rebase \"\n>>   \t\t\t\t       \"--continue' again.\"));\n>> +\t\t/*\n>> +\t\t * When skipping a failed fixup/squash, we need to edit the\n>> +\t\t * commit message, the current fixup list and count, and if it\n>> +\t\t * was the last fixup/squash in the chain, we need to clean up\n>> +\t\t * the commit message and if there was a squash, let the user\n>> +\t\t * edit it.\n>> +\t\t */\n>> +\t\tif (is_clean && !oidcmp(&head, &to_amend) &&\n>> +\t\t    opts->current_fixup_count > 0 &&\n>> +\t\t    file_exists(rebase_path_stopped_sha())) {\n>> +\t\t\tconst char *p = opts->current_fixups.buf;\n>> +\t\t\tint len = opts->current_fixups.len;\n>> +\n>> +\t\t\topts->current_fixup_count--;\n>> +\t\t\tif (!len)\n>> +\t\t\t\tBUG(\"Incorrect current_fixups:\\n%s\", p);\n>> +\t\t\twhile (len && p[len - 1] != '\\n')\n>> +\t\t\t\tlen--;\n>> +\t\t\tstrbuf_setlen(&opts->current_fixups, len);\n>> +\t\t\tif (write_message(p, len, rebase_path_current_fixups(),\n>> +\t\t\t\t\t  0) < 0)\n>> +\t\t\t\treturn error(_(\"could not write file: '%s'\"),\n>> +\t\t\t\t\t     rebase_path_current_fixups());\n>> +\n>> +\t\t\t/*\n>> +\t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n>> +\t\t\t * commit message is already correct, no need to commit\n>> +\t\t\t * it again.\n>> +\t\t\t *\n>> +\t\t\t * Only if it is the final command in the fixup/squash\n>> +\t\t\t * chain, and only if the chain is longer than a single\n>> +\t\t\t * fixup/squash command (which was just skipped), do we\n>> +\t\t\t * actually need to re-commit with a cleaned up commit\n>> +\t\t\t * message.\n>> +\t\t\t */\n>> +\t\t\tif (opts->current_fixup_count > 0 &&\n>> +\t\t\t    !is_fixup(peek_command(todo_list, 0))) {\n>> +\t\t\t\tfinal_fixup = 1;\n>> +\t\t\t\t/*\n>> +\t\t\t\t * If there was not a single \"squash\" in the\n>> +\t\t\t\t * chain, we only need to clean up the commit\n>> +\t\t\t\t * message, no need to bother the user with\n>> +\t\t\t\t * opening the commit message in the editor.\n>> +\t\t\t\t */\n>> +\t\t\t\tif (!starts_with(p, \"squash \") &&\n>> +\t\t\t\t    !strstr(p, \"\\nsquash \"))\n>> +\t\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n>> +\t\t\t} else if (is_fixup(peek_command(todo_list, 0))) {\n>> +\t\t\t\t/*\n>> +\t\t\t\t * We need to update the squash message to skip\n>> +\t\t\t\t * the latest commit message.\n>> +\t\t\t\t */\n>> +\t\t\t\tstruct commit *commit;\n>> +\t\t\t\tconst char *path = rebase_path_squash_msg();\n>> +\n>> +\t\t\t\tif (parse_head(&commit) ||\n>> +\t\t\t\t    !(p = get_commit_buffer(commit, NULL)) ||\n>> +\t\t\t\t    write_message(p, strlen(p), path, 0)) {\n>> +\t\t\t\t\tunuse_commit_buffer(commit, p);\n\nI forgot to mention last time that if parse_head() returns an error then \ncommit is passed uninitialized to unuse_commit_buffer(). It also \npossible that p is not pointing to a commit buffer if \ngit_commit_buffer() fails. Looking at the current implementation that is \nprobably ok but it's a bit ugly and passing uninitialized variables \naround could cause problems in the future.\n\n>> +\t\t\t\t\treturn error(_(\"could not write file: \"\n>> +\t\t\t\t\t\t       \"'%s'\"), path);\n>> +\t\t\t\t}\n> \n> I think it should probably recreate the fixup message as well. If there\n> is a sequence\n> \n> pick commit\n> fixup a\n> fixup b\n> fixup c\n> \n> and 'fixup b' gets skipped then when 'fixup c' is applied the user will\n> be prompted to edit the message unless rebase_path_fixup_msg() exists.\n> \n> Best Wishes\n> \n> Phillip\n> \n>> +\t\t\t\tunuse_commit_buffer(commit, p);\n>> +\t\t\t}\n>> +\t\t}\n>>   \n>>   \t\tstrbuf_release(&rev);\n>>   \t\tflags |= AMEND_MSG;\n>>   \t}\n>>   \n>> -\tif (run_git_commit(rebase_path_message(), opts, flags))\n>> +\tif (is_clean) {\n>> +\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n>> +\n>> +\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n>> +\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n>> +\t\tif (!final_fixup)\n>> +\t\t\treturn 0;\n>> +\t}\n>> +\n>> +\tif (run_git_commit(final_fixup ? NULL : rebase_path_message(),\n>> +\t\t\t   opts, flags))\n>>   \t\treturn error(_(\"could not commit staged changes.\"));\n>>   \tunlink(rebase_path_amend());\n>> +\tif (final_fixup) {\n>> +\t\tunlink(rebase_path_fixup_msg());\n>> +\t\tunlink(rebase_path_squash_msg());\n>> +\t}\n>> +\tif (opts->current_fixup_count > 0) {\n>> +\t\t/*\n>> +\t\t * Whether final fixup or not, we just cleaned up the commit\n>> +\t\t * message...\n>> +\t\t */\n>> +\t\tunlink(rebase_path_current_fixups());\n>> +\t\tstrbuf_reset(&opts->current_fixups);\n>> +\t\topts->current_fixup_count = 0;\n>> +\t}\n>>   \treturn 0;\n>>   }\n>>   \n>> @@ -2828,14 +2913,16 @@ int sequencer_continue(struct replay_opts *opts)\n>>   \tif (read_and_refresh_cache(opts))\n>>   \t\treturn -1;\n>>   \n>> +\tif (read_populate_opts(opts))\n>> +\t\treturn -1;\n>>   \tif (is_rebase_i(opts)) {\n>> -\t\tif (commit_staged_changes(opts))\n>> +\t\tif ((res = read_populate_todo(&todo_list, opts)))\n>> +\t\t\tgoto release_todo_list;\n>> +\t\tif (commit_staged_changes(opts, &todo_list))\n>>   \t\t\treturn -1;\n>>   \t} else if (!file_exists(get_todo_path(opts)))\n>>   \t\treturn continue_single_pick();\n>> -\tif (read_populate_opts(opts))\n>> -\t\treturn -1;\n>> -\tif ((res = read_populate_todo(&todo_list, opts)))\n>> +\telse if ((res = read_populate_todo(&todo_list, opts)))\n>>   \t\tgoto release_todo_list;\n>>   \n>>   \tif (!is_rebase_i(opts)) {\n>> diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n>> index 3874f187246..03bf1b8a3b3 100755\n>> --- a/t/t3418-rebase-continue.sh\n>> +++ b/t/t3418-rebase-continue.sh\n>> @@ -88,14 +88,14 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n>>   \tgit rebase --continue\n>>   '\n>>   \n>> -test_expect_failure '--skip after failed fixup cleans commit message' '\n>> +test_expect_success '--skip after failed fixup cleans commit message' '\n>>   \ttest_when_finished \"test_might_fail git rebase --abort\" &&\n>>   \tgit checkout -b with-conflicting-fixup &&\n>>   \ttest_commit wants-fixup &&\n>>   \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n>>   \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n>>   \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n>> -\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n>> +\ttest_must_fail env FAKE_LINES=\"1 fixup 2 squash 4\" \\\n>>   \t\tgit rebase -i HEAD~4 &&\n>>   \n>>   \t: now there is a conflict, and comments in the commit message &&\n>> @@ -103,11 +103,38 @@ test_expect_failure '--skip after failed fixup cleans commit message' '\n>>   \tgrep \"fixup! wants-fixup\" out &&\n>>   \n>>   \t: skip and continue &&\n>> -\tgit rebase --skip &&\n>> +\techo \"cp \\\"\\$1\\\" .git/copy.txt\" | write_script copy-editor.sh &&\n>> +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n>> +\n>> +\t: the user should not have had to edit the commit message &&\n>> +\ttest_path_is_missing .git/copy.txt &&\n>>   \n>>   \t: now the comments in the commit message should have been cleaned up &&\n>>   \tgit show HEAD >out &&\n>> -\t! grep \"fixup! wants-fixup\" out\n>> +\t! grep \"fixup! wants-fixup\" out &&\n>> +\n>> +\t: now, let us ensure that \"squash\" is handled correctly &&\n>> +\tgit reset --hard wants-fixup-3 &&\n>> +\ttest_must_fail env FAKE_LINES=\"1 squash 4 squash 2 squash 4\" \\\n>> +\t\tgit rebase -i HEAD~4 &&\n>> +\n>> +\t: the first squash failed, but there are two more in the chain &&\n>> +\t(test_set_editor \"$PWD/copy-editor.sh\" &&\n>> +\t test_must_fail git rebase --skip) &&\n>> +\n>> +\t: not the final squash, no need to edit the commit message &&\n>> +\ttest_path_is_missing .git/copy.txt &&\n>> +\n>> +\t: The first squash was skipped, therefore: &&\n>> +\tgit show HEAD >out &&\n>> +\ttest_i18ngrep \"# This is a combination of 2 commits\" out &&\n>> +\n>> +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n>> +\tgit show HEAD >out &&\n>> +\ttest_i18ngrep ! \"# This is a combination\" out &&\n>> +\n>> +\t: Final squash failed, but there was still a squash &&\n>> +\ttest_i18ngrep \"# This is a combination of 2 commits\" .git/copy.txt\n>>   '\n>>   \n>>   test_expect_success 'setup rerere database' '\n>>\n> \n\n"},{"id":"347789","messageId":"CABPp-BHm4B5vXZGHn_i6Aycvc_PDNWSFxLFo9s-ijb511VY9RA@mail.gmail.com","threadId":"47635","inReplyTo":"20180428113257.25425-1-martin.agren@gmail.com","subject":"Re: [PATCH 2/2] unpack_trees_options: free messages when done","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-16T14:32:15Z","receivedAt":"2018-05-16T14:32:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Martin,\n\nOn Sat, Apr 28, 2018 at 4:32 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n> As you noted elsewhere [1], Ben is also working in this area. I'd be\n> perfectly happy to sit on these patches until both of your contributions\n> come through to master.\n>\n> [1] https://public-inbox.org/git/CABPp-BFh=gL6RnbST2bgtynkij1Z5TMgAr1Via5_VyteF5eBMg@mail.gmail.com/\n\nInstead of waiting for these to come through to master, could you just\nsubmit based on the top of bp/merge-rename-config?  I've got several\nother merge-recursive changes, some about ready to send and others in\nthe works.  I don't think any conflict yet, but I would rather avoid\ncausing you any more waiting or conflicts and would rather just have\nboth your and Ben's changes in pu and then I can just build mine on\ntop of those.  Besides, I want to see that FIXME go away and have\nfewer leaks.  :-)\n\nThanks,\nElijah\n"},{"id":"347794","messageId":"cover.1526488122.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"CABPp-BHm4B5vXZGHn_i6Aycvc_PDNWSFxLFo9s-ijb511VY9RA@mail.gmail.com","subject":"[PATCH v2 0/3] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-16T16:30:57Z","receivedAt":"2018-05-16T16:31:46Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"Hi Elijah\n\nOn 16 May 2018 at 16:32, Elijah Newren <newren@gmail.com> wrote:\n> On Sat, Apr 28, 2018 at 4:32 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>> As you noted elsewhere [1], Ben is also working in this area. I'd be\n>> perfectly happy to sit on these patches until both of your contributions\n>> come through to master.\n>>\n>> [1] https://public-inbox.org/git/CABPp-BFh=gL6RnbST2bgtynkij1Z5TMgAr1Via5_VyteF5eBMg@mail.gmail.com/\n>\n> Instead of waiting for these to come through to master, could you just\n> submit based on the top of bp/merge-rename-config? \n\nSure, here goes. This is based on bp/merge-rename-config, gets rid of\nall leaks of memory allocated in `setup_unpack_trees_porcelain()` and\ncuts the number of leaks in the test-suite (i.e., the subset of the \ntests that I run) by around 10%.\n\nMartin\n\nElijah Newren (1):\n  merge-recursive: provide pair of `unpack_trees_{start,finish}()`\n\nMartin Ågren (2):\n  merge: setup `opts` later in `checkout_fast_forward()`\n  unpack_trees_options: free messages when done\n\n unpack-trees.h     |  5 +++++\n builtin/checkout.c |  1 +\n merge-recursive.c  | 30 ++++++++++++++++--------------\n merge.c            | 37 +++++++++++++++++++++----------------\n unpack-trees.c     | 11 +++++++++++\n 5 files changed, 54 insertions(+), 30 deletions(-)\n\n-- \n2.17.0.583.g9a75a153ac\n\n"},{"id":"347795","messageId":"0b7440f21e9ff7a066802fe1e7cb3078cf4ed2b8.1526488122.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526488122.git.martin.agren@gmail.com","subject":"[PATCH v2 1/3] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-16T16:30:58Z","receivedAt":"2018-05-16T16:31:55Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"After we initialize the various fields in `opts` but before we actually\nuse them, we might return early. Move the initialization further down,\nto immediately before we use `opts`.\n\nThis limits the scope of `opts` and will help a later commit fix a\nmemory leak without having to worry about those early returns.\n\nThis patch is best viewed using something like this (note the tab!):\n--color-moved --anchored=\"\ttrees[nr_trees] = parse_tree_indirect\"\n\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n merge.c | 34 ++++++++++++++++++----------------\n 1 file changed, 18 insertions(+), 16 deletions(-)\n\ndiff --git a/merge.c b/merge.c\nindex f06a4773d4..f123658e58 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -94,23 +94,7 @@ int checkout_fast_forward(const struct object_id *head,\n \t\treturn -1;\n \n \tmemset(&trees, 0, sizeof(trees));\n-\tmemset(&opts, 0, sizeof(opts));\n \tmemset(&t, 0, sizeof(t));\n-\tif (overwrite_ignore) {\n-\t\tmemset(&dir, 0, sizeof(dir));\n-\t\tdir.flags |= DIR_SHOW_IGNORED;\n-\t\tsetup_standard_excludes(&dir);\n-\t\topts.dir = &dir;\n-\t}\n-\n-\topts.head_idx = 1;\n-\topts.src_index = &the_index;\n-\topts.dst_index = &the_index;\n-\topts.update = 1;\n-\topts.verbose_update = 1;\n-\topts.merge = 1;\n-\topts.fn = twoway_merge;\n-\tsetup_unpack_trees_porcelain(&opts, \"merge\");\n \n \ttrees[nr_trees] = parse_tree_indirect(head);\n \tif (!trees[nr_trees++]) {\n@@ -126,6 +110,24 @@ int checkout_fast_forward(const struct object_id *head,\n \t\tparse_tree(trees[i]);\n \t\tinit_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n \t}\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\tif (overwrite_ignore) {\n+\t\tmemset(&dir, 0, sizeof(dir));\n+\t\tdir.flags |= DIR_SHOW_IGNORED;\n+\t\tsetup_standard_excludes(&dir);\n+\t\topts.dir = &dir;\n+\t}\n+\n+\topts.head_idx = 1;\n+\topts.src_index = &the_index;\n+\topts.dst_index = &the_index;\n+\topts.update = 1;\n+\topts.verbose_update = 1;\n+\topts.merge = 1;\n+\topts.fn = twoway_merge;\n+\tsetup_unpack_trees_porcelain(&opts, \"merge\");\n+\n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n \t\treturn -1;\n-- \n2.17.0.583.g9a75a153ac\n\n"},{"id":"347796","messageId":"f38f5e7467ad1a916cb6ff10638c70587f83d831.1526488122.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526488122.git.martin.agren@gmail.com","subject":"[PATCH v2 2/3] merge-recursive: provide pair of `unpack_trees_{start,finish}()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-16T16:30:59Z","receivedAt":"2018-05-16T16:31:59Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"From: Elijah Newren <newren@gmail.com>\n\nRename `git_merge_trees()` to `unpack_trees_start()` and extract the\ncall to `discard_index()` into a new function `unpack_trees_finish()`.\nAs a result, these are called early resp. late in `merge_trees()`,\nmaking the resource handling clearer. The next commit will expand on\nthat, teaching `..._finish()` to free more memory. (So rather than\nmoving the FIXME-comment, just drop it, since it will be addressed soon\nenough.)\n\nAlso call `..._finish()` when `merge_trees()` returns early.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n merge-recursive.c | 29 +++++++++++++++--------------\n 1 file changed, 15 insertions(+), 14 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 680e01226b..ddb0fa7369 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -337,10 +337,10 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n \tinit_tree_desc(desc, tree->buffer, tree->size);\n }\n \n-static int git_merge_trees(struct merge_options *o,\n-\t\t\t   struct tree *common,\n-\t\t\t   struct tree *head,\n-\t\t\t   struct tree *merge)\n+static int unpack_trees_start(struct merge_options *o,\n+\t\t\t      struct tree *common,\n+\t\t\t      struct tree *head,\n+\t\t\t      struct tree *merge)\n {\n \tint rc;\n \tstruct tree_desc t[3];\n@@ -379,6 +379,11 @@ static int git_merge_trees(struct merge_options *o,\n \treturn rc;\n }\n \n+static void unpack_trees_finish(struct merge_options *o)\n+{\n+\tdiscard_index(&o->orig_index);\n+}\n+\n struct tree *write_tree_from_memory(struct merge_options *o)\n {\n \tstruct tree *result = NULL;\n@@ -3088,13 +3093,14 @@ int merge_trees(struct merge_options *o,\n \t\treturn 1;\n \t}\n \n-\tcode = git_merge_trees(o, common, head, merge);\n+\tcode = unpack_trees_start(o, common, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(o, 4) || o->call_depth)\n \t\t\terr(o, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n+\t\tunpack_trees_finish(o);\n \t\treturn -1;\n \t}\n \n@@ -3147,20 +3153,15 @@ int merge_trees(struct merge_options *o,\n \n \t\thashmap_free(&o->current_file_dir_set, 1);\n \n-\t\tif (clean < 0)\n+\t\tif (clean < 0) {\n+\t\t\tunpack_trees_finish(o);\n \t\t\treturn clean;\n+\t\t}\n \t}\n \telse\n \t\tclean = 1;\n \n-\t/* Free the extra index left from git_merge_trees() */\n-\t/*\n-\t * FIXME: Need to also free data allocated by\n-\t * setup_unpack_trees_porcelain() tucked away in o->unpack_opts.msgs,\n-\t * but the problem is that only half of it refers to dynamically\n-\t * allocated data, while the other half points at static strings.\n-\t */\n-\tdiscard_index(&o->orig_index);\n+\tunpack_trees_finish(o);\n \n \tif (o->call_depth && !(*result = write_tree_from_memory(o)))\n \t\treturn -1;\n-- \n2.17.0.583.g9a75a153ac\n\n"},{"id":"347797","messageId":"abc39c9991d6df627b40fd8b4fe9812e7ef78d77.1526488122.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526488122.git.martin.agren@gmail.com","subject":"[PATCH v2 3/3] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-16T16:31:00Z","receivedAt":"2018-05-16T16:32:01Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"The strings allocated in `setup_unpack_trees_porcelain()` are never\nfreed. Provide a function `clear_unpack_trees_porcelain()` to do so and\ncall it where we use `setup_unpack_trees_porcelain()`. The only\nnon-trivial user is `unpack_trees_start()`, where we should place the\nnew call in `unpack_trees_finish()`.\n\nThe `opts` string array contains multiple copies of the same pointers.\nBe careful to only free each pointer once, then zeroize the whole array\nso that we do not leave any dangling pointers.\n\nNote that we only take responsibility for the memory allocated in\n`setup_unpack_trees_porcelain()` and not any other members of the\n`struct unpack_trees_options`.\n\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n unpack-trees.h     |  5 +++++\n builtin/checkout.c |  1 +\n merge-recursive.c  |  1 +\n merge.c            |  3 +++\n unpack-trees.c     | 11 +++++++++++\n 5 files changed, 21 insertions(+)\n\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 41178ada94..70053cb3ff 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -33,6 +33,11 @@ enum unpack_trees_error_types {\n void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t\t  const char *cmd);\n \n+/*\n+ * Frees resources allocated by setup_unpack_trees_porcelain().\n+ */\n+extern void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);\n+\n struct unpack_trees_options {\n \tunsigned int reset,\n \t\t     merge,\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b49b582071..5cebe170fc 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -526,6 +526,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tinit_tree_desc(&trees[1], tree->buffer, tree->size);\n \n \t\tret = unpack_trees(2, trees, &topts);\n+\t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n \t\t\t/*\n \t\t\t * Unpack couldn't do a trivial merge; either\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ddb0fa7369..338f63a952 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -382,6 +382,7 @@ static int unpack_trees_start(struct merge_options *o,\n static void unpack_trees_finish(struct merge_options *o)\n {\n \tdiscard_index(&o->orig_index);\n+\tclear_unpack_trees_porcelain(&o->unpack_opts);\n }\n \n struct tree *write_tree_from_memory(struct merge_options *o)\ndiff --git a/merge.c b/merge.c\nindex f123658e58..b433291d0c 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -130,8 +130,11 @@ int checkout_fast_forward(const struct object_id *head,\n \n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n+\t\tclear_unpack_trees_porcelain(&opts);\n \t\treturn -1;\n \t}\n+\tclear_unpack_trees_porcelain(&opts);\n+\n \tif (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 79fd97074e..25e766d30e 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -179,6 +179,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\topts->unpack_rejects[i].strdup_strings = 1;\n }\n \n+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n+{\n+\tchar **msgs = (char **)opts->msgs;\n+\n+\tfree(msgs[ERROR_WOULD_OVERWRITE]);\n+\tfree(msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED]);\n+\tfree(msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN]);\n+\n+\tmemset(opts->msgs, 0, sizeof(opts->msgs));\n+}\n+\n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n \t\t\t unsigned int set, unsigned int clear)\n {\n-- \n2.17.0.583.g9a75a153ac\n\n"},{"id":"347798","messageId":"CAGZ79kYBykcbzKxc_FHiL6dt=jkmK2Khe-e5aSxFephe=RsBFA@mail.gmail.com","threadId":"47635","inReplyTo":"0b7440f21e9ff7a066802fe1e7cb3078cf4ed2b8.1526488122.git.martin.agren@gmail.com","subject":"Re: [PATCH v2 1/3] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-16T16:41:04Z","receivedAt":"2018-05-16T16:41:10Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"+ Jonathan Tan for a side discussion on anchoring.\n\nOn Wed, May 16, 2018 at 9:30 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>\n> This patch is best viewed using something like this (note the tab!):\n> --color-moved --anchored=\"      trees[nr_trees] = parse_tree_indirect\"\n\nHeh! Having a \"is best viewed\" paragraph is the new shiny thing in\ncommit messages as 'git log origin/pu --grep \"is best viewed\"' tells me.\n\nRegarding the anchoring, I wonder if we can improve it by ignoring\nwhitespaces or just looking for substrings, or by allowing regexes or ...\n\nThanks,\nStefan\n"},{"id":"347815","messageId":"CAN0heSqdVB3SmQnBeJB06f4uXzUBM6OC2ySZNZnhJdisbR28Kg@mail.gmail.com","threadId":"47635","inReplyTo":"CAGZ79kYBykcbzKxc_FHiL6dt=jkmK2Khe-e5aSxFephe=RsBFA@mail.gmail.com","subject":"Re: [PATCH v2 1/3] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-16T19:29:41Z","receivedAt":"2018-05-16T19:29:46Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 16 May 2018 at 18:41, Stefan Beller <sbeller@google.com> wrote:\n> On Wed, May 16, 2018 at 9:30 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>>\n>> This patch is best viewed using something like this (note the tab!):\n>> --color-moved --anchored=\"      trees[nr_trees] = parse_tree_indirect\"\n>\n> Heh! Having a \"is best viewed\" paragraph is the new shiny thing in\n> commit messages as 'git log origin/pu --grep \"is best viewed\"' tells me.\n\n:-)\n\n> Regarding the anchoring, I wonder if we can improve it by ignoring\n> whitespaces or just looking for substrings, or by allowing regexes or ...\n\nFWIW, because my first naive attempt failed (for some reason I did not\nconsider the leading tab part of the \"line\" so I did not provide it), I\nhad the same thought. Ignoring leading whitespace seemed easy enough in\nthe implementation.\n\nThen I started thinking about all the ways in which whitespace can be\nignored. My reaction in the end was to not try and open that can right\nthere and then. I did not think about regexes.\n\nI guess this boils down to the usage. Copying the line to anchor on from\nan editor could run into these kind of whitespace-issues, and shell\nescaping. Typing an anchor could become easier with regexes since one\ncould skip typing common substrings and just anchor on /unique-part/.\n\nMartin\n"},{"id":"347826","messageId":"CA+P7+xpDMz+Drqk0MoC0y1nQgjK_rtqow2rshPotR3EGaOB2bw@mail.gmail.com","threadId":"47635","inReplyTo":"CAGZ79kYBykcbzKxc_FHiL6dt=jkmK2Khe-e5aSxFephe=RsBFA@mail.gmail.com","subject":"Re: [PATCH v2 1/3] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-05-16T21:20:22Z","receivedAt":"2018-05-16T21:20:48Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Wed, May 16, 2018 at 9:41 AM, Stefan Beller <sbeller@google.com> wrote:\n> + Jonathan Tan for a side discussion on anchoring.\n>\n> On Wed, May 16, 2018 at 9:30 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>>\n>> This patch is best viewed using something like this (note the tab!):\n>> --color-moved --anchored=\"      trees[nr_trees] = parse_tree_indirect\"\n>\n> Heh! Having a \"is best viewed\" paragraph is the new shiny thing in\n> commit messages as 'git log origin/pu --grep \"is best viewed\"' tells me.\n>\n> Regarding the anchoring, I wonder if we can improve it by ignoring\n> whitespaces or just looking for substrings, or by allowing regexes or ...\n>\n> Thanks,\n> Stefan\n\nI think expanding it to be regexp would be nicest. To be honest, I\nalready thought it was substring based....\n\nIt'd be *really* cool if we had a way for a commit messages (or maybe\nnotes?) to indicate the anchor so that git show could (optionally)\nfigure out the anchor automatically. It's been REALLY useful for me\nwhen showing diffs to be able to provide a better idea of what a human\n*actually* did vs what the smallest diff was.\n\nThanks,\nJake\n"},{"id":"347827","messageId":"CA+P7+xrgB2NNFViy41bjhv10mxj4QpAuzUZOY9ghZ8E65tyaXg@mail.gmail.com","threadId":"47635","inReplyTo":"CAN0heSqdVB3SmQnBeJB06f4uXzUBM6OC2ySZNZnhJdisbR28Kg@mail.gmail.com","subject":"Re: [PATCH v2 1/3] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-05-16T21:21:21Z","receivedAt":"2018-05-16T21:21:45Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Wed, May 16, 2018 at 12:29 PM, Martin Ågren <martin.agren@gmail.com> wrote:\n> On 16 May 2018 at 18:41, Stefan Beller <sbeller@google.com> wrote:\n>> On Wed, May 16, 2018 at 9:30 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>>>\n>>> This patch is best viewed using something like this (note the tab!):\n>>> --color-moved --anchored=\"      trees[nr_trees] = parse_tree_indirect\"\n>>\n>> Heh! Having a \"is best viewed\" paragraph is the new shiny thing in\n>> commit messages as 'git log origin/pu --grep \"is best viewed\"' tells me.\n>\n> :-)\n>\n>> Regarding the anchoring, I wonder if we can improve it by ignoring\n>> whitespaces or just looking for substrings, or by allowing regexes or ...\n>\n> FWIW, because my first naive attempt failed (for some reason I did not\n> consider the leading tab part of the \"line\" so I did not provide it), I\n> had the same thought. Ignoring leading whitespace seemed easy enough in\n> the implementation.\n>\n> Then I started thinking about all the ways in which whitespace can be\n> ignored. My reaction in the end was to not try and open that can right\n> there and then. I did not think about regexes.\n>\n> I guess this boils down to the usage. Copying the line to anchor on from\n> an editor could run into these kind of whitespace-issues, and shell\n> escaping. Typing an anchor could become easier with regexes since one\n> could skip typing common substrings and just anchor on /unique-part/.\n>\n> Martin\n\nSimpler approach is to just match substring instead. Then, the user\ncan decide how much of the string is required to get the anchor they\nwanted.\n\nThanks,\nJake\n"},{"id":"347832","messageId":"CABPp-BFtMQMK_4RAQk=XHoXdFYDxkvwz-Gws9Fz-0DdcfiJKcg@mail.gmail.com","threadId":"47635","inReplyTo":"cover.1526488122.git.martin.agren@gmail.com","subject":"Re: [PATCH v2 0/3] unpack_trees_options: free messages when done","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-16T21:54:57Z","receivedAt":"2018-05-16T21:55:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Martin,\n\nOn Wed, May 16, 2018 at 9:30 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n> On 16 May 2018 at 16:32, Elijah Newren <newren@gmail.com> wrote:\n>> On Sat, Apr 28, 2018 at 4:32 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>>> As you noted elsewhere [1], Ben is also working in this area. I'd be\n>>> perfectly happy to sit on these patches until both of your contributions\n>>> come through to master.\n>>>\n>>> [1] https://public-inbox.org/git/CABPp-BFh=gL6RnbST2bgtynkij1Z5TMgAr1Via5_VyteF5eBMg@mail.gmail.com/\n>>\n>> Instead of waiting for these to come through to master, could you just\n>> submit based on the top of bp/merge-rename-config?\n>\n> Sure, here goes. This is based on bp/merge-rename-config, gets rid of\n> all leaks of memory allocated in `setup_unpack_trees_porcelain()` and\n> cuts the number of leaks in the test-suite (i.e., the subset of the\n> tests that I run) by around 10%.\n\nAwesome, thanks.  I've looked over patches 2 & 3; they look good to me.\n"},{"id":"347906","messageId":"af7af988-33d8-4237-8653-2020daf5d4c2@gmail.com","threadId":"47635","inReplyTo":"CABPp-BFtMQMK_4RAQk=XHoXdFYDxkvwz-Gws9Fz-0DdcfiJKcg@mail.gmail.com","subject":"Re: [PATCH v2 0/3] unpack_trees_options: free messages when done","fromName":"Ben Peart","fromEmail":"peartben@gmail.com","sentAt":"2018-05-17T12:09:52Z","receivedAt":"2018-05-17T12:10:01Z","isPatch":true,"sender":{"key":"benpeart@microsoft.com","avatar":"https://avatars.githubusercontent.com/u/15252029?v=4"},"body":"\n\nOn 5/16/2018 5:54 PM, Elijah Newren wrote:\n> Hi Martin,\n> \n> On Wed, May 16, 2018 at 9:30 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>> On 16 May 2018 at 16:32, Elijah Newren <newren@gmail.com> wrote:\n>>> On Sat, Apr 28, 2018 at 4:32 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>>>> As you noted elsewhere [1], Ben is also working in this area. I'd be\n>>>> perfectly happy to sit on these patches until both of your contributions\n>>>> come through to master.\n>>>>\n>>>> [1] https://public-inbox.org/git/CABPp-BFh=gL6RnbST2bgtynkij1Z5TMgAr1Via5_VyteF5eBMg@mail.gmail.com/\n>>>\n>>> Instead of waiting for these to come through to master, could you just\n>>> submit based on the top of bp/merge-rename-config?\n>>\n>> Sure, here goes. This is based on bp/merge-rename-config, gets rid of\n>> all leaks of memory allocated in `setup_unpack_trees_porcelain()` and\n>> cuts the number of leaks in the test-suite (i.e., the subset of the\n>> tests that I run) by around 10%.\n> \n> Awesome, thanks.  I've looked over patches 2 & 3; they look good to me.\n> \n\nI like the symmetry of the naming and locality of the functions.  Should \nhelp people remember to keep the xstrfmt() and associated free() in \nsync.  Patches look good to me as well.\n"},{"id":"347935","messageId":"xmqqmuwy54yj.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"0b7440f21e9ff7a066802fe1e7cb3078cf4ed2b8.1526488122.git.martin.agren@gmail.com","subject":"Re: [PATCH v2 1/3] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-17T21:48:20Z","receivedAt":"2018-05-17T21:48:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Martin Ågren <martin.agren@gmail.com> writes:\n\n> After we initialize the various fields in `opts` but before we actually\n> use them, we might return early. Move the initialization further down,\n> to immediately before we use `opts`.\n>\n> This limits the scope of `opts` and will help a later commit fix a\n> memory leak without having to worry about those early returns.\n>\n> This patch is best viewed using something like this (note the tab!):\n> --color-moved --anchored=\"\ttrees[nr_trees] = parse_tree_indirect\"\n\nThis side remark is interesting because it totally depends on how\nyou look at it.  I think \"initialize opts late\" and \"attempt to\nparse the trees first and fail early\" are the sides of the same\ncoin, and the diff shown without the anchor matches the latter,\nwhich is also perfectly acceptable interpretation of what this patch\ndoes.\n\n"},{"id":"347936","messageId":"xmqqin7m53x0.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"abc39c9991d6df627b40fd8b4fe9812e7ef78d77.1526488122.git.martin.agren@gmail.com","subject":"Re: [PATCH v2 3/3] unpack_trees_options: free messages when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-17T22:10:51Z","receivedAt":"2018-05-17T22:10:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Martin Ågren <martin.agren@gmail.com> writes:\n\n> The strings allocated in `setup_unpack_trees_porcelain()` are never\n> freed. Provide a function `clear_unpack_trees_porcelain()` to do so and\n> call it where we use `setup_unpack_trees_porcelain()`. The only\n> non-trivial user is `unpack_trees_start()`, where we should place the\n> new call in `unpack_trees_finish()`.\n>\n> The `opts` string array contains multiple copies of the same pointers.\n> Be careful to only free each pointer once, then zeroize the whole array\n> so that we do not leave any dangling pointers.\n\nThe verb to make it zero or fill it with zero is \"to zero\", I would\nthink.\n\nTo be honest I am not sure if I like the way this change is done.\nThe clear_unpack_trees_porcelain() function has too intimate\nknowledge of what happens inside the setup_unpack_trees_porcelain()\nfunction; it not just knows which fields are always allocated but\nwhich are duplicates, which must be double checked for updates\nwhenever the latter gets modified, yet there is no large warning\nsign painted in red in the latter, so it is easy to change the\nlatter and invalidate the assumption the former makes by mistake,\nleading to new leaks and/or double freeing.\n\nI wonder if an approach that is longer-term a bit more maintainable\nis to add a new string-list instance to opts, save these xstrfmt()'ed\nmessages to it when setup_unpack_trees_porcelain() create them, and\nthen make clear_unpack_trees_porcelain() pay *no* attention to msg[]\narray and the positions of these allocated messages and duplicates\nbut just reclaim the resources held in that string-list, or\nsomething like that.\n\n"},{"id":"347965","messageId":"CA+P7+xrXRGZe9AOR0+mZZTL9_nsHDMBv=1F6ea-p8aec3dhNuA@mail.gmail.com","threadId":"47635","inReplyTo":"xmqqmuwy54yj.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 1/3] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-05-18T01:59:04Z","receivedAt":"2018-05-18T01:59:30Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, May 17, 2018 at 2:48 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Martin Ågren <martin.agren@gmail.com> writes:\n>\n>> After we initialize the various fields in `opts` but before we actually\n>> use them, we might return early. Move the initialization further down,\n>> to immediately before we use `opts`.\n>>\n>> This limits the scope of `opts` and will help a later commit fix a\n>> memory leak without having to worry about those early returns.\n>>\n>> This patch is best viewed using something like this (note the tab!):\n>> --color-moved --anchored=\"    trees[nr_trees] = parse_tree_indirect\"\n>\n> This side remark is interesting because it totally depends on how\n> you look at it.  I think \"initialize opts late\" and \"attempt to\n> parse the trees first and fail early\" are the sides of the same\n> coin, and the diff shown without the anchor matches the latter,\n> which is also perfectly acceptable interpretation of what this patch\n> does.\n>\n\nYes. I like that we have tools available to show diffs in different\nhopefully meaningful ways.\n\nI happen to like when the diff matches my mental map of the change\nafter reading the commit message, so having the author indicate how\nbest to view it is useful, but definitely cool to see that we can get\ndifferent interpretations.\n\nThanks,\nJake\n"},{"id":"347969","messageId":"CAN0heSo80SjjGtC2x9s-TmNY0=W=YWTYxyjeuAQ3utEAEynXeA@mail.gmail.com","threadId":"47635","inReplyTo":"xmqqin7m53x0.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 3/3] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-18T05:08:58Z","receivedAt":"2018-05-18T05:09:02Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 18 May 2018 at 00:10, Junio C Hamano <gitster@pobox.com> wrote:\n> Martin Ågren <martin.agren@gmail.com> writes:\n>\n>> The `opts` string array contains multiple copies of the same pointers.\n>> Be careful to only free each pointer once, then zeroize the whole array\n>> so that we do not leave any dangling pointers.\n\n> I wonder if an approach that is longer-term a bit more maintainable\n> is to add a new string-list instance to opts, save these xstrfmt()'ed\n> messages to it when setup_unpack_trees_porcelain() create them, and\n> then make clear_unpack_trees_porcelain() pay *no* attention to msg[]\n> array and the positions of these allocated messages and duplicates\n> but just reclaim the resources held in that string-list, or\n> something like that.\n\nThank you for thoughts and this suggestion. I will try this out,\nhopefully later today.\n\nMartin\n"},{"id":"348002","messageId":"cover.1526677881.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"CAN0heSo80SjjGtC2x9s-TmNY0=W=YWTYxyjeuAQ3utEAEynXeA@mail.gmail.com","subject":"[PATCH v3 0/3] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-18T21:23:24Z","receivedAt":"2018-05-18T21:23:56Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"This is a reroll of my attempt at freeing the memory allocated by\n`setup_unpack_trees_porcelain()`. The first two patches are identical to\nv2. The third patch no longer relies on rather intimate knowledge of\nwhich strings are on the heap and which pointers are duplicates.\nInstead, as suggested by Junio, I keep a separate string-list of strings\nto free. That should make things more future-proof.\n\nv2: https://public-inbox.org/git/cover.1526488122.git.martin.agren@gmail.com/\n\nMartin\n\nElijah Newren (1):\n  merge-recursive: provide pair of `unpack_trees_{start,finish}()`\n\nMartin Ågren (2):\n  merge: setup `opts` later in `checkout_fast_forward()`\n  unpack_trees_options: free messages when done\n\n unpack-trees.h     |  6 ++++++\n builtin/checkout.c |  1 +\n merge-recursive.c  | 30 ++++++++++++++++--------------\n merge.c            | 35 ++++++++++++++++++++---------------\n unpack-trees.c     | 23 +++++++++++++++++++----\n 5 files changed, 62 insertions(+), 33 deletions(-)\n\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348003","messageId":"27965d978f203c5b55ce19fa03fb3e183d442c70.1526677881.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526677881.git.martin.agren@gmail.com","subject":"[PATCH v3 1/3] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-18T21:23:25Z","receivedAt":"2018-05-18T21:24:10Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"After we initialize the various fields in `opts` but before we actually\nuse them, we might return early. Move the initialization further down,\nto immediately before we use `opts`.\n\nThis limits the scope of `opts` and will help a later commit fix a\nmemory leak without having to worry about those early returns.\n\nThis patch is best viewed using something like this (note the tab!):\n--color-moved --anchored=\"\ttrees[nr_trees] = parse_tree_indirect\"\n\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n merge.c | 32 +++++++++++++++++---------------\n 1 file changed, 17 insertions(+), 15 deletions(-)\n\ndiff --git a/merge.c b/merge.c\nindex f06a4773d4..f123658e58 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -94,8 +94,24 @@ int checkout_fast_forward(const struct object_id *head,\n \t\treturn -1;\n \n \tmemset(&trees, 0, sizeof(trees));\n-\tmemset(&opts, 0, sizeof(opts));\n \tmemset(&t, 0, sizeof(t));\n+\n+\ttrees[nr_trees] = parse_tree_indirect(head);\n+\tif (!trees[nr_trees++]) {\n+\t\trollback_lock_file(&lock_file);\n+\t\treturn -1;\n+\t}\n+\ttrees[nr_trees] = parse_tree_indirect(remote);\n+\tif (!trees[nr_trees++]) {\n+\t\trollback_lock_file(&lock_file);\n+\t\treturn -1;\n+\t}\n+\tfor (i = 0; i < nr_trees; i++) {\n+\t\tparse_tree(trees[i]);\n+\t\tinit_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n+\t}\n+\n+\tmemset(&opts, 0, sizeof(opts));\n \tif (overwrite_ignore) {\n \t\tmemset(&dir, 0, sizeof(dir));\n \t\tdir.flags |= DIR_SHOW_IGNORED;\n@@ -112,20 +128,6 @@ int checkout_fast_forward(const struct object_id *head,\n \topts.fn = twoway_merge;\n \tsetup_unpack_trees_porcelain(&opts, \"merge\");\n \n-\ttrees[nr_trees] = parse_tree_indirect(head);\n-\tif (!trees[nr_trees++]) {\n-\t\trollback_lock_file(&lock_file);\n-\t\treturn -1;\n-\t}\n-\ttrees[nr_trees] = parse_tree_indirect(remote);\n-\tif (!trees[nr_trees++]) {\n-\t\trollback_lock_file(&lock_file);\n-\t\treturn -1;\n-\t}\n-\tfor (i = 0; i < nr_trees; i++) {\n-\t\tparse_tree(trees[i]);\n-\t\tinit_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n-\t}\n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n \t\treturn -1;\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348004","messageId":"2fae1279567cedf3f6672ce5e4b59d794641f7d0.1526677881.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526677881.git.martin.agren@gmail.com","subject":"[PATCH v3 2/3] merge-recursive: provide pair of `unpack_trees_{start,finish}()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-18T21:23:26Z","receivedAt":"2018-05-18T21:24:19Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"From: Elijah Newren <newren@gmail.com>\n\nRename `git_merge_trees()` to `unpack_trees_start()` and extract the\ncall to `discard_index()` into a new function `unpack_trees_finish()`.\nAs a result, these are called early resp. late in `merge_trees()`,\nmaking the resource handling clearer. The next commit will expand on\nthat, teaching `..._finish()` to free more memory. (So rather than\nmoving the FIXME-comment, just drop it, since it will be addressed soon\nenough.)\n\nAlso call `..._finish()` when `merge_trees()` returns early.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n merge-recursive.c | 29 +++++++++++++++--------------\n 1 file changed, 15 insertions(+), 14 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 680e01226b..ddb0fa7369 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -337,10 +337,10 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n \tinit_tree_desc(desc, tree->buffer, tree->size);\n }\n \n-static int git_merge_trees(struct merge_options *o,\n-\t\t\t   struct tree *common,\n-\t\t\t   struct tree *head,\n-\t\t\t   struct tree *merge)\n+static int unpack_trees_start(struct merge_options *o,\n+\t\t\t      struct tree *common,\n+\t\t\t      struct tree *head,\n+\t\t\t      struct tree *merge)\n {\n \tint rc;\n \tstruct tree_desc t[3];\n@@ -379,6 +379,11 @@ static int git_merge_trees(struct merge_options *o,\n \treturn rc;\n }\n \n+static void unpack_trees_finish(struct merge_options *o)\n+{\n+\tdiscard_index(&o->orig_index);\n+}\n+\n struct tree *write_tree_from_memory(struct merge_options *o)\n {\n \tstruct tree *result = NULL;\n@@ -3088,13 +3093,14 @@ int merge_trees(struct merge_options *o,\n \t\treturn 1;\n \t}\n \n-\tcode = git_merge_trees(o, common, head, merge);\n+\tcode = unpack_trees_start(o, common, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(o, 4) || o->call_depth)\n \t\t\terr(o, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n+\t\tunpack_trees_finish(o);\n \t\treturn -1;\n \t}\n \n@@ -3147,20 +3153,15 @@ int merge_trees(struct merge_options *o,\n \n \t\thashmap_free(&o->current_file_dir_set, 1);\n \n-\t\tif (clean < 0)\n+\t\tif (clean < 0) {\n+\t\t\tunpack_trees_finish(o);\n \t\t\treturn clean;\n+\t\t}\n \t}\n \telse\n \t\tclean = 1;\n \n-\t/* Free the extra index left from git_merge_trees() */\n-\t/*\n-\t * FIXME: Need to also free data allocated by\n-\t * setup_unpack_trees_porcelain() tucked away in o->unpack_opts.msgs,\n-\t * but the problem is that only half of it refers to dynamically\n-\t * allocated data, while the other half points at static strings.\n-\t */\n-\tdiscard_index(&o->orig_index);\n+\tunpack_trees_finish(o);\n \n \tif (o->call_depth && !(*result = write_tree_from_memory(o)))\n \t\treturn -1;\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348005","messageId":"f4e7822ebe8fcab8243ae3931084e10f3b199788.1526677881.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526677881.git.martin.agren@gmail.com","subject":"[PATCH v3 3/3] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-18T21:23:27Z","receivedAt":"2018-05-18T21:24:22Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"The strings allocated in `setup_unpack_trees_porcelain()` are never\nfreed. Provide a function `clear_unpack_trees_porcelain()` to do so and\ncall it where we use `setup_unpack_trees_porcelain()`. The only\nnon-trivial user is `unpack_trees_start()`, where we should place the\nnew call in `unpack_trees_finish()`.\n\nWe keep the string pointers in an array, mixing pointers to static\nmemory and memory that we allocate on the heap. We also keep several\ncopies of the individual pointers. So we need to make sure that we do\nnot free what we must not free and that we do not double-free. Keep the\nunique, heap-allocated pointers in a separate string list, to make the\nfreeing safe and future-proof.\n\nZero the whole array of string pointers to make sure that we do not\nleave any dangling pointers.\n\nNote that we only take responsibility for the memory allocated in\n`setup_unpack_trees_porcelain()` and not any other members of the\n`struct unpack_trees_options`.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n unpack-trees.h     |  6 ++++++\n builtin/checkout.c |  1 +\n merge-recursive.c  |  1 +\n merge.c            |  3 +++\n unpack-trees.c     | 23 +++++++++++++++++++----\n 5 files changed, 30 insertions(+), 4 deletions(-)\n\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 41178ada94..5a84123a40 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -33,6 +33,11 @@ enum unpack_trees_error_types {\n void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t\t  const char *cmd);\n \n+/*\n+ * Frees resources allocated by setup_unpack_trees_porcelain().\n+ */\n+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);\n+\n struct unpack_trees_options {\n \tunsigned int reset,\n \t\t     merge,\n@@ -57,6 +62,7 @@ struct unpack_trees_options {\n \tstruct pathspec *pathspec;\n \tmerge_fn_t fn;\n \tconst char *msgs[NB_UNPACK_TREES_ERROR_TYPES];\n+\tstruct string_list msgs_to_free;\n \t/*\n \t * Store error messages in an array, each case\n \t * corresponding to a error message type\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b49b582071..5cebe170fc 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -526,6 +526,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tinit_tree_desc(&trees[1], tree->buffer, tree->size);\n \n \t\tret = unpack_trees(2, trees, &topts);\n+\t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n \t\t\t/*\n \t\t\t * Unpack couldn't do a trivial merge; either\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ddb0fa7369..338f63a952 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -382,6 +382,7 @@ static int unpack_trees_start(struct merge_options *o,\n static void unpack_trees_finish(struct merge_options *o)\n {\n \tdiscard_index(&o->orig_index);\n+\tclear_unpack_trees_porcelain(&o->unpack_opts);\n }\n \n struct tree *write_tree_from_memory(struct merge_options *o)\ndiff --git a/merge.c b/merge.c\nindex f123658e58..b433291d0c 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -130,8 +130,11 @@ int checkout_fast_forward(const struct object_id *head,\n \n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n+\t\tclear_unpack_trees_porcelain(&opts);\n \t\treturn -1;\n \t}\n+\tclear_unpack_trees_porcelain(&opts);\n+\n \tif (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 79fd97074e..60293ff536 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -103,6 +103,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \tconst char **msgs = opts->msgs;\n \tconst char *msg;\n \n+\topts->msgs_to_free.strdup_strings = 0;\n+\n \tif (!strcmp(cmd, \"checkout\"))\n \t\tmsg = advice_commit_before_merge\n \t\t      ? _(\"Your local changes to the following files would be overwritten by checkout:\\n%%s\"\n@@ -118,8 +120,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t      ? _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\"\n \t\t\t  \"Please commit your changes or stash them before you %s.\")\n \t\t      : _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\");\n-\tmsgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =\n-\t\txstrfmt(msg, cmd, cmd);\n+\tmsg = xstrfmt(msg, cmd, cmd);\n+\tmsgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] = msg;\n+\tstring_list_append(&opts->msgs_to_free, msg);\n \n \tmsgs[ERROR_NOT_UPTODATE_DIR] =\n \t\t_(\"Updating the following directories would lose untracked files in them:\\n%s\");\n@@ -139,7 +142,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t      ? _(\"The following untracked working tree files would be removed by %s:\\n%%s\"\n \t\t\t  \"Please move or remove them before you %s.\")\n \t\t      : _(\"The following untracked working tree files would be removed by %s:\\n%%s\");\n-\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, cmd, cmd);\n+\tmsg = xstrfmt(msg, cmd, cmd);\n+\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = msg;\n+\tstring_list_append(&opts->msgs_to_free, msg);\n \n \tif (!strcmp(cmd, \"checkout\"))\n \t\tmsg = advice_commit_before_merge\n@@ -156,7 +161,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t      ? _(\"The following untracked working tree files would be overwritten by %s:\\n%%s\"\n \t\t\t  \"Please move or remove them before you %s.\")\n \t\t      : _(\"The following untracked working tree files would be overwritten by %s:\\n%%s\");\n-\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, cmd, cmd);\n+\tmsg = xstrfmt(msg, cmd, cmd);\n+\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = msg;\n+\tstring_list_append(&opts->msgs_to_free, msg);\n \n \t/*\n \t * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we\n@@ -179,6 +186,14 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\topts->unpack_rejects[i].strdup_strings = 1;\n }\n \n+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n+{\n+\topts->msgs_to_free.strdup_strings = 1;\n+\tstring_list_clear(&opts->msgs_to_free, 0);\n+\n+\tmemset(opts->msgs, 0, sizeof(opts->msgs));\n+}\n+\n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n \t\t\t unsigned int set, unsigned int clear)\n {\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348007","messageId":"20180518213333.GB21797@sigill.intra.peff.net","threadId":"47635","inReplyTo":"f4e7822ebe8fcab8243ae3931084e10f3b199788.1526677881.git.martin.agren@gmail.com","subject":"Re: [PATCH v3 3/3] unpack_trees_options: free messages when done","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-18T21:33:33Z","receivedAt":"2018-05-18T21:33:42Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 18, 2018 at 11:23:27PM +0200, Martin Ågren wrote:\n\n> diff --git a/unpack-trees.c b/unpack-trees.c\n> index 79fd97074e..60293ff536 100644\n> --- a/unpack-trees.c\n> +++ b/unpack-trees.c\n> @@ -103,6 +103,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n>  \tconst char **msgs = opts->msgs;\n>  \tconst char *msg;\n>  \n> +\topts->msgs_to_free.strdup_strings = 0;\n> +\n> [...]\n> +void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n> +{\n> +\topts->msgs_to_free.strdup_strings = 1;\n> +\tstring_list_clear(&opts->msgs_to_free, 0);\n\nI like this string_list approach much better, but it's too bad we have\nto go through these contortions with the strdup flag to get the memory\nownership right.\n\nIf we had a string_list_appendf(), then we could just leave that flag\nalone and this:\n\n> @@ -118,8 +120,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n>  \t\t      ? _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\"\n>  \t\t\t  \"Please commit your changes or stash them before you %s.\")\n>  \t\t      : _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\");\n> -\tmsgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =\n> -\t\txstrfmt(msg, cmd, cmd);\n> +\tmsg = xstrfmt(msg, cmd, cmd);\n> +\tmsgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] = msg;\n> +\tstring_list_append(&opts->msgs_to_free, msg);\n\nwould become:\n\n  msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOUPTODATE_FILE] =\n\tstring_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n\nI don't know if that's worth it or not (I suspect that there are other\nplaces where appendf would be handy, but I didn't poke around).\n\n-Peff\n"},{"id":"348013","messageId":"CABPp-BFdKFNLHxqt-rbSVPx_cXVG3iyad42qYFbWvP9_2fW2gQ@mail.gmail.com","threadId":"47635","inReplyTo":"20180518213333.GB21797@sigill.intra.peff.net","subject":"Re: [PATCH v3 3/3] unpack_trees_options: free messages when done","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-18T22:30:44Z","receivedAt":"2018-05-18T22:30:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, May 18, 2018 at 2:33 PM, Jeff King <peff@peff.net> wrote:\n> On Fri, May 18, 2018 at 11:23:27PM +0200, Martin Ågren wrote:\n>\n>> diff --git a/unpack-trees.c b/unpack-trees.c\n>> index 79fd97074e..60293ff536 100644\n>> --- a/unpack-trees.c\n>> +++ b/unpack-trees.c\n>> @@ -103,6 +103,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n>>       const char **msgs = opts->msgs;\n>>       const char *msg;\n>>\n>> +     opts->msgs_to_free.strdup_strings = 0;\n>> +\n>> [...]\n>> +void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n>> +{\n>> +     opts->msgs_to_free.strdup_strings = 1;\n>> +     string_list_clear(&opts->msgs_to_free, 0);\n>\n> I like this string_list approach much better, but it's too bad we have\n> to go through these contortions with the strdup flag to get the memory\n> ownership right.\n>\n> If we had a string_list_appendf(), then we could just leave that flag\n> alone and this:\n>\n>> @@ -118,8 +120,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n>>                     ? _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\"\n>>                         \"Please commit your changes or stash them before you %s.\")\n>>                     : _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\");\n>> -     msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =\n>> -             xstrfmt(msg, cmd, cmd);\n>> +     msg = xstrfmt(msg, cmd, cmd);\n>> +     msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] = msg;\n>> +     string_list_append(&opts->msgs_to_free, msg);\n>\n> would become:\n>\n>   msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOUPTODATE_FILE] =\n>         string_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n>\n> I don't know if that's worth it or not (I suspect that there are other\n> places where appendf would be handy, but I didn't poke around).\n\nThe strdup_strings=1 immediately before calling string_list_clear()\nhas been used in one other place in merge-recursive.c, and tripped up\nthe reviewer requiring a big code comment to explain it. (See the very\nend of https://public-inbox.org/git/CABPp-BGh7QTTfu3kgH4KO5DrrXiQjtrNhx_uaQsB6fHXT+9hLQ@mail.gmail.com/\n).  So there's already one other place in merge-recursive.c that might\nbenefit from such a change.\n\n\nA quick search shows about half a dozen other sites throughout the\ncode that are doing something similar:\n\n$ git grep -3 strdup_strings | grep -B 1 string_list_clear\nbisect.c: refs_for_removal.strdup_strings = 1;\nbisect.c- string_list_clear(&refs_for_removal, 0);\n--\nbuiltin/shortlog.c: onelines->strdup_strings = 1;\nbuiltin/shortlog.c- string_list_clear(onelines, 0);\n--\nbuiltin/shortlog.c: log->list.strdup_strings = 1;\nbuiltin/shortlog.c- string_list_clear(&log->list, 1);\n--\nmailmap.c: me->namemap.strdup_strings = 1;\nmailmap.c- string_list_clear_func(&me->namemap, free_mailmap_info);\n--\nmailmap.c: map->strdup_strings = 1;\nmailmap.c- string_list_clear_func(map, free_mailmap_entry);\n--\nmerge-recursive.c: entry->possible_new_dirs.strdup_strings = 1;\nmerge-recursive.c- string_list_clear(&entry->possible_new_dirs, 1);\n--\nrevision.c: revs->notes_opt.extra_notes_refs.strdup_strings = 1;\nrevision.c- string_list_clear(&revs->notes_opt.extra_notes_refs, 0);\n\n\nMaybe someone wants to tackle that as a separate patch series?  (Maybe\nwe make it a micro-project for future GSoC'ers?)\n"},{"id":"348017","messageId":"20180519010220.GA16071@sigill.intra.peff.net","threadId":"47635","inReplyTo":"CABPp-BFdKFNLHxqt-rbSVPx_cXVG3iyad42qYFbWvP9_2fW2gQ@mail.gmail.com","subject":"Re: [PATCH v3 3/3] unpack_trees_options: free messages when done","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-19T01:02:21Z","receivedAt":"2018-05-19T01:02:28Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 18, 2018 at 03:30:44PM -0700, Elijah Newren wrote:\n\n> > would become:\n> >\n> >   msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOUPTODATE_FILE] =\n> >         string_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n> >\n> > I don't know if that's worth it or not (I suspect that there are other\n> > places where appendf would be handy, but I didn't poke around).\n> \n> The strdup_strings=1 immediately before calling string_list_clear()\n> has been used in one other place in merge-recursive.c, and tripped up\n> the reviewer requiring a big code comment to explain it. (See the very\n> end of https://public-inbox.org/git/CABPp-BGh7QTTfu3kgH4KO5DrrXiQjtrNhx_uaQsB6fHXT+9hLQ@mail.gmail.com/\n> ).  So there's already one other place in merge-recursive.c that might\n> benefit from such a change.\n\nThanks. I knew I had seen such hackery before, but it's nice to have a\nspecific site that would benefit.\n\nIMHO the \"nodup\" variant of string_list is quite often a sign that\nthings are more complicated than they need to be. Even in cases that are\ntruly pointing to existing strings, is the complication really worth\nsaving a few strdups? Perhaps sometimes, but I have a suspicion it's\nmostly premature optimization.\n\n> Maybe someone wants to tackle that as a separate patch series?  (Maybe\n> we make it a micro-project for future GSoC'ers?)\n\nYeah, I'm fine with these patches if somebody wants to do it separately.\nIt would be a good micro-project, but I'd also be just as happy if\nsomebody did it before next year. :)\n\n-Peff\n"},{"id":"348067","messageId":"CAN0heSp7DqxOy-UeLgj8t_T_4f-fNthMpGNS8oUbcs7+NvS-zw@mail.gmail.com","threadId":"47635","inReplyTo":"20180519010220.GA16071@sigill.intra.peff.net","subject":"Re: [PATCH v3 3/3] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-19T06:13:56Z","receivedAt":"2018-05-19T06:14:05Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 19 May 2018 at 03:02, Jeff King <peff@peff.net> wrote:\n> On Fri, May 18, 2018 at 03:30:44PM -0700, Elijah Newren wrote:\n>\n>> > would become:\n>> >\n>> >   msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOUPTODATE_FILE] =\n>> >         string_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n>> >\n>> > I don't know if that's worth it or not (I suspect that there are other\n>> > places where appendf would be handy, but I didn't poke around).\n\nThis does poke at the `string` member, but there is precedent for doing\nthat. That also feels much closer to the purpose of a string list than\nthe fiddling with `strdup_strings` that I do in my patch.\n\nI'll look into this over the weekend. Thanks for the suggestion.\n\n>> The strdup_strings=1 immediately before calling string_list_clear()\n>> has been used in one other place in merge-recursive.c, and tripped up\n>> the reviewer requiring a big code comment to explain it. (See the very\n>> end of https://public-inbox.org/git/CABPp-BGh7QTTfu3kgH4KO5DrrXiQjtrNhx_uaQsB6fHXT+9hLQ@mail.gmail.com/\n>> ).  So there's already one other place in merge-recursive.c that might\n>> benefit from such a change.\n>\n> Thanks. I knew I had seen such hackery before, but it's nice to have a\n> specific site that would benefit.\n>\n> IMHO the \"nodup\" variant of string_list is quite often a sign that\n> things are more complicated than they need to be. Even in cases that are\n> truly pointing to existing strings, is the complication really worth\n> saving a few strdups? Perhaps sometimes, but I have a suspicion it's\n> mostly premature optimization.\n>\n>> Maybe someone wants to tackle that as a separate patch series?  (Maybe\n>> we make it a micro-project for future GSoC'ers?)\n>\n> Yeah, I'm fine with these patches if somebody wants to do it separately.\n> It would be a good micro-project, but I'd also be just as happy if\n> somebody did it before next year. :)\n\nObviously, I won't be tackling all of that now. I'll just look into\nmaking this final patch better and leave any further cleaning up for\nlater.\n\nMartin\n"},{"id":"348085","messageId":"cover.1526810549.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"CAN0heSp7DqxOy-UeLgj8t_T_4f-fNthMpGNS8oUbcs7+NvS-zw@mail.gmail.com","subject":"[PATCH v4 0/4] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-20T10:17:33Z","receivedAt":"2018-05-20T10:18:23Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"This is v4 of my series for taking care of the memory allocated by\n`setup_unpack_trees_porcelain()`. As before, this is based on\nbp/merge-rename-config.\n\nOn 19 May 2018 at 08:13, Martin Ågren <martin.agren@gmail.com> wrote:\n> On 19 May 2018 at 03:02, Jeff King <peff@peff.net> wrote:\n>>\n>>> > would become:\n>>> >\n>>> >   msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOUPTODATE_FILE] =\n>>> >         string_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n>>> >\n>>> > I don't know if that's worth it or not (I suspect that there are other\n>>> > places where appendf would be handy, but I didn't poke around).\n>\n> I'll look into this over the weekend. Thanks for the suggestion.\n\nThe difference to v3 is indeed the new patch 3/4, which introduces\n`string_list_appendf()`. I think that makes patch 4/4 clearer and the\nresulting code less surprising.\n\nThere is an obvious candidate for using this new function in bisect.c,\nbut I refrained from doing that conversion in this series. While\nconverting that user to use this new function would be trivial and safe,\nsuch a change might not look entirely sane on its own. The reason is\nthat the user does the whole `strdup_strings`-dance that I did in v3.\n\nI think it would be much better to do that conversion as a part of a\n\"let's not play with strdup_strings\"-patch. I have one prepared and it\nlooks quite ok to me. I should be able to be able to collect more\n`strdup_string`-cleanups soonish and submit a series later (say, when/if\nthis here series has matured).\n\nElijah Newren (1):\n  merge-recursive: provide pair of `unpack_trees_{start,finish}()`\n\nMartin Ågren (3):\n  merge: setup `opts` later in `checkout_fast_forward()`\n  string-list: provide `string_list_appendf()`\n  unpack_trees_options: free messages when done\n\n string-list.h      |  9 +++++++++\n unpack-trees.h     |  6 ++++++\n builtin/checkout.c |  1 +\n merge-recursive.c  | 30 ++++++++++++++++--------------\n merge.c            | 35 ++++++++++++++++++++---------------\n string-list.c      | 13 +++++++++++++\n unpack-trees.c     | 20 +++++++++++++++++---\n 7 files changed, 82 insertions(+), 32 deletions(-)\n\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348086","messageId":"27965d978f203c5b55ce19fa03fb3e183d442c70.1526810549.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526810549.git.martin.agren@gmail.com","subject":"[PATCH v4 1/4] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-20T10:17:34Z","receivedAt":"2018-05-20T10:18:28Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"After we initialize the various fields in `opts` but before we actually\nuse them, we might return early. Move the initialization further down,\nto immediately before we use `opts`.\n\nThis limits the scope of `opts` and will help a later commit fix a\nmemory leak without having to worry about those early returns.\n\nThis patch is best viewed using something like this (note the tab!):\n--color-moved --anchored=\"\ttrees[nr_trees] = parse_tree_indirect\"\n\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n merge.c | 34 ++++++++++++++++++----------------\n 1 file changed, 18 insertions(+), 16 deletions(-)\n\ndiff --git a/merge.c b/merge.c\nindex f06a4773d4..f123658e58 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -94,23 +94,7 @@ int checkout_fast_forward(const struct object_id *head,\n \t\treturn -1;\n \n \tmemset(&trees, 0, sizeof(trees));\n-\tmemset(&opts, 0, sizeof(opts));\n \tmemset(&t, 0, sizeof(t));\n-\tif (overwrite_ignore) {\n-\t\tmemset(&dir, 0, sizeof(dir));\n-\t\tdir.flags |= DIR_SHOW_IGNORED;\n-\t\tsetup_standard_excludes(&dir);\n-\t\topts.dir = &dir;\n-\t}\n-\n-\topts.head_idx = 1;\n-\topts.src_index = &the_index;\n-\topts.dst_index = &the_index;\n-\topts.update = 1;\n-\topts.verbose_update = 1;\n-\topts.merge = 1;\n-\topts.fn = twoway_merge;\n-\tsetup_unpack_trees_porcelain(&opts, \"merge\");\n \n \ttrees[nr_trees] = parse_tree_indirect(head);\n \tif (!trees[nr_trees++]) {\n@@ -126,6 +110,24 @@ int checkout_fast_forward(const struct object_id *head,\n \t\tparse_tree(trees[i]);\n \t\tinit_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n \t}\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\tif (overwrite_ignore) {\n+\t\tmemset(&dir, 0, sizeof(dir));\n+\t\tdir.flags |= DIR_SHOW_IGNORED;\n+\t\tsetup_standard_excludes(&dir);\n+\t\topts.dir = &dir;\n+\t}\n+\n+\topts.head_idx = 1;\n+\topts.src_index = &the_index;\n+\topts.dst_index = &the_index;\n+\topts.update = 1;\n+\topts.verbose_update = 1;\n+\topts.merge = 1;\n+\topts.fn = twoway_merge;\n+\tsetup_unpack_trees_porcelain(&opts, \"merge\");\n+\n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n \t\treturn -1;\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348087","messageId":"e837222c8031e2186876a51559589fdb0022c8aa.1526810549.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526810549.git.martin.agren@gmail.com","subject":"[PATCH v4 2/4] merge-recursive: provide pair of `unpack_trees_{start,finish}()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-20T10:17:35Z","receivedAt":"2018-05-20T10:18:36Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"From: Elijah Newren <newren@gmail.com>\n\nRename `git_merge_trees()` to `unpack_trees_start()` and extract the\ncall to `discard_index()` into a new function `unpack_trees_finish()`.\nAs a result, these are called early resp. late in `merge_trees()`,\nmaking the resource handling clearer. A later commit will expand on\nthat, teaching `..._finish()` to free more memory. (So rather than\nmoving the FIXME-comment, just drop it, since it will be addressed soon\nenough.)\n\nAlso call `..._finish()` when `merge_trees()` returns early.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n merge-recursive.c | 29 +++++++++++++++--------------\n 1 file changed, 15 insertions(+), 14 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 680e01226b..ddb0fa7369 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -337,10 +337,10 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n \tinit_tree_desc(desc, tree->buffer, tree->size);\n }\n \n-static int git_merge_trees(struct merge_options *o,\n-\t\t\t   struct tree *common,\n-\t\t\t   struct tree *head,\n-\t\t\t   struct tree *merge)\n+static int unpack_trees_start(struct merge_options *o,\n+\t\t\t      struct tree *common,\n+\t\t\t      struct tree *head,\n+\t\t\t      struct tree *merge)\n {\n \tint rc;\n \tstruct tree_desc t[3];\n@@ -379,6 +379,11 @@ static int git_merge_trees(struct merge_options *o,\n \treturn rc;\n }\n \n+static void unpack_trees_finish(struct merge_options *o)\n+{\n+\tdiscard_index(&o->orig_index);\n+}\n+\n struct tree *write_tree_from_memory(struct merge_options *o)\n {\n \tstruct tree *result = NULL;\n@@ -3088,13 +3093,14 @@ int merge_trees(struct merge_options *o,\n \t\treturn 1;\n \t}\n \n-\tcode = git_merge_trees(o, common, head, merge);\n+\tcode = unpack_trees_start(o, common, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(o, 4) || o->call_depth)\n \t\t\terr(o, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n+\t\tunpack_trees_finish(o);\n \t\treturn -1;\n \t}\n \n@@ -3147,20 +3153,15 @@ int merge_trees(struct merge_options *o,\n \n \t\thashmap_free(&o->current_file_dir_set, 1);\n \n-\t\tif (clean < 0)\n+\t\tif (clean < 0) {\n+\t\t\tunpack_trees_finish(o);\n \t\t\treturn clean;\n+\t\t}\n \t}\n \telse\n \t\tclean = 1;\n \n-\t/* Free the extra index left from git_merge_trees() */\n-\t/*\n-\t * FIXME: Need to also free data allocated by\n-\t * setup_unpack_trees_porcelain() tucked away in o->unpack_opts.msgs,\n-\t * but the problem is that only half of it refers to dynamically\n-\t * allocated data, while the other half points at static strings.\n-\t */\n-\tdiscard_index(&o->orig_index);\n+\tunpack_trees_finish(o);\n \n \tif (o->call_depth && !(*result = write_tree_from_memory(o)))\n \t\treturn -1;\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348088","messageId":"77b025bb47869478e3eef1c3c34d379164f5d91c.1526810549.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526810549.git.martin.agren@gmail.com","subject":"[PATCH v4 3/4] string-list: provide `string_list_appendf()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-20T10:17:36Z","receivedAt":"2018-05-20T10:18:39Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"Add a function `string_list_appendf(list, fmt, ...)` to the string-list\nAPI. The next commit will add a user.\n\nThis function naturally ignores the `strdup_strings`-setting and always\nappends a freshly allocated string. Thus, using this function with\n`strdup_strings = 0` risks making ownership unclear and leaking memory.\nWith `strdup_strings = 1` on the other hand, we can easily add formatted\nstrings without going through `string_list_append_nodup()` or playing\nwith `strdup_strings`.\n\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n string-list.h |  9 +++++++++\n string-list.c | 13 +++++++++++++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/string-list.h b/string-list.h\nindex ff8f6094a3..3a73b86ffa 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -208,6 +208,15 @@ void string_list_remove_duplicates(struct string_list *sorted_list, int free_uti\n  */\n struct string_list_item *string_list_append(struct string_list *list, const char *string);\n \n+/**\n+ * Add formatted string to the end of `list`. This function ignores\n+ * the value of `list->strdup_strings` and always appends a freshly\n+ * allocated string, so you will probably not want to use it with\n+ * `strdup_strings = 0`.\n+ */\n+struct string_list_item *string_list_appendf(struct string_list *list,\n+\t\t\t\t\t     const char *fmt, ...);\n+\n /**\n  * Like string_list_append(), except string is never copied.  When\n  * list->strdup_strings is set, this function can be used to hand\ndiff --git a/string-list.c b/string-list.c\nindex a0cf0cfe88..b54d31c1cf 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -224,6 +224,19 @@ struct string_list_item *string_list_append(struct string_list *list,\n \t\t\tlist->strdup_strings ? xstrdup(string) : (char *)string);\n }\n \n+struct string_list_item *string_list_appendf(struct string_list *list,\n+\t\t\t\t\t     const char *fmt, ...)\n+{\n+\tstruct string_list_item *retval;\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\tretval = string_list_append_nodup(list, xstrvfmt(fmt, ap));\n+\tva_end(ap);\n+\n+\treturn retval;\n+}\n+\n static int cmp_items(const void *a, const void *b, void *ctx)\n {\n \tcompare_strings_fn cmp = ctx;\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348089","messageId":"93230899d8491b0282a617233d23e9773fe8ec73.1526810549.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526810549.git.martin.agren@gmail.com","subject":"[PATCH v4 4/4] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-20T10:17:37Z","receivedAt":"2018-05-20T10:18:42Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"The strings allocated in `setup_unpack_trees_porcelain()` are never\nfreed. Provide a function `clear_unpack_trees_porcelain()` to do so and\ncall it where we use `setup_unpack_trees_porcelain()`. The only\nnon-trivial user is `unpack_trees_start()`, where we should place the\nnew call in `unpack_trees_finish()`.\n\nWe keep the string pointers in an array, mixing pointers to static\nmemory and memory that we allocate on the heap. We also keep several\ncopies of the individual pointers. So we need to make sure that we do\nnot free what we must not free and that we do not double-free. Keep the\nunique, heap-allocated pointers in a separate string list, to make the\nfreeing safe and future-proof.\n\nZero the whole array of string pointers to make sure that we do not\nleave any dangling pointers.\n\nNote that we only take responsibility for the memory allocated in\n`setup_unpack_trees_porcelain()` and not any other members of the\n`struct unpack_trees_options`.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Jeff King <peff@peff.net>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n unpack-trees.h     |  6 ++++++\n builtin/checkout.c |  1 +\n merge-recursive.c  |  1 +\n merge.c            |  3 +++\n unpack-trees.c     | 20 +++++++++++++++++---\n 5 files changed, 28 insertions(+), 3 deletions(-)\n\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 41178ada94..5a84123a40 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -33,6 +33,11 @@ enum unpack_trees_error_types {\n void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t\t  const char *cmd);\n \n+/*\n+ * Frees resources allocated by setup_unpack_trees_porcelain().\n+ */\n+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);\n+\n struct unpack_trees_options {\n \tunsigned int reset,\n \t\t     merge,\n@@ -57,6 +62,7 @@ struct unpack_trees_options {\n \tstruct pathspec *pathspec;\n \tmerge_fn_t fn;\n \tconst char *msgs[NB_UNPACK_TREES_ERROR_TYPES];\n+\tstruct string_list msgs_to_free;\n \t/*\n \t * Store error messages in an array, each case\n \t * corresponding to a error message type\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b49b582071..5cebe170fc 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -526,6 +526,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tinit_tree_desc(&trees[1], tree->buffer, tree->size);\n \n \t\tret = unpack_trees(2, trees, &topts);\n+\t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n \t\t\t/*\n \t\t\t * Unpack couldn't do a trivial merge; either\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ddb0fa7369..338f63a952 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -382,6 +382,7 @@ static int unpack_trees_start(struct merge_options *o,\n static void unpack_trees_finish(struct merge_options *o)\n {\n \tdiscard_index(&o->orig_index);\n+\tclear_unpack_trees_porcelain(&o->unpack_opts);\n }\n \n struct tree *write_tree_from_memory(struct merge_options *o)\ndiff --git a/merge.c b/merge.c\nindex f123658e58..b433291d0c 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -130,8 +130,11 @@ int checkout_fast_forward(const struct object_id *head,\n \n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n+\t\tclear_unpack_trees_porcelain(&opts);\n \t\treturn -1;\n \t}\n+\tclear_unpack_trees_porcelain(&opts);\n+\n \tif (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 79fd97074e..86046b987a 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -103,6 +103,12 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \tconst char **msgs = opts->msgs;\n \tconst char *msg;\n \n+\t/*\n+\t * As we add strings using `...appendf()`, this does not matter,\n+\t * but when we clear the string list, we want them to be freed.\n+\t */\n+\topts->msgs_to_free.strdup_strings = 1;\n+\n \tif (!strcmp(cmd, \"checkout\"))\n \t\tmsg = advice_commit_before_merge\n \t\t      ? _(\"Your local changes to the following files would be overwritten by checkout:\\n%%s\"\n@@ -119,7 +125,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t  \"Please commit your changes or stash them before you %s.\")\n \t\t      : _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\");\n \tmsgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =\n-\t\txstrfmt(msg, cmd, cmd);\n+\t\tstring_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n \n \tmsgs[ERROR_NOT_UPTODATE_DIR] =\n \t\t_(\"Updating the following directories would lose untracked files in them:\\n%s\");\n@@ -139,7 +145,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t      ? _(\"The following untracked working tree files would be removed by %s:\\n%%s\"\n \t\t\t  \"Please move or remove them before you %s.\")\n \t\t      : _(\"The following untracked working tree files would be removed by %s:\\n%%s\");\n-\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, cmd, cmd);\n+\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] =\n+\t\tstring_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n \n \tif (!strcmp(cmd, \"checkout\"))\n \t\tmsg = advice_commit_before_merge\n@@ -156,7 +163,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t      ? _(\"The following untracked working tree files would be overwritten by %s:\\n%%s\"\n \t\t\t  \"Please move or remove them before you %s.\")\n \t\t      : _(\"The following untracked working tree files would be overwritten by %s:\\n%%s\");\n-\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, cmd, cmd);\n+\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] =\n+\t\tstring_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n \n \t/*\n \t * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we\n@@ -179,6 +187,12 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\topts->unpack_rejects[i].strdup_strings = 1;\n }\n \n+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n+{\n+\tstring_list_clear(&opts->msgs_to_free, 0);\n+\tmemset(opts->msgs, 0, sizeof(opts->msgs));\n+}\n+\n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n \t\t\t unsigned int set, unsigned int clear)\n {\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348128","messageId":"CA+P7+xqPnuuQpp5e7fduLgaSd+JrTn95w=Da7q=iCXUgo0QHFA@mail.gmail.com","threadId":"47635","inReplyTo":"77b025bb47869478e3eef1c3c34d379164f5d91c.1526810549.git.martin.agren@gmail.com","subject":"Re: [PATCH v4 3/4] string-list: provide `string_list_appendf()`","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-05-20T19:12:33Z","receivedAt":"2018-05-20T19:12:58Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Sun, May 20, 2018 at 3:17 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n> +/**\n> + * Add formatted string to the end of `list`. This function ignores\n> + * the value of `list->strdup_strings` and always appends a freshly\n> + * allocated string, so you will probably not want to use it with\n> + * `strdup_strings = 0`.\n> + */\n> +struct string_list_item *string_list_appendf(struct string_list *list,\n> +                                            const char *fmt, ...);\n> +\n\nWould it make sense to verify that strdup_strings == 0? I guess we'd\nhave to use die or BUG(), but that would mean that the program could\ncrash..\n\nI doubt this could be verified at compilation time....\n\nThanks,\nJake\n"},{"id":"348139","messageId":"xmqqvabh3mim.fsf_-_@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"CA+P7+xqPnuuQpp5e7fduLgaSd+JrTn95w=Da7q=iCXUgo0QHFA@mail.gmail.com","subject":"Re*: [PATCH v4 3/4] string-list: provide `string_list_appendf()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-21T00:01:05Z","receivedAt":"2018-05-21T00:01:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.keller@gmail.com> writes:\n\n> On Sun, May 20, 2018 at 3:17 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n>> +/**\n>> + * Add formatted string to the end of `list`. This function ignores\n>> + * the value of `list->strdup_strings` and always appends a freshly\n>> + * allocated string, so you will probably not want to use it with\n>> + * `strdup_strings = 0`.\n>> + */\n>> +struct string_list_item *string_list_appendf(struct string_list *list,\n>> +                                            const char *fmt, ...);\n>> +\n>\n> Would it make sense to verify that strdup_strings == 0? I guess we'd\n> have to use die or BUG(), but that would mean that the program could\n> crash..\n\nIt probably is clear to readers that any reasonable implementation\nof *_appendf() will create a new and unique string, as the point of\n*f() is to give a customized instantiation of fmt string for given\nparameters.  So it would be natural to expect that the storage that\nholds the generated string will belong to the list.  We _could_ make\nit honor strdup_strings and make one extra copy when strdup_strings\nis set to true, but the only effect such a stupid implementation has\nis to unnecessarily leak ;-)\n\nI think it is probably OK to check and BUG() when strdup_strings==0,\nbut such a check means that we now declare that a string list must\neither borrow all of its strings from elsewhere or own all of its\nstrings itself, and mixture is not allowed.\n\nThe (overly) flexible string_list API could be used to mix both\nborrowed and owned strings (an obvious strategy to do this without\nleaking and crashing is to use the .util field to mark which ones\nare owned and which ones are borrowed), so there might already be\ncurrent users of the API that violates that rule.\n\nI have a feeling that argv_array might be a better fit for the\npurpose of keeping track of to_free[] strings in the context of this\nseries.  Moving away from string_list would allow us to sidestep the\nstorage ownership issues the API has, and we do not need the .util\nthing string_list gives us (which is one distinct advantage string_list\nhas over argv_array, if the application needs that feature).\n\nWe would need to make _pushf() and friends return \"const char *\" if\nwe go that route to make the resulting API more useful, though.\n\n-- >8 --\nSubject: argv-array: return the pushed string from argv_push*()\n\nSuch an API change allows us to use an argv_array this way:\n\n\tstruct argv_array to_free = ARGV_ARRAY_INIT;\n        const char *msg;\n\n        if (some condition) {\n\t\tmsg = \"constant string message\";\n\t\t... other logic ...\n\t} else {\n\t\tmsg = argv_pushf(&to_free, \"format %s\", var);\n\t}\n\t... use \"msg\" ...\n\t... do other things ...\n\targv_clear(&to_free);\n\nNote that argv_array_pushl() and argv_array_pushv() are used to push\none or more strings with a single call, so we do not return any one\nof these strings from these two functions in order to reduce the\nchance to misuse the API.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n argv-array.c | 6 ++++--\n argv-array.h | 4 ++--\n 2 files changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/argv-array.c b/argv-array.c\nindex 5d370fa336..449dfc105a 100644\n--- a/argv-array.c\n+++ b/argv-array.c\n@@ -21,12 +21,13 @@ static void argv_array_push_nodup(struct argv_array *array, const char *value)\n \tarray->argv[array->argc] = NULL;\n }\n \n-void argv_array_push(struct argv_array *array, const char *value)\n+const char *argv_array_push(struct argv_array *array, const char *value)\n {\n \targv_array_push_nodup(array, xstrdup(value));\n+\treturn array->argv[array->argc - 1];\n }\n \n-void argv_array_pushf(struct argv_array *array, const char *fmt, ...)\n+const char *argv_array_pushf(struct argv_array *array, const char *fmt, ...)\n {\n \tva_list ap;\n \tstruct strbuf v = STRBUF_INIT;\n@@ -36,6 +37,7 @@ void argv_array_pushf(struct argv_array *array, const char *fmt, ...)\n \tva_end(ap);\n \n \targv_array_push_nodup(array, strbuf_detach(&v, NULL));\n+\treturn array->argv[array->argc - 1];\n }\n \n void argv_array_pushl(struct argv_array *array, ...)\ndiff --git a/argv-array.h b/argv-array.h\nindex 29056e49a1..715c93b246 100644\n--- a/argv-array.h\n+++ b/argv-array.h\n@@ -12,9 +12,9 @@ struct argv_array {\n #define ARGV_ARRAY_INIT { empty_argv, 0, 0 }\n \n void argv_array_init(struct argv_array *);\n-void argv_array_push(struct argv_array *, const char *);\n+const char *argv_array_push(struct argv_array *, const char *);\n __attribute__((format (printf,2,3)))\n-void argv_array_pushf(struct argv_array *, const char *fmt, ...);\n+const char *argv_array_pushf(struct argv_array *, const char *fmt, ...);\n LAST_ARG_MUST_BE_NULL\n void argv_array_pushl(struct argv_array *, ...);\n void argv_array_pushv(struct argv_array *, const char **);\n"},{"id":"348140","messageId":"xmqqr2m53leq.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"xmqqvabh3mim.fsf_-_@gitster-ct.c.googlers.com","subject":"Re: Re*: [PATCH v4 3/4] string-list: provide `string_list_appendf()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-21T00:25:01Z","receivedAt":"2018-05-21T00:29:46Z","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> I have a feeling that argv_array might be a better fit for the\n> purpose of keeping track of to_free[] strings in the context of this\n> series.  Moving away from string_list would allow us to sidestep the\n> storage ownership issues the API has, and we do not need the .util\n> thing string_list gives us (which is one distinct advantage string_list\n> has over argv_array, if the application needs that feature).\n>\n> We would need to make _pushf() and friends return \"const char *\" if\n> we go that route to make the resulting API more useful, though.\n\n... and redoing the 4/4 patch using argv_array_pushf() makes the\nresult look like this, which does not look too bad.\n\n-- >8 --\nFrom: Junio C Hamano <gitster@pobox.com>\nSubject: [PATCH] unpack_trees_options: keep track of owned messages with argv_array\n\nInstead of the string_list API, which is overly flexible and require\ncallers to be careful about memory ownership issues, use the\nargv_array API that always takes ownership to redo the earlier\ncommit.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n unpack-trees.c | 16 ++++++----------\n unpack-trees.h |  4 ++--\n 2 files changed, 8 insertions(+), 12 deletions(-)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 86046b987a..b28f0c6e9d 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1,5 +1,6 @@\n #define NO_THE_INDEX_COMPATIBILITY_MACROS\n #include \"cache.h\"\n+#include \"argv-array.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"dir.h\"\n@@ -103,11 +104,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \tconst char **msgs = opts->msgs;\n \tconst char *msg;\n \n-\t/*\n-\t * As we add strings using `...appendf()`, this does not matter,\n-\t * but when we clear the string list, we want them to be freed.\n-\t */\n-\topts->msgs_to_free.strdup_strings = 1;\n+\targv_array_init(&opts->msgs_to_free);\n \n \tif (!strcmp(cmd, \"checkout\"))\n \t\tmsg = advice_commit_before_merge\n@@ -125,7 +122,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t  \"Please commit your changes or stash them before you %s.\")\n \t\t      : _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\");\n \tmsgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =\n-\t\tstring_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n+\t\targv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);\n \n \tmsgs[ERROR_NOT_UPTODATE_DIR] =\n \t\t_(\"Updating the following directories would lose untracked files in them:\\n%s\");\n@@ -146,7 +143,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t  \"Please move or remove them before you %s.\")\n \t\t      : _(\"The following untracked working tree files would be removed by %s:\\n%%s\");\n \tmsgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] =\n-\t\tstring_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n+\t\targv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);\n \n \tif (!strcmp(cmd, \"checkout\"))\n \t\tmsg = advice_commit_before_merge\n@@ -164,7 +161,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t  \"Please move or remove them before you %s.\")\n \t\t      : _(\"The following untracked working tree files would be overwritten by %s:\\n%%s\");\n \tmsgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] =\n-\t\tstring_list_appendf(&opts->msgs_to_free, msg, cmd, cmd)->string;\n+\t\targv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);\n \n \t/*\n \t * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we\n@@ -189,8 +186,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \n void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n {\n-\tstring_list_clear(&opts->msgs_to_free, 0);\n-\tmemset(opts->msgs, 0, sizeof(opts->msgs));\n+\targv_array_clear(&opts->msgs_to_free);\n }\n \n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 5a84123a40..c2b434c606 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -2,7 +2,7 @@\n #define UNPACK_TREES_H\n \n #include \"tree-walk.h\"\n-#include \"string-list.h\"\n+#include \"argv-array.h\"\n \n #define MAX_UNPACK_TREES 8\n \n@@ -62,7 +62,7 @@ struct unpack_trees_options {\n \tstruct pathspec *pathspec;\n \tmerge_fn_t fn;\n \tconst char *msgs[NB_UNPACK_TREES_ERROR_TYPES];\n-\tstruct string_list msgs_to_free;\n+\tstruct argv_array msgs_to_free;\n \t/*\n \t * Store error messages in an array, each case\n \t * corresponding to a error message type\n-- \n2.17.0-582-gccdcbd54c4\n\n"},{"id":"348174","messageId":"20180521023840.GA16236@sigill.intra.peff.net","threadId":"47635","inReplyTo":"xmqqvabh3mim.fsf_-_@gitster-ct.c.googlers.com","subject":"Re: Re*: [PATCH v4 3/4] string-list: provide `string_list_appendf()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-21T02:38:40Z","receivedAt":"2018-05-21T02:38:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 21, 2018 at 09:01:05AM +0900, Junio C Hamano wrote:\n\n> Jacob Keller <jacob.keller@gmail.com> writes:\n> \n> > On Sun, May 20, 2018 at 3:17 AM, Martin Ågren <martin.agren@gmail.com> wrote:\n> >> +/**\n> >> + * Add formatted string to the end of `list`. This function ignores\n> >> + * the value of `list->strdup_strings` and always appends a freshly\n> >> + * allocated string, so you will probably not want to use it with\n> >> + * `strdup_strings = 0`.\n> >> + */\n> >> +struct string_list_item *string_list_appendf(struct string_list *list,\n> >> +                                            const char *fmt, ...);\n> >> +\n> >\n> > Would it make sense to verify that strdup_strings == 0? I guess we'd\n> > have to use die or BUG(), but that would mean that the program could\n> > crash..\n> \n> It probably is clear to readers that any reasonable implementation\n> of *_appendf() will create a new and unique string, as the point of\n> *f() is to give a customized instantiation of fmt string for given\n> parameters.  So it would be natural to expect that the storage that\n> holds the generated string will belong to the list.  We _could_ make\n> it honor strdup_strings and make one extra copy when strdup_strings\n> is set to true, but the only effect such a stupid implementation has\n> is to unnecessarily leak ;-)\n> \n> I think it is probably OK to check and BUG() when strdup_strings==0,\n> but such a check means that we now declare that a string list must\n> either borrow all of its strings from elsewhere or own all of its\n> strings itself, and mixture is not allowed.\n>\n> The (overly) flexible string_list API could be used to mix both\n> borrowed and owned strings (an obvious strategy to do this without\n> leaking and crashing is to use the .util field to mark which ones\n> are owned and which ones are borrowed), so there might already be\n> current users of the API that violates that rule.\n\nIMHO such a mixed use is mildly crazy. At any rate, we would know that\nanybody using appendf() would not have this problem, since we are just\nintroducing it now.\n\n> I have a feeling that argv_array might be a better fit for the\n> purpose of keeping track of to_free[] strings in the context of this\n> series.  Moving away from string_list would allow us to sidestep the\n> storage ownership issues the API has, and we do not need the .util\n> thing string_list gives us (which is one distinct advantage string_list\n> has over argv_array, if the application needs that feature).\n\nI do agree that argv_array is generally a better fit for most cases.\nDidn't we want to rename it to strarray or something? That's probably\ntoo much yak-shaving for this series, though. :)\n\n> We would need to make _pushf() and friends return \"const char *\" if\n> we go that route to make the resulting API more useful, though.\n\nThis is the first time I think that's been suggested, but I agree it's\nthe only sensible thing for the functions to return.\n\n> -- >8 --\n> Subject: argv-array: return the pushed string from argv_push*()\n> \n> Such an API change allows us to use an argv_array this way:\n> \n> \tstruct argv_array to_free = ARGV_ARRAY_INIT;\n>         const char *msg;\n> \n>         if (some condition) {\n> \t\tmsg = \"constant string message\";\n> \t\t... other logic ...\n> \t} else {\n> \t\tmsg = argv_pushf(&to_free, \"format %s\", var);\n> \t}\n> \t... use \"msg\" ...\n> \t... do other things ...\n> \targv_clear(&to_free);\n> \n> Note that argv_array_pushl() and argv_array_pushv() are used to push\n> one or more strings with a single call, so we do not return any one\n> of these strings from these two functions in order to reduce the\n> chance to misuse the API.\n> \n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n\nYup, this looks good to me.\n\n-Peff\n"},{"id":"348175","messageId":"20180521023913.GB16236@sigill.intra.peff.net","threadId":"47635","inReplyTo":"xmqqr2m53leq.fsf@gitster-ct.c.googlers.com","subject":"Re: Re*: [PATCH v4 3/4] string-list: provide `string_list_appendf()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-21T02:39:14Z","receivedAt":"2018-05-21T02:39:18Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 21, 2018 at 09:25:01AM +0900, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > I have a feeling that argv_array might be a better fit for the\n> > purpose of keeping track of to_free[] strings in the context of this\n> > series.  Moving away from string_list would allow us to sidestep the\n> > storage ownership issues the API has, and we do not need the .util\n> > thing string_list gives us (which is one distinct advantage string_list\n> > has over argv_array, if the application needs that feature).\n> >\n> > We would need to make _pushf() and friends return \"const char *\" if\n> > we go that route to make the resulting API more useful, though.\n> \n> ... and redoing the 4/4 patch using argv_array_pushf() makes the\n> result look like this, which does not look too bad.\n\nAgreed.\n\n-Peff\n"},{"id":"348205","messageId":"cover.1526913784.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"xmqqr2m53leq.fsf@gitster-ct.c.googlers.com","subject":"[PATCH v5 0/4] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-21T14:54:24Z","receivedAt":"2018-05-21T14:54:57Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 21 May 2018 at 02:25, Junio C Hamano <gitster@pobox.com> wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> I have a feeling that argv_array might be a better fit for the\n>> purpose of keeping track of to_free[] strings in the context of this\n>> series.  Moving away from string_list would allow us to sidestep the\n>> storage ownership issues the API has, and we do not need the .util\n>> thing string_list gives us (which is one distinct advantage string_list\n>> has over argv_array, if the application needs that feature).\n>>\n>> We would need to make _pushf() and friends return \"const char *\" if\n>> we go that route to make the resulting API more useful, though.\n>\n> ... and redoing the 4/4 patch using argv_array_pushf() makes the\n> result look like this, which does not look too bad.\n\nThanks to Jacob, Junio and Peff for comments on the previous iteration.\n\nI've taken the six patches that Junio has queued and rebuilt the series\nto get rid of the new and possibly bug-prone function that no-one uses\nonce the series is over.\n\nThat is, I've replaced the `string_list_appendf()`-patch with Junio's\n`argv_push*()`-patch, then squashed Junio's \"redoing the 4/4\"-patch into\npatch 4/4 -- with the exception of keeping the `memset(opts->msgs, ...)`\nwhich I suspect was mistakenly dropped.\n\nAgain, thanks for all the helpful comments and patches pointing me in\nthe right direction.\n\nMartin\n\nElijah Newren (1):\n  merge-recursive: provide pair of `unpack_trees_{start,finish}()`\n\nJunio C Hamano (1):\n  argv-array: return the pushed string from argv_push*()\n\nMartin Ågren (2):\n  merge: setup `opts` later in `checkout_fast_forward()`\n  unpack_trees_options: free messages when done\n\n argv-array.h       |  4 ++--\n unpack-trees.h     |  8 +++++++-\n argv-array.c       |  6 ++++--\n builtin/checkout.c |  1 +\n merge-recursive.c  | 30 ++++++++++++++++--------------\n merge.c            | 35 ++++++++++++++++++++---------------\n unpack-trees.c     | 17 ++++++++++++++---\n 7 files changed, 64 insertions(+), 37 deletions(-)\n\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348206","messageId":"89e653da5b1bee9cf645e9d4bdd95f6bb31cc4b9.1526913784.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526913784.git.martin.agren@gmail.com","subject":"[PATCH v5 1/4] merge: setup `opts` later in `checkout_fast_forward()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-21T14:54:25Z","receivedAt":"2018-05-21T14:55:05Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"After we initialize the various fields in `opts` but before we actually\nuse them, we might return early. Move the initialization further down,\nto immediately before we use `opts`.\n\nThis limits the scope of `opts` and will help a later commit fix a\nmemory leak without having to worry about those early returns.\n\nThis patch is best viewed using something like this (note the tab!):\n--color-moved --anchored=\"\ttrees[nr_trees] = parse_tree_indirect\"\n\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n merge.c | 32 +++++++++++++++++---------------\n 1 file changed, 17 insertions(+), 15 deletions(-)\n\ndiff --git a/merge.c b/merge.c\nindex f06a4773d4..f123658e58 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -94,8 +94,24 @@ int checkout_fast_forward(const struct object_id *head,\n \t\treturn -1;\n \n \tmemset(&trees, 0, sizeof(trees));\n-\tmemset(&opts, 0, sizeof(opts));\n \tmemset(&t, 0, sizeof(t));\n+\n+\ttrees[nr_trees] = parse_tree_indirect(head);\n+\tif (!trees[nr_trees++]) {\n+\t\trollback_lock_file(&lock_file);\n+\t\treturn -1;\n+\t}\n+\ttrees[nr_trees] = parse_tree_indirect(remote);\n+\tif (!trees[nr_trees++]) {\n+\t\trollback_lock_file(&lock_file);\n+\t\treturn -1;\n+\t}\n+\tfor (i = 0; i < nr_trees; i++) {\n+\t\tparse_tree(trees[i]);\n+\t\tinit_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n+\t}\n+\n+\tmemset(&opts, 0, sizeof(opts));\n \tif (overwrite_ignore) {\n \t\tmemset(&dir, 0, sizeof(dir));\n \t\tdir.flags |= DIR_SHOW_IGNORED;\n@@ -112,20 +128,6 @@ int checkout_fast_forward(const struct object_id *head,\n \topts.fn = twoway_merge;\n \tsetup_unpack_trees_porcelain(&opts, \"merge\");\n \n-\ttrees[nr_trees] = parse_tree_indirect(head);\n-\tif (!trees[nr_trees++]) {\n-\t\trollback_lock_file(&lock_file);\n-\t\treturn -1;\n-\t}\n-\ttrees[nr_trees] = parse_tree_indirect(remote);\n-\tif (!trees[nr_trees++]) {\n-\t\trollback_lock_file(&lock_file);\n-\t\treturn -1;\n-\t}\n-\tfor (i = 0; i < nr_trees; i++) {\n-\t\tparse_tree(trees[i]);\n-\t\tinit_tree_desc(t+i, trees[i]->buffer, trees[i]->size);\n-\t}\n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n \t\treturn -1;\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348207","messageId":"3f1c1c360080114fcc9492211601f41d56112e3c.1526913784.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526913784.git.martin.agren@gmail.com","subject":"[PATCH v5 2/4] merge-recursive: provide pair of `unpack_trees_{start,finish}()`","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-21T14:54:26Z","receivedAt":"2018-05-21T14:55:10Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"From: Elijah Newren <newren@gmail.com>\n\nRename `git_merge_trees()` to `unpack_trees_start()` and extract the\ncall to `discard_index()` into a new function `unpack_trees_finish()`.\nAs a result, these are called early resp. late in `merge_trees()`,\nmaking the resource handling clearer. A later commit will expand on\nthat, teaching `..._finish()` to free more memory. (So rather than\nmoving the FIXME-comment, just drop it, since it will be addressed soon\nenough.)\n\nAlso call `..._finish()` when `merge_trees()` returns early.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n merge-recursive.c | 29 +++++++++++++++--------------\n 1 file changed, 15 insertions(+), 14 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 680e01226b..ddb0fa7369 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -337,10 +337,10 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n \tinit_tree_desc(desc, tree->buffer, tree->size);\n }\n \n-static int git_merge_trees(struct merge_options *o,\n-\t\t\t   struct tree *common,\n-\t\t\t   struct tree *head,\n-\t\t\t   struct tree *merge)\n+static int unpack_trees_start(struct merge_options *o,\n+\t\t\t      struct tree *common,\n+\t\t\t      struct tree *head,\n+\t\t\t      struct tree *merge)\n {\n \tint rc;\n \tstruct tree_desc t[3];\n@@ -379,6 +379,11 @@ static int git_merge_trees(struct merge_options *o,\n \treturn rc;\n }\n \n+static void unpack_trees_finish(struct merge_options *o)\n+{\n+\tdiscard_index(&o->orig_index);\n+}\n+\n struct tree *write_tree_from_memory(struct merge_options *o)\n {\n \tstruct tree *result = NULL;\n@@ -3088,13 +3093,14 @@ int merge_trees(struct merge_options *o,\n \t\treturn 1;\n \t}\n \n-\tcode = git_merge_trees(o, common, head, merge);\n+\tcode = unpack_trees_start(o, common, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(o, 4) || o->call_depth)\n \t\t\terr(o, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n+\t\tunpack_trees_finish(o);\n \t\treturn -1;\n \t}\n \n@@ -3147,20 +3153,15 @@ int merge_trees(struct merge_options *o,\n \n \t\thashmap_free(&o->current_file_dir_set, 1);\n \n-\t\tif (clean < 0)\n+\t\tif (clean < 0) {\n+\t\t\tunpack_trees_finish(o);\n \t\t\treturn clean;\n+\t\t}\n \t}\n \telse\n \t\tclean = 1;\n \n-\t/* Free the extra index left from git_merge_trees() */\n-\t/*\n-\t * FIXME: Need to also free data allocated by\n-\t * setup_unpack_trees_porcelain() tucked away in o->unpack_opts.msgs,\n-\t * but the problem is that only half of it refers to dynamically\n-\t * allocated data, while the other half points at static strings.\n-\t */\n-\tdiscard_index(&o->orig_index);\n+\tunpack_trees_finish(o);\n \n \tif (o->call_depth && !(*result = write_tree_from_memory(o)))\n \t\treturn -1;\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348208","messageId":"ae7e8194c258c6fac3b5478ad914fa0287f7b62d.1526913784.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526913784.git.martin.agren@gmail.com","subject":"[PATCH v5 3/4] argv-array: return the pushed string from argv_push*()","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-21T14:54:27Z","receivedAt":"2018-05-21T14:55:14Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSuch an API change allows us to use an argv_array this way:\n\n\tstruct argv_array to_free = ARGV_ARRAY_INIT;\n        const char *msg;\n\n        if (some condition) {\n\t\tmsg = \"constant string message\";\n\t\t... other logic ...\n\t} else {\n\t\tmsg = argv_pushf(&to_free, \"format %s\", var);\n\t}\n\t... use \"msg\" ...\n\t... do other things ...\n\targv_clear(&to_free);\n\nNote that argv_array_pushl() and argv_array_pushv() are used to push\none or more strings with a single call, so we do not return any one\nof these strings from these two functions in order to reduce the\nchance to misuse the API.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n argv-array.h | 4 ++--\n argv-array.c | 6 ++++--\n 2 files changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/argv-array.h b/argv-array.h\nindex 29056e49a1..715c93b246 100644\n--- a/argv-array.h\n+++ b/argv-array.h\n@@ -12,9 +12,9 @@ struct argv_array {\n #define ARGV_ARRAY_INIT { empty_argv, 0, 0 }\n \n void argv_array_init(struct argv_array *);\n-void argv_array_push(struct argv_array *, const char *);\n+const char *argv_array_push(struct argv_array *, const char *);\n __attribute__((format (printf,2,3)))\n-void argv_array_pushf(struct argv_array *, const char *fmt, ...);\n+const char *argv_array_pushf(struct argv_array *, const char *fmt, ...);\n LAST_ARG_MUST_BE_NULL\n void argv_array_pushl(struct argv_array *, ...);\n void argv_array_pushv(struct argv_array *, const char **);\ndiff --git a/argv-array.c b/argv-array.c\nindex 5d370fa336..449dfc105a 100644\n--- a/argv-array.c\n+++ b/argv-array.c\n@@ -21,12 +21,13 @@ static void argv_array_push_nodup(struct argv_array *array, const char *value)\n \tarray->argv[array->argc] = NULL;\n }\n \n-void argv_array_push(struct argv_array *array, const char *value)\n+const char *argv_array_push(struct argv_array *array, const char *value)\n {\n \targv_array_push_nodup(array, xstrdup(value));\n+\treturn array->argv[array->argc - 1];\n }\n \n-void argv_array_pushf(struct argv_array *array, const char *fmt, ...)\n+const char *argv_array_pushf(struct argv_array *array, const char *fmt, ...)\n {\n \tva_list ap;\n \tstruct strbuf v = STRBUF_INIT;\n@@ -36,6 +37,7 @@ void argv_array_pushf(struct argv_array *array, const char *fmt, ...)\n \tva_end(ap);\n \n \targv_array_push_nodup(array, strbuf_detach(&v, NULL));\n+\treturn array->argv[array->argc - 1];\n }\n \n void argv_array_pushl(struct argv_array *array, ...)\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348209","messageId":"18a37ca30fe644ebd635807aba1513992220ebd6.1526913784.git.martin.agren@gmail.com","threadId":"47635","inReplyTo":"cover.1526913784.git.martin.agren@gmail.com","subject":"[PATCH v5 4/4] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-21T14:54:28Z","receivedAt":"2018-05-21T14:55:17Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"The strings allocated in `setup_unpack_trees_porcelain()` are never\nfreed. Provide a function `clear_unpack_trees_porcelain()` to do so and\ncall it where we use `setup_unpack_trees_porcelain()`. The only\nnon-trivial user is `unpack_trees_start()`, where we should place the\nnew call in `unpack_trees_finish()`.\n\nWe keep the string pointers in an array, mixing pointers to static\nmemory and memory that we allocate on the heap. We also keep several\ncopies of the individual pointers. So we need to make sure that we do\nnot free what we must not free and that we do not double-free. Let a\nseparate argv_array take ownership of all the strings we create so that\nwe can easily free them.\n\nZero the whole array of string pointers to make sure that we do not\nleave any dangling pointers.\n\nNote that we only take responsibility for the memory allocated in\n`setup_unpack_trees_porcelain()` and not any other members of the\n`struct unpack_trees_options`.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Martin Ågren <martin.agren@gmail.com>\n---\n unpack-trees.h     |  8 +++++++-\n builtin/checkout.c |  1 +\n merge-recursive.c  |  1 +\n merge.c            |  3 +++\n unpack-trees.c     | 17 ++++++++++++++---\n 5 files changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 41178ada94..c2b434c606 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -2,7 +2,7 @@\n #define UNPACK_TREES_H\n \n #include \"tree-walk.h\"\n-#include \"string-list.h\"\n+#include \"argv-array.h\"\n \n #define MAX_UNPACK_TREES 8\n \n@@ -33,6 +33,11 @@ enum unpack_trees_error_types {\n void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t\t  const char *cmd);\n \n+/*\n+ * Frees resources allocated by setup_unpack_trees_porcelain().\n+ */\n+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);\n+\n struct unpack_trees_options {\n \tunsigned int reset,\n \t\t     merge,\n@@ -57,6 +62,7 @@ struct unpack_trees_options {\n \tstruct pathspec *pathspec;\n \tmerge_fn_t fn;\n \tconst char *msgs[NB_UNPACK_TREES_ERROR_TYPES];\n+\tstruct argv_array msgs_to_free;\n \t/*\n \t * Store error messages in an array, each case\n \t * corresponding to a error message type\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b49b582071..5cebe170fc 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -526,6 +526,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tinit_tree_desc(&trees[1], tree->buffer, tree->size);\n \n \t\tret = unpack_trees(2, trees, &topts);\n+\t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n \t\t\t/*\n \t\t\t * Unpack couldn't do a trivial merge; either\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ddb0fa7369..338f63a952 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -382,6 +382,7 @@ static int unpack_trees_start(struct merge_options *o,\n static void unpack_trees_finish(struct merge_options *o)\n {\n \tdiscard_index(&o->orig_index);\n+\tclear_unpack_trees_porcelain(&o->unpack_opts);\n }\n \n struct tree *write_tree_from_memory(struct merge_options *o)\ndiff --git a/merge.c b/merge.c\nindex f123658e58..b433291d0c 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -130,8 +130,11 @@ int checkout_fast_forward(const struct object_id *head,\n \n \tif (unpack_trees(nr_trees, t, &opts)) {\n \t\trollback_lock_file(&lock_file);\n+\t\tclear_unpack_trees_porcelain(&opts);\n \t\treturn -1;\n \t}\n+\tclear_unpack_trees_porcelain(&opts);\n+\n \tif (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 79fd97074e..73a6dc1701 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1,5 +1,6 @@\n #define NO_THE_INDEX_COMPATIBILITY_MACROS\n #include \"cache.h\"\n+#include \"argv-array.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"dir.h\"\n@@ -103,6 +104,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \tconst char **msgs = opts->msgs;\n \tconst char *msg;\n \n+\targv_array_init(&opts->msgs_to_free);\n+\n \tif (!strcmp(cmd, \"checkout\"))\n \t\tmsg = advice_commit_before_merge\n \t\t      ? _(\"Your local changes to the following files would be overwritten by checkout:\\n%%s\"\n@@ -119,7 +122,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t\t  \"Please commit your changes or stash them before you %s.\")\n \t\t      : _(\"Your local changes to the following files would be overwritten by %s:\\n%%s\");\n \tmsgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =\n-\t\txstrfmt(msg, cmd, cmd);\n+\t\targv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);\n \n \tmsgs[ERROR_NOT_UPTODATE_DIR] =\n \t\t_(\"Updating the following directories would lose untracked files in them:\\n%s\");\n@@ -139,7 +142,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t      ? _(\"The following untracked working tree files would be removed by %s:\\n%%s\"\n \t\t\t  \"Please move or remove them before you %s.\")\n \t\t      : _(\"The following untracked working tree files would be removed by %s:\\n%%s\");\n-\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, cmd, cmd);\n+\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] =\n+\t\targv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);\n \n \tif (!strcmp(cmd, \"checkout\"))\n \t\tmsg = advice_commit_before_merge\n@@ -156,7 +160,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\t      ? _(\"The following untracked working tree files would be overwritten by %s:\\n%%s\"\n \t\t\t  \"Please move or remove them before you %s.\")\n \t\t      : _(\"The following untracked working tree files would be overwritten by %s:\\n%%s\");\n-\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, cmd, cmd);\n+\tmsgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] =\n+\t\targv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);\n \n \t/*\n \t * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we\n@@ -179,6 +184,12 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \t\topts->unpack_rejects[i].strdup_strings = 1;\n }\n \n+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n+{\n+\targv_array_clear(&opts->msgs_to_free);\n+\tmemset(opts->msgs, 0, sizeof(opts->msgs));\n+}\n+\n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n \t\t\t unsigned int set, unsigned int clear)\n {\n-- \n2.17.0.840.g5d83f92caf\n\n"},{"id":"348248","messageId":"20180521213804.GA16285@sigill.intra.peff.net","threadId":"47635","inReplyTo":"cover.1526913784.git.martin.agren@gmail.com","subject":"Re: [PATCH v5 0/4] unpack_trees_options: free messages when done","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-21T21:38:04Z","receivedAt":"2018-05-21T21:38:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 21, 2018 at 04:54:24PM +0200, Martin Ågren wrote:\n\n> That is, I've replaced the `string_list_appendf()`-patch with Junio's\n> `argv_push*()`-patch, then squashed Junio's \"redoing the 4/4\"-patch into\n> patch 4/4 -- with the exception of keeping the `memset(opts->msgs, ...)`\n> which I suspect was mistakenly dropped.\n> \n> Again, thanks for all the helpful comments and patches pointing me in\n> the right direction.\n\nI like it. Thanks for seeing it through. That was a lot of\nback-and-forth for a small cleanup, but I hope we've established a\npattern that can be used elsewhere.\n\n-Peff\n"},{"id":"348280","messageId":"xmqqa7ssz9u1.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"cover.1526913784.git.martin.agren@gmail.com","subject":"Re: [PATCH v5 0/4] unpack_trees_options: free messages when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-22T02:46:14Z","receivedAt":"2018-05-22T02:46:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Martin Ågren <martin.agren@gmail.com> writes:\n\n> I've taken the six patches that Junio has queued and rebuilt the series\n> to get rid of the new and possibly bug-prone function that no-one uses\n> once the series is over.\n\nHmph, this unfortunately depends on 'next', which means we cannot\nmerge it down to 'maint' later to fix these leaks.  I guess it is\nnot a huge deal, though.  We've lived with these message leaks for\nquite some time now and earth still kept rotating ;-)\n\n"},{"id":"348282","messageId":"xmqq1se4z9fu.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"xmqqa7ssz9u1.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 0/4] unpack_trees_options: free messages when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-22T02:54:45Z","receivedAt":"2018-05-22T02:54:50Z","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> Martin Ågren <martin.agren@gmail.com> writes:\n>\n>> I've taken the six patches that Junio has queued and rebuilt the series\n>> to get rid of the new and possibly bug-prone function that no-one uses\n>> once the series is over.\n>\n> Hmph, this unfortunately depends on 'next', which means we cannot\n> merge it down to 'maint' later to fix these leaks.  I guess it is\n> not a huge deal, though.  We've lived with these message leaks for\n> quite some time now and earth still kept rotating ;-)\n\nOh, what was I thinking.  This, just like its previous rounds, is on\ntop of bp/merge-rename-config^0 and it is expected *not* to be\nmergeable to 'maint' (or 'master', for that matter, at least not\nyet).\n\nWill queue.  Thanks.\n"},{"id":"348295","messageId":"CAN0heSr0uZvaRqjNmD5=8RLpcLiWHjG2OoZovYK7TkGkCKu=7g@mail.gmail.com","threadId":"47635","inReplyTo":"xmqq1se4z9fu.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 0/4] unpack_trees_options: free messages when done","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-22T11:11:20Z","receivedAt":"2018-05-22T11:11:24Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 22 May 2018 at 04:54, Junio C Hamano <gitster@pobox.com> wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n\n>> Hmph, this unfortunately depends on 'next', which means we cannot\n>> merge it down to 'maint' later to fix these leaks.  I guess it is\n>> not a huge deal, though.  We've lived with these message leaks for\n>> quite some time now and earth still kept rotating ;-)\n>\n> Oh, what was I thinking.  This, just like its previous rounds, is on\n> top of bp/merge-rename-config^0 and it is expected *not* to be\n> mergeable to 'maint' (or 'master', for that matter, at least not\n> yet).\n\nRight. The reason it depends on that topic is the user in\nmerge-recursive.c. Other than patch 2 and a small part of patch 4, this\nshould be mergeable to 'master' (as I recall) and probably also to\n'maint'. I suppose this series could have been done as three patches to\nfix all users except one, then one or two patches to fix\nmerge-recursive.c.\n\nThat would have allowed merging the first part of the series to 'maint'.\n(Maybe not to fix the leaking as such, but to keep 'maint' more up to\ndate with 'master' for easier merging of other topics?) If you'd prefer\nan ordering like that (now and/or in the future), just let me know.\n\nMartin\n"},{"id":"348339","messageId":"xmqq7envxkms.fsf@gitster-ct.c.googlers.com","threadId":"47635","inReplyTo":"CAN0heSr0uZvaRqjNmD5=8RLpcLiWHjG2OoZovYK7TkGkCKu=7g@mail.gmail.com","subject":"Re: [PATCH v5 0/4] unpack_trees_options: free messages when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-23T00:48:11Z","receivedAt":"2018-05-23T00:48:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Martin Ågren <martin.agren@gmail.com> writes:\n\n> (Maybe not to fix the leaking as such, but to keep 'maint' more up to\n> date with 'master' for easier merging of other topics?)\n\nI admit that I occasionally do such a \"presumably no-op\" merge to\n'maint' out of sheer laziness, but in general I'd prefer to keep\n'maint' quieter than that.  As we do not have any repeated caller\nthat calls unpack_trees unbounded number of times in a process, I\nthink it is OK to limit this update only to 'master', especially at\nthis late in the cycle.\n\nThanks.\n"},{"id":"348541","messageId":"87zi0nvmvx.fsf@javad.com","threadId":"47635","inReplyTo":"cover.1524659287.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v9 00/17] rebase -i: offer to recreate commit topology by rebasing merges","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-05-25T14:19:14Z","receivedAt":"2018-05-25T14:19:21Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"\nJohannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Junio, I think this is now ready for `next`. Thank you for your patience\n> and help with this.\n>\n> Once upon a time, I dreamed of an interactive rebase that would not\n> linearize all patches and drop all merge commits, but instead recreate\n> the commit topology faithfully.\n>\n> My original attempt was --preserve-merges, but that design was so\n> limited that I did not even enable it in interactive mode.\n>\n> Subsequently, it *was* enabled in interactive mode, with the predictable\n> consequences: as the --preserve-merges design does not allow for\n> specifying the parents of merge commits explicitly, all the new commits'\n> parents are defined *implicitly* by the previous commit history, and\n> hence it is *not possible to even reorder commits*.\n>\n> This design flaw cannot be fixed. Not without a complete re-design, at\n> least. This patch series offers such a re-design.\n>\n> Think of --rebase-merges as \"--preserve-merges done right\". It\n> introduces new verbs for the todo list, `label`, `reset` and `merge`.\n> For a commit topology like this:\n>\n>             A - B - C\n>               \\   /\n>                 D\n>\n> the generated todo list would look like this:\n>\n>             # branch D\n>             pick 0123 A\n>             label branch-point\n>             pick 1234 D\n>             label D\n>\n>             reset branch-point\n>             pick 2345 B\n>             merge -C 3456 D # C\n>\n> There are more patches in the pipeline, based on this patch series, but\n> left for later in the interest of reviewable patch series: one mini\n> series to use the sequencer even for `git rebase -i --root`, and another\n> one to add support for octopus merges to --rebase-merges. And then one\n> to allow for rebasing merge commits in a smarter way (this one will need\n> a bit more work, though, as it can result in very complicated, nested\n> merge conflicts *very* easily).\n>\n> Changes since v8:\n>\n> - Disentangled the patch introducing `label`/`reset` from the one\n>   introducing `merge` again (this was one stupid, tired `git commit\n>   --amend` too many).\n>\n> - Augmented the commit message of \"introduce the `merge` command\" to\n>   describe what the `label onto` is all about.\n>\n> - Fixed the error message when `reset` would overwrite untracked files to\n>   actually say that a \"reset\" failed (not a \"merge\").\n>\n> - Clarified the rationale for `label onto` in the commit message of\n>   \"rebase-helper --make-script: introduce a flag to rebase merges\".\n>\n> - Edited the description of `--rebase-merges` heavily, for clarity, in\n>   \"rebase: introduce the --rebase-merges option\".\n>\n> - Edited the commit message of (and the documentation introduced by) \" rebase\n>   -i: introduce --rebase-merges=[no-]rebase-cousins\" for clarity (also\n>   mentioning the `--ancestry-path` option).\n>\n> - When run_git_commit() fails after a successful merge, we now take pains\n>   not to reschedule the `merge` command.\n>\n> - Rebased the patch series on top of current `master`, i.e. both\n>   `pw/rebase-keep-empty-fixes` and `pw/rebase-signoff`, to resolve merge\n>   conflicts myself.\n>\n>\n> Johannes Schindelin (15):\n>   sequencer: avoid using errno clobbered by rollback_lock_file()\n>   sequencer: make rearrange_squash() a bit more obvious\n>   sequencer: refactor how original todo list lines are accessed\n>   sequencer: offer helpful advice when a command was rescheduled\n>   sequencer: introduce new commands to reset the revision\n>   sequencer: introduce the `merge` command\n>   sequencer: fast-forward `merge` commands, if possible\n>   rebase-helper --make-script: introduce a flag to rebase merges\n>   rebase: introduce the --rebase-merges option\n>   sequencer: make refs generated by the `label` command worktree-local\n>   sequencer: handle post-rewrite for merge commands\n>   rebase --rebase-merges: avoid \"empty merges\"\n>   pull: accept --rebase=merges to recreate the branch topology\n>   rebase -i: introduce --rebase-merges=[no-]rebase-cousins\n>   rebase -i --rebase-merges: add a section to the man page\n>\n> Phillip Wood (1):\n>   rebase --rebase-merges: add test for --keep-empty\n>\n> Stefan Beller (1):\n>   git-rebase--interactive: clarify arguments\n>\n>  Documentation/config.txt               |   8 +\n>  Documentation/git-pull.txt             |   6 +-\n>  Documentation/git-rebase.txt           | 163 ++++-\n>  builtin/pull.c                         |  14 +-\n>  builtin/rebase--helper.c               |  13 +-\n>  builtin/remote.c                       |  18 +-\n>  contrib/completion/git-completion.bash |   4 +-\n>  git-rebase--interactive.sh             |  22 +-\n>  git-rebase.sh                          |  16 +\n>  refs.c                                 |   3 +-\n>  sequencer.c                            | 892 ++++++++++++++++++++++++-\n>  sequencer.h                            |   7 +\n>  t/t3421-rebase-topology-linear.sh      |   1 +\n>  t/t3430-rebase-merges.sh               | 244 +++++++\n>  14 files changed, 1352 insertions(+), 59 deletions(-)\n>  create mode 100755 t/t3430-rebase-merges.sh\n>\n>\n> base-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d\n> Published-As: https://github.com/dscho/git/releases/tag/recreate-merges-v9\n> Fetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v9\n>\n> Interdiff vs v8:\n>  diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n>  index e691b93e920..bd5ecff980e 100644\n>  --- a/Documentation/git-rebase.txt\n>  +++ b/Documentation/git-rebase.txt\n>  @@ -381,21 +381,24 @@ have the long commit hash prepended to the format.\n>   \n>   -r::\n>   --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n>  -\tBy default, a rebase will simply drop merge commits and only rebase\n>  -\tthe non-merge commits. With this option, it will try to preserve\n>  +\tBy default, a rebase will simply drop merge commits from the todo\n>  +\tlist, and put the rebased commits into a single, linear branch.\n>  +\tWith `--rebase-merges`, the rebase will instead try to preserve\n>   \tthe branching structure within the commits that are to be rebased,\n>  -\tby recreating the merge commits. If a merge commit resolved any merge\n>  -\tor contained manual amendments, then they will have to be re-applied\n>  -\tmanually.\n>  +\tby recreating the merge commits. Any resolved merge conflicts or\n>  +\tmanual amendments in these merge commits will have to be\n>  +\tresolved/re-applied manually.\n>   +\n>   By default, or when `no-rebase-cousins` was specified, commits which do not\n>  -have `<upstream>` as direct ancestor will keep their original branch point.\n>  -If the `rebase-cousins` mode is turned on, such commits are instead rebased\n>  +have `<upstream>` as direct ancestor will keep their original branch point,\n>  +i.e. commits that would be excluded by gitlink:git-log[1]'s\n>  +`--ancestry-path` option will keep their original ancestry by default. If\n>  +the `rebase-cousins` mode is turned on, such commits are instead rebased\n>   onto `<upstream>` (or `<onto>`, if specified).\n>   +\n>  -This mode is similar in spirit to `--preserve-merges`, but in contrast to\n>  -that option works well in interactive rebases: commits can be reordered,\n>  -inserted and dropped at will.\n>  +The `--rebase-merges` mode is similar in spirit to `--preserve-merges`, but\n>  +in contrast to that option works well in interactive rebases: commits can be\n>  +reordered, inserted and dropped at will.\n>   +\n>   It is currently only possible to recreate the merge commits using the\n>   `recursive` merge strategy; Different merge strategies can be used only via\n>  diff --git a/sequencer.c b/sequencer.c\n>  index b5715f69450..e2f83942843 100644\n>  --- a/sequencer.c\n>  +++ b/sequencer.c\n>  @@ -2635,6 +2635,7 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)\n>   \t}\n>   \n>   \tmemset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));\n>  +\tsetup_unpack_trees_porcelain(&unpack_tree_opts, \"reset\");\n>   \tunpack_tree_opts.head_idx = 1;\n>   \tunpack_tree_opts.src_index = &the_index;\n>   \tunpack_tree_opts.dst_index = &the_index;\n>  @@ -2855,7 +2856,12 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,\n>   \tif (ret)\n>   \t\trerere(opts->allow_rerere_auto);\n>   \telse\n>  -\t\tret = run_git_commit(git_path_merge_msg(), opts,\n>  +\t\t/*\n>  +\t\t * In case of problems, we now want to return a positive\n>  +\t\t * value (a negative one would indicate that the `merge`\n>  +\t\t * command needs to be rescheduled).\n>  +\t\t */\n>  +\t\tret = !!run_git_commit(git_path_merge_msg(), opts,\n>   \t\t\t\t     run_commit_flags);\n>   \n>   leave_merge:\n>  @@ -3809,12 +3815,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,\n>   \n>   \tinit_revisions(&revs, NULL);\n>   \trevs.verbose_header = 1;\n>  -\tif (rebase_merges)\n>  -\t\trevs.cherry_mark = 1;\n>  -\telse {\n>  +\tif (!rebase_merges)\n>   \t\trevs.max_parents = 1;\n>  -\t\trevs.cherry_pick = 1;\n>  -\t}\n>  +\trevs.cherry_mark = 1;\n>   \trevs.limited = 1;\n>   \trevs.reverse = 1;\n>   \trevs.right_only = 1;\n"},{"id":"348544","messageId":"87vabbu4d7.fsf@javad.com","threadId":"47635","inReplyTo":"87zi0nvmvx.fsf@javad.com","subject":"Re: [PATCH v9 00/17] rebase -i: offer to recreate commit topology by rebasing merges","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2018-05-25T15:44:36Z","receivedAt":"2018-05-25T15:44:43Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"\nThis has been sent by mistake, I'm sorry, please disregard.\n\nSergey Organov <sorganov@gmail.com> writes:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>\n>> Junio, I think this is now ready for `next`. Thank you for your patience\n>> and help with this.\n\n[...]\n\n"},{"id":"359414","messageId":"nycvar.QRO.7.76.6.1810021546500.2034@tvgsbejvaqbjf.bet","threadId":"47635","inReplyTo":"4bd08893-52b4-3a01-d546-4884e2762670@talktalk.net","subject":"Re: [PATCH v4 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-10-02T13:50:48Z","receivedAt":"2018-10-02T13:51:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\n[sorry, I just got to this mail now]\n\nOn Sun, 6 May 2018, Phillip Wood wrote:\n\n> On 27/04/18 21:48, Johannes Schindelin wrote:\n> > \n> > During a series of fixup/squash commands, the interactive rebase builds\n> > up a commit message with comments. This will be presented to the user in\n> > the editor if at least one of those commands was a `squash`.\n> > \n> > In any case, the commit message will be cleaned up eventually, removing\n> > all those intermediate comments, in the final step of such a\n> > fixup/squash chain.\n> > \n> > However, if the last fixup/squash command in such a chain fails with\n> > merge conflicts, and if the user then decides to skip it (or resolve it\n> > to a clean worktree and then continue the rebase), the current code\n> > fails to clean up the commit message.\n> > \n> > This commit fixes that behavior.\n> > \n> > The fix is quite a bit more involved than meets the eye because it is\n> > not only about the question whether we are `git rebase --skip`ing a\n> > fixup or squash. It is also about removing the skipped fixup/squash's\n> > commit message from the accumulated commit message. And it is also about\n> > the question whether we should let the user edit the final commit\n> > message or not (\"Was there a squash in the chain *that was not\n> > skipped*?\").\n> > \n> > For example, in this case we will want to fix the commit message, but\n> > not open it in an editor:\n> > \n> > \tpick\t<- succeeds\n> > \tfixup\t<- succeeds\n> > \tsquash\t<- fails, will be skipped\n> > \n> > This is where the newly-introduced `current-fixups` file comes in real\n> > handy. A quick look and we can determine whether there was a non-skipped\n> > squash. We only need to make sure to keep it up to date with respect to\n> > skipped fixup/squash commands. As a bonus, we can even avoid committing\n> > unnecessarily, e.g. when there was only one fixup, and it failed, and\n> > was skipped.\n> > \n> > To fix only the bug where the final commit message was not cleaned up\n> > properly, but without fixing the rest, would have been more complicated\n> > than fixing it all in one go, hence this commit lumps together more than\n> > a single concern.\n> > \n> > For the same reason, this commit also adds a bit more to the existing\n> > test case for the regression we just fixed.\n> > \n> > The diff is best viewed with --color-moved.\n> > \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  sequencer.c                | 113 ++++++++++++++++++++++++++++++++-----\n> >  t/t3418-rebase-continue.sh |  35 ++++++++++--\n> >  2 files changed, 131 insertions(+), 17 deletions(-)\n> > \n> > diff --git a/sequencer.c b/sequencer.c\n> > index 56166b0d6c7..cec180714ef 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > [...]\n> > @@ -2804,19 +2801,107 @@ static int commit_staged_changes(struct replay_opts *opts)\n> >  \t\tif (get_oid_hex(rev.buf, &to_amend))\n> >  \t\t\treturn error(_(\"invalid contents: '%s'\"),\n> >  \t\t\t\trebase_path_amend());\n> > -\t\tif (oidcmp(&head, &to_amend))\n> > +\t\tif (!is_clean && oidcmp(&head, &to_amend))\n> >  \t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n> >  \t\t\t\t       \"working tree. Please, commit them\\n\"\n> >  \t\t\t\t       \"first and then run 'git rebase \"\n> >  \t\t\t\t       \"--continue' again.\"));\n> > +\t\t/*\n> > +\t\t * When skipping a failed fixup/squash, we need to edit the\n> > +\t\t * commit message, the current fixup list and count, and if it\n> > +\t\t * was the last fixup/squash in the chain, we need to clean up\n> > +\t\t * the commit message and if there was a squash, let the user\n> > +\t\t * edit it.\n> > +\t\t */\n> > +\t\tif (is_clean && !oidcmp(&head, &to_amend) &&\n> > +\t\t    opts->current_fixup_count > 0 &&\n> > +\t\t    file_exists(rebase_path_stopped_sha())) {\n> > +\t\t\tconst char *p = opts->current_fixups.buf;\n> > +\t\t\tint len = opts->current_fixups.len;\n> > +\n> > +\t\t\topts->current_fixup_count--;\n> > +\t\t\tif (!len)\n> > +\t\t\t\tBUG(\"Incorrect current_fixups:\\n%s\", p);\n> > +\t\t\twhile (len && p[len - 1] != '\\n')\n> > +\t\t\t\tlen--;\n> > +\t\t\tstrbuf_setlen(&opts->current_fixups, len);\n> > +\t\t\tif (write_message(p, len, rebase_path_current_fixups(),\n> > +\t\t\t\t\t  0) < 0)\n> > +\t\t\t\treturn error(_(\"could not write file: '%s'\"),\n> > +\t\t\t\t\t     rebase_path_current_fixups());\n> > +\n> > +\t\t\t/*\n> > +\t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n> > +\t\t\t * commit message is already correct, no need to commit\n> > +\t\t\t * it again.\n> > +\t\t\t *\n> > +\t\t\t * Only if it is the final command in the fixup/squash\n> > +\t\t\t * chain, and only if the chain is longer than a single\n> > +\t\t\t * fixup/squash command (which was just skipped), do we\n> > +\t\t\t * actually need to re-commit with a cleaned up commit\n> > +\t\t\t * message.\n> > +\t\t\t */\n> > +\t\t\tif (opts->current_fixup_count > 0 &&\n> > +\t\t\t    !is_fixup(peek_command(todo_list, 0))) {\n> > +\t\t\t\tfinal_fixup = 1;\n> > +\t\t\t\t/*\n> > +\t\t\t\t * If there was not a single \"squash\" in the\n> > +\t\t\t\t * chain, we only need to clean up the commit\n> > +\t\t\t\t * message, no need to bother the user with\n> > +\t\t\t\t * opening the commit message in the editor.\n> > +\t\t\t\t */\n> > +\t\t\t\tif (!starts_with(p, \"squash \") &&\n> > +\t\t\t\t    !strstr(p, \"\\nsquash \"))\n> > +\t\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n> > +\t\t\t} else if (is_fixup(peek_command(todo_list, 0))) {\n> > +\t\t\t\t/*\n> > +\t\t\t\t * We need to update the squash message to skip\n> > +\t\t\t\t * the latest commit message.\n> > +\t\t\t\t */\n> > +\t\t\t\tstruct commit *commit;\n> > +\t\t\t\tconst char *path = rebase_path_squash_msg();\n> > +\n> > +\t\t\t\tif (parse_head(&commit) ||\n> > +\t\t\t\t    !(p = get_commit_buffer(commit, NULL)) ||\n> > +\t\t\t\t    write_message(p, strlen(p), path, 0)) {\n> > +\t\t\t\t\tunuse_commit_buffer(commit, p);\n> > +\t\t\t\t\treturn error(_(\"could not write file: \"\n> > +\t\t\t\t\t\t       \"'%s'\"), path);\n> > +\t\t\t\t}\n> \n> I think it should probably recreate the fixup message as well. If there\n> is a sequence\n> \n> pick commit\n> fixup a\n> fixup b\n> fixup c\n> \n> and 'fixup b' gets skipped then when 'fixup c' is applied the user will\n> be prompted to edit the message unless rebase_path_fixup_msg() exists.\n\nI was already on my way to fix this when I encountered this mail 30\nminutes ago, but when I tried to implement the regression test, I took a\nstep back: apart from \"disk full\" and \"pilot error\", there is really no\ngood reason for the `message-squash` file not being able to be written.\n\nAnd in both cases, a benign `git status` will show the latest command,\ni.e. the one that failed.\n\nIn other words: I now deem this such a corner case that it is not worth\nspending more time on the error message than we already did...\n\nCiao,\nDscho\n\n> \n> Best Wishes\n> \n> Phillip\n> \n> > +\t\t\t\tunuse_commit_buffer(commit, p);\n> > +\t\t\t}\n> > +\t\t}\n> >  \n> >  \t\tstrbuf_release(&rev);\n> >  \t\tflags |= AMEND_MSG;\n> >  \t}\n> >  \n> > -\tif (run_git_commit(rebase_path_message(), opts, flags))\n> > +\tif (is_clean) {\n> > +\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n> > +\n> > +\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n> > +\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n> > +\t\tif (!final_fixup)\n> > +\t\t\treturn 0;\n> > +\t}\n> > +\n> > +\tif (run_git_commit(final_fixup ? NULL : rebase_path_message(),\n> > +\t\t\t   opts, flags))\n> >  \t\treturn error(_(\"could not commit staged changes.\"));\n> >  \tunlink(rebase_path_amend());\n> > +\tif (final_fixup) {\n> > +\t\tunlink(rebase_path_fixup_msg());\n> > +\t\tunlink(rebase_path_squash_msg());\n> > +\t}\n> > +\tif (opts->current_fixup_count > 0) {\n> > +\t\t/*\n> > +\t\t * Whether final fixup or not, we just cleaned up the commit\n> > +\t\t * message...\n> > +\t\t */\n> > +\t\tunlink(rebase_path_current_fixups());\n> > +\t\tstrbuf_reset(&opts->current_fixups);\n> > +\t\topts->current_fixup_count = 0;\n> > +\t}\n> >  \treturn 0;\n> >  }\n> >  \n> > @@ -2828,14 +2913,16 @@ int sequencer_continue(struct replay_opts *opts)\n> >  \tif (read_and_refresh_cache(opts))\n> >  \t\treturn -1;\n> >  \n> > +\tif (read_populate_opts(opts))\n> > +\t\treturn -1;\n> >  \tif (is_rebase_i(opts)) {\n> > -\t\tif (commit_staged_changes(opts))\n> > +\t\tif ((res = read_populate_todo(&todo_list, opts)))\n> > +\t\t\tgoto release_todo_list;\n> > +\t\tif (commit_staged_changes(opts, &todo_list))\n> >  \t\t\treturn -1;\n> >  \t} else if (!file_exists(get_todo_path(opts)))\n> >  \t\treturn continue_single_pick();\n> > -\tif (read_populate_opts(opts))\n> > -\t\treturn -1;\n> > -\tif ((res = read_populate_todo(&todo_list, opts)))\n> > +\telse if ((res = read_populate_todo(&todo_list, opts)))\n> >  \t\tgoto release_todo_list;\n> >  \n> >  \tif (!is_rebase_i(opts)) {\n> > diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n> > index 3874f187246..03bf1b8a3b3 100755\n> > --- a/t/t3418-rebase-continue.sh\n> > +++ b/t/t3418-rebase-continue.sh\n> > @@ -88,14 +88,14 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n> >  \tgit rebase --continue\n> >  '\n> >  \n> > -test_expect_failure '--skip after failed fixup cleans commit message' '\n> > +test_expect_success '--skip after failed fixup cleans commit message' '\n> >  \ttest_when_finished \"test_might_fail git rebase --abort\" &&\n> >  \tgit checkout -b with-conflicting-fixup &&\n> >  \ttest_commit wants-fixup &&\n> >  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n> >  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n> >  \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n> > -\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n> > +\ttest_must_fail env FAKE_LINES=\"1 fixup 2 squash 4\" \\\n> >  \t\tgit rebase -i HEAD~4 &&\n> >  \n> >  \t: now there is a conflict, and comments in the commit message &&\n> > @@ -103,11 +103,38 @@ test_expect_failure '--skip after failed fixup cleans commit message' '\n> >  \tgrep \"fixup! wants-fixup\" out &&\n> >  \n> >  \t: skip and continue &&\n> > -\tgit rebase --skip &&\n> > +\techo \"cp \\\"\\$1\\\" .git/copy.txt\" | write_script copy-editor.sh &&\n> > +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n> > +\n> > +\t: the user should not have had to edit the commit message &&\n> > +\ttest_path_is_missing .git/copy.txt &&\n> >  \n> >  \t: now the comments in the commit message should have been cleaned up &&\n> >  \tgit show HEAD >out &&\n> > -\t! grep \"fixup! wants-fixup\" out\n> > +\t! grep \"fixup! wants-fixup\" out &&\n> > +\n> > +\t: now, let us ensure that \"squash\" is handled correctly &&\n> > +\tgit reset --hard wants-fixup-3 &&\n> > +\ttest_must_fail env FAKE_LINES=\"1 squash 4 squash 2 squash 4\" \\\n> > +\t\tgit rebase -i HEAD~4 &&\n> > +\n> > +\t: the first squash failed, but there are two more in the chain &&\n> > +\t(test_set_editor \"$PWD/copy-editor.sh\" &&\n> > +\t test_must_fail git rebase --skip) &&\n> > +\n> > +\t: not the final squash, no need to edit the commit message &&\n> > +\ttest_path_is_missing .git/copy.txt &&\n> > +\n> > +\t: The first squash was skipped, therefore: &&\n> > +\tgit show HEAD >out &&\n> > +\ttest_i18ngrep \"# This is a combination of 2 commits\" out &&\n> > +\n> > +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n> > +\tgit show HEAD >out &&\n> > +\ttest_i18ngrep ! \"# This is a combination\" out &&\n> > +\n> > +\t: Final squash failed, but there was still a squash &&\n> > +\ttest_i18ngrep \"# This is a combination of 2 commits\" .git/copy.txt\n> >  '\n> >  \n> >  test_expect_success 'setup rerere database' '\n> > \n> \n> \n"},{"id":"359821","messageId":"2861c8c3-9c49-de59-277a-21c2857bb914@talktalk.net","threadId":"47635","inReplyTo":"nycvar.QRO.7.76.6.1810021546500.2034@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 4/4] rebase --skip: clean up commit message after a failed fixup/squash","fromName":"Phillip Wood","fromEmail":"phillip.wood@talktalk.net","sentAt":"2018-10-08T13:37:57Z","receivedAt":"2018-10-08T13:38:04Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Johannes\n\nOn 02/10/2018 14:50, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> [sorry, I just got to this mail now]\n\nDon't worry, I'm impressed you remembered it, I'd completely forgotten \nabout it.\n\n> \n> On Sun, 6 May 2018, Phillip Wood wrote:\n> \n>> On 27/04/18 21:48, Johannes Schindelin wrote:\n>>>\n>>> During a series of fixup/squash commands, the interactive rebase builds\n>>> up a commit message with comments. This will be presented to the user in\n>>> the editor if at least one of those commands was a `squash`.\n>>>\n>>> In any case, the commit message will be cleaned up eventually, removing\n>>> all those intermediate comments, in the final step of such a\n>>> fixup/squash chain.\n>>>\n>>> However, if the last fixup/squash command in such a chain fails with\n>>> merge conflicts, and if the user then decides to skip it (or resolve it\n>>> to a clean worktree and then continue the rebase), the current code\n>>> fails to clean up the commit message.\n>>>\n>>> This commit fixes that behavior.\n>>>\n>>> The fix is quite a bit more involved than meets the eye because it is\n>>> not only about the question whether we are `git rebase --skip`ing a\n>>> fixup or squash. It is also about removing the skipped fixup/squash's\n>>> commit message from the accumulated commit message. And it is also about\n>>> the question whether we should let the user edit the final commit\n>>> message or not (\"Was there a squash in the chain *that was not\n>>> skipped*?\").\n>>>\n>>> For example, in this case we will want to fix the commit message, but\n>>> not open it in an editor:\n>>>\n>>> \tpick\t<- succeeds\n>>> \tfixup\t<- succeeds\n>>> \tsquash\t<- fails, will be skipped\n>>>\n>>> This is where the newly-introduced `current-fixups` file comes in real\n>>> handy. A quick look and we can determine whether there was a non-skipped\n>>> squash. We only need to make sure to keep it up to date with respect to\n>>> skipped fixup/squash commands. As a bonus, we can even avoid committing\n>>> unnecessarily, e.g. when there was only one fixup, and it failed, and\n>>> was skipped.\n>>>\n>>> To fix only the bug where the final commit message was not cleaned up\n>>> properly, but without fixing the rest, would have been more complicated\n>>> than fixing it all in one go, hence this commit lumps together more than\n>>> a single concern.\n>>>\n>>> For the same reason, this commit also adds a bit more to the existing\n>>> test case for the regression we just fixed.\n>>>\n>>> The diff is best viewed with --color-moved.\n>>>\n>>> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>>> ---\n>>>   sequencer.c                | 113 ++++++++++++++++++++++++++++++++-----\n>>>   t/t3418-rebase-continue.sh |  35 ++++++++++--\n>>>   2 files changed, 131 insertions(+), 17 deletions(-)\n>>>\n>>> diff --git a/sequencer.c b/sequencer.c\n>>> index 56166b0d6c7..cec180714ef 100644\n>>> --- a/sequencer.c\n>>> +++ b/sequencer.c\n>>> [...]\n>>> @@ -2804,19 +2801,107 @@ static int commit_staged_changes(struct replay_opts *opts)\n>>>   \t\tif (get_oid_hex(rev.buf, &to_amend))\n>>>   \t\t\treturn error(_(\"invalid contents: '%s'\"),\n>>>   \t\t\t\trebase_path_amend());\n>>> -\t\tif (oidcmp(&head, &to_amend))\n>>> +\t\tif (!is_clean && oidcmp(&head, &to_amend))\n>>>   \t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n>>>   \t\t\t\t       \"working tree. Please, commit them\\n\"\n>>>   \t\t\t\t       \"first and then run 'git rebase \"\n>>>   \t\t\t\t       \"--continue' again.\"));\n>>> +\t\t/*\n>>> +\t\t * When skipping a failed fixup/squash, we need to edit the\n>>> +\t\t * commit message, the current fixup list and count, and if it\n>>> +\t\t * was the last fixup/squash in the chain, we need to clean up\n>>> +\t\t * the commit message and if there was a squash, let the user\n>>> +\t\t * edit it.\n>>> +\t\t */\n>>> +\t\tif (is_clean && !oidcmp(&head, &to_amend) &&\n>>> +\t\t    opts->current_fixup_count > 0 &&\n>>> +\t\t    file_exists(rebase_path_stopped_sha())) {\n>>> +\t\t\tconst char *p = opts->current_fixups.buf;\n>>> +\t\t\tint len = opts->current_fixups.len;\n>>> +\n>>> +\t\t\topts->current_fixup_count--;\n>>> +\t\t\tif (!len)\n>>> +\t\t\t\tBUG(\"Incorrect current_fixups:\\n%s\", p);\n>>> +\t\t\twhile (len && p[len - 1] != '\\n')\n>>> +\t\t\t\tlen--;\n>>> +\t\t\tstrbuf_setlen(&opts->current_fixups, len);\n>>> +\t\t\tif (write_message(p, len, rebase_path_current_fixups(),\n>>> +\t\t\t\t\t  0) < 0)\n>>> +\t\t\t\treturn error(_(\"could not write file: '%s'\"),\n>>> +\t\t\t\t\t     rebase_path_current_fixups());\n>>> +\n>>> +\t\t\t/*\n>>> +\t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n>>> +\t\t\t * commit message is already correct, no need to commit\n>>> +\t\t\t * it again.\n>>> +\t\t\t *\n>>> +\t\t\t * Only if it is the final command in the fixup/squash\n>>> +\t\t\t * chain, and only if the chain is longer than a single\n>>> +\t\t\t * fixup/squash command (which was just skipped), do we\n>>> +\t\t\t * actually need to re-commit with a cleaned up commit\n>>> +\t\t\t * message.\n>>> +\t\t\t */\n>>> +\t\t\tif (opts->current_fixup_count > 0 &&\n>>> +\t\t\t    !is_fixup(peek_command(todo_list, 0))) {\n>>> +\t\t\t\tfinal_fixup = 1;\n>>> +\t\t\t\t/*\n>>> +\t\t\t\t * If there was not a single \"squash\" in the\n>>> +\t\t\t\t * chain, we only need to clean up the commit\n>>> +\t\t\t\t * message, no need to bother the user with\n>>> +\t\t\t\t * opening the commit message in the editor.\n>>> +\t\t\t\t */\n>>> +\t\t\t\tif (!starts_with(p, \"squash \") &&\n>>> +\t\t\t\t    !strstr(p, \"\\nsquash \"))\n>>> +\t\t\t\t\tflags = (flags & ~EDIT_MSG) | CLEANUP_MSG;\n>>> +\t\t\t} else if (is_fixup(peek_command(todo_list, 0))) {\n>>> +\t\t\t\t/*\n>>> +\t\t\t\t * We need to update the squash message to skip\n>>> +\t\t\t\t * the latest commit message.\n>>> +\t\t\t\t */\n>>> +\t\t\t\tstruct commit *commit;\n>>> +\t\t\t\tconst char *path = rebase_path_squash_msg();\n>>> +\n>>> +\t\t\t\tif (parse_head(&commit) ||\n>>> +\t\t\t\t    !(p = get_commit_buffer(commit, NULL)) ||\n>>> +\t\t\t\t    write_message(p, strlen(p), path, 0)) {\n>>> +\t\t\t\t\tunuse_commit_buffer(commit, p);\n>>> +\t\t\t\t\treturn error(_(\"could not write file: \"\n>>> +\t\t\t\t\t\t       \"'%s'\"), path);\n>>> +\t\t\t\t}\n>>\n>> I think it should probably recreate the fixup message as well. If there\n>> is a sequence\n>>\n>> pick commit\n>> fixup a\n>> fixup b\n>> fixup c\n>>\n>> and 'fixup b' gets skipped then when 'fixup c' is applied the user will\n>> be prompted to edit the message unless rebase_path_fixup_msg() exists.\n> \n> I was already on my way to fix this when I encountered this mail 30\n> minutes ago, but when I tried to implement the regression test, I took a\n> step back: apart from \"disk full\" and \"pilot error\", there is really no\n> good reason for the `message-squash` file not being able to be written.\n\nAh, I should perhaps have cut the patch a line higher to make it clearer \nI was not talking about the error condition. The code rewrites \n'message-squash' to remove the commit message from the skipped \nfixup/squash. However if we have skipped a fixup and there are no \npreceding squash commands then when rebase stopped for the user to fix \nthe merge conflicts it removed the 'message-fixup' file. I thought (and \nstill think) it would be nice if 'rebase --skip' recreated the \n'message-fixup' file so that the user is not unnecessarily prompted to \nedit the commit message at the end of the fixup chain but it's not the \nend of the world if it stays as it is now.\n\nBest Wishes\n\nPhillip\n\n> \n> And in both cases, a benign `git status` will show the latest command,\n> i.e. the one that failed.\n> \n> In other words: I now deem this such a corner case that it is not worth\n> spending more time on the error message than we already did...\n> \n> Ciao,\n> Dscho\n> \n>>\n>> Best Wishes\n>>\n>> Phillip\n>>\n>>> +\t\t\t\tunuse_commit_buffer(commit, p);\n>>> +\t\t\t}\n>>> +\t\t}\n>>>   \n>>>   \t\tstrbuf_release(&rev);\n>>>   \t\tflags |= AMEND_MSG;\n>>>   \t}\n>>>   \n>>> -\tif (run_git_commit(rebase_path_message(), opts, flags))\n>>> +\tif (is_clean) {\n>>> +\t\tconst char *cherry_pick_head = git_path_cherry_pick_head();\n>>> +\n>>> +\t\tif (file_exists(cherry_pick_head) && unlink(cherry_pick_head))\n>>> +\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n>>> +\t\tif (!final_fixup)\n>>> +\t\t\treturn 0;\n>>> +\t}\n>>> +\n>>> +\tif (run_git_commit(final_fixup ? NULL : rebase_path_message(),\n>>> +\t\t\t   opts, flags))\n>>>   \t\treturn error(_(\"could not commit staged changes.\"));\n>>>   \tunlink(rebase_path_amend());\n>>> +\tif (final_fixup) {\n>>> +\t\tunlink(rebase_path_fixup_msg());\n>>> +\t\tunlink(rebase_path_squash_msg());\n>>> +\t}\n>>> +\tif (opts->current_fixup_count > 0) {\n>>> +\t\t/*\n>>> +\t\t * Whether final fixup or not, we just cleaned up the commit\n>>> +\t\t * message...\n>>> +\t\t */\n>>> +\t\tunlink(rebase_path_current_fixups());\n>>> +\t\tstrbuf_reset(&opts->current_fixups);\n>>> +\t\topts->current_fixup_count = 0;\n>>> +\t}\n>>>   \treturn 0;\n>>>   }\n>>>   \n>>> @@ -2828,14 +2913,16 @@ int sequencer_continue(struct replay_opts *opts)\n>>>   \tif (read_and_refresh_cache(opts))\n>>>   \t\treturn -1;\n>>>   \n>>> +\tif (read_populate_opts(opts))\n>>> +\t\treturn -1;\n>>>   \tif (is_rebase_i(opts)) {\n>>> -\t\tif (commit_staged_changes(opts))\n>>> +\t\tif ((res = read_populate_todo(&todo_list, opts)))\n>>> +\t\t\tgoto release_todo_list;\n>>> +\t\tif (commit_staged_changes(opts, &todo_list))\n>>>   \t\t\treturn -1;\n>>>   \t} else if (!file_exists(get_todo_path(opts)))\n>>>   \t\treturn continue_single_pick();\n>>> -\tif (read_populate_opts(opts))\n>>> -\t\treturn -1;\n>>> -\tif ((res = read_populate_todo(&todo_list, opts)))\n>>> +\telse if ((res = read_populate_todo(&todo_list, opts)))\n>>>   \t\tgoto release_todo_list;\n>>>   \n>>>   \tif (!is_rebase_i(opts)) {\n>>> diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n>>> index 3874f187246..03bf1b8a3b3 100755\n>>> --- a/t/t3418-rebase-continue.sh\n>>> +++ b/t/t3418-rebase-continue.sh\n>>> @@ -88,14 +88,14 @@ test_expect_success 'rebase passes merge strategy options correctly' '\n>>>   \tgit rebase --continue\n>>>   '\n>>>   \n>>> -test_expect_failure '--skip after failed fixup cleans commit message' '\n>>> +test_expect_success '--skip after failed fixup cleans commit message' '\n>>>   \ttest_when_finished \"test_might_fail git rebase --abort\" &&\n>>>   \tgit checkout -b with-conflicting-fixup &&\n>>>   \ttest_commit wants-fixup &&\n>>>   \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 1 wants-fixup-1 &&\n>>>   \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 2 wants-fixup-2 &&\n>>>   \ttest_commit \"fixup! wants-fixup\" wants-fixup.t 3 wants-fixup-3 &&\n>>> -\ttest_must_fail env FAKE_LINES=\"1 fixup 2 fixup 4\" \\\n>>> +\ttest_must_fail env FAKE_LINES=\"1 fixup 2 squash 4\" \\\n>>>   \t\tgit rebase -i HEAD~4 &&\n>>>   \n>>>   \t: now there is a conflict, and comments in the commit message &&\n>>> @@ -103,11 +103,38 @@ test_expect_failure '--skip after failed fixup cleans commit message' '\n>>>   \tgrep \"fixup! wants-fixup\" out &&\n>>>   \n>>>   \t: skip and continue &&\n>>> -\tgit rebase --skip &&\n>>> +\techo \"cp \\\"\\$1\\\" .git/copy.txt\" | write_script copy-editor.sh &&\n>>> +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n>>> +\n>>> +\t: the user should not have had to edit the commit message &&\n>>> +\ttest_path_is_missing .git/copy.txt &&\n>>>   \n>>>   \t: now the comments in the commit message should have been cleaned up &&\n>>>   \tgit show HEAD >out &&\n>>> -\t! grep \"fixup! wants-fixup\" out\n>>> +\t! grep \"fixup! wants-fixup\" out &&\n>>> +\n>>> +\t: now, let us ensure that \"squash\" is handled correctly &&\n>>> +\tgit reset --hard wants-fixup-3 &&\n>>> +\ttest_must_fail env FAKE_LINES=\"1 squash 4 squash 2 squash 4\" \\\n>>> +\t\tgit rebase -i HEAD~4 &&\n>>> +\n>>> +\t: the first squash failed, but there are two more in the chain &&\n>>> +\t(test_set_editor \"$PWD/copy-editor.sh\" &&\n>>> +\t test_must_fail git rebase --skip) &&\n>>> +\n>>> +\t: not the final squash, no need to edit the commit message &&\n>>> +\ttest_path_is_missing .git/copy.txt &&\n>>> +\n>>> +\t: The first squash was skipped, therefore: &&\n>>> +\tgit show HEAD >out &&\n>>> +\ttest_i18ngrep \"# This is a combination of 2 commits\" out &&\n>>> +\n>>> +\t(test_set_editor \"$PWD/copy-editor.sh\" && git rebase --skip) &&\n>>> +\tgit show HEAD >out &&\n>>> +\ttest_i18ngrep ! \"# This is a combination\" out &&\n>>> +\n>>> +\t: Final squash failed, but there was still a squash &&\n>>> +\ttest_i18ngrep \"# This is a combination of 2 commits\" .git/copy.txt\n>>>   '\n>>>   \n>>>   test_expect_success 'setup rerere database' '\n>>>\n>>\n>>\n\n"}]}