From: Stefan Sperling Subject: fix 'got revert' with path-prefix issue To: gameoftrees@openbsd.org Date: Mon, 21 Sep 2026 14:56:47 +0200 This fixes an edge-case where 'got revert' fails in a work tree which uses a path-prefix, with the following error message: got: fopen: $testroot/wt/epsilon/.cvsignore: Not a directory The conditions which trigger this are as follows: - A path prefix must be set via got checkout -p. - A file which matches the name of the first component of the path prefix must exist in the work tree. This file can be unversioned. - A cherrypick or backout log message ref must exist for this work tree, i.e. 'got cherrypick' or 'got backout' must be run before attempting to revert changes. The error is thrown during the status walk which is supposed to find changes in the work tree which correspond to the log message's commit as recorded by 'got cherrypick' or 'got backout'. The fix is to make this status crawl path-prefix aware by stripping the prefix from all paths returned by the repository-side diff driver. OK? M got/got.c | 31+ 2- M regress/cmdline/revert.sh | 54+ 0- 2 files changed, 85 insertions(+), 2 deletions(-) commit - 81c98775c04265beb18bc4203bca6a627ddfcfac commit + 05b2e2f98dce550750d3b2ac2225ba9f369b151a blob - 590d82ff913f113811ff2c911339e3bd2ccd76a7 blob + 164be77bca30fb8735c3a4607dafcbb0b44b43b3 --- got/got.c +++ got/got.c @@ -9039,12 +9039,16 @@ commit_path_changed_in_worktree(struct wt_commitable_p struct got_repository *repo) { const struct got_error *err; - struct got_pathlist_head paths; + struct got_pathlist_head paths, wt_paths; + struct got_pathlist_head *status_paths; + struct got_pathlist_entry *pe; struct got_commit_object *commit = NULL, *pcommit = NULL; struct got_tree_object *tree = NULL, *ptree = NULL; struct got_object_qid *pid; + const char *prefix; RB_INIT(&paths); + RB_INIT(&wt_paths); err = got_object_open_as_commit(&commit, repo, id); if (err) @@ -9072,7 +9076,31 @@ commit_path_changed_in_worktree(struct wt_commitable_p if (err) goto done; - err = got_worktree_status(worktree, &paths, repo, 0, + prefix = got_worktree_get_path_prefix(worktree); + if (!got_path_is_root_dir(prefix)) { + RB_FOREACH(pe, got_pathlist_head, &paths) { + char *abspath, *wt_path; + + if (asprintf(&abspath, "/%s", pe->path) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + err = got_path_skip_common_ancestor(&wt_path, + prefix, abspath); + free(abspath); + if (err) + goto done; + + err = got_pathlist_insert(NULL, &wt_paths, wt_path, + NULL); + if (err) + goto done; + } + status_paths = &wt_paths; + } else + status_paths = &paths; + + err = got_worktree_status(worktree, status_paths, repo, 0, worktree_has_commitable_path, wcpa, check_cancelled, NULL); if (err && err->code == GOT_ERR_FILE_MODIFIED) { /* @@ -9084,6 +9112,7 @@ commit_path_changed_in_worktree(struct wt_commitable_p done: got_pathlist_free(&paths, GOT_PATHLIST_FREE_ALL); + got_pathlist_free(&wt_paths, GOT_PATHLIST_FREE_PATH); if (commit) got_object_commit_close(commit); if (pcommit) blob - ee4ebfecae595a8fb6d759b45ba4df154fd4aeeb blob + 9d235aa78cacc3cd2a5a524c997b3dc136b46579 --- regress/cmdline/revert.sh +++ regress/cmdline/revert.sh @@ -2078,6 +2078,59 @@ test_revert_partially_staged_file() { test_done "$testroot" "$ret" } +test_revert_backout_with_path_prefix() { + local testroot=`test_init revert_backout_with_path_prefix` + + got checkout $testroot/repo $testroot/wt2 > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + echo "modified zeta" > $testroot/wt2/epsilon/zeta + (cd $testroot/wt2 && got commit -m 'modify zeta' > /dev/null) + local commit_id=`git_show_head $testroot/repo` + + got checkout -p epsilon $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got backout "$commit_id" >/dev/null) + + echo "unversioned file" > $testroot/wt/epsilon + + echo 'R zeta' > $testroot/stdout.expected + + # This used to fail with: + # "got: fopen: $testroot/wt/epsilon/.cvsignore: Not a directory" + (cd $testroot/wt && got revert -R . > $testroot/stdout) + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "modified zeta" > $testroot/content.expected + cat $testroot/wt/zeta > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/content.expected $testroot/content + fi + test_done "$testroot" "$ret" + +} + test_parseargs "$@" run_test test_revert_basic run_test test_revert_rm @@ -2100,3 +2153,4 @@ run_test test_revert_umask run_test test_revert_patch_binary run_test test_revert_staged_file run_test test_revert_partially_staged_file +run_test test_revert_backout_with_path_prefix