Commit 5b87815ed2f346f81374261ce2d633feae711886

Stefan Sperling 2020-03-05T11:20:24

prevent commits from being listed more than once in a histedit script While merging a commit multiple times during a histedit operation could potentially make sense in some corner case, a commit appearing more than once in the script is more likely to happen accidentally. If desired, the same effect can still be achieved by running multiple histedit operations, or by using 'got cherrypick' while the histedit operation is paused for arbitrary editing.

diff --git a/got/got.1 b/got/got.1
index ed10056..f5ab2f9 100644
--- a/got/got.1
+++ b/got/got.1
@@ -1098,6 +1098,7 @@ log message can be written.
 .Pp
 Every commit in the history being edited must be mentioned in the script.
 Lines may be re-ordered to change the order of commits in the edited history.
+No commit may be listed more than once.
 .Pp
 Edited commits are accumulated on a temporary branch which the work tree
 will remain switched to throughout the entire histedit operation.
diff --git a/got/got.c b/got/got.c
index 3ac85b3..16acb26 100644
--- a/got/got.c
+++ b/got/got.c
@@ -6082,7 +6082,7 @@ histedit_check_script(struct got_histedit_list *histedit_cmds,
 	const struct got_error *err = NULL;
 	struct got_object_qid *qid;
 	struct got_histedit_list_entry *hle;
-	static char msg[80];
+	static char msg[92];
 	char *id_str;
 
 	if (TAILQ_EMPTY(histedit_cmds))
@@ -6091,6 +6091,24 @@ histedit_check_script(struct got_histedit_list *histedit_cmds,
 	if (SIMPLEQ_EMPTY(commits))
 		return got_error(GOT_ERR_EMPTY_HISTEDIT);
 
+	TAILQ_FOREACH(hle, histedit_cmds, entry) {
+		struct got_histedit_list_entry *hle2;
+		TAILQ_FOREACH(hle2, histedit_cmds, entry) {
+			if (hle == hle2)
+				continue;
+			if (got_object_id_cmp(hle->commit_id,
+			    hle2->commit_id) != 0)
+				continue;
+			err = got_object_id_str(&id_str, hle->commit_id);
+			if (err)
+				return err;
+			snprintf(msg, sizeof(msg), "commit %s is listed "
+			    "more than once in histedit script", id_str);
+			free(id_str);
+			return got_error_msg(GOT_ERR_HISTEDIT_CMD, msg);
+		}
+	}
+
 	SIMPLEQ_FOREACH(qid, commits, entry) {
 		TAILQ_FOREACH(hle, histedit_cmds, entry) {
 			if (got_object_id_cmp(qid->id, hle->commit_id) == 0)
diff --git a/regress/cmdline/histedit.sh b/regress/cmdline/histedit.sh
index 11f94a5..d858406 100744
--- a/regress/cmdline/histedit.sh
+++ b/regress/cmdline/histedit.sh
@@ -1289,6 +1289,57 @@ function test_histedit_split_commit {
 
 }
 
+function test_histedit_duplicate_commit_in_script {
+	local testroot=`test_init histedit_duplicate_commit_in_script`
+
+	local orig_commit=`git_show_head $testroot/repo`
+
+	echo "modified alpha on master" > $testroot/repo/alpha
+	(cd $testroot/repo && git rm -q beta)
+	echo "new file on master" > $testroot/repo/epsilon/new
+	(cd $testroot/repo && git add epsilon/new)
+	git_commit $testroot/repo -m "committing changes 1"
+	local old_commit1=`git_show_head $testroot/repo`
+
+	echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+	git_commit $testroot/repo -m "committing changes 2"
+	local old_commit2=`git_show_head $testroot/repo`
+
+	got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# This histedit script lists commit1 more than once
+	echo "p $old_commit1" > $testroot/histedit-script
+	echo "p $old_commit1" >> $testroot/histedit-script
+	echo "p $old_commit2" >> $testroot/histedit-script
+
+	(cd $testroot/wt && got histedit -F $testroot/histedit-script \
+		> $testroot/stdout 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "histedit succeeded unexpectedly:" >&2
+		cat $testroot/stdout >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo -n "got: commit $old_commit1 is listed more than once " \
+		> $testroot/stderr.expected
+	echo "in histedit script" >> $testroot/stderr.expected
+
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+	fi
+	test_done "$testroot" "$ret"
+
+}
+
 run_test test_histedit_no_op
 run_test test_histedit_swap
 run_test test_histedit_drop
@@ -1302,3 +1353,4 @@ run_test test_histedit_path_prefix_edit
 run_test test_histedit_outside_refs_heads
 run_test test_histedit_fold_last_commit_swap
 run_test test_histedit_split_commit
+run_test test_histedit_duplicate_commit_in_script