Commit 088449d31db27c8682d5e9dc737d92d05df6605e

Stefan Sperling 2021-09-26T17:51:38

implement 'got merge -n' which interrupts before creating a merge commit

diff --git a/got/got.1 b/got/got.1
index c408a29..87a57e1 100644
--- a/got/got.1
+++ b/got/got.1
@@ -2132,7 +2132,7 @@ or reverted with
 .It Cm ig
 Short alias for
 .Cm integrate .
-.It Cm merge Oo Fl a Oc Oo Fl c Oc Op Ar branch
+.It Cm merge Oo Fl a Oc Oo Fl c Oc Oo Fl n Oc Op Ar branch
 Create a merge commit based on the current branch of the work tree and
 the specified
 .Ar branch .
@@ -2246,6 +2246,14 @@ If this option is used, no other command-line arguments are allowed.
 .It Fl c
 Continue an interrupted merge operation.
 If this option is used, no other command-line arguments are allowed.
+.It Fl n
+Merge changes into the work tree as usual but do not create a merge
+commit immediately.
+The merge result can be adjusted as desired before a merge commit is
+created with
+.Cm got merge -c .
+Alternatively, the merge may be aborted with
+.Cm got merge -a .
 .El
 .It Cm mg
 Short alias for
diff --git a/got/got.c b/got/got.c
index 461ac8d..2ffb450 100644
--- a/got/got.c
+++ b/got/got.c
@@ -10590,7 +10590,7 @@ done:
 __dead static void
 usage_merge(void)
 {
-	fprintf(stderr, "usage: %s merge [-a] [-c] [branch]\n",
+	fprintf(stderr, "usage: %s merge [-a] [-c] [-n] [branch]\n",
 	    getprogname());
 	exit(1);
 }
@@ -10607,13 +10607,14 @@ cmd_merge(int argc, char *argv[])
 	struct got_object_id *branch_tip = NULL, *yca_id = NULL;
 	struct got_object_id *wt_branch_tip = NULL;
 	int ch, merge_in_progress = 0, abort_merge = 0, continue_merge = 0;
+	int interrupt_merge = 0;
 	struct got_update_progress_arg upa;
 	struct got_object_id *merge_commit_id = NULL;
 	char *branch_name = NULL;
 
 	memset(&upa, 0, sizeof(upa));
 
-	while ((ch = getopt(argc, argv, "ac")) != -1) {
+	while ((ch = getopt(argc, argv, "acn")) != -1) {
 		switch (ch) {
 		case 'a':
 			abort_merge = 1;
@@ -10621,6 +10622,9 @@ cmd_merge(int argc, char *argv[])
 		case 'c':
 			continue_merge = 1;
 			break;
+		case 'n':
+			interrupt_merge = 1;
+			break;
 		default:
 			usage_rebase();
 			/* NOTREACHED */
@@ -10780,7 +10784,12 @@ cmd_merge(int argc, char *argv[])
 		}
 	}
 
-	if (upa.conflicts > 0 || upa.missing > 0) {
+	if (interrupt_merge) {
+		error = got_worktree_merge_postpone(worktree, fileindex);
+		if (error)
+			goto done;
+		printf("Merge of %s interrupted on request\n", branch_name);
+	} else if (upa.conflicts > 0 || upa.missing > 0) {
 		error = got_worktree_merge_postpone(worktree, fileindex);
 		if (error)
 			goto done;
diff --git a/regress/cmdline/merge.sh b/regress/cmdline/merge.sh
index 1d534c5..38220a8 100755
--- a/regress/cmdline/merge.sh
+++ b/regress/cmdline/merge.sh
@@ -1236,6 +1236,152 @@ EOF
 	test_done "$testroot" "$ret"
 }
 
+test_merge_interrupt() {
+	local testroot=`test_init merge_interrupt`
+	local commit0=`git_show_head $testroot/repo`
+	local commit0_author_time=`git_show_author_time $testroot/repo`
+
+	(cd $testroot/repo && git checkout -q -b newbranch)
+	echo "modified alpha on branch" > $testroot/repo/alpha
+	git_commit $testroot/repo -m "committing to alpha on newbranch"
+	local branch_commit0=`git_show_branch_head $testroot/repo newbranch`
+
+	got checkout -b master $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got checkout failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# create a non-conflicting commit
+	(cd $testroot/repo && git checkout -q master)
+	echo "modified beta on master" > $testroot/repo/beta
+	git_commit $testroot/repo -m "committing to beta on master"
+	local master_commit=`git_show_head $testroot/repo`
+
+	# need an up-to-date work tree for 'got merge' 
+	(cd $testroot/wt && got update > /dev/null)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got update failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got merge -n newbranch \
+		> $testroot/stdout 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got merge failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo "G  alpha" > $testroot/stdout.expected
+	echo "Merge of refs/heads/newbranch interrupted on request" \
+		>> $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got status > $testroot/stdout)
+
+	echo "M  alpha" > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified alpha on branch" > $testroot/content.expected
+	cat $testroot/wt/alpha > $testroot/content
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# adjust merge result
+	echo "adjusted merge result" > $testroot/wt/alpha
+
+	# continue the merge
+	(cd $testroot/wt && got merge -c > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got merge failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	local merge_commit=`git_show_head $testroot/repo`
+
+	echo -n "Merged refs/heads/newbranch into refs/heads/master: " \
+		> $testroot/stdout.expected
+	echo $merge_commit >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got status > $testroot/stdout)
+
+	echo -n > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout)
+	echo "commit $merge_commit (master)" > $testroot/stdout.expected
+	echo "commit $master_commit" >> $testroot/stdout.expected
+	echo "commit $commit0" >> $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got update > $testroot/stdout)
+
+	echo 'Already up-to-date' > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# We should have created a merge commit with two parents.
+	(cd $testroot/wt && got log -l1 | grep ^parent > $testroot/stdout)
+	echo "parent 1: $master_commit" > $testroot/stdout.expected
+	echo "parent 2: $branch_commit0" >> $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_merge_basic
 run_test test_merge_continue
@@ -1245,3 +1391,4 @@ run_test test_merge_path_prefix
 run_test test_merge_missing_file
 run_test test_merge_no_op
 run_test test_merge_imported_branch
+run_test test_merge_interrupt