This file is for AI coding assistants (GitHub Copilot, Claude Code, Cursor, OpenAI
Codex, Gemini CLI, Aider, etc.). It captures project-specific knowledge that isn't
obvious from a fresh git clone and which routinely trips up agents. See
CONTRIBUTING.md for coding standards (StyleCop rules, error
handling, exception logging) — do not duplicate those here.
The build scripts (scripts\Build.bat and friends) put build outputs one
level up from the git working tree. The Readme documents the recommended
clone-into-src\ convention, which matches the layout that gvfs clone
creates for end users:
# Recommended (matches Readme.md and CI):
git clone https://github.com/microsoft/VFSForGit C:\Repos\VFSForGit\srcC:\Repos\VFSForGit\
├── src\ ← git working tree (.git, GVFS.sln, all source)
├── out\ ← build output (gitignored — it's outside the working tree)
└── packages\ ← NuGet cache
Cloning without the src\ suffix also works — outputs just land one level
above wherever you cloned. The rest of this document and the project's own
scripts (CI, Readme) use the src\ form, so commands assume it. The
parent directory must be writable (cloning to C:\ itself won't work
because the build can't create C:\out).
All commands below run from the enlistment root (the parent of src\):
cd C:\Repos\VFSForGit # NOT C:\Repos\VFSForGit\srcThis keeps src\... and out\... paths symmetric and matches what
Build.bat does internally.
scripts\Build.bat does a full installer build with NativeAOT publish plus
Inno Setup. That's ~5 minutes minimum, and AOT has no incremental support
— ilc relinks every executable on every dotnet publish. Do not use
Build.bat for dev-loop iteration. Pick the right path for what you're
doing.
For C# changes verified by unit tests only.
dotnet build src\GVFS\GVFS.UnitTests\GVFS.UnitTests.csproj -c Debug
& "out\GVFS.UnitTests\bin\Debug\net10.0-windows10.0.17763.0\win-x64\GVFS.UnitTests.exe" --test Fully.Qualified.Class.Or.MethodSkips dotnet publish, AOT, native C++ projects, payload assembly, installer.
For changes that need the GVFS payload (gvfs.exe, hooks, service) but not
an installer. PublishAot=false skips ilc (~3–4 min saved);
SkipCreateInstaller=true skips Inno Setup (~95 s saved).
GVFS.Payload cascades to its dependencies (GVFS, GVFS.Mount, GVFS.Hooks,
GVFS.Service) via ProjectReference.
Prerequisite: the native C++ projects must already be built. They are
.vcxproj(see Native C++ projects below) anddotnet publishwill not build them for you. If you have not already done aBuild.batonce in this enlistment, build the native projects via VS MSBuild first (or runBuild.batonce to populateout\, then iterate with the commands below). After that they are incremental and only rebuild when their own sources change.
dotnet publish src\GVFS\GVFS.FunctionalTests\GVFS.FunctionalTests.csproj `
-c Debug /p:PublishAot=false
dotnet publish src\GVFS\GVFS.Payload\GVFS.Payload.csproj `
-c Debug /p:PublishAot=false /p:SkipCreateInstaller=true
src\scripts\RunFunctionalTests-Dev.ps1 Debug --test=GVFS.FunctionalTests.Tests.<Namespace>.<Class>.<Method>layout.bat (invoked by GVFS.Payload) xcopys from each project's publish\
or native-output directory — the C# projects do not require AOT, so
PublishAot=false produces a fully functional test payload. The native
hook binaries are copied straight from the vcxproj output.
RunFunctionalTests-Dev.ps1 runs functional tests against the build output
without requiring admin or a system-wide install. It launches the test
service as a console process. Each invocation gets a unique service name
and data dir, so concurrent runs from different worktrees don't collide.
For producing an installable package (testing install/upgrade flows, or
shipping a build). This is the only correct use of Build.bat.
# Build.bat takes (configuration, version, verbosity). The 0. prefix on the
# version tells GVFS to treat this as a development build and skip the
# server-side version check.
$v = & { $n = [DateTime]::Now; "0.2.$($n.ToString('yy'))$($n.DayOfYear.ToString('D3')).$([int]($n.TimeOfDay.TotalSeconds / 2))" }
src\scripts\Build.bat Debug $v minimalInstaller output: out\GVFS.Installers\bin\Debug\win-x64\SetupGVFS.<version>.exe.
These five projects are .vcxproj and require Visual Studio MSBuild with
the C++ workload. Build.bat invokes MSBuild for them automatically; if
you need to rebuild them outside Build.bat, use msbuild, not
dotnet build.
GVFS\GitHooksLoader\GitHooksLoader.vcxprojGVFS\GVFS.NativeTests\GVFS.NativeTests.vcxprojGVFS\GVFS.PostIndexChangedHook\GVFS.PostIndexChangedHook.vcxprojGVFS\GVFS.ReadObjectHook\GVFS.ReadObjectHook.vcxprojGVFS\GVFS.VirtualFileSystemHook\GVFS.VirtualFileSystemHook.vcxproj
These only need to be (re)built when their own sources change; they don't participate in the C# inner-loop paths above.
⚠️ This codebase uses NUnitLite, which supports ONLY--testfor name filtering. The--wherefilter (from NUnit Console) is silently ignored and runs every test.
# ✅ Correct
& "out\GVFS.UnitTests\bin\...\GVFS.UnitTests.exe" --test "GVFS.UnitTests.Common.WorktreeInfoTests"
src\scripts\RunFunctionalTests-Dev.ps1 Debug --test=GVFS.FunctionalTests.Tests.GVFSVerbTests.UnknownVerb
# ❌ Wrong — silently runs the entire suite
& "out\GVFS.UnitTests\bin\...\GVFS.UnitTests.exe" --where "class =~ Worktree"
src\scripts\RunFunctionalTests-Dev.ps1 Debug --where "cat == Smoke"For unit tests, --where is merely annoying (the whole suite runs in
~10 seconds). For functional tests, it's a disaster: each test provisions
its own fresh enlistment, so accidentally running the full suite eats
hours and masks whichever failure you were actually investigating.
--test matches against the fully qualified name (Namespace.Class.Method
or Namespace.Class). Short names like --test=ReproCherryPickRestoreCorruption
silently match nothing and the runner reports "0 tests selected" without
making the typo obvious.
Build.bat checks for out\vcpkg_installed\dynamic\x64-windows-dynamic\bin\ git2.dll as a "vcpkg already installed" marker and skips the vcpkg step
if present. The vcpkg step is the slowest part of a from-scratch build
(several minutes of native compilation), so this caching matters.
Do not manually delete or re-run vcpkg unless you've changed an overlay
port. If you've copied out\ from another enlistment as a build-time
shortcut, vcpkg results come along with it.
See CONTRIBUTING.md for StyleCop rules, error-handling
patterns (TryXxx over exceptions, "fail fast" on data-loss risks),
tracing/logging conventions (Error level reserved for unrecoverable
failures), and the mock:\\ / mock:// URL convention for unit tests.