From: Stefan Sperling Subject: fix revert and remove with missing directories To: gameoftrees@openbsd.org Date: Tue, 11 Apr 2023 14:12:49 +0200 When directories in a work tree are missing from disk it is currently impossible to remove or revert files within the directory. With the patch below, 'got remove' will remove the missing files from version control and 'got revert' will restore missing the files. robert@ noticed the 'got revert' issue. The rm.sh tests made the same issue in 'got remove' visible once I had fixed the revert command. Ok? ----------------------------------------------- make 'got revert' and 'got rm' work on non-existent directories problem found by robert@ diff cc88020e952af813c1e01b91ab6516969562e972 0ef3ee5b4259299d2bef17091d336aed268c746f commit - cc88020e952af813c1e01b91ab6516969562e972 commit + 0ef3ee5b4259299d2bef17091d336aed268c746f blob - 9e87414b038cb6aa0ef965f162999f1108c5d82a blob + c6f46c807bd40c15c0cf2c5ba8b5fc399d5d6d88 --- lib/worktree.c +++ lib/worktree.c @@ -3879,7 +3879,74 @@ static const struct got_error * return err; } +struct find_missing_children_args { + const char *parent_path; + size_t parent_len; + struct got_pathlist_head *children; + got_cancel_cb cancel_cb; + void *cancel_arg; + +}; + static const struct got_error * +find_missing_children(void *arg, struct got_fileindex_entry *ie) +{ + const struct got_error *err = NULL; + struct find_missing_children_args *a = arg; + + if (a->cancel_cb) { + err = a->cancel_cb(a->cancel_arg); + if (err) + return err; + } + + if (got_path_is_child(ie->path, a->parent_path, a->parent_len)) + err = got_pathlist_append(a->children, ie->path, NULL); + + return err; +} + +static const struct got_error * +report_children(struct got_pathlist_head *children, + struct got_worktree *worktree, struct got_fileindex *fileindex, + struct got_repository *repo, int is_root_dir, int report_unchanged, + struct got_pathlist_head *ignores, int no_ignores, + got_worktree_status_cb status_cb, void *status_arg, + got_cancel_cb cancel_cb, void *cancel_arg) +{ + const struct got_error *err = NULL; + struct got_pathlist_entry *pe; + char *ondisk_path = NULL; + + TAILQ_FOREACH(pe, children, entry) { + if (cancel_cb) { + err = cancel_cb(cancel_arg); + if (err) + break; + } + + if (asprintf(&ondisk_path, "%s%s%s", worktree->root_path, + !is_root_dir ? "/" : "", pe->path) == -1) { + err = got_error_from_errno("asprintf"); + ondisk_path = NULL; + break; + } + + err = report_single_file_status(pe->path, ondisk_path, + fileindex, status_cb, status_arg, repo, report_unchanged, + ignores, no_ignores); + if (err) + break; + + free(ondisk_path); + ondisk_path = NULL; + } + + free(ondisk_path); + return err; +} + +static const struct got_error * worktree_status(struct got_worktree *worktree, const char *path, struct got_fileindex *fileindex, struct got_repository *repo, got_worktree_status_cb status_cb, void *status_arg, @@ -3891,10 +3958,11 @@ worktree_status(struct got_worktree *worktree, const c struct got_fileindex_diff_dir_cb fdiff_cb; struct diff_dir_cb_arg arg; char *ondisk_path = NULL; - struct got_pathlist_head ignores; + struct got_pathlist_head ignores, missing_children; struct got_fileindex_entry *ie; TAILQ_INIT(&ignores); + TAILQ_INIT(&missing_children); if (asprintf(&ondisk_path, "%s%s%s", worktree->root_path, path[0] ? "/" : "", path) == -1) @@ -3906,6 +3974,17 @@ worktree_status(struct got_worktree *worktree, const c fileindex, status_cb, status_arg, repo, report_unchanged, &ignores, no_ignores); goto done; + } else { + struct find_missing_children_args fmca; + fmca.parent_path = path; + fmca.parent_len = strlen(path); + fmca.children = &missing_children; + fmca.cancel_cb = cancel_cb; + fmca.cancel_arg = cancel_arg; + err = got_fileindex_for_each_entry_safe(fileindex, + find_missing_children, &fmca); + if (err) + goto done; } fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); @@ -3920,9 +3999,21 @@ worktree_status(struct got_worktree *worktree, const c if (err) goto done; } - err = report_single_file_status(path, ondisk_path, - fileindex, status_cb, status_arg, repo, - report_unchanged, &ignores, no_ignores); + if (TAILQ_EMPTY(&missing_children)) { + err = report_single_file_status(path, + ondisk_path, fileindex, + status_cb, status_arg, repo, + report_unchanged, &ignores, no_ignores); + if (err) + goto done; + } else { + err = report_children(&missing_children, + worktree, fileindex, repo, + (path[0] == '\0'), report_unchanged, + &ignores, no_ignores, + status_cb, status_arg, + cancel_cb, cancel_arg); + } } } else { fdiff_cb.diff_old_new = status_old_new; blob - 3ae6b968f8817e9efb7b1abb879557fe19622e08 blob + dcea98961cf673717907f69c3761b59218c16e1a --- regress/cmdline/revert.sh +++ regress/cmdline/revert.sh @@ -369,6 +369,41 @@ test_revert_patch() { test_done "$testroot" "$ret" } +test_revert_missing_directory() { + local testroot=`test_init revert_missing_directory` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + + rm -r $testroot/wt/epsilon + + (cd $testroot/wt && got revert -R epsilon > $testroot/stdout) + + echo 'R epsilon/zeta' >> $testroot/stdout.expected + 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 "zeta" > $testroot/content.expected + cat $testroot/wt/epsilon/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_revert_patch() { local testroot=`test_init revert_patch` @@ -1533,6 +1568,7 @@ run_test test_revert_patch run_test test_revert_no_arguments run_test test_revert_directory run_test test_revert_directory_unknown +run_test test_revert_missing_directory run_test test_revert_patch run_test test_revert_patch_added run_test test_revert_patch_removed blob - b48ce4011c7e953f442dffb07e92ceeb2fc2a62b blob + 6d656a615120185ccc425dcf94d85399757b7de5 --- regress/cmdline/rm.sh +++ regress/cmdline/rm.sh @@ -648,7 +648,7 @@ test_rm_nonexistent_directory() { return 1 fi - echo "got: epsilon: No such file or directory" \ + echo "got: epsilon/zeta: No such file or directory" \ > $testroot/stderr.expected cmp -s $testroot/stderr.expected $testroot/stderr ret=$? @@ -668,7 +668,7 @@ test_rm_nonexistent_directory() { return 1 fi - echo -n '' > $testroot/stdout.expected + echo 'D epsilon/zeta' > $testroot/stdout.expected cmp -s $testroot/stdout.expected $testroot/stdout ret=$? if [ $ret -ne 0 ]; then