Skip to content

Document why GitProcess does not use Enlistment.DotGitRoot - #2102

Draft
tyrielv wants to merge 1 commit into
microsoft:vnextfrom
tyrielv:tyrielv/worktree-dotgitroot
Draft

Document why GitProcess does not use Enlistment.DotGitRoot#2102
tyrielv wants to merge 1 commit into
microsoft:vnextfrom
tyrielv:tyrielv/worktree-dotgitroot

Conversation

@tyrielv

@tyrielv tyrielv commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

In a linked git worktree, Enlistment.DotGitRoot and the git directory that GitProcess passes as --git-dir point at different locations:

Field Value in a linked worktree
Enlistment.DotGitRoot the shared .git directory of the main repo
GitProcess git dir <worktree>\.git — a file holding gitdir: <shared>/.git/worktrees/<name>

This looks like an inconsistency. It is not. Each value is correct for its own purpose, and the divergence is load-bearing.

Git follows the gitdir: pointer, so the worktree .git file resolves the per-worktree state (HEAD, index, per-worktree refs, reflog) and, through commondir, the shared state (config, objects, packed-refs). DotGitRoot is used for shared state that VFS for Git reads or writes directly on disk (objects, objects/info/alternates, hooks).

Why "aligning" the two would be a regression

Changing GitProcess to read Enlistment.DotGitRoot binds every command to the main worktree. Verified against a real worktree with divergent HEADs (git 2.55):

--git-dir rev-parse --abbrev-ref HEAD log -1 --format=%s HEAD
<worktree>\.git (current behavior) feature onfeature
<shared>\.git (DotGitRoot) main onmaster

So from inside a linked worktree, rev-parse, name-rev, branch, symbolic-ref, update-ref, and read-tree would all silently operate on the wrong worktree.

The second GitProcess(gitBinPath, workingDirectoryRoot) constructor needs no change — it has no Enlistment to consult, and its computed path is already the correct one.

What this PR changes

Nothing functional. It documents the divergence and pins it with tests so a future "cleanup" cannot quietly introduce the regression above.

  • GitProcess.cs — comment explaining why the git dir is deliberately not Enlistment.DotGitRoot; new GitDirectoryPath accessor so the value is testable.
  • GVFSEnlistment.cs — comment recording that DotGitRoot is shared-state-only, and that per-worktree paths must come from Worktree.WorktreeGitDir (as GitIndexPath already does).
  • WorktreeEnlistmentTests.cs — two tests pinning the current values.

Testing

  • Full unit suite: 919 passed, 0 failed, 11 skipped — identical before and after.
  • Mutation-verified: applying the rejected change (GitProcess reading Enlistment.DotGitRoot) fails GitProcessUsesWorktreeGitFileNotSharedGitDir and nothing else, confirming the new test actually pins the behavior.

Note for reviewers

The audit behind this PR also found callers that use DotGitRoot for genuinely per-worktree state, where GitIndexPath already resolves correctly — FileSystemCallbacks.cs (watches the main repo's logs/HEAD), GitIndexGenerator.cs, RepairJobs/GitIndexRepairJob.cs, DehydrateVerb.cs, and FastFetch/CheckoutPrefetcher.cs. Those are real divergences with real blast radius, and are deliberately left out of this PR to keep it a no-op. They will be evaluated separately.

Targets vnext: long-standing behavior, not a 2.0 regression.

In a linked git worktree, Enlistment.DotGitRoot and the git directory that
GitProcess passes as --git-dir point at different locations:

  Enlistment.DotGitRoot -> the shared .git directory of the main repo
  GitProcess git dir    -> "<worktree>\.git", a file holding
                           "gitdir: <shared>/.git/worktrees/<name>"

The two values look inconsistent, but each is correct for its purpose. Git
follows the gitdir: pointer, so the worktree .git file resolves the
per-worktree state (HEAD, index, per-worktree refs, reflog) and, through
commondir, the shared state (config, objects, packed-refs). DotGitRoot is used
for shared state that GVFS reads or writes directly on disk (objects,
objects/info/alternates, hooks).

Making GitProcess use DotGitRoot would be a regression. Every command would
bind to the main worktree. From inside a linked worktree, "rev-parse HEAD" and
"name-rev --name-only HEAD" would report the main worktree's branch and commit.

No behavior changes. This adds comments on both sides of the divergence, a
GitProcess.GitDirectoryPath accessor, and tests that pin the current values.
The new test fails if GitProcess is changed to read Enlistment.DotGitRoot.

Assisted-by: Claude Opus 4.5
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant