From dedaa377c829bf161c8e841b6c614d344e57eee5 Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Wed, 11 Mar 2026 13:50:13 -0700 Subject: [PATCH] worktree: conditionally allow worktree on VFS-enabled repos Add GVFS_SUPPORTS_WORKTREES flag (1<<8) to core.gvfs bitmask. When set, allow git worktree commands to run on VFS-enabled repos instead of blocking them with BLOCK_ON_VFS_ENABLED. Force --no-checkout during worktree add when VFS is active so ProjFS can be mounted before files are projected. Support skip-clean-check marker file in worktree gitdir: if .git/worktrees//skip-clean-check exists, skip the cleanliness check during worktree remove. This allows VFSForGit's pre-command hook to unmount ProjFS after its own status check, then let git proceed without re-checking (which would fail without the virtual filesystem). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- builtin/worktree.c | 25 +++++++++++++++- git.c | 3 +- gvfs.h | 7 +++++ t/t0402-block-command-on-gvfs.sh | 50 +++++++++++++++++++++++++++++++- 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/builtin/worktree.c b/builtin/worktree.c index 0ec68b6e090db8..60106940f71fe0 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -838,6 +838,14 @@ static int add(int ac, const char **av, const char *prefix, if (ac < 1 || ac > 2) usage_with_options(git_worktree_add_usage, options); + /* + * When the virtual file system is active, skip checkout during + * worktree creation. The VFS layer will handle the checkout + * after the worktree structure is set up. + */ + if (gvfs_config_is_set(the_repository, GVFS_USE_VIRTUAL_FILESYSTEM)) + opts.checkout = 0; + path = prefix_filename(prefix, av[0]); branch = ac < 2 ? "HEAD" : av[1]; used_new_branch_options = new_branch || new_branch_force; @@ -1358,6 +1366,21 @@ static int delete_git_work_tree(struct worktree *wt) return ret; } +/* + * Check if a pre-command hook has already verified worktree cleanliness + * and written a marker file to skip git's own check. VFSForGit uses this + * to unmount ProjFS after its own status check; without it, git's status + * call would fail because the virtual filesystem is no longer available. + */ +static int should_skip_clean_check(struct worktree *wt) +{ + char *path = repo_common_path(the_repository, + "worktrees/%s/skip-clean-check", wt->id); + int skip = file_exists(path); + free(path); + return skip; +} + static int remove_worktree(int ac, const char **av, const char *prefix, struct repository *repo UNUSED) { @@ -1397,7 +1420,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix, strbuf_release(&errmsg); if (file_exists(wt->path)) { - if (!force) + if (!force && !should_skip_clean_check(wt)) check_clean_worktree(wt, av[0]); ret |= delete_git_work_tree(wt); diff --git a/git.c b/git.c index e36d3d6d7f1b10..af30930e34fa84 100644 --- a/git.c +++ b/git.c @@ -574,7 +574,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv, struct die("'git %s' is not supported on a GVFS repo", p->cmd); if (!help && p->option & BLOCK_ON_VFS_ENABLED && gvfs_config_is_set(repo, GVFS_USE_VIRTUAL_FILESYSTEM)) - die("'git %s' is not supported when using the virtual file system", p->cmd); + if (strcmp(p->cmd, "worktree") != 0 || !gvfs_config_is_set(repo, GVFS_SUPPORTS_WORKTREES)) + die("'git %s' is not supported when using the virtual file system", p->cmd); if (run_pre_command_hook(the_repository, argv)) die("pre-command hook aborted command"); diff --git a/gvfs.h b/gvfs.h index 26a0617fce661a..f905ec93bf8013 100644 --- a/gvfs.h +++ b/gvfs.h @@ -30,6 +30,13 @@ struct repository; #define GVFS_BLOCK_FILTERS_AND_EOL_CONVERSIONS (1 << 6) #define GVFS_PREFETCH_DURING_FETCH (1 << 7) +/* + * When set, this flag indicates that the VFS layer supports + * git worktrees. This allows `git worktree add/remove` to + * operate on VFS-enabled repositories. + */ +#define GVFS_SUPPORTS_WORKTREES (1 << 8) + #define GVFS_ANY_MASK 0xFFFFFFFF int gvfs_config_is_set(struct repository *r, int mask); diff --git a/t/t0402-block-command-on-gvfs.sh b/t/t0402-block-command-on-gvfs.sh index a8f7fb07a603e2..7405c2f2ec7bd2 100755 --- a/t/t0402-block-command-on-gvfs.sh +++ b/t/t0402-block-command-on-gvfs.sh @@ -27,7 +27,55 @@ not_with_gvfs update-index --index-version 2 not_with_gvfs update-index --skip-worktree not_with_gvfs update-index --no-skip-worktree not_with_gvfs update-index --split-index -not_with_gvfs worktree list + +# worktree is conditionally allowed: blocked when VFS enabled without +# GVFS_SUPPORTS_WORKTREES, but core.gvfs=true sets all bits including +# SUPPORTS_WORKTREES, so we test the blocked case with a specific bitmask. +test_expect_success 'worktree blocked with VFS but without SUPPORTS_WORKTREES' ' + test_config core.gvfs 95 && + test_must_fail git worktree list 2>err && + test_grep "not supported when using the virtual file system" err +' + +# core.gvfs bitmask values: +# GVFS_USE_VIRTUAL_FILESYSTEM = (1 << 3) = 8 +# GVFS_SUPPORTS_WORKTREES = (1 << 8) = 256 +# A typical VFS-enabled repo has core.gvfs=95 (bits 0-4,6). +# Adding SUPPORTS_WORKTREES: 95 + 256 = 351. + +test_expect_success 'setup for worktree tests' ' + test_commit initial +' + +test_expect_success 'worktree allowed when SUPPORTS_WORKTREES bit is set' ' + test_when_finished "git worktree remove --force ../allowed-wt 2>/dev/null; true" && + test_config core.gvfs 351 && + git worktree add ../allowed-wt && + test_path_exists ../allowed-wt/.git +' + +test_expect_success 'worktree add forces --no-checkout when VFS active' ' + test_when_finished "git worktree remove --force ../nocheckout-wt 2>/dev/null; true" && + test_config core.gvfs 351 && + git worktree add ../nocheckout-wt && + test_path_exists ../nocheckout-wt/.git && + ! test_path_exists ../nocheckout-wt/initial.t +' + +test_expect_success 'worktree list works with SUPPORTS_WORKTREES' ' + test_when_finished "git worktree remove --force ../list-wt 2>/dev/null; true" && + test_config core.gvfs 351 && + git worktree add ../list-wt && + git worktree list >out && + grep "list-wt" out +' + +test_expect_success 'worktree remove works with SUPPORTS_WORKTREES' ' + test_config core.gvfs 351 && + git worktree add ../remove-wt && + git worktree remove --force ../remove-wt && + ! test_path_exists ../remove-wt +' test_expect_success 'test gc --auto succeeds when disabled via config' ' test_config core.gvfs true &&