Re: [PATCH 8/8] rebase -i: introduce --recreate-merges=no-rebase-cousins

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: "Johannes Schindelin" <johannes.schindelin@xxxxxx>
This one is a bit tricky to explain, so let's try with a diagram:

       C
     /   \
A - B - E - F
 \   /
   D

To illustrate what this new mode is all about, let's consider what
happens upon `git rebase -i --recreate-merges B`, in particular to
the commit `D`. In the default mode, the new branch structure is:

      --- C' --
     /         \
A - B ------ E' - F'
     \    /
       D'

This is not really preserving the branch topology from before! The
reason is that the commit `D` does not have `B` as ancestor, and
therefore it gets rebased onto `B`.

However, when recreating branch structure, there are legitimate use
cases where one might want to preserve the branch points of commits that
do not descend from the <upstream> commit that was passed to the rebase
command, e.g. when a branch from core Git's `next` was merged into Git
for Windows' master we will not want to rebase those commits on top of a
Windows-specific commit. In the example above, the desired outcome would
look like this:

      --- C' --
     /         \
A - B ------ E' - F'
 \        /
  -- D' --

I'm not understanding this. I see that D properly starts from A, but don't see why it is now D'. Surely it's unchanged. Maybe it's the arc/node confusion. Maybe even spell out that the rebased commits from the command are B..HEAD, but that includes D, which may not be what folk had expected. (not even sure if the reflog comes into determining merge-bases here..)

I do think an exact definition is needed (e.g. via --ancestry-path or its equivalent?).


Let's introduce the term "cousins" for such commits ("D" in the
example), and the "no-rebase-cousins" mode of the merge-recreating
rebase, to help those use cases.

Signed-off-by: Johannes Schindelin <johannes.schindelin@xxxxxx>
---
Documentation/git-rebase.txt      |  7 ++++++-
builtin/rebase--helper.c          |  9 ++++++++-
git-rebase--interactive.sh        |  1 +
git-rebase.sh                     | 12 +++++++++++-
sequencer.c                       |  4 ++++
sequencer.h                       |  8 ++++++++
t/t3430-rebase-recreate-merges.sh | 23 +++++++++++++++++++++++
7 files changed, 61 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 1d061373288..ac07a5c3fc9 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -368,10 +368,15 @@ The commit list format can be changed by setting the configuration option rebase.instructionFormat. A customized instruction format will automatically
have the long commit hash prepended to the format.

---recreate-merges::
+--recreate-merges[=(rebase-cousins|no-rebase-cousins)]::
 Recreate merge commits instead of flattening the history by replaying
 merges. Merge conflict resolutions or manual amendments to merge
 commits are not preserved.
++
+By default, or when `rebase-cousins` was specified, commits which do not have +`<upstream>` as direct ancestor are rebased onto `<upstream>` (or `<onto>`, +if specified). If the `rebase-cousins` mode is turned off, such commits will
+retain their original branch point.

