"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
"Omar Polo" <op@omarpolo.com>
Subject:
fix duplicated tree item with prefix checkout
To:
gameoftrees@openbsd.org
Date:
Fri, 19 Jun 2026 01:08:47 +0200

Download raw body.

Thread
Hello =)

I've noticed that if I include a trailing '/' when checking out a
portion of the tree, then `got add' might fail on subdirectories.

The underlying issue is that we end up with two consecutive '//' which
messes up the logic for commit, and we end up internally trying to add
twice (or more) the subdirectories, which fails with a cryptic
'duplicated tree item'.

Diff below improves the error message, add a regression test for this
case, and hopefully fixes it in a decent way ;)

(if okay, i'll commit the got_error_path part in a separate commit)

diff /home/op/w/got
path + /home/op/w/got
commit - e3df3fa2b325547b4c69fcb62527422eb7d5f108
blob - 7b2602c1b70e154f1f39d3222ca03af18120b5ff
file + got/got.c
--- got/got.c
+++ got/got.c
@@ -3189,6 +3189,7 @@ cmd_checkout(int argc, char *argv[])
 			allow_nonempty = 1;
 			break;
 		case 'p':
+			got_path_strip_trailing_slashes(optarg);
 			path_prefix = optarg;
 			break;
 		case 'q':
commit - e3df3fa2b325547b4c69fcb62527422eb7d5f108
blob - 2f2919d37662afe63851b5b51815d5022a69051e
file + lib/worktree.c
--- lib/worktree.c
+++ lib/worktree.c
@@ -6088,7 +6088,7 @@ insert_tree_entry(struct got_tree_entry *new_te,
 	if (err)
 		return err;
 	if (new_pe == NULL)
-		return got_error(GOT_ERR_TREE_DUP_ENTRY);
+		return got_error_path(new_te->name, GOT_ERR_TREE_DUP_ENTRY);
 	return NULL;
 }
 
@@ -6845,7 +6845,6 @@ got_worktree_commit(struct got_object_id **new_commit_
 		    head_commit_id, repo, GOT_ERR_COMMIT_OUT_OF_DATE);
 		if (err)
 			goto done;
-
 	}
 
 	err = commit_worktree(new_commit_id, &commitable_paths,
commit - e3df3fa2b325547b4c69fcb62527422eb7d5f108
blob - 997d20f28754d7e0afc5af0f4375e2f0d19ed2d2
file + lib/worktree_open.c
--- lib/worktree_open.c
+++ lib/worktree_open.c
@@ -195,6 +195,8 @@ open_worktree(struct got_worktree **worktree, const ch
 	    GOT_WORKTREE_PATH_PREFIX);
 	if (err)
 		goto done;
+	if (strcmp((*worktree)->path_prefix, "/") != 0)
+		got_path_strip_trailing_slashes((*worktree)->path_prefix);
 
 	err = read_meta_file(&base_commit_id_str, path_meta,
 	    GOT_WORKTREE_BASE_COMMIT);
commit - e3df3fa2b325547b4c69fcb62527422eb7d5f108
blob - c8651ab26909a5ce8ff104fcc69b1029cc18605e
file + regress/cmdline/commit.sh
--- regress/cmdline/commit.sh
+++ regress/cmdline/commit.sh
@@ -107,6 +107,46 @@ test_commit_subdir() {
 	test_done "$testroot" "$ret"
 }
 
+test_commit_subdirs() {
+	local testroot=`test_init commit_subdir`
+
+	got checkout -p epsilon/ $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	mkdir -p $testroot/wt/foo/bar
+	mkdir -p $testroot/wt/foo/baz
+
+	echo omega > $testroot/wt/foo/bar/omega
+	echo kappa > $testroot/wt/foo/bar/kappa
+
+	(cd $testroot/wt && got add -R foo >/dev/null)
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "got add failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && \
+		got commit -m 'test commit_subdirs' > $testroot/stdout)
+
+	local head_rev=`git_show_head $testroot/repo`
+	echo "A  foo/bar/kappa" >> $testroot/stdout.expected
+	echo "A  foo/bar/omega" >> $testroot/stdout.expected
+	echo "Created commit $head_rev" >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 test_commit_single_file() {
 	local testroot=`test_init commit_single_file`
 
@@ -2117,6 +2157,7 @@ test_parseargs "$@"
 run_test test_commit_basic
 run_test test_commit_new_subdir
 run_test test_commit_subdir
+run_test test_commit_subdirs
 run_test test_commit_single_file
 run_test test_commit_out_of_date
 run_test test_commit_added_subdirs