[PATCH 4/4] git-rebase -i: New option to support rebase with merges

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

 



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

[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