-p::
--preserve-merges::
diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index a34ab5c0655..ef08fef4d14 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
{
 struct replay_opts opts = REPLAY_OPTS_INIT;
 unsigned flags = 0, keep_empty = 0, recreate_merges = 0;
- int abbreviate_commands = 0;
+ int abbreviate_commands = 0, no_rebase_cousins = -1;
 enum {
 CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
 CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
@@ -23,6 +23,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
 OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
OPT_BOOL(0, "recreate-merges", &recreate_merges, N_("recreate merge commits")),
+ OPT_BOOL(0, "no-rebase-cousins", &no_rebase_cousins,
+ N_("keep original branch points of cousins")),
 OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
 CONTINUE),
 OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
@@ -57,8 +59,13 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
 flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
 flags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0;
+ flags |= no_rebase_cousins > 0 ? TODO_LIST_NO_REBASE_COUSINS : 0;
 flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;

+ if (no_rebase_cousins >= 0&& !recreate_merges)
+ warning(_("--[no-]rebase-cousins has no effect without "
+   "--recreate-merges"));
+
 if (command == CONTINUE && argc == 1)
 return !!sequencer_continue(&opts);
 if (command == ABORT && argc == 1)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3459ec5a018..23184c77e88 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -901,6 +901,7 @@ if test t != "$preserve_merges"
then
 git rebase--helper --make-script ${keep_empty:+--keep-empty} \
 ${recreate_merges:+--recreate-merges} \
+ ${no_rebase_cousins:+--no-rebase-cousins} \
 $revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
 die "$(gettext "Could not generate todo list")"
else
diff --git a/git-rebase.sh b/git-rebase.sh
index d69bc7d0e0d..3403b1416a8 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -17,7 +17,7 @@ q,quiet!           be quiet. implies --no-stat
autostash          automatically stash/stash pop before and after
fork-point         use 'merge-base --fork-point' to refine upstream
onto=!             rebase onto given branch instead of upstream
-recreate-merges!   try to recreate merges instead of skipping them
+recreate-merges?   try to recreate merges instead of skipping them
p,preserve-merges! try to recreate merges instead of ignoring them
s,strategy=!       use the given merge strategy
no-ff!             cherry-pick all commits, even if unchanged
@@ -88,6 +88,7 @@ state_dir=
# One of {'', continue, skip, abort}, as parsed from command line
action=
recreate_merges=
+no_rebase_cousins=
preserve_merges=
autosquash=
keep_empty=
@@ -268,6 +269,15 @@ do
 recreate_merges=t
 test -z "$interactive_rebase" && interactive_rebase=implied
 ;;
+ --recreate-merges=*)
+ recreate_merges=t
+ case "${1#*=}" in
+ rebase-cousins) no_rebase_cousins=;;
+ no-rebase-cousins) no_rebase_cousins=t;;
+ *) die "Unknown mode: $1";;
+ esac
+ test -z "$interactive_rebase" && interactive_rebase=implied
+ ;;
 --preserve-merges)
 preserve_merges=t
 test -z "$interactive_rebase" && interactive_rebase=implied
diff --git a/sequencer.c b/sequencer.c
index b63bfb9a141..2b4e6b12321 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2905,6 +2905,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
    unsigned flags)
{
 int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+ int no_rebase_cousins = flags & TODO_LIST_NO_REBASE_COUSINS;
 struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
 struct strbuf label = STRBUF_INIT;
 struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -3078,6 +3079,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
    &commit->object.oid);
 if (entry)
 to = entry->string;
+ else if (no_rebase_cousins)
+ to = label_oid(&commit->object.oid, NULL,
+        &state);

 if (!to || !strcmp("onto", to))
 fprintf(out, "%s\n", b);
diff --git a/sequencer.h b/sequencer.h
index 11d1ac925ef..9530dba3cba 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -49,6 +49,14 @@ int sequencer_remove_state(struct replay_opts *opts);
#define TODO_LIST_SHORTEN_IDS (1U << 1)
#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
#define TODO_LIST_RECREATE_MERGES (1U << 3)
+/*
+ * When recreating merges, commits that do have the base commit as ancestor
+ * ("cousins") are rebased onto the new base by default. If those commits
+ * should keep their original branch point, this flag needs to be passed.
+ *
+ * This flag only makes sense when <base> and <onto> are different.
+ */
+#define TODO_LIST_NO_REBASE_COUSINS (1U << 4)
int sequencer_make_script(FILE *out, int argc, const char **argv,
   unsigned flags);

diff --git a/t/t3430-rebase-recreate-merges.sh b/t/t3430-rebase-recreate-merges.sh
index 76e615bd7c1..22930e470a4 100755
--- a/t/t3430-rebase-recreate-merges.sh
+++ b/t/t3430-rebase-recreate-merges.sh
@@ -143,6 +143,29 @@ test_expect_success 'with a branch tip that was cherry-picked already' '
 EOF
'

+test_expect_success 'rebase cousins unless told not to' '
+ write_script copy-editor.sh <<-\EOF &&
+ cp "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
+ EOF
+
+ test_config sequence.editor \""$PWD"/copy-editor.sh\" &&
+ git checkout -b cousins master &&
+ before="$(git rev-parse --verify HEAD)" &&
+ test_tick &&
+ git rebase -i --recreate-merges=no-rebase-cousins HEAD^ &&
+ test_cmp_rev HEAD $before &&
+ test_tick &&
+ git rebase -i --recreate-merges HEAD^ &&
+ test_cmp_graph HEAD^.. <<-\EOF
+ *   Merge the topic branch '\''onebranch'\''
+ |\
+ | * D
+ | * G
+ |/
+ o H
+ EOF
+'
+
test_expect_success 'refs/rewritten/* is worktree-local' '
 git worktree add wt &&
 cat >wt/script-from-scratch <<-\EOF &&
--
2.15.1.windows.2.1430.ga56c4f9e2a9




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux