Commit Diff


commit - 69844fbab193c41fa361eefe874f37ed2f7d26a3
commit + 64c6d99023a672f220d1de2662dfe52300f51ea2
blob - 1780abc217ffcc573874381bf38a884d642ed56e
blob + f5918cd2aab9138de84e3108140aac04a83037df
--- got/got.1
+++ got/got.1
@@ -581,6 +581,10 @@ to a single base commit with
 .Cm got update .
 If the work tree contains local changes, these changes must be committed
 or reverted first.
+If the
+.Ar branch
+contains changes to files outside of the work tree's path prefix,
+the work tree cannot be used to rebase this branch.
 .Pp
 The
 .Cm got update
blob - 178e89a6d7544bc1ba03c425048f3307dd462312
blob + 26e1d49401834d67cbff109f6d3e09cd46be8449
--- got/got.c
+++ got/got.c
@@ -3309,6 +3309,73 @@ rebase_commit(struct got_worktree *worktree, struct go
 done:
 	got_object_commit_close(commit);
 	return error;
+}
+
+struct check_path_prefix_arg {
+	const char *path_prefix;
+	size_t len;
+};
+
+static const struct got_error *
+check_path_prefix(void *arg, struct got_blob_object *blob1,
+    struct got_blob_object *blob2, struct got_object_id *id1,
+    struct got_object_id *id2, const char *path1, const char *path2,
+    struct got_repository *repo)
+{
+	struct check_path_prefix_arg *a = arg;
+
+	if ((path1 && !got_path_is_child(path1, a->path_prefix, a->len)) ||
+	    (path2 && !got_path_is_child(path2, a->path_prefix, a->len)))
+		return got_error(GOT_ERR_REBASE_PATH);
+
+	return NULL;
+}
+
+static const struct got_error *
+rebase_check_path_prefix(struct got_object_id *parent_id,
+    struct got_object_id *commit_id, const char *path_prefix,
+    struct got_repository *repo)
+{
+	const struct got_error *err;
+	struct got_tree_object *tree1 = NULL, *tree2 = NULL;
+	struct got_commit_object *commit = NULL, *parent_commit = NULL;
+	struct check_path_prefix_arg cpp_arg;
+
+	if (got_path_is_root_dir(path_prefix))
+		return NULL;
+
+	err = got_object_open_as_commit(&commit, repo, commit_id);
+	if (err)
+		goto done;
+
+	err = got_object_open_as_commit(&parent_commit, repo, parent_id);
+	if (err)
+		goto done;
+
+	err = got_object_open_as_tree(&tree1, repo,
+	    got_object_commit_get_tree_id(parent_commit));
+	if (err)
+		goto done;
+
+	err = got_object_open_as_tree(&tree2, repo,
+	    got_object_commit_get_tree_id(commit));
+	if (err)
+		goto done;
+
+	cpp_arg.path_prefix = path_prefix;
+	cpp_arg.len = strlen(path_prefix);
+	err = got_diff_tree(tree1, tree2, "", "", repo, check_path_prefix,
+	    &cpp_arg);
+done:
+	if (tree1)
+		got_object_tree_close(tree1);
+	if (tree2)
+		got_object_tree_close(tree2);
+	if (commit)
+		got_object_commit_close(commit);
+	if (parent_commit)
+		got_object_commit_close(parent_commit);
+	return err;
 }
 
 static const struct got_error *
@@ -3487,6 +3554,11 @@ cmd_rebase(int argc, char *argv[])
 			if (error)
 				goto done;
 		} else {
+			error = rebase_check_path_prefix(parent_id, commit_id,
+			    got_worktree_get_path_prefix(worktree), repo);
+			if (error)
+				goto done;
+
 			error = got_object_qid_alloc(&qid, commit_id);
 			if (error)
 				goto done;
blob - 4a4ef0bdd9c32b7b02ae4cccbc6f721e54dfcc43
blob + 48ede3e31fd5acad44260af32cff4957fb97fdad
--- include/got_error.h
+++ include/got_error.h
@@ -102,6 +102,7 @@
 #define GOT_ERR_EMPTY_REBASE	86
 #define GOT_ERR_REBASE_COMMITID	87
 #define GOT_ERR_REBASING	88
+#define GOT_ERR_REBASE_PATH	89
 
 static const struct got_error {
 	int code;
@@ -201,6 +202,8 @@ static const struct got_error {
 	{ GOT_ERR_REBASE_COMMITID,"rebase commit ID mismatch" },
 	{ GOT_ERR_REBASING,	"a rebase operation is in progress in this "
 	    "work tree and must be continued or aborted first" },
+	{ GOT_ERR_REBASE_PATH,	"cannot rebase branch which contains "
+	    "changes outside of this work tree's path prefix" },
 };
 
 /*
blob - 91b9ba8be58e42753c9f676acbdaa9514e14bddb
blob + 7ea64d7ebe0847c750649789dc27c2f0d8a27428
--- regress/cmdline/rebase.sh
+++ regress/cmdline/rebase.sh
@@ -585,7 +585,52 @@ function test_rebase_in_progress {
 			return 1
 		fi
 	done
+
+	test_done "$testroot" "$ret"
+}
+
+function test_rebase_path_prefix {
+	local testroot=`test_init rebase_path_prefix`
+
+	(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"
+
+	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_commit=`git_show_head $testroot/repo`
+
+	got checkout -p epsilon $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: cannot rebase branch which contains changes outside " \
+		> $testroot/stderr.expected
+	echo "of this work tree's path prefix" >> $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"
 }
 
@@ -595,3 +640,4 @@ run_test test_rebase_continue
 run_test test_rebase_abort
 run_test test_rebase_no_op_change
 run_test test_rebase_in_progress
+run_test test_rebase_path_prefix