The option --preserve-merges does not allow to change the order of commits or squash them. The new option --linear-history does support this, but doing so it can only look at the commits reachable with through the first parent of each merge. Joining merge commits with other commits leads to problems, because git merge fails with a dirty index (the case “COMMIT squash MERGE”) and squashing a merge leads to the lost of the parents (case “MERGE squash COMMIT”). Therefore, I've prohibited these cases. Signed-off-by: Jörg Sommer <joerg@xxxxxxxxxxxx> --- Documentation/git-rebase.txt | 8 ++++ git-rebase--interactive.sh | 27 +++++++++++++++- t/t3404-rebase-interactive.sh | 72 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletions(-) I had no better idea for a name of this new option. Propositions are welcome. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index e0412e0..354b6f0 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] [-s <strategy> | --strategy=<strategy>] [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges] + [-l | --linear-history] [--onto <newbase>] <upstream> [<branch>] 'git-rebase' --continue | --skip | --abort @@ -247,6 +248,13 @@ OPTIONS Instead of ignoring merges, try to recreate them. This option only works in interactive mode. +-l, \--linear-history:: + Use only commits of the branch they are not merged in, i.e. + follow only the first parent of a merge. Merges are part of this + list and they will be redone. It's possible to move merges in the + history forward and backward, but they can't take part on a join + (squash). This option only works in interactive mode. + include::merge-strategies.txt[] NOTES diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 94c6827..a2a61f8 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -26,9 +26,11 @@ REWRITTEN="$DOTEST"/rewritten PRESERVE_MERGES= STRATEGY= VERBOSE= +LINEAR_HISTORY= test -d "$REWRITTEN" && PRESERVE_MERGES=t test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" test -f "$DOTEST"/verbose && VERBOSE=t +test -f "$DOTEST"/linear_history && LINEAR_HISTORY=t GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add <paths>', and @@ -150,7 +152,18 @@ pick_one () { sha1=$(git rev-parse --short $sha1) output warn Fast forward to $sha1 else - output git cherry-pick "$@" + if test t = "$LINEAR_HISTORY" && + other_parents="$(parents_of_commit $sha1 | cut -s -d' ' -f2-)" && + test -n "$other_parents" + then + if test a"$1" = a-n + then + merge_opt=--no-commit + fi + redo_merge $sha1 $no_commit $other_parents + else + output git cherry-pick "$@" + fi fi } @@ -288,6 +301,11 @@ do_next () { has_action "$DONE" || die "Cannot 'squash' without a previous commit" + test t = "$LINEAR_HISTORY" && + ( test "$(parents_of_commit HEAD |wc -w)" -gt 1 || + test "$(parents_of_commit $sha1 |wc -w)" -gt 1) && + die "Joining a merge with a commit is not supported" + mark_action_done make_squash_message $sha1 > "$MSG" case "$(peek_next_command)" in @@ -459,6 +477,9 @@ do -i|--interactive) # yeah, we know ;; + -l|--linear-history) + LINEAR_HISTORY=t + ;; ''|-h) usage ;; @@ -522,6 +543,10 @@ do die "Could not init rewritten commits" done MERGES_OPTION= + elif test t = "$LINEAR_HISTORY" + then + : > "$DOTEST"/linear_history + MERGES_OPTION=--first-parent else MERGES_OPTION=--no-merges fi diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 9cf873f..0476f6a 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -361,4 +361,76 @@ test_expect_success 'rebase with a file named HEAD in worktree' ' ' +test_expect_success 'rebase linear history preserves merges' ' + git tag linear-history-base to-be-preserved~2 + git checkout -b linear-history linear-history-base && + for i in 1 2 3 + do + test $? -eq 0 && + echo linear history test > lin-h-$i && + git add lin-h-$i && + test_tick && + git commit -m "rebase linear history commit $i" + done + test_tick && + git merge to-be-preserved && + old_head=$(git rev-parse HEAD) && + test_tick && + EXPECT_COUNT=4 FAKE_LINES="2 4 edit 1 3" \ + git rebase -v -i -l linear-history-base && + EXPECT_COUNT=invalid git rebase --continue && + test "$(git rev-list --parents -1 HEAD~2 | cut -d" " -f3-)" = \ + "$(git rev-parse to-be-preserved)" && + test "$(git show HEAD~2: | grep ^lin-h- | cut -c7- | tr -d \\012)" = 2 && + test "$(git show HEAD~1: | grep ^lin-h- | cut -c7- | tr -d \\012)" = 12 && + test "$(git cat-file commit HEAD | sed -n "/^tree/{p;q;}")" = \ + "$(git cat-file commit $old_head | sed -n "/^tree/{p;q;}")" +' + +test_expect_success 'rebase linear history is noop, if base = base' ' + old_head=$(git rev-parse HEAD) && + test_tick && + EXPECT_COUNT=4 git rebase -v -i -l linear-history-base && + test "$(git rev-parse HEAD)" = $old_head +' + +test_expect_success 'ensure rebase linear history persits across edits' ' + old_head=$(git rev-parse HEAD) && + test_tick && + EXPECT_COUNT=4 FAKE_LINES="edit 1 2 3 4" \ + git rebase -v -i -l linear-history-base && + EXPECT_COUNT=invalid git rebase --continue && + test "$(git rev-parse HEAD)" = $old_head +' + +test_str='test_tick && + ( + outp=$(test_must_fail git rebase -v -i -l HEAD~3 2>&1) + rc=$? + echo "$outp" + echo "$outp" | grep "^Joining .* not supported\$" >/dev/null && + return $rc + ) && + git rebase --abort' + +test_expect_success 'rebase linear with squashing a merge fails' " + export EXPECT_COUNT=3 FAKE_LINES='1 squash 2 3' && + $test_str && + FAKE_LINES='2 squash 1 3' && + $test_str +" + +test_expect_success 'rebase linear history does a fast forward' ' + old_head=$(git rev-parse HEAD) && + test_tick && + EXPECT_COUNT=4 FAKE_LINES="2 3 4 1" \ + git rebase -v -i -l linear-history-base && + test "$(git rev-parse HEAD~3)" = "$(git rev-parse to-be-preserved)" && + test "$(git show HEAD~2: | grep ^lin-h- | cut -c7- | tr -d \\012)" = 1 && + test "$(git show HEAD~1: | grep ^lin-h- | cut -c7- | tr -d \\012)" = 13 && + test -z "$(git rev-list --parents -1 HEAD~3 | cut -d" " -f3-)" && + test "$(git cat-file commit HEAD | sed -n "/^tree/{p;q;}")" = \ + "$(git cat-file commit $old_head | sed -n "/^tree/{p;q;}")" +' + test_done -- 1.5.4.4 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html