From dedaa377c829bf161c8e841b6c614d344e57eee5 Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Wed, 11 Mar 2026 13:50:13 -0700 Subject: [PATCH 1/2] 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 && From 0d078aeb8ea1347eaf2ea816d6ced57ca44c122c Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Mon, 16 Mar 2026 12:55:00 -0700 Subject: [PATCH 2/2] worktree: address PR feedback Move the VFS worktree block from the generic BLOCK_ON_VFS_ENABLED check in run_builtin() to an explicit check in cmd_worktree(). This keeps the worktree entry in the commands table clean (RUN_SETUP only) and makes the VFS guard self-contained in builtin/worktree.c. Guard the skip-clean-check marker behind core_virtualfilesystem so it only takes effect when VFS is active. Consolidate the worktree tests into a single test case that covers add (with --no-checkout verification), list, and remove. Use core.gvfs=true instead of the magic number 351. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- builtin/worktree.c | 10 ++++++- git.c | 5 ++-- t/t0402-block-command-on-gvfs.sh | 49 +++++++++----------------------- 3 files changed, 25 insertions(+), 39 deletions(-) diff --git a/builtin/worktree.c b/builtin/worktree.c index 60106940f71fe0..5d2c458b6b8cf1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -1420,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 && !should_skip_clean_check(wt)) + if (!force && !(core_virtualfilesystem && should_skip_clean_check(wt))) check_clean_worktree(wt, av[0]); ret |= delete_git_work_tree(wt); @@ -1492,6 +1492,14 @@ int cmd_worktree(int ac, ac = parse_options(ac, av, prefix, options, git_worktree_usage, 0); + /* + * Block worktree commands when VFS is active unless the VFS layer + * has signaled worktree support via GVFS_SUPPORTS_WORKTREES. + */ + if (gvfs_config_is_set(the_repository, GVFS_USE_VIRTUAL_FILESYSTEM) && + !gvfs_config_is_set(the_repository, GVFS_SUPPORTS_WORKTREES)) + die("'git worktree' is not supported when using the virtual file system"); + prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; diff --git a/git.c b/git.c index af30930e34fa84..08661187ecb0e5 100644 --- a/git.c +++ b/git.c @@ -574,8 +574,7 @@ 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)) - 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); + 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"); @@ -762,7 +761,7 @@ static struct cmd_struct commands[] = { #ifndef WITH_BREAKING_CHANGES { "whatchanged", cmd_whatchanged, RUN_SETUP | DEPRECATED }, #endif - { "worktree", cmd_worktree, RUN_SETUP | BLOCK_ON_VFS_ENABLED }, + { "worktree", cmd_worktree, RUN_SETUP }, { "write-tree", cmd_write_tree, RUN_SETUP }, }; diff --git a/t/t0402-block-command-on-gvfs.sh b/t/t0402-block-command-on-gvfs.sh index 7405c2f2ec7bd2..d2ef75b9f62c71 100755 --- a/t/t0402-block-command-on-gvfs.sh +++ b/t/t0402-block-command-on-gvfs.sh @@ -29,52 +29,31 @@ not_with_gvfs update-index --no-skip-worktree not_with_gvfs update-index --split-index # 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. +# GVFS_SUPPORTS_WORKTREES. 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 'worktree operations work when SUPPORTS_WORKTREES is set' ' + test_commit initial && -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 -' + # Use core.gvfs=true which sets all bits including SUPPORTS_WORKTREES. + test_config core.gvfs true && -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 -' + # add: succeeds, forces --no-checkout (no initial.t on disk) + git worktree add ../vfs-wt && + test_path_exists ../vfs-wt/.git && + ! test_path_exists ../vfs-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 && + # list: shows the worktree git worktree list >out && - grep "list-wt" out -' + grep "vfs-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 + # remove: cleans up + git worktree remove --force ../vfs-wt && + ! test_path_exists ../vfs-wt ' test_expect_success 'test gc --auto succeeds when disabled via config' '