Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions GVFS/GVFS.Common/GVFSEnlistment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ private GVFSEnlistment(string enlistmentRoot, string gitBinPath, GitAuthenticati
// Override DotGitRoot to point to the shared .git directory.
// The base constructor sets it to WorkingDirectoryBackingRoot/.git
// which is a file (not directory) in worktrees.
//
// NOTE: DotGitRoot is the SHARED git directory, and is used for shared state that GVFS
// reads or writes directly on disk (objects, objects/info/alternates, hooks). It is
// deliberately NOT what GitProcess passes as --git-dir; GitProcess uses the worktree's own
// ".git" file so git resolves per-worktree HEAD/index/refs as well as the shared state.
// See GitProcess's constructor. Per-worktree paths that GVFS touches directly must come
// from Worktree.WorktreeGitDir (see GitIndexPath), never from DotGitRoot.
this.DotGitRoot = worktreeInfo.SharedGitDir;

this.DotGVFSRoot = Path.Combine(worktreeInfo.WorktreeGitDir, GVFSPlatform.Instance.Constants.DotGVFSRoot);
Expand Down
26 changes: 26 additions & 0 deletions GVFS/GVFS.Common/Git/GitProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,36 @@ public GitProcess(string gitBinPath, string workingDirectoryRoot)

if (this.workingDirectoryRoot != null)
{
// This is deliberately NOT Enlistment.DotGitRoot. In a linked worktree those two
// values differ, and each is correct for its own purpose:
//
// Enlistment.DotGitRoot -> the SHARED .git directory of the main repo. GVFS uses it
// for shared state it manages directly on disk (objects,
// objects/info/alternates, hooks).
// this.dotGitRoot -> "<workingDirectoryRoot>\.git", which in a linked worktree
// is a FILE containing "gitdir: <shared>/.git/worktrees/<name>".
//
// We pass this.dotGitRoot as --git-dir. Git follows the gitdir: pointer, so it resolves
// BOTH the per-worktree state (HEAD, index, per-worktree refs, reflog) and, via the
// commondir file, the shared state (config, objects, packed-refs). Passing
// Enlistment.DotGitRoot instead would silently bind every command to the MAIN worktree's
// HEAD and index -- e.g. "rev-parse HEAD" and "name-rev --name-only HEAD" would report the
// main worktree's branch and commit while running inside a linked worktree.
this.dotGitRoot = Path.Combine(this.workingDirectoryRoot, GVFSConstants.DotGit.Root);
}
}

/// <summary>
/// The path passed as --git-dir to every InvokeGitAgainstDotGitFolder call.
/// In a linked worktree this is the worktree's own .git file, NOT
/// <see cref="Enlistment.DotGitRoot"/> (the shared .git directory). See the constructor
/// for why the two intentionally differ.
/// </summary>
public string GitDirectoryPath
{
get { return this.dotGitRoot; }
}

public static string ExpireTimeDateString
{
get
Expand Down
26 changes: 26 additions & 0 deletions GVFS/GVFS.UnitTests/Common/WorktreeEnlistmentTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GVFS.Common;
using GVFS.Common.Git;
using GVFS.Tests.Should;
using NUnit.Framework;
using System.IO;
Expand Down Expand Up @@ -91,6 +92,31 @@ public void DotGitRootPointsToSharedGitDir()
enlistment.DotGitRoot.ShouldEqual(this.sharedGitDir);
}

[TestCase]
public void GitProcessUsesWorktreeGitFileNotSharedGitDir()
{
// GitProcess passes its own git dir as --git-dir. For a worktree that must be the
// worktree's ".git" file, which git resolves to both the per-worktree state
// (HEAD, index, refs) and, through commondir, the shared state (config, objects).
// Using DotGitRoot (the shared .git directory) would bind every command to the
// MAIN worktree's HEAD and index instead.
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GitProcess gitProcess = new GitProcess(enlistment);

gitProcess.GitDirectoryPath.ShouldEqual(Path.Combine(this.worktreePath, ".git"));
gitProcess.GitDirectoryPath.ShouldNotEqual(enlistment.DotGitRoot);
}

[TestCase]
public void WorkingDirectoryBackingRootIsWorktreePath()
{
// GitProcess derives its --git-dir from WorkingDirectoryBackingRoot. If the worktree
// constructor ever redirected this to the primary enlistment, GitProcess would silently
// start operating on the main worktree.
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
enlistment.WorkingDirectoryBackingRoot.ShouldEqual(this.worktreePath);
}

[TestCase]
public void WorkingDirectoryRootIsWorktreePath()
{
Expand Down
Loading