Junio, I think this is now ready for `next`. Thank you for your patience
and help with this.
Once upon a time, I dreamed of an interactive rebase that would not
linearize all patches and drop all merge commits, but instead recreate
the commit topology faithfully.
My original attempt was --preserve-merges, but that design was so
limited that I did not even enable it in interactive mode.
Subsequently, it *was* enabled in interactive mode, with the predictable
consequences: as the --preserve-merges design does not allow for
specifying the parents of merge commits explicitly, all the new commits'
parents are defined *implicitly* by the previous commit history, and
hence it is *not possible to even reorder commits*.
This design flaw cannot be fixed. Not without a complete re-design, at
least. This patch series offers such a re-design.
Think of --rebase-merges as "--preserve-merges done right". It
introduces new verbs for the todo list, `label`, `reset` and `merge`.
For a commit topology like this:
A - B - C
\ /
D
the generated todo list would look like this:
# branch D
pick 0123 A
label branch-point
pick 1234 D
label D
reset branch-point
pick 2345 B
merge -C 3456 D # C
There are more patches in the pipeline, based on this patch series, but
left for later in the interest of reviewable patch series: one mini
series to use the sequencer even for `git rebase -i --root`, and another
one to add support for octopus merges to --rebase-merges. And then one
to allow for rebasing merge commits in a smarter way (this one will need
a bit more work, though, as it can result in very complicated, nested
merge conflicts *very* easily).
Changes since v8:
- Disentangled the patch introducing `label`/`reset` from the one
introducing `merge` again (this was one stupid, tired `git commit
--amend` too many).
- Augmented the commit message of "introduce the `merge` command" to
describe what the `label onto` is all about.
- Fixed the error message when `reset` would overwrite untracked files to
actually say that a "reset" failed (not a "merge").
- Clarified the rationale for `label onto` in the commit message of
"rebase-helper --make-script: introduce a flag to rebase merges".
- Edited the description of `--rebase-merges` heavily, for clarity, in
"rebase: introduce the --rebase-merges option".
- Edited the commit message of (and the documentation introduced by) " rebase
-i: introduce --rebase-merges=[no-]rebase-cousins" for clarity (also
mentioning the `--ancestry-path` option).
- When run_git_commit() fails after a successful merge, we now take pains
not to reschedule the `merge` command.
- Rebased the patch series on top of current `master`, i.e. both
`pw/rebase-keep-empty-fixes` and `pw/rebase-signoff`, to resolve merge
conflicts myself.
Johannes Schindelin (15):
sequencer: avoid using errno clobbered by rollback_lock_file()
sequencer: make rearrange_squash() a bit more obvious
sequencer: refactor how original todo list lines are accessed
sequencer: offer helpful advice when a command was rescheduled
sequencer: introduce new commands to reset the revision
sequencer: introduce the `merge` command
sequencer: fast-forward `merge` commands, if possible
rebase-helper --make-script: introduce a flag to rebase merges
rebase: introduce the --rebase-merges option
sequencer: make refs generated by the `label` command worktree-local
sequencer: handle post-rewrite for merge commands
rebase --rebase-merges: avoid "empty merges"
pull: accept --rebase=merges to recreate the branch topology
rebase -i: introduce --rebase-merges=[no-]rebase-cousins
rebase -i --rebase-merges: add a section to the man page
Phillip Wood (1):
rebase --rebase-merges: add test for --keep-empty
Stefan Beller (1):
git-rebase--interactive: clarify arguments
Documentation/config.txt | 8 +
Documentation/git-pull.txt | 6 +-
Documentation/git-rebase.txt | 163 ++++-
builtin/pull.c | 14 +-
builtin/rebase--helper.c | 13 +-
builtin/remote.c | 18 +-
contrib/completion/git-completion.bash | 4 +-
git-rebase--interactive.sh | 22 +-
git-rebase.sh | 16 +
refs.c | 3 +-
sequencer.c | 892 ++++++++++++++++++++++++-
sequencer.h | 7 +
t/t3421-rebase-topology-linear.sh | 1 +
t/t3430-rebase-merges.sh | 244 +++++++
14 files changed, 1352 insertions(+), 59 deletions(-)
create mode 100755 t/t3430-rebase-merges.sh
base-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d
Published-As: https://github.com/dscho/git/releases/tag/recreate-merges-v9
Fetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v9
Interdiff vs v8:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index e691b93e920..bd5ecff980e 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -381,21 +381,24 @@ have the long commit hash prepended to the format.
-r::
--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
- By default, a rebase will simply drop merge commits and only rebase
- the non-merge commits. With this option, it will try to preserve
+ By default, a rebase will simply drop merge commits from the todo
+ list, and put the rebased commits into a single, linear branch.
+ With `--rebase-merges`, the rebase will instead try to preserve
the branching structure within the commits that are to be rebased,
- by recreating the merge commits. If a merge commit resolved any merge
- or contained manual amendments, then they will have to be re-applied
- manually.
+ by recreating the merge commits. Any resolved merge conflicts or
+ manual amendments in these merge commits will have to be
+ resolved/re-applied manually.
+
By default, or when `no-rebase-cousins` was specified, commits which do not
-have `<upstream>` as direct ancestor will keep their original branch point.
-If the `rebase-cousins` mode is turned on, such commits are instead rebased
+have `<upstream>` as direct ancestor will keep their original branch point,
+i.e. commits that would be excluded by gitlink:git-log[1]'s
+`--ancestry-path` option will keep their original ancestry by default. If
+the `rebase-cousins` mode is turned on, such commits are instead rebased
onto `<upstream>` (or `<onto>`, if specified).
+
-This mode is similar in spirit to `--preserve-merges`, but in contrast to
-that option works well in interactive rebases: commits can be reordered,
-inserted and dropped at will.
+The `--rebase-merges` mode is similar in spirit to `--preserve-merges`, but
+in contrast to that option works well in interactive rebases: commits can be
+reordered, inserted and dropped at will.
+
It is currently only possible to recreate the merge commits using the
`recursive` merge strategy; Different merge strategies can be used only via
diff --git a/sequencer.c b/sequencer.c
index b5715f69450..e2f83942843 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2635,6 +2635,7 @@ static int do_reset(const char *name, int len, struct
replay_opts *opts)
}
memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
+ setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
unpack_tree_opts.head_idx = 1;
unpack_tree_opts.src_index = &the_index;
unpack_tree_opts.dst_index = &the_index;
@@ -2855,7 +2856,12 @@ static int do_merge(struct commit *commit, const char
*arg, int arg_len,
if (ret)
rerere(opts->allow_rerere_auto);
else
- ret = run_git_commit(git_path_merge_msg(), opts,
+ /*
+ * In case of problems, we now want to return a positive
+ * value (a negative one would indicate that the `merge`
+ * command needs to be rescheduled).
+ */
+ ret = !!run_git_commit(git_path_merge_msg(), opts,
run_commit_flags);
leave_merge:
@@ -3809,12 +3815,9 @@ int sequencer_make_script(FILE *out, int argc, const
char **argv,
init_revisions(&revs, NULL);
revs.verbose_header = 1;
- if (rebase_merges)
- revs.cherry_mark = 1;
- else {
+ if (!rebase_merges)
revs.max_parents = 1;
- revs.cherry_pick = 1;
- }
+ revs.cherry_mark = 1;
revs.limited = 1;
revs.reverse = 1;
revs.right_only = 1;
--
2.17.0.windows.1.33.gfcbb1fa0445