git worktree support for VFSForGit#1911
Open
tyrielv wants to merge 7 commits intomicrosoft:masterfrom
Open
Conversation
2ff5914 to
ab088d8
Compare
Add TryGetWorktreeInfo() to detect git worktrees by checking for a .git file (not directory) and reading its gitdir pointer. WorktreeInfo carries the worktree name, paths, and derived pipe suffix. Add GVFSEnlistment.CreateForWorktree() factory that constructs an enlistment with worktree-specific paths: WorkingDirectoryRoot points to the worktree, DotGitRoot uses the shared .git directory, and NamedPipeName includes a worktree-specific suffix. Add WorktreeCommandParser to extract subcommands and positional args from git worktree hook arguments. Add GVFS_SUPPORTS_WORKTREES to GitCoreGVFSFlags enum. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update GetGVFSPipeName() in common.windows.cpp to detect when running inside a git worktree. If the current directory contains a .git file (not directory), read the gitdir pointer, extract the worktree name, and append a _WT_<NAME> suffix to the pipe name. This single change makes all native hooks (read-object, post-index-changed, virtual-filesystem) connect to the correct worktree-specific GVFS mount process. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MountVerb: detect worktree paths via TryGetWorktreeInfo(), create worktree-specific GVFSEnlistment, check worktree-specific pipe for already-mounted state, register worktrees by their own path (not the primary enlistment root). UnmountVerb: resolve worktree pipe name for unmount, unregister by worktree path so the primary enlistment registration is not affected. InProcessMount: bootstrap worktree metadata (.gvfs/ inside worktree gitdir), set absolute paths for core.hookspath and core.virtualfilesystem, skip hook installation for worktree mounts (hooks are shared via hookspath), set GVFS_SUPPORTS_WORKTREES bit. GitIndexProjection/FileSystemCallbacks: use worktree-specific index path instead of assuming primary .git/index. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
153f071 to
07dbad0
Compare
1 task
In the managed pre/post-command hooks, intercept git worktree
subcommands to transparently manage GVFS mounts:
add: Post-command runs 'git checkout -f' to create the index,
then 'gvfs mount' to start ProjFS projection.
remove: Pre-command checks for uncommitted changes while ProjFS
is alive, writes skip-clean-check marker, unmounts.
Post-command remounts if removal failed (dir + .git exist).
move: Pre-command unmounts old path, post-command mounts new.
prune: Post-command cleans stale worktree metadata.
Add WorktreeCommandParser reference to GVFS.Hooks.csproj.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Unit tests: WorktreeInfoTests — TryGetWorktreeInfo detection, pipe suffix WorktreeEnlistmentTests — CreateForWorktree path mappings WorktreeCommandParserTests — subcommand and arg extraction Functional tests: WorktreeTests — end-to-end add/list/remove with live GVFS mount GitBlockCommandsTests — update existing test for conditional block Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
07dbad0 to
e4314b3
Compare
ProjFS cannot handle nested virtualization roots. Add a pre-command check that blocks 'git worktree add' when the target path is inside the primary enlistment's working directory. Add IsPathInsideDirectory() utility to GVFSEnlistment.Shared.cs with unit tests for path matching (case-insensitive, sibling paths allowed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Before removing a worktree, probe the named pipe to verify the GVFS mount is running. If not mounted: - Without --force: error with guidance to mount or use --force - With --force: skip unmount and let git proceed Refactor UnmountWorktree to accept a pre-resolved WorktreeInfo to avoid redundant TryGetWorktreeInfo calls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
winstliu
reviewed
Mar 13, 2026
| /// Returns true if <paramref name="path"/> is equal to or a subdirectory of | ||
| /// <paramref name="directory"/> (case-insensitive). | ||
| /// </summary> | ||
| public static bool IsPathInsideDirectory(string path, string directory) |
Member
There was a problem hiding this comment.
You may want to make sure this is security-hardened. While it doesn't look like it's being used in security-critical contexts now, something else in the future that is security-critical might decide to use this.
(Mostly: You can pass in D:\os, D:\os\.. to trivially bypass.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
git worktreesupport for VFSForGitMotivation
VS Code Copilot Chat background agents use
git worktreeto create isolated working directories for code editing. On VFSForGit enlistments,git worktreeis blocked byBLOCK_ON_VFS_ENABLEDbecause the VFS layer had no support for multiple working trees. This PR enables worktree support by running an independent GVFS mount (ProjFS instance) per worktree.Companion PR
microsoft/git: tyrielv/gvfs-worktree (1 commit, 4 files, +82 lines)
GVFS_SUPPORTS_WORKTREES(1<<8) to thecore.gvfsbitmaskgit worktreewhen the bit is set--no-checkoutwhen VFS is active (GVFS handles checkout after mount)skip-clean-checkmarker for pre-unmount cleanliness verificationt0402-block-command-on-gvfs.shDesign
Each worktree gets its own:
GVFS_<ROOT>_WT_<NAME>) for IPC with hooks.git/worktrees/<name>/.gvfs/Worktrees share:
core.hookspath(absolute path).git/configvia git'scommondirmechanismHow it works
git worktree add <path>→ git creates the worktree structure with--no-checkout(forced by the git-side change)worktree add→ runsgit checkout -fto create the index → runsgvfs mount <path>.gitfile →gitdir:→.git/worktrees/<name>/) → creates worktree-specificGVFSEnlistment→ starts ProjFS → files appear_WT_<NAME>pipe suffixgit worktree remove→ pre-command hook unmounts ProjFS → git deletes the directoryCommits (review in order)
TryGetWorktreeInfo()detection logic,CreateForWorktree()path mappings.gitfile detection inGetGVFSPipeName(), pipe suffix derivationMountVerb/UnmountVerbworktree detection, service registration by worktree path,InProcessMountmetadata bootstrap, index path fixesskip-clean-checkflow, remount-on-failure recovery,WorktreeCommandParserarg extractionTesting done
git worktree add— auto-mounts, files visible via ProjFSgit worktree list— shows primary + worktreesgit worktree move— unmounts old, mounts newgit worktree remove— unmounts, cleans upgit worktree prune— cleans stale entriesgvfs service --unmount-all/--mount-all— worktrees survive the cyclet0402) — 18/18 passKnown limitations
worktree removepartially succeeds (ProjFS unmounted but directory deletion fails due to locked file), the worktree is left in a degraded state. The post-command hook attempts remount only if both the directory and.gitfile still exist.