{"owner":"microsoft","repo":"VFSForGit","hasSkills":true,"totalSkillsCount":1,"totalTokensCount":3305,"categories":["root-instruction"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# Agent instructions for VFS for Git\r\n\r\nThis file is for AI coding assistants (GitHub Copilot, Claude Code, Cursor, OpenAI\r\nCodex, Gemini CLI, Aider, etc.). It captures project-specific knowledge that isn't\r\nobvious from a fresh `git clone` and which routinely trips up agents. See\r\n[CONTRIBUTING.md](CONTRIBUTING.md) for coding standards (StyleCop rules, error\r\nhandling, exception logging) — do not duplicate those here.\r\n\r\n## Repository layout\r\n\r\nThe build scripts (`scripts\\Build.bat` and friends) put build outputs **one\r\nlevel up** from the git working tree. The Readme documents the recommended\r\nclone-into-`src\\` convention, which matches the layout that `gvfs clone`\r\ncreates for end users:\r\n\r\n```powershell\r\n# Recommended (matches Readme.md and CI):\r\ngit clone https://github.com/microsoft/VFSForGit C:\\Repos\\VFSForGit\\src\r\n```\r\n\r\n```\r\nC:\\Repos\\VFSForGit\\\r\n├── src\\                    ← git working tree (.git, GVFS.sln, all source)\r\n├── out\\                    ← build output (gitignored — it's outside the working tree)\r\n└── packages\\               ← NuGet cache\r\n```\r\n\r\nCloning without the `src\\` suffix also works — outputs just land one level\r\nabove wherever you cloned. The rest of this document and the project's own\r\nscripts (CI, Readme) use the `src\\` form, so commands assume it. **The\r\nparent directory must be writable** (cloning to `C:\\` itself won't work\r\nbecause the build can't create `C:\\out`).\r\n\r\nAll commands below run from the **enlistment root** (the parent of `src\\`):\r\n\r\n```powershell\r\ncd C:\\Repos\\VFSForGit         # NOT C:\\Repos\\VFSForGit\\src\r\n```\r\n\r\nThis keeps `src\\...` and `out\\...` paths symmetric and matches what\r\n`Build.bat` does internally.\r\n\r\n## Build paths\r\n\r\n`scripts\\Build.bat` does a full installer build with NativeAOT publish plus\r\nInno Setup. That's ~5 minutes minimum, and AOT has no incremental support\r\n— ilc relinks every executable on every `dotnet publish`. **Do not use\r\n`Build.bat` for dev-loop iteration.** Pick the right path for what you're\r\ndoing.\r\n\r\n### Path A — Unit-test inner loop (~10–15 s incremental)\r\n\r\nFor C# changes verified by unit tests only.\r\n\r\n```powershell\r\ndotnet build src\\GVFS\\GVFS.UnitTests\\GVFS.UnitTests.csproj -c Debug\r\n& \"out\\GVFS.UnitTests\\bin\\Debug\\net10.0-windows10.0.17763.0\\win-x64\\GVFS.UnitTests.exe\" --test Fully.Qualified.Class.Or.Method\r\n```\r\n\r\nSkips `dotnet publish`, AOT, native C++ projects, payload assembly, installer.\r\n\r\n### Path B — Functional-test inner loop (~30–60 s incremental)\r\n\r\nFor changes that need the GVFS payload (`gvfs.exe`, hooks, service) but not\r\nan installer. `PublishAot=false` skips ilc (~3–4 min saved);\r\n`SkipCreateInstaller=true` skips Inno Setup (~95 s saved).\r\n`GVFS.Payload` only assembles the payload directory. It does not build or\r\npublish the projects that it copies from.\r\n\r\n> **Prerequisite: the native C++ projects and all managed payload projects must\r\n> already have built outputs.** The native projects are `.vcxproj` (see\r\n> [Native C++ projects](#native-c-projects-need-msbuild-not-dotnet-build)\r\n> below) and `dotnet publish` will not build them for you. If you have not\r\n> already done a `Build.bat` once in this enlistment, build the native\r\n> projects via VS MSBuild and publish the managed payload projects below\r\n> (or run `Build.bat` once to populate `out\\`, then iterate with the commands\r\n> below). After that they are incremental and only rebuild when their own\r\n> sources change.\r\n\r\n```powershell\r\ndotnet publish src\\GVFS\\GVFS.FunctionalTests\\GVFS.FunctionalTests.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS\\GVFS.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Hooks\\GVFS.Hooks.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Mount\\GVFS.Mount.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Service\\GVFS.Service.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Payload\\GVFS.Payload.csproj `\r\n    -c Debug /p:PublishAot=false /p:SkipCreateInstaller=true\r\n\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --test=GVFS.FunctionalTests.Tests.<Namespace>.<Class>.<Method>\r\n```\r\n\r\n`layout.bat` (invoked by GVFS.Payload) `xcopy`s from each project's `publish\\`\r\nor native-output directory — the C# projects do not require AOT, so\r\n`PublishAot=false` produces a fully functional test payload. The native\r\nhook binaries are copied straight from the vcxproj output.\r\n\r\n> **Publish each changed project explicitly.** `GVFS.Payload.csproj` has no\r\n> `ProjectReference` items. Its `CreatePayload` target runs `layout.bat`, which\r\n> copies existing project output. A project that you do not publish can\r\n> contribute stale output from an earlier build. Publish all four managed\r\n> payload projects at least once in a new enlistment, and publish a project\r\n> again when you change it.\r\n\r\n`RunFunctionalTests-Dev.ps1` runs functional tests against the build output\r\nwithout requiring admin or a system-wide install. It launches the test\r\nservice as a console process. Each invocation gets a unique service name and\r\nservice-data directory. Some fixtures still use shared machine paths or drive\r\nmappings, so do not assume that separate test processes can run concurrently.\r\n\r\nThe script sets dev-mode variables. `Settings.Default.Initialize` then resolves\r\n`gvfs.exe` and `GVFS.Service.exe` from `out\\GVFS.Payload\\...`, not\r\n`C:\\Program Files\\VFS for Git\\`. This path does not require a GVFS\r\ninstallation.\r\n\r\n> **Pass `-Configuration` and `-Arch` by name.** The script's first two\r\n> positional parameters are `Configuration` and `Arch`. A bare\r\n> `RunFunctionalTests-Dev.ps1 Debug --test=...` binds `--test=...` to `-Arch`\r\n> and fails its `ValidateSet`.\r\n\r\n### Path C — Installer build (~5 min — only when you need an installer)\r\n\r\nFor producing an installable package (testing install/upgrade flows, or\r\nshipping a build). This is the only correct use of `Build.bat`.\r\n\r\n```powershell\r\n# Build.bat takes (configuration, version, verbosity). The 0. prefix on the\r\n# version tells GVFS to treat this as a development build and skip the\r\n# server-side version check.\r\n$v = & { $n = [DateTime]::Now; \"0.2.$($n.ToString('yy'))$($n.DayOfYear.ToString('D3')).$([int]($n.TimeOfDay.TotalSeconds / 2))\" }\r\nsrc\\scripts\\Build.bat Debug $v minimal\r\n```\r\n\r\nInstaller output: `out\\GVFS.Installers\\bin\\Debug\\win-x64\\SetupGVFS.<version>.exe`.\r\n\r\n## Native C++ projects (need MSBuild, not `dotnet build`)\r\n\r\nThese five projects are `.vcxproj` and require Visual Studio MSBuild with\r\nthe C++ workload. `Build.bat` invokes MSBuild for them automatically; if\r\nyou need to rebuild them outside `Build.bat`, use `msbuild`, not\r\n`dotnet build`.\r\n\r\n- `GVFS\\GitHooksLoader\\GitHooksLoader.vcxproj`\r\n- `GVFS\\GVFS.NativeTests\\GVFS.NativeTests.vcxproj`\r\n- `GVFS\\GVFS.PostIndexChangedHook\\GVFS.PostIndexChangedHook.vcxproj`\r\n- `GVFS\\GVFS.ReadObjectHook\\GVFS.ReadObjectHook.vcxproj`\r\n- `GVFS\\GVFS.VirtualFileSystemHook\\GVFS.VirtualFileSystemHook.vcxproj`\r\n\r\nThese only need to be (re)built when their own sources change; they don't\r\nparticipate in the C# inner-loop paths above.\r\n\r\n## Running tests\r\n\r\n### NUnit filter syntax — `--test`, NEVER `--where`\r\n\r\n> **⚠️ This codebase uses NUnitLite, which supports ONLY `--test` for name\r\n> filtering. The `--where` filter (from NUnit Console) is silently ignored\r\n> and runs every test.**\r\n\r\n```powershell\r\n# ✅ Correct\r\n& \"out\\GVFS.UnitTests\\bin\\...\\GVFS.UnitTests.exe\" --test \"GVFS.UnitTests.Common.WorktreeInfoTests\"\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --test=GVFS.FunctionalTests.Tests.GVFSVerbTests.UnknownVerb\r\n\r\n# ❌ Wrong — silently runs the entire suite\r\n& \"out\\GVFS.UnitTests\\bin\\...\\GVFS.UnitTests.exe\" --where \"class =~ Worktree\"\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --where \"cat == Smoke\"\r\n```\r\n\r\nFor unit tests, `--where` is merely annoying (the whole suite runs in\r\n~10 seconds). For functional tests, it's a disaster: each test provisions\r\nits own fresh enlistment, so accidentally running the full suite eats\r\nhours and masks whichever failure you were actually investigating.\r\n\r\n### Fully qualified names required\r\n\r\n`--test` matches against the fully qualified name (`Namespace.Class.Method`\r\nor `Namespace.Class`). Short names like `--test=ReproCherryPickRestoreCorruption`\r\nsilently match nothing and the runner reports \"0 tests selected\" without\r\nmaking the typo obvious.\r\n\r\n## vcpkg caching\r\n\r\n`Build.bat` checks for `out\\vcpkg_installed\\dynamic\\x64-windows-dynamic\\bin\\\r\ngit2.dll` as a \"vcpkg already installed\" marker and skips the vcpkg step\r\nif present. The vcpkg step is the slowest part of a from-scratch build\r\n(several minutes of native compilation), so this caching matters.\r\n\r\nDo not manually delete or re-run vcpkg unless you've changed an overlay\r\nport. If you've copied `out\\` from another enlistment as a build-time\r\nshortcut, vcpkg results come along with it.\r\n\r\n## What ships in a public release\r\n\r\nThe signed release pipeline builds several artifacts, but the **public GitHub\r\nrelease only carries the installer and symbols**: `SetupGVFS.<version>.exe`\r\n(per arch) and `Symbols.zip`.\r\n\r\n- **FastFetch is NOT shipped publicly.** `FastFetch.exe` is built and\r\n  ESRP-signed as a pipeline artifact, but it is not attached to the GitHub\r\n  release. Treat FastFetch-only changes as **non-shipping** when scoping a\r\n  release or writing changelog notes — they don't reach the installer end\r\n  users get.\r\n- **Git is NOT bundled.** VFS for Git does not ship Microsoft Git. PRs titled\r\n  \"update default Microsoft Git version\" change only the CI `GIT_VERSION` in\r\n  `.github/workflows/build.yaml` (used to install Git for build + functional\r\n  test runs) — they are **non-shipping**. The only shipped Git constraint is\r\n  the compiled `MinimumGitVersion` constant in `Version.props`, which is\r\n  changed separately and rarely.\r\n\r\n## Feature flags\r\n\r\nProduct feature flags are **git config** keys under the `gvfs.` prefix (not a\r\nseparate config system). To add one, mirror `gvfs.show-hydration-status`:\r\n\r\n- Declare the key name and default in `GVFS.Common/GVFSConstants.cs` under\r\n  `GitConfig` (e.g. `ShowHydrationStatus = GVFSPrefix + \"show-hydration-status\"`\r\n  and `ShowHydrationStatusDefault = false`).\r\n- Read it via `repo.GetConfigBoolOrDefault(name, default)` (or\r\n  `LibGit2RepoInvoker.GetConfigBoolOrDefault`) at the point of use.\r\n\r\nDefault new gates to `false` and gate the **runtime entry point** into a\r\nfeature, not its build, so the code still compiles and ships (and keeps\r\ngetting exercised) while its behavior stays off by default.\r\n\r\n## libgit2 P/Invoke string marshalling (UTF-8, not ANSI)\r\n\r\nThe libgit2 bindings live in `GVFS.Common/Git/LibGit2Repo.cs` under the\r\n`Native` class. **libgit2 treats every string it receives and returns as\r\nUTF-8** — paths, revspecs, config keys and values, and error messages.\r\n\r\nThe .NET default for a bare `[DllImport]` string parameter is\r\n`CharSet.Ansi`, which encodes through the Windows ANSI code page and\r\n**silently corrupts non-ASCII input** (a repo path under a non-English user\r\nname, a non-ASCII branch name, etc.) — an unmappable character becomes `?`,\r\nso `git_repository_open` and friends resolve the wrong path or fail. There is\r\nno `[module: DefaultCharSet]` override in `GVFS.Common`, so the ANSI default\r\napplies unless each declaration opts out.\r\n\r\nWhen you add a libgit2 P/Invoke:\r\n\r\n- Annotate **every** `string` parameter, `out string` parameter, string return\r\n  value, and string struct field with\r\n  `[MarshalAs(UnmanagedType.LPUTF8Str)]`.\r\n- Do **not** use `CharSet.Unicode` — that marshals UTF-16, which libgit2 does\r\n  not accept (it is the same bug in the other direction).\r\n- For a function that returns a **borrowed** pointer owned by libgit2 (e.g.\r\n  `git_config_get_string`), marshal it as `IntPtr` and copy with\r\n  `Marshal.PtrToStringUTF8(...)`; do not marshal it as `out string`, or the\r\n  interop marshaller frees libgit2's heap memory with the wrong allocator.\r\n\r\nBecause the mismatch only manifests through the native call, cover new\r\nstring-carrying paths with a real-libgit2 test that uses a non-ASCII input\r\n(see `GVFS.FunctionalTests/Tests/LibGit2NonAsciiPathTests.cs`), not a\r\nmock-based unit test.\r\n\r\n## Public repository hygiene\r\n\r\nDo not include Microsoft-internal work item IDs, ADO URLs, incident IDs, or\r\ninternal service names in repository content or pull requests. Describe the\r\npublic problem and fix without requiring internal access.\r\n\r\n## Coding standards\r\n\r\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for StyleCop rules, error-handling\r\npatterns (`TryXxx` over exceptions, \"fail fast\" on data-loss risks),\r\ntracing/logging conventions (Error level reserved for unrecoverable\r\nfailures), and the `mock:\\\\` / `mock://` URL convention for unit tests.\r\n"},"files":{"AGENTS.md":"# Agent instructions for VFS for Git\r\n\r\nThis file is for AI coding assistants (GitHub Copilot, Claude Code, Cursor, OpenAI\r\nCodex, Gemini CLI, Aider, etc.). It captures project-specific knowledge that isn't\r\nobvious from a fresh `git clone` and which routinely trips up agents. See\r\n[CONTRIBUTING.md](CONTRIBUTING.md) for coding standards (StyleCop rules, error\r\nhandling, exception logging) — do not duplicate those here.\r\n\r\n## Repository layout\r\n\r\nThe build scripts (`scripts\\Build.bat` and friends) put build outputs **one\r\nlevel up** from the git working tree. The Readme documents the recommended\r\nclone-into-`src\\` convention, which matches the layout that `gvfs clone`\r\ncreates for end users:\r\n\r\n```powershell\r\n# Recommended (matches Readme.md and CI):\r\ngit clone https://github.com/microsoft/VFSForGit C:\\Repos\\VFSForGit\\src\r\n```\r\n\r\n```\r\nC:\\Repos\\VFSForGit\\\r\n├── src\\                    ← git working tree (.git, GVFS.sln, all source)\r\n├── out\\                    ← build output (gitignored — it's outside the working tree)\r\n└── packages\\               ← NuGet cache\r\n```\r\n\r\nCloning without the `src\\` suffix also works — outputs just land one level\r\nabove wherever you cloned. The rest of this document and the project's own\r\nscripts (CI, Readme) use the `src\\` form, so commands assume it. **The\r\nparent directory must be writable** (cloning to `C:\\` itself won't work\r\nbecause the build can't create `C:\\out`).\r\n\r\nAll commands below run from the **enlistment root** (the parent of `src\\`):\r\n\r\n```powershell\r\ncd C:\\Repos\\VFSForGit         # NOT C:\\Repos\\VFSForGit\\src\r\n```\r\n\r\nThis keeps `src\\...` and `out\\...` paths symmetric and matches what\r\n`Build.bat` does internally.\r\n\r\n## Build paths\r\n\r\n`scripts\\Build.bat` does a full installer build with NativeAOT publish plus\r\nInno Setup. That's ~5 minutes minimum, and AOT has no incremental support\r\n— ilc relinks every executable on every `dotnet publish`. **Do not use\r\n`Build.bat` for dev-loop iteration.** Pick the right path for what you're\r\ndoing.\r\n\r\n### Path A — Unit-test inner loop (~10–15 s incremental)\r\n\r\nFor C# changes verified by unit tests only.\r\n\r\n```powershell\r\ndotnet build src\\GVFS\\GVFS.UnitTests\\GVFS.UnitTests.csproj -c Debug\r\n& \"out\\GVFS.UnitTests\\bin\\Debug\\net10.0-windows10.0.17763.0\\win-x64\\GVFS.UnitTests.exe\" --test Fully.Qualified.Class.Or.Method\r\n```\r\n\r\nSkips `dotnet publish`, AOT, native C++ projects, payload assembly, installer.\r\n\r\n### Path B — Functional-test inner loop (~30–60 s incremental)\r\n\r\nFor changes that need the GVFS payload (`gvfs.exe`, hooks, service) but not\r\nan installer. `PublishAot=false` skips ilc (~3–4 min saved);\r\n`SkipCreateInstaller=true` skips Inno Setup (~95 s saved).\r\n`GVFS.Payload` only assembles the payload directory. It does not build or\r\npublish the projects that it copies from.\r\n\r\n> **Prerequisite: the native C++ projects and all managed payload projects must\r\n> already have built outputs.** The native projects are `.vcxproj` (see\r\n> [Native C++ projects](#native-c-projects-need-msbuild-not-dotnet-build)\r\n> below) and `dotnet publish` will not build them for you. If you have not\r\n> already done a `Build.bat` once in this enlistment, build the native\r\n> projects via VS MSBuild and publish the managed payload projects below\r\n> (or run `Build.bat` once to populate `out\\`, then iterate with the commands\r\n> below). After that they are incremental and only rebuild when their own\r\n> sources change.\r\n\r\n```powershell\r\ndotnet publish src\\GVFS\\GVFS.FunctionalTests\\GVFS.FunctionalTests.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS\\GVFS.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Hooks\\GVFS.Hooks.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Mount\\GVFS.Mount.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Service\\GVFS.Service.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Payload\\GVFS.Payload.csproj `\r\n    -c Debug /p:PublishAot=false /p:SkipCreateInstaller=true\r\n\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --test=GVFS.FunctionalTests.Tests.<Namespace>.<Class>.<Method>\r\n```\r\n\r\n`layout.bat` (invoked by GVFS.Payload) `xcopy`s from each project's `publish\\`\r\nor native-output directory — the C# projects do not require AOT, so\r\n`PublishAot=false` produces a fully functional test payload. The native\r\nhook binaries are copied straight from the vcxproj output.\r\n\r\n> **Publish each changed project explicitly.** `GVFS.Payload.csproj` has no\r\n> `ProjectReference` items. Its `CreatePayload` target runs `layout.bat`, which\r\n> copies existing project output. A project that you do not publish can\r\n> contribute stale output from an earlier build. Publish all four managed\r\n> payload projects at least once in a new enlistment, and publish a project\r\n> again when you change it.\r\n\r\n`RunFunctionalTests-Dev.ps1` runs functional tests against the build output\r\nwithout requiring admin or a system-wide install. It launches the test\r\nservice as a console process. Each invocation gets a unique service name and\r\nservice-data directory. Some fixtures still use shared machine paths or drive\r\nmappings, so do not assume that separate test processes can run concurrently.\r\n\r\nThe script sets dev-mode variables. `Settings.Default.Initialize` then resolves\r\n`gvfs.exe` and `GVFS.Service.exe` from `out\\GVFS.Payload\\...`, not\r\n`C:\\Program Files\\VFS for Git\\`. This path does not require a GVFS\r\ninstallation.\r\n\r\n> **Pass `-Configuration` and `-Arch` by name.** The script's first two\r\n> positional parameters are `Configuration` and `Arch`. A bare\r\n> `RunFunctionalTests-Dev.ps1 Debug --test=...` binds `--test=...` to `-Arch`\r\n> and fails its `ValidateSet`.\r\n\r\n### Path C — Installer build (~5 min — only when you need an installer)\r\n\r\nFor producing an installable package (testing install/upgrade flows, or\r\nshipping a build). This is the only correct use of `Build.bat`.\r\n\r\n```powershell\r\n# Build.bat takes (configuration, version, verbosity). The 0. prefix on the\r\n# version tells GVFS to treat this as a development build and skip the\r\n# server-side version check.\r\n$v = & { $n = [DateTime]::Now; \"0.2.$($n.ToString('yy'))$($n.DayOfYear.ToString('D3')).$([int]($n.TimeOfDay.TotalSeconds / 2))\" }\r\nsrc\\scripts\\Build.bat Debug $v minimal\r\n```\r\n\r\nInstaller output: `out\\GVFS.Installers\\bin\\Debug\\win-x64\\SetupGVFS.<version>.exe`.\r\n\r\n## Native C++ projects (need MSBuild, not `dotnet build`)\r\n\r\nThese five projects are `.vcxproj` and require Visual Studio MSBuild with\r\nthe C++ workload. `Build.bat` invokes MSBuild for them automatically; if\r\nyou need to rebuild them outside `Build.bat`, use `msbuild`, not\r\n`dotnet build`.\r\n\r\n- `GVFS\\GitHooksLoader\\GitHooksLoader.vcxproj`\r\n- `GVFS\\GVFS.NativeTests\\GVFS.NativeTests.vcxproj`\r\n- `GVFS\\GVFS.PostIndexChangedHook\\GVFS.PostIndexChangedHook.vcxproj`\r\n- `GVFS\\GVFS.ReadObjectHook\\GVFS.ReadObjectHook.vcxproj`\r\n- `GVFS\\GVFS.VirtualFileSystemHook\\GVFS.VirtualFileSystemHook.vcxproj`\r\n\r\nThese only need to be (re)built when their own sources change; they don't\r\nparticipate in the C# inner-loop paths above.\r\n\r\n## Running tests\r\n\r\n### NUnit filter syntax — `--test`, NEVER `--where`\r\n\r\n> **⚠️ This codebase uses NUnitLite, which supports ONLY `--test` for name\r\n> filtering. The `--where` filter (from NUnit Console) is silently ignored\r\n> and runs every test.**\r\n\r\n```powershell\r\n# ✅ Correct\r\n& \"out\\GVFS.UnitTests\\bin\\...\\GVFS.UnitTests.exe\" --test \"GVFS.UnitTests.Common.WorktreeInfoTests\"\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --test=GVFS.FunctionalTests.Tests.GVFSVerbTests.UnknownVerb\r\n\r\n# ❌ Wrong — silently runs the entire suite\r\n& \"out\\GVFS.UnitTests\\bin\\...\\GVFS.UnitTests.exe\" --where \"class =~ Worktree\"\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --where \"cat == Smoke\"\r\n```\r\n\r\nFor unit tests, `--where` is merely annoying (the whole suite runs in\r\n~10 seconds). For functional tests, it's a disaster: each test provisions\r\nits own fresh enlistment, so accidentally running the full suite eats\r\nhours and masks whichever failure you were actually investigating.\r\n\r\n### Fully qualified names required\r\n\r\n`--test` matches against the fully qualified name (`Namespace.Class.Method`\r\nor `Namespace.Class`). Short names like `--test=ReproCherryPickRestoreCorruption`\r\nsilently match nothing and the runner reports \"0 tests selected\" without\r\nmaking the typo obvious.\r\n\r\n## vcpkg caching\r\n\r\n`Build.bat` checks for `out\\vcpkg_installed\\dynamic\\x64-windows-dynamic\\bin\\\r\ngit2.dll` as a \"vcpkg already installed\" marker and skips the vcpkg step\r\nif present. The vcpkg step is the slowest part of a from-scratch build\r\n(several minutes of native compilation), so this caching matters.\r\n\r\nDo not manually delete or re-run vcpkg unless you've changed an overlay\r\nport. If you've copied `out\\` from another enlistment as a build-time\r\nshortcut, vcpkg results come along with it.\r\n\r\n## What ships in a public release\r\n\r\nThe signed release pipeline builds several artifacts, but the **public GitHub\r\nrelease only carries the installer and symbols**: `SetupGVFS.<version>.exe`\r\n(per arch) and `Symbols.zip`.\r\n\r\n- **FastFetch is NOT shipped publicly.** `FastFetch.exe` is built and\r\n  ESRP-signed as a pipeline artifact, but it is not attached to the GitHub\r\n  release. Treat FastFetch-only changes as **non-shipping** when scoping a\r\n  release or writing changelog notes — they don't reach the installer end\r\n  users get.\r\n- **Git is NOT bundled.** VFS for Git does not ship Microsoft Git. PRs titled\r\n  \"update default Microsoft Git version\" change only the CI `GIT_VERSION` in\r\n  `.github/workflows/build.yaml` (used to install Git for build + functional\r\n  test runs) — they are **non-shipping**. The only shipped Git constraint is\r\n  the compiled `MinimumGitVersion` constant in `Version.props`, which is\r\n  changed separately and rarely.\r\n\r\n## Feature flags\r\n\r\nProduct feature flags are **git config** keys under the `gvfs.` prefix (not a\r\nseparate config system). To add one, mirror `gvfs.show-hydration-status`:\r\n\r\n- Declare the key name and default in `GVFS.Common/GVFSConstants.cs` under\r\n  `GitConfig` (e.g. `ShowHydrationStatus = GVFSPrefix + \"show-hydration-status\"`\r\n  and `ShowHydrationStatusDefault = false`).\r\n- Read it via `repo.GetConfigBoolOrDefault(name, default)` (or\r\n  `LibGit2RepoInvoker.GetConfigBoolOrDefault`) at the point of use.\r\n\r\nDefault new gates to `false` and gate the **runtime entry point** into a\r\nfeature, not its build, so the code still compiles and ships (and keeps\r\ngetting exercised) while its behavior stays off by default.\r\n\r\n## libgit2 P/Invoke string marshalling (UTF-8, not ANSI)\r\n\r\nThe libgit2 bindings live in `GVFS.Common/Git/LibGit2Repo.cs` under the\r\n`Native` class. **libgit2 treats every string it receives and returns as\r\nUTF-8** — paths, revspecs, config keys and values, and error messages.\r\n\r\nThe .NET default for a bare `[DllImport]` string parameter is\r\n`CharSet.Ansi`, which encodes through the Windows ANSI code page and\r\n**silently corrupts non-ASCII input** (a repo path under a non-English user\r\nname, a non-ASCII branch name, etc.) — an unmappable character becomes `?`,\r\nso `git_repository_open` and friends resolve the wrong path or fail. There is\r\nno `[module: DefaultCharSet]` override in `GVFS.Common`, so the ANSI default\r\napplies unless each declaration opts out.\r\n\r\nWhen you add a libgit2 P/Invoke:\r\n\r\n- Annotate **every** `string` parameter, `out string` parameter, string return\r\n  value, and string struct field with\r\n  `[MarshalAs(UnmanagedType.LPUTF8Str)]`.\r\n- Do **not** use `CharSet.Unicode` — that marshals UTF-16, which libgit2 does\r\n  not accept (it is the same bug in the other direction).\r\n- For a function that returns a **borrowed** pointer owned by libgit2 (e.g.\r\n  `git_config_get_string`), marshal it as `IntPtr` and copy with\r\n  `Marshal.PtrToStringUTF8(...)`; do not marshal it as `out string`, or the\r\n  interop marshaller frees libgit2's heap memory with the wrong allocator.\r\n\r\nBecause the mismatch only manifests through the native call, cover new\r\nstring-carrying paths with a real-libgit2 test that uses a non-ASCII input\r\n(see `GVFS.FunctionalTests/Tests/LibGit2NonAsciiPathTests.cs`), not a\r\nmock-based unit test.\r\n\r\n## Public repository hygiene\r\n\r\nDo not include Microsoft-internal work item IDs, ADO URLs, incident IDs, or\r\ninternal service names in repository content or pull requests. Describe the\r\npublic problem and fix without requiring internal access.\r\n\r\n## Coding standards\r\n\r\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for StyleCop rules, error-handling\r\npatterns (`TryXxx` over exceptions, \"fail fast\" on data-loss risks),\r\ntracing/logging conventions (Error level reserved for unrecoverable\r\nfailures), and the `mock:\\\\` / `mock://` URL convention for unit tests.\r\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/microsoft/VFSForGit/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Agent instructions for VFS for Git\r\n\r\nThis file is for AI coding assistants (GitHub Copilot, Claude Code, Cursor, OpenAI\r\nCodex, Gemini CLI, Aider, etc.). It captures project-specific knowledge that isn't\r\nobvious from a fresh `git clone` and which routinely trips up agents. See\r\n[CONTRIBUTING.md](CONTRIBUTING.md) for coding standards (StyleCop rules, error\r\nhandling, exception logging) — do not duplicate those here.\r\n\r\n## Repository layout\r\n\r\nThe build scripts (`scripts\\Build.bat` and friends) put build outputs **one\r\nlevel up** from the git working tree. The Readme documents the recommended\r\nclone-into-`src\\` convention, which matches the layout that `gvfs clone`\r\ncreates for end users:\r\n\r\n```powershell\r\n# Recommended (matches Readme.md and CI):\r\ngit clone https://github.com/microsoft/VFSForGit C:\\Repos\\VFSForGit\\src\r\n```\r\n\r\n```\r\nC:\\Repos\\VFSForGit\\\r\n├── src\\                    ← git working tree (.git, GVFS.sln, all source)\r\n├── out\\                    ← build output (gitignored — it's outside the working tree)\r\n└── packages\\               ← NuGet cache\r\n```\r\n\r\nCloning without the `src\\` suffix also works — outputs just land one level\r\nabove wherever you cloned. The rest of this document and the project's own\r\nscripts (CI, Readme) use the `src\\` form, so commands assume it. **The\r\nparent directory must be writable** (cloning to `C:\\` itself won't work\r\nbecause the build can't create `C:\\out`).\r\n\r\nAll commands below run from the **enlistment root** (the parent of `src\\`):\r\n\r\n```powershell\r\ncd C:\\Repos\\VFSForGit         # NOT C:\\Repos\\VFSForGit\\src\r\n```\r\n\r\nThis keeps `src\\...` and `out\\...` paths symmetric and matches what\r\n`Build.bat` does internally.\r\n\r\n## Build paths\r\n\r\n`scripts\\Build.bat` does a full installer build with NativeAOT publish plus\r\nInno Setup. That's ~5 minutes minimum, and AOT has no incremental support\r\n— ilc relinks every executable on every `dotnet publish`. **Do not use\r\n`Build.bat` for dev-loop iteration.** Pick the right path for what you're\r\ndoing.\r\n\r\n### Path A — Unit-test inner loop (~10–15 s incremental)\r\n\r\nFor C# changes verified by unit tests only.\r\n\r\n```powershell\r\ndotnet build src\\GVFS\\GVFS.UnitTests\\GVFS.UnitTests.csproj -c Debug\r\n& \"out\\GVFS.UnitTests\\bin\\Debug\\net10.0-windows10.0.17763.0\\win-x64\\GVFS.UnitTests.exe\" --test Fully.Qualified.Class.Or.Method\r\n```\r\n\r\nSkips `dotnet publish`, AOT, native C++ projects, payload assembly, installer.\r\n\r\n### Path B — Functional-test inner loop (~30–60 s incremental)\r\n\r\nFor changes that need the GVFS payload (`gvfs.exe`, hooks, service) but not\r\nan installer. `PublishAot=false` skips ilc (~3–4 min saved);\r\n`SkipCreateInstaller=true` skips Inno Setup (~95 s saved).\r\n`GVFS.Payload` only assembles the payload directory. It does not build or\r\npublish the projects that it copies from.\r\n\r\n> **Prerequisite: the native C++ projects and all managed payload projects must\r\n> already have built outputs.** The native projects are `.vcxproj` (see\r\n> [Native C++ projects](#native-c-projects-need-msbuild-not-dotnet-build)\r\n> below) and `dotnet publish` will not build them for you. If you have not\r\n> already done a `Build.bat` once in this enlistment, build the native\r\n> projects via VS MSBuild and publish the managed payload projects below\r\n> (or run `Build.bat` once to populate `out\\`, then iterate with the commands\r\n> below). After that they are incremental and only rebuild when their own\r\n> sources change.\r\n\r\n```powershell\r\ndotnet publish src\\GVFS\\GVFS.FunctionalTests\\GVFS.FunctionalTests.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS\\GVFS.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Hooks\\GVFS.Hooks.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Mount\\GVFS.Mount.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Service\\GVFS.Service.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Payload\\GVFS.Payload.csproj `\r\n    -c Debug /p:PublishAot=false /p:SkipCreateInstaller=true\r\n\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --test=GVFS.FunctionalTests.Tests.<Namespace>.<Class>.<Method>\r\n```\r\n\r\n`layout.bat` (invoked by GVFS.Payload) `xcopy`s from each project's `publish\\`\r\nor native-output directory — the C# projects do not require AOT, so\r\n`PublishAot=false` produces a fully functional test payload. The native\r\nhook binaries are copied straight from the vcxproj output.\r\n\r\n> **Publish each changed project explicitly.** `GVFS.Payload.csproj` has no\r\n> `ProjectReference` items. Its `CreatePayload` target runs `layout.bat`, which\r\n> copies existing project output. A project that you do not publish can\r\n> contribute stale output from an earlier build. Publish all four managed\r\n> payload projects at least once in a new enlistment, and publish a project\r\n> again when you change it.\r\n\r\n`RunFunctionalTests-Dev.ps1` runs functional tests against the build output\r\nwithout requiring admin or a system-wide install. It launches the test\r\nservice as a console process. Each invocation gets a unique service name and\r\nservice-data directory. Some fixtures still use shared machine paths or drive\r\nmappings, so do not assume that separate test processes can run concurrently.\r\n\r\nThe script sets dev-mode variables. `Settings.Default.Initialize` then resolves\r\n`gvfs.exe` and `GVFS.Service.exe` from `out\\GVFS.Payload\\...`, not\r\n`C:\\Program Files\\VFS for Git\\`. This path does not require a GVFS\r\ninstallation.\r\n\r\n> **Pass `-Configuration` and `-Arch` by name.** The script's first two\r\n> positional parameters are `Configuration` and `Arch`. A bare\r\n> `RunFunctionalTests-Dev.ps1 Debug --test=...` binds `--test=...` to `-Arch`\r\n> and fails its `ValidateSet`.\r\n\r\n### Path C — Installer build (~5 min — only when you need an installer)\r\n\r\nFor producing an installable package (testing install/upgrade flows, or\r\nshipping a build). This is the only correct use of `Build.bat`.\r\n\r\n```powershell\r\n# Build.bat takes (configuration, version, verbosity). The 0. prefix on the\r\n# version tells GVFS to treat this as a development build and skip the\r\n# server-side version check.\r\n$v = & { $n = [DateTime]::Now; \"0.2.$($n.ToString('yy'))$($n.DayOfYear.ToString('D3')).$([int]($n.TimeOfDay.TotalSeconds / 2))\" }\r\nsrc\\scripts\\Build.bat Debug $v minimal\r\n```\r\n\r\nInstaller output: `out\\GVFS.Installers\\bin\\Debug\\win-x64\\SetupGVFS.<version>.exe`.\r\n\r\n## Native C++ projects (need MSBuild, not `dotnet build`)\r\n\r\nThese five projects are `.vcxproj` and require Visual Studio MSBuild with\r\nthe C++ workload. `Build.bat` invokes MSBuild for them automatically; if\r\nyou need to rebuild them outside `Build.bat`, use `msbuild`, not\r\n`dotnet build`.\r\n\r\n- `GVFS\\GitHooksLoader\\GitHooksLoader.vcxproj`\r\n- `GVFS\\GVFS.NativeTests\\GVFS.NativeTests.vcxproj`\r\n- `GVFS\\GVFS.PostIndexChangedHook\\GVFS.PostIndexChangedHook.vcxproj`\r\n- `GVFS\\GVFS.ReadObjectHook\\GVFS.ReadObjectHook.vcxproj`\r\n- `GVFS\\GVFS.VirtualFileSystemHook\\GVFS.VirtualFileSystemHook.vcxproj`\r\n\r\nThese only need to be (re)built when their own sources change; they don't\r\nparticipate in the C# inner-loop paths above.\r\n\r\n## Running tests\r\n\r\n### NUnit filter syntax — `--test`, NEVER `--where`\r\n\r\n> **⚠️ This codebase uses NUnitLite, which supports ONLY `--test` for name\r\n> filtering. The `--where` filter (from NUnit Console) is silently ignored\r\n> and runs every test.**\r\n\r\n```powershell\r\n# ✅ Correct\r\n& \"out\\GVFS.UnitTests\\bin\\...\\GVFS.UnitTests.exe\" --test \"GVFS.UnitTests.Common.WorktreeInfoTests\"\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --test=GVFS.FunctionalTests.Tests.GVFSVerbTests.UnknownVerb\r\n\r\n# ❌ Wrong — silently runs the entire suite\r\n& \"out\\GVFS.UnitTests\\bin\\...\\GVFS.UnitTests.exe\" --where \"class =~ Worktree\"\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --where \"cat == Smoke\"\r\n```\r\n\r\nFor unit tests, `--where` is merely annoying (the whole suite runs in\r\n~10 seconds). For functional tests, it's a disaster: each test provisions\r\nits own fresh enlistment, so accidentally running the full suite eats\r\nhours and masks whichever failure you were actually investigating.\r\n\r\n### Fully qualified names required\r\n\r\n`--test` matches against the fully qualified name (`Namespace.Class.Method`\r\nor `Namespace.Class`). Short names like `--test=ReproCherryPickRestoreCorruption`\r\nsilently match nothing and the runner reports \"0 tests selected\" without\r\nmaking the typo obvious.\r\n\r\n## vcpkg caching\r\n\r\n`Build.bat` checks for `out\\vcpkg_installed\\dynamic\\x64-windows-dynamic\\bin\\\r\ngit2.dll` as a \"vcpkg already installed\" marker and skips the vcpkg step\r\nif present. The vcpkg step is the slowest part of a from-scratch build\r\n(several minutes of native compilation), so this caching matters.\r\n\r\nDo not manually delete or re-run vcpkg unless you've changed an overlay\r\nport. If you've copied `out\\` from another enlistment as a build-time\r\nshortcut, vcpkg results come along with it.\r\n\r\n## What ships in a public release\r\n\r\nThe signed release pipeline builds several artifacts, but the **public GitHub\r\nrelease only carries the installer and symbols**: `SetupGVFS.<version>.exe`\r\n(per arch) and `Symbols.zip`.\r\n\r\n- **FastFetch is NOT shipped publicly.** `FastFetch.exe` is built and\r\n  ESRP-signed as a pipeline artifact, but it is not attached to the GitHub\r\n  release. Treat FastFetch-only changes as **non-shipping** when scoping a\r\n  release or writing changelog notes — they don't reach the installer end\r\n  users get.\r\n- **Git is NOT bundled.** VFS for Git does not ship Microsoft Git. PRs titled\r\n  \"update default Microsoft Git version\" change only the CI `GIT_VERSION` in\r\n  `.github/workflows/build.yaml` (used to install Git for build + functional\r\n  test runs) — they are **non-shipping**. The only shipped Git constraint is\r\n  the compiled `MinimumGitVersion` constant in `Version.props`, which is\r\n  changed separately and rarely.\r\n\r\n## Feature flags\r\n\r\nProduct feature flags are **git config** keys under the `gvfs.` prefix (not a\r\nseparate config system). To add one, mirror `gvfs.show-hydration-status`:\r\n\r\n- Declare the key name and default in `GVFS.Common/GVFSConstants.cs` under\r\n  `GitConfig` (e.g. `ShowHydrationStatus = GVFSPrefix + \"show-hydration-status\"`\r\n  and `ShowHydrationStatusDefault = false`).\r\n- Read it via `repo.GetConfigBoolOrDefault(name, default)` (or\r\n  `LibGit2RepoInvoker.GetConfigBoolOrDefault`) at the point of use.\r\n\r\nDefault new gates to `false` and gate the **runtime entry point** into a\r\nfeature, not its build, so the code still compiles and ships (and keeps\r\ngetting exercised) while its behavior stays off by default.\r\n\r\n## libgit2 P/Invoke string marshalling (UTF-8, not ANSI)\r\n\r\nThe libgit2 bindings live in `GVFS.Common/Git/LibGit2Repo.cs` under the\r\n`Native` class. **libgit2 treats every string it receives and returns as\r\nUTF-8** — paths, revspecs, config keys and values, and error messages.\r\n\r\nThe .NET default for a bare `[DllImport]` string parameter is\r\n`CharSet.Ansi`, which encodes through the Windows ANSI code page and\r\n**silently corrupts non-ASCII input** (a repo path under a non-English user\r\nname, a non-ASCII branch name, etc.) — an unmappable character becomes `?`,\r\nso `git_repository_open` and friends resolve the wrong path or fail. There is\r\nno `[module: DefaultCharSet]` override in `GVFS.Common`, so the ANSI default\r\napplies unless each declaration opts out.\r\n\r\nWhen you add a libgit2 P/Invoke:\r\n\r\n- Annotate **every** `string` parameter, `out string` parameter, string return\r\n  value, and string struct field with\r\n  `[MarshalAs(UnmanagedType.LPUTF8Str)]`.\r\n- Do **not** use `CharSet.Unicode` — that marshals UTF-16, which libgit2 does\r\n  not accept (it is the same bug in the other direction).\r\n- For a function that returns a **borrowed** pointer owned by libgit2 (e.g.\r\n  `git_config_get_string`), marshal it as `IntPtr` and copy with\r\n  `Marshal.PtrToStringUTF8(...)`; do not marshal it as `out string`, or the\r\n  interop marshaller frees libgit2's heap memory with the wrong allocator.\r\n\r\nBecause the mismatch only manifests through the native call, cover new\r\nstring-carrying paths with a real-libgit2 test that uses a non-ASCII input\r\n(see `GVFS.FunctionalTests/Tests/LibGit2NonAsciiPathTests.cs`), not a\r\nmock-based unit test.\r\n\r\n## Public repository hygiene\r\n\r\nDo not include Microsoft-internal work item IDs, ADO URLs, incident IDs, or\r\ninternal service names in repository content or pull requests. Describe the\r\npublic problem and fix without requiring internal access.\r\n\r\n## Coding standards\r\n\r\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for StyleCop rules, error-handling\r\npatterns (`TryXxx` over exceptions, \"fail fast\" on data-loss risks),\r\ntracing/logging conventions (Error level reserved for unrecoverable\r\nfailures), and the `mock:\\\\` / `mock://` URL convention for unit tests.\r\n","isInternal":false,"tokens":3305,"sizeBytes":12951}],"systemPromptSnippet":"<yakaai_skills repo=\"microsoft/VFSForGit\">\n<!-- File: AGENTS.md (Tokens: ~3305 | Category: root-instruction) -->\n# Agent instructions for VFS for Git\r\n\r\nThis file is for AI coding assistants (GitHub Copilot, Claude Code, Cursor, OpenAI\r\nCodex, Gemini CLI, Aider, etc.). It captures project-specific knowledge that isn't\r\nobvious from a fresh `git clone` and which routinely trips up agents. See\r\n[CONTRIBUTING.md](CONTRIBUTING.md) for coding standards (StyleCop rules, error\r\nhandling, exception logging) — do not duplicate those here.\r\n\r\n## Repository layout\r\n\r\nThe build scripts (`scripts\\Build.bat` and friends) put build outputs **one\r\nlevel up** from the git working tree. The Readme documents the recommended\r\nclone-into-`src\\` convention, which matches the layout that `gvfs clone`\r\ncreates for end users:\r\n\r\n```powershell\r\n# Recommended (matches Readme.md and CI):\r\ngit clone https://github.com/microsoft/VFSForGit C:\\Repos\\VFSForGit\\src\r\n```\r\n\r\n```\r\nC:\\Repos\\VFSForGit\\\r\n├── src\\                    ← git working tree (.git, GVFS.sln, all source)\r\n├── out\\                    ← build output (gitignored — it's outside the working tree)\r\n└── packages\\               ← NuGet cache\r\n```\r\n\r\nCloning without the `src\\` suffix also works — outputs just land one level\r\nabove wherever you cloned. The rest of this document and the project's own\r\nscripts (CI, Readme) use the `src\\` form, so commands assume it. **The\r\nparent directory must be writable** (cloning to `C:\\` itself won't work\r\nbecause the build can't create `C:\\out`).\r\n\r\nAll commands below run from the **enlistment root** (the parent of `src\\`):\r\n\r\n```powershell\r\ncd C:\\Repos\\VFSForGit         # NOT C:\\Repos\\VFSForGit\\src\r\n```\r\n\r\nThis keeps `src\\...` and `out\\...` paths symmetric and matches what\r\n`Build.bat` does internally.\r\n\r\n## Build paths\r\n\r\n`scripts\\Build.bat` does a full installer build with NativeAOT publish plus\r\nInno Setup. That's ~5 minutes minimum, and AOT has no incremental support\r\n— ilc relinks every executable on every `dotnet publish`. **Do not use\r\n`Build.bat` for dev-loop iteration.** Pick the right path for what you're\r\ndoing.\r\n\r\n### Path A — Unit-test inner loop (~10–15 s incremental)\r\n\r\nFor C# changes verified by unit tests only.\r\n\r\n```powershell\r\ndotnet build src\\GVFS\\GVFS.UnitTests\\GVFS.UnitTests.csproj -c Debug\r\n& \"out\\GVFS.UnitTests\\bin\\Debug\\net10.0-windows10.0.17763.0\\win-x64\\GVFS.UnitTests.exe\" --test Fully.Qualified.Class.Or.Method\r\n```\r\n\r\nSkips `dotnet publish`, AOT, native C++ projects, payload assembly, installer.\r\n\r\n### Path B — Functional-test inner loop (~30–60 s incremental)\r\n\r\nFor changes that need the GVFS payload (`gvfs.exe`, hooks, service) but not\r\nan installer. `PublishAot=false` skips ilc (~3–4 min saved);\r\n`SkipCreateInstaller=true` skips Inno Setup (~95 s saved).\r\n`GVFS.Payload` only assembles the payload directory. It does not build or\r\npublish the projects that it copies from.\r\n\r\n> **Prerequisite: the native C++ projects and all managed payload projects must\r\n> already have built outputs.** The native projects are `.vcxproj` (see\r\n> [Native C++ projects](#native-c-projects-need-msbuild-not-dotnet-build)\r\n> below) and `dotnet publish` will not build them for you. If you have not\r\n> already done a `Build.bat` once in this enlistment, build the native\r\n> projects via VS MSBuild and publish the managed payload projects below\r\n> (or run `Build.bat` once to populate `out\\`, then iterate with the commands\r\n> below). After that they are incremental and only rebuild when their own\r\n> sources change.\r\n\r\n```powershell\r\ndotnet publish src\\GVFS\\GVFS.FunctionalTests\\GVFS.FunctionalTests.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS\\GVFS.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Hooks\\GVFS.Hooks.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Mount\\GVFS.Mount.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Service\\GVFS.Service.csproj `\r\n    -c Debug /p:PublishAot=false\r\ndotnet publish src\\GVFS\\GVFS.Payload\\GVFS.Payload.csproj `\r\n    -c Debug /p:PublishAot=false /p:SkipCreateInstaller=true\r\n\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --test=GVFS.FunctionalTests.Tests.<Namespace>.<Class>.<Method>\r\n```\r\n\r\n`layout.bat` (invoked by GVFS.Payload) `xcopy`s from each project's `publish\\`\r\nor native-output directory — the C# projects do not require AOT, so\r\n`PublishAot=false` produces a fully functional test payload. The native\r\nhook binaries are copied straight from the vcxproj output.\r\n\r\n> **Publish each changed project explicitly.** `GVFS.Payload.csproj` has no\r\n> `ProjectReference` items. Its `CreatePayload` target runs `layout.bat`, which\r\n> copies existing project output. A project that you do not publish can\r\n> contribute stale output from an earlier build. Publish all four managed\r\n> payload projects at least once in a new enlistment, and publish a project\r\n> again when you change it.\r\n\r\n`RunFunctionalTests-Dev.ps1` runs functional tests against the build output\r\nwithout requiring admin or a system-wide install. It launches the test\r\nservice as a console process. Each invocation gets a unique service name and\r\nservice-data directory. Some fixtures still use shared machine paths or drive\r\nmappings, so do not assume that separate test processes can run concurrently.\r\n\r\nThe script sets dev-mode variables. `Settings.Default.Initialize` then resolves\r\n`gvfs.exe` and `GVFS.Service.exe` from `out\\GVFS.Payload\\...`, not\r\n`C:\\Program Files\\VFS for Git\\`. This path does not require a GVFS\r\ninstallation.\r\n\r\n> **Pass `-Configuration` and `-Arch` by name.** The script's first two\r\n> positional parameters are `Configuration` and `Arch`. A bare\r\n> `RunFunctionalTests-Dev.ps1 Debug --test=...` binds `--test=...` to `-Arch`\r\n> and fails its `ValidateSet`.\r\n\r\n### Path C — Installer build (~5 min — only when you need an installer)\r\n\r\nFor producing an installable package (testing install/upgrade flows, or\r\nshipping a build). This is the only correct use of `Build.bat`.\r\n\r\n```powershell\r\n# Build.bat takes (configuration, version, verbosity). The 0. prefix on the\r\n# version tells GVFS to treat this as a development build and skip the\r\n# server-side version check.\r\n$v = & { $n = [DateTime]::Now; \"0.2.$($n.ToString('yy'))$($n.DayOfYear.ToString('D3')).$([int]($n.TimeOfDay.TotalSeconds / 2))\" }\r\nsrc\\scripts\\Build.bat Debug $v minimal\r\n```\r\n\r\nInstaller output: `out\\GVFS.Installers\\bin\\Debug\\win-x64\\SetupGVFS.<version>.exe`.\r\n\r\n## Native C++ projects (need MSBuild, not `dotnet build`)\r\n\r\nThese five projects are `.vcxproj` and require Visual Studio MSBuild with\r\nthe C++ workload. `Build.bat` invokes MSBuild for them automatically; if\r\nyou need to rebuild them outside `Build.bat`, use `msbuild`, not\r\n`dotnet build`.\r\n\r\n- `GVFS\\GitHooksLoader\\GitHooksLoader.vcxproj`\r\n- `GVFS\\GVFS.NativeTests\\GVFS.NativeTests.vcxproj`\r\n- `GVFS\\GVFS.PostIndexChangedHook\\GVFS.PostIndexChangedHook.vcxproj`\r\n- `GVFS\\GVFS.ReadObjectHook\\GVFS.ReadObjectHook.vcxproj`\r\n- `GVFS\\GVFS.VirtualFileSystemHook\\GVFS.VirtualFileSystemHook.vcxproj`\r\n\r\nThese only need to be (re)built when their own sources change; they don't\r\nparticipate in the C# inner-loop paths above.\r\n\r\n## Running tests\r\n\r\n### NUnit filter syntax — `--test`, NEVER `--where`\r\n\r\n> **⚠️ This codebase uses NUnitLite, which supports ONLY `--test` for name\r\n> filtering. The `--where` filter (from NUnit Console) is silently ignored\r\n> and runs every test.**\r\n\r\n```powershell\r\n# ✅ Correct\r\n& \"out\\GVFS.UnitTests\\bin\\...\\GVFS.UnitTests.exe\" --test \"GVFS.UnitTests.Common.WorktreeInfoTests\"\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --test=GVFS.FunctionalTests.Tests.GVFSVerbTests.UnknownVerb\r\n\r\n# ❌ Wrong — silently runs the entire suite\r\n& \"out\\GVFS.UnitTests\\bin\\...\\GVFS.UnitTests.exe\" --where \"class =~ Worktree\"\r\nsrc\\scripts\\RunFunctionalTests-Dev.ps1 -Configuration Debug -Arch x64 `\r\n    --where \"cat == Smoke\"\r\n```\r\n\r\nFor unit tests, `--where` is merely annoying (the whole suite runs in\r\n~10 seconds). For functional tests, it's a disaster: each test provisions\r\nits own fresh enlistment, so accidentally running the full suite eats\r\nhours and masks whichever failure you were actually investigating.\r\n\r\n### Fully qualified names required\r\n\r\n`--test` matches against the fully qualified name (`Namespace.Class.Method`\r\nor `Namespace.Class`). Short names like `--test=ReproCherryPickRestoreCorruption`\r\nsilently match nothing and the runner reports \"0 tests selected\" without\r\nmaking the typo obvious.\r\n\r\n## vcpkg caching\r\n\r\n`Build.bat` checks for `out\\vcpkg_installed\\dynamic\\x64-windows-dynamic\\bin\\\r\ngit2.dll` as a \"vcpkg already installed\" marker and skips the vcpkg step\r\nif present. The vcpkg step is the slowest part of a from-scratch build\r\n(several minutes of native compilation), so this caching matters.\r\n\r\nDo not manually delete or re-run vcpkg unless you've changed an overlay\r\nport. If you've copied `out\\` from another enlistment as a build-time\r\nshortcut, vcpkg results come along with it.\r\n\r\n## What ships in a public release\r\n\r\nThe signed release pipeline builds several artifacts, but the **public GitHub\r\nrelease only carries the installer and symbols**: `SetupGVFS.<version>.exe`\r\n(per arch) and `Symbols.zip`.\r\n\r\n- **FastFetch is NOT shipped publicly.** `FastFetch.exe` is built and\r\n  ESRP-signed as a pipeline artifact, but it is not attached to the GitHub\r\n  release. Treat FastFetch-only changes as **non-shipping** when scoping a\r\n  release or writing changelog notes — they don't reach the installer end\r\n  users get.\r\n- **Git is NOT bundled.** VFS for Git does not ship Microsoft Git. PRs titled\r\n  \"update default Microsoft Git version\" change only the CI `GIT_VERSION` in\r\n  `.github/workflows/build.yaml` (used to install Git for build + functional\r\n  test runs) — they are **non-shipping**. The only shipped Git constraint is\r\n  the compiled `MinimumGitVersion` constant in `Version.props`, which is\r\n  changed separately and rarely.\r\n\r\n## Feature flags\r\n\r\nProduct feature flags are **git config** keys under the `gvfs.` prefix (not a\r\nseparate config system). To add one, mirror `gvfs.show-hydration-status`:\r\n\r\n- Declare the key name and default in `GVFS.Common/GVFSConstants.cs` under\r\n  `GitConfig` (e.g. `ShowHydrationStatus = GVFSPrefix + \"show-hydration-status\"`\r\n  and `ShowHydrationStatusDefault = false`).\r\n- Read it via `repo.GetConfigBoolOrDefault(name, default)` (or\r\n  `LibGit2RepoInvoker.GetConfigBoolOrDefault`) at the point of use.\r\n\r\nDefault new gates to `false` and gate the **runtime entry point** into a\r\nfeature, not its build, so the code still compiles and ships (and keeps\r\ngetting exercised) while its behavior stays off by default.\r\n\r\n## libgit2 P/Invoke string marshalling (UTF-8, not ANSI)\r\n\r\nThe libgit2 bindings live in `GVFS.Common/Git/LibGit2Repo.cs` under the\r\n`Native` class. **libgit2 treats every string it receives and returns as\r\nUTF-8** — paths, revspecs, config keys and values, and error messages.\r\n\r\nThe .NET default for a bare `[DllImport]` string parameter is\r\n`CharSet.Ansi`, which encodes through the Windows ANSI code page and\r\n**silently corrupts non-ASCII input** (a repo path under a non-English user\r\nname, a non-ASCII branch name, etc.) — an unmappable character becomes `?`,\r\nso `git_repository_open` and friends resolve the wrong path or fail. There is\r\nno `[module: DefaultCharSet]` override in `GVFS.Common`, so the ANSI default\r\napplies unless each declaration opts out.\r\n\r\nWhen you add a libgit2 P/Invoke:\r\n\r\n- Annotate **every** `string` parameter, `out string` parameter, string return\r\n  value, and string struct field with\r\n  `[MarshalAs(UnmanagedType.LPUTF8Str)]`.\r\n- Do **not** use `CharSet.Unicode` — that marshals UTF-16, which libgit2 does\r\n  not accept (it is the same bug in the other direction).\r\n- For a function that returns a **borrowed** pointer owned by libgit2 (e.g.\r\n  `git_config_get_string`), marshal it as `IntPtr` and copy with\r\n  `Marshal.PtrToStringUTF8(...)`; do not marshal it as `out string`, or the\r\n  interop marshaller frees libgit2's heap memory with the wrong allocator.\r\n\r\nBecause the mismatch only manifests through the native call, cover new\r\nstring-carrying paths with a real-libgit2 test that uses a non-ASCII input\r\n(see `GVFS.FunctionalTests/Tests/LibGit2NonAsciiPathTests.cs`), not a\r\nmock-based unit test.\r\n\r\n## Public repository hygiene\r\n\r\nDo not include Microsoft-internal work item IDs, ADO URLs, incident IDs, or\r\ninternal service names in repository content or pull requests. Describe the\r\npublic problem and fix without requiring internal access.\r\n\r\n## Coding standards\r\n\r\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for StyleCop rules, error-handling\r\npatterns (`TryXxx` over exceptions, \"fail fast\" on data-loss risks),\r\ntracing/logging conventions (Error level reserved for unrecoverable\r\nfailures), and the `mock:\\\\` / `mock://` URL convention for unit tests.\n</yakaai_skills>"}