Commit e51d7b55a3d7fcd307ad6f65fa793a52a24dd697

Stefan Sperling 2020-01-04T08:35:17

prevent rebase with an out-of-date work tree

diff --git a/include/got_error.h b/include/got_error.h
index d0df125..577bc08 100644
--- a/include/got_error.h
+++ b/include/got_error.h
@@ -128,6 +128,7 @@
 #define GOT_ERR_REGEX		112
 #define GOT_ERR_REF_NAME_MINUS	113
 #define GOT_ERR_GITCONFIG_SYNTAX 114
+#define GOT_ERR_REBASE_OUT_OF_DATE 115
 
 static const struct got_error {
 	int code;
@@ -262,6 +263,8 @@ static const struct got_error {
 	{ GOT_ERR_REGEX, "regular expression error" },
 	{ GOT_ERR_REF_NAME_MINUS, "reference name may not start with '-'" },
 	{ GOT_ERR_GITCONFIG_SYNTAX, "gitconfig syntax error" },
+	{ GOT_ERR_REBASE_OUT_OF_DATE, "work tree must be updated before it "
+	    "can be used to rebase a branch" },
 };
 
 /*
diff --git a/lib/worktree.c b/lib/worktree.c
index ba58428..3f75e03 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -4679,6 +4679,7 @@ got_worktree_rebase_prepare(struct got_reference **new_base_branch_ref,
 	char *fileindex_path = NULL;
 	struct check_rebase_ok_arg ok_arg;
 	struct got_reference *wt_branch = NULL, *branch_ref = NULL;
+	struct got_object_id *wt_branch_tip = NULL;
 
 	*new_base_branch_ref = NULL;
 	*tmp_branch = NULL;
@@ -4716,6 +4717,14 @@ got_worktree_rebase_prepare(struct got_reference **new_base_branch_ref,
 	if (err)
 		goto done;
 
+	err = got_ref_resolve(&wt_branch_tip, repo, wt_branch);
+	if (err)
+		goto done;
+	if (got_object_id_cmp(worktree->base_commit_id, wt_branch_tip) != 0) {
+		err = got_error(GOT_ERR_REBASE_OUT_OF_DATE);
+		goto done;
+	}
+
 	err = got_ref_alloc_symref(new_base_branch_ref,
 	    new_base_branch_ref_name, wt_branch);
 	if (err)
@@ -4754,6 +4763,7 @@ done:
 		got_ref_close(branch_ref);
 	if (wt_branch)
 		got_ref_close(wt_branch);
+	free(wt_branch_tip);
 	if (err) {
 		if (*new_base_branch_ref) {
 			got_ref_close(*new_base_branch_ref);
diff --git a/regress/cmdline/rebase.sh b/regress/cmdline/rebase.sh
index e504cd7..57da5df 100755
--- a/regress/cmdline/rebase.sh
+++ b/regress/cmdline/rebase.sh
@@ -870,6 +870,76 @@ function test_rebase_forward {
 	test_done "$testroot" "$ret"
 }
 
+function test_rebase_out_of_date {
+	local testroot=`test_init rebase_out_of_date`
+	local initial_commit=`git_show_head $testroot/repo`
+
+	(cd $testroot/repo && git checkout -q -b newbranch)
+	echo "modified delta on branch" > $testroot/repo/gamma/delta
+	git_commit $testroot/repo -m "committing to delta on newbranch"
+
+	echo "modified alpha on branch" > $testroot/repo/alpha
+	(cd $testroot/repo && git rm -q beta)
+	echo "new file on branch" > $testroot/repo/epsilon/new
+	(cd $testroot/repo && git add epsilon/new)
+	git_commit $testroot/repo -m "committing more changes on newbranch"
+
+	local orig_commit1=`git_show_parent_commit $testroot/repo`
+	local orig_commit2=`git_show_head $testroot/repo`
+
+	(cd $testroot/repo && git checkout -q master)
+	echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+	git_commit $testroot/repo -m "committing to zeta on master"
+	local master_commit1=`git_show_head $testroot/repo`
+
+	(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_commit2=`git_show_head $testroot/repo`
+
+	got checkout -c $master_commit1 $testroot/repo $testroot/wt \
+		> /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got rebase newbranch > $testroot/stdout \
+		2> $testroot/stderr)
+
+	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
+
+	echo -n "got: work tree must be updated before it can be " \
+		> $testroot/stderr.expected
+	echo "used to rebase a branch" >> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout)
+	echo "commit $master_commit2 (master)" > $testroot/stdout.expected
+	echo "commit $master_commit1" >> $testroot/stdout.expected
+	echo "commit $initial_commit" >> $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"
+}
+
 run_test test_rebase_basic
 run_test test_rebase_ancestry_check
 run_test test_rebase_continue
@@ -880,3 +950,4 @@ run_test test_rebase_path_prefix
 run_test test_rebase_preserves_logmsg
 run_test test_rebase_no_commits_to_rebase
 run_test test_rebase_forward
+run_test test_rebase_out_of_date