From: "Omar Polo" Subject: fix duplicated tree item with prefix checkout To: gameoftrees@openbsd.org Date: Fri, 19 Jun 2026 01:08:47 +0200 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