Commit 916f288c7e2f6e7bcc2643b8287a4b32f518899f

Stefan Sperling 2019-07-30T11:30:20

prevent 'got commit' on branches outside "refs/heads/" (the only exception is the work tree's temporary histedit branch)

diff --git a/got/got.1 b/got/got.1
index 3412f52..fb33529 100644
--- a/got/got.1
+++ b/got/got.1
@@ -522,6 +522,9 @@ opens a temporary file in an editor where a log message can be written.
 .Pp
 .Cm got commit
 will refuse to run if certain preconditions are not met.
+If the work tree's current branch is not in the
+.Dq refs/heads/
+reference namespace, new commits may not be created on this branch.
 Local changes may only be committed if they are based on file content
 found in the most recent commit on the work tree's branch.
 If a path is found to be out of date,
diff --git a/got/got.c b/got/got.c
index dc2cda4..7c2acec 100644
--- a/got/got.c
+++ b/got/got.c
@@ -3228,7 +3228,7 @@ cmd_commit(int argc, char *argv[])
 	const char *got_author = getenv("GOT_AUTHOR");
 	struct collect_commit_logmsg_arg cl_arg;
 	char *editor = NULL;
-	int ch, rebase_in_progress;
+	int ch, rebase_in_progress, histedit_in_progress;
 	struct got_pathlist_head paths;
 
 	TAILQ_INIT(&paths);
@@ -3275,6 +3275,11 @@ cmd_commit(int argc, char *argv[])
 		goto done;
 	}
 
+	error = got_worktree_histedit_in_progress(&histedit_in_progress,
+	    worktree);
+	if (error)
+		goto done;
+
 	error = get_worktree_paths_from_argv(&paths, argc, argv, worktree);
 	if (error)
 		goto done;
@@ -3299,10 +3304,13 @@ cmd_commit(int argc, char *argv[])
 	cl_arg.cmdline_log = logmsg;
 	cl_arg.worktree_path = got_worktree_get_root_path(worktree);
 	cl_arg.branch_name = got_worktree_get_head_ref_name(worktree);
-	if (strncmp(cl_arg.branch_name, "refs/", 5) == 0)
-		cl_arg.branch_name += 5;
-	if (strncmp(cl_arg.branch_name, "heads/", 6) == 0)
-		cl_arg.branch_name += 6;
+	if (!histedit_in_progress) {
+		if (strncmp(cl_arg.branch_name, "refs/heads/", 11) != 0) {
+			error = got_error(GOT_ERR_COMMIT_BRANCH);
+			goto done;
+		}
+		cl_arg.branch_name += 11;
+	}
 	cl_arg.repo_path = got_repo_get_path(repo);
 	cl_arg.logmsg_path = NULL;
 	error = got_worktree_commit(&id, worktree, &paths, got_author, NULL,
diff --git a/include/got_error.h b/include/got_error.h
index 37595c5..f37c253 100644
--- a/include/got_error.h
+++ b/include/got_error.h
@@ -113,6 +113,7 @@
 #define GOT_ERR_HISTEDIT_CMD	97
 #define GOT_ERR_HISTEDIT_PATH	98
 #define GOT_ERR_NO_MERGED_PATHS 99
+#define GOT_ERR_COMMIT_BRANCH	100
 
 static const struct got_error {
 	int code;
@@ -227,6 +228,8 @@ static const struct got_error {
 	{ GOT_ERR_HISTEDIT_PATH, "cannot edit branch history which contains "
 	    "changes outside of this work tree's path prefix" },
 	{ GOT_ERR_NO_MERGED_PATHS, "empty list of merged paths" },
+	{ GOT_ERR_COMMIT_BRANCH, "will not commit to a branch outside the "
+	    "\"refs/heads/\" reference namespace" },
 };
 
 /*
diff --git a/regress/cmdline/commit.sh b/regress/cmdline/commit.sh
index 579f23d..55bad57 100755
--- a/regress/cmdline/commit.sh
+++ b/regress/cmdline/commit.sh
@@ -471,6 +471,52 @@ function test_commit_selected_paths {
 	test_done "$testroot" "$ret"
 }
 
+function test_commit_outside_refs_heads {
+	local testroot=`test_init commit_outside_refs_heads`
+
+	got ref -r $testroot/repo refs/remotes/origin/master master
+
+	got checkout -b refs/remotes/origin/master \
+	    $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified alpha" > $testroot/wt/alpha
+
+	(cd $testroot/wt && got commit -m 'change alpha' \
+		> $testroot/stdout 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "commit succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	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: will not commit to a branch outside the " \
+		> $testroot/stderr.expected
+	echo '"refs/heads/" reference namespace' \
+		>> $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_commit_basic
 run_test test_commit_new_subdir
 run_test test_commit_subdir
@@ -483,3 +529,4 @@ run_test test_commit_added_and_modified_in_same_dir
 run_test test_commit_path_prefix
 run_test test_commit_dir_path
 run_test test_commit_selected_paths
+run_test test_commit_outside_refs_heads