Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ea81e68
Handle case-only file and directory renames in FastFetch on Windows
erickulcyk May 11, 2026
47a5253
refactor: Extract directory-collision lookup helper in DiffHelper
derrickstolee May 11, 2026
f10d0e1
fix: Handle case-only directory rename when Add precedes Delete
derrickstolee May 11, 2026
89e9a13
fix: Restore source directory when case-rename second move fails
derrickstolee May 11, 2026
f622278
chore: Remove dead variable in EnqueueFileAddOperation
derrickstolee May 11, 2026
826bd90
refactor: Guard DiffHelper against accidental reuse
derrickstolee May 11, 2026
a120b44
refactor: Rename caseRenamedDirectoryDeletes to convey purpose
derrickstolee May 11, 2026
3e80cc7
refactor: Restrict DiffTreeResult.SourcePath setter to assembly
derrickstolee May 11, 2026
bd6bcd8
refactor: Replace HashSet linear scans with Dictionary lookups in Dif…
derrickstolee May 11, 2026
abaa757
feat: Restore tracing for case renames and duplicate diff entries
derrickstolee May 11, 2026
f6c0cd7
refactor: Extract case-only directory rename to ApplyCaseOnlyDirector…
derrickstolee May 11, 2026
745653c
test: Add coverage for file-only case rename
derrickstolee May 11, 2026
cbd7c82
test: Add coverage for nested directory case rename
derrickstolee May 11, 2026
4748e73
Merge pull request #2 from derrickstolee/dstolee/case-rename-fixes
erickulcyk May 11, 2026
8f25747
Statically link libgit2 into NativeAOT executables via vcpkg
tyrielv May 11, 2026
4951c39
Address PR feedback: vcpkg auto-restore, build improvements
tyrielv May 13, 2026
e4d1f83
Merge pull request #1965 from tyrielv/tyrielv/static-native-linking
tyrielv May 15, 2026
4ef6893
Apply staged upgrade when mount processes exit
tyrielv May 15, 2026
db45285
Merge pull request #1976 from tyrielv/tyrielv/fix-unmountall-upgrade-…
tyrielv May 18, 2026
5eeba9e
Replace object locks with System.Threading.Lock
tyrielv May 18, 2026
1e748bb
Merge pull request #1977 from tyrielv/tyrielv/lock-object-migration
tyrielv May 18, 2026
55bcda1
Fix handle leak in GetFinalPathName
tyrielv May 19, 2026
1a5741b
ProjFS: tolerate ACCESS_DENIED on FilterAttach, remove dead bundled-d…
tyrielv May 19, 2026
3d08ce4
Merge pull request #1964 from erickulcyk/erickul/noignorecase
tyrielv May 22, 2026
c142e76
Add non-elevated admin owner patch to libgit2 overlay
tyrielv May 22, 2026
c3808d2
TryUpdateHooks: also update pre-command/post-command hook loaders on …
tyrielv May 22, 2026
d0e779c
ci: add retry for artifact download steps
tyrielv May 22, 2026
43681f7
Add unique exit code for mount authentication failures
tyrielv May 22, 2026
e4409d6
Merge pull request #1982 from tyrielv/tyrielv/test-hook-version-upgrade
tyrielv May 26, 2026
96e08ea
Merge pull request #1983 from tyrielv/tyrielv/libgit2-overlay-patch
tyrielv May 26, 2026
c62d9dd
Merge pull request #1984 from tyrielv/tyrielv/retry-artifact-downloads
tyrielv May 26, 2026
c585068
Merge pull request #1978 from tyrielv/tyrielv/fix-handle-leak-getfina…
tyrielv May 26, 2026
d334b5a
Defer and retry telemetry pipe attachment for GVFS.Service
tyrielv May 26, 2026
b2c81a1
Add unit tests for deferred telemetry pipe attachment
tyrielv May 26, 2026
23d4c9c
Merge pull request #1985 from tyrielv/tyrielv/auth-exit-code
tyrielv May 26, 2026
781ff2c
Merge pull request #1980 from tyrielv/tyrielv/fix-filter-attach
tyrielv May 26, 2026
c53aea6
Merge pull request #1986 from tyrielv/tyrielv/service-telemetry-pipe
tyrielv May 26, 2026
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 .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,13 @@ jobs:
if: steps.skip.outputs.result != 'true'
uses: microsoft/setup-msbuild@v3.0.0

- name: Install vcpkg native dependencies
if: steps.skip.outputs.result != 'true'
shell: cmd
run: |
"%VCPKG_INSTALLATION_ROOT%\vcpkg.exe" install --triplet x64-windows-static-aot --x-install-root=out\vcpkg_installed\static --x-manifest-root=src || exit /b 1
"%VCPKG_INSTALLATION_ROOT%\vcpkg.exe" install --triplet x64-windows-dynamic --x-install-root=out\vcpkg_installed\dynamic --x-manifest-root=src || exit /b 1

- name: Build VFS for Git
if: steps.skip.outputs.result != 'true'
shell: cmd
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/functional-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,19 @@ jobs:
return true

- name: Download Git installer
id: download-git
if: steps.skip.outputs.result != 'true'
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: ${{ inputs.git_artifact_name }}
path: git
repository: ${{ inputs.git_repository || github.repository }}
run-id: ${{ inputs.git_run_id || github.run_id }}
github-token: ${{ secrets.git_token || github.token }}

- name: Download Git installer (retry)
if: steps.skip.outputs.result != 'true' && steps.download-git.outcome == 'failure'
uses: actions/download-artifact@v8
with:
name: ${{ inputs.git_artifact_name }}
Expand All @@ -87,7 +99,19 @@ jobs:
github-token: ${{ secrets.git_token || github.token }}

- name: Download GVFS installer
id: download-gvfs
if: steps.skip.outputs.result != 'true'
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: GVFS_${{ matrix.configuration }}
path: gvfs
repository: ${{ inputs.vfs_repository || github.repository }}
run-id: ${{ inputs.vfs_run_id || github.run_id }}
github-token: ${{ secrets.vfs_token || github.token }}

- name: Download GVFS installer (retry)
if: steps.skip.outputs.result != 'true' && steps.download-gvfs.outcome == 'failure'
uses: actions/download-artifact@v8
with:
name: GVFS_${{ matrix.configuration }}
Expand All @@ -97,7 +121,19 @@ jobs:
github-token: ${{ secrets.vfs_token || github.token }}

- name: Download functional tests drop
id: download-ft
if: steps.skip.outputs.result != 'true'
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: FunctionalTests_${{ matrix.configuration }}
path: ft
repository: ${{ inputs.vfs_repository || github.repository }}
run-id: ${{ inputs.vfs_run_id || github.run_id }}
github-token: ${{ secrets.vfs_token || github.token }}

- name: Download functional tests drop (retry)
if: steps.skip.outputs.result != 'true' && steps.download-ft.outcome == 'failure'
uses: actions/download-artifact@v8
with:
name: FunctionalTests_${{ matrix.configuration }}
Expand Down
89 changes: 89 additions & 0 deletions .github/workflows/upgrade-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
- double-staging
- staging-then-clean
- mount-safety-deferral
- unmount-all-triggers-upgrade
fail-fast: false

steps:
Expand Down Expand Up @@ -62,14 +63,32 @@ jobs:
gh release download $tag --repo microsoft/VFSForGit --pattern "SetupGVFS*.exe" --dir gvfs-lkg

- name: Download Git installer
id: download-git
if: steps.skip.outputs.result != 'true'
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: MicrosoftGit
path: git

- name: Download Git installer (retry)
if: steps.skip.outputs.result != 'true' && steps.download-git.outcome == 'failure'
uses: actions/download-artifact@v8
with:
name: MicrosoftGit
path: git

- name: Download current GVFS installer
id: download-gvfs
if: steps.skip.outputs.result != 'true'
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: GVFS_${{ matrix.configuration }}
path: gvfs-new

- name: Download current GVFS installer (retry)
if: steps.skip.outputs.result != 'true' && steps.download-gvfs.outcome == 'failure'
uses: actions/download-artifact@v8
with:
name: GVFS_${{ matrix.configuration }}
Expand Down Expand Up @@ -172,6 +191,36 @@ jobs:
}
}

function Assert-HookVersionsMatch {
Write-Host "Verifying hook binary versions in enlistment..."
$hooksDir = Join-Path $enlistment "src\.git\hooks"

# Native hooks: each has its own source binary in the install directory
# Command hooks: copies of GitHooksLoader.exe
$hooks = @(
@{ Source = "GVFS.ReadObjectHook.exe"; Target = "read-object.exe" }
@{ Source = "GVFS.VirtualFileSystemHook.exe"; Target = "virtual-filesystem.exe" }
@{ Source = "GVFS.PostIndexChangedHook.exe"; Target = "post-index-change.exe" }
@{ Source = "GitHooksLoader.exe"; Target = "pre-command.exe" }
@{ Source = "GitHooksLoader.exe"; Target = "post-command.exe" }
)

foreach ($hook in $hooks) {
$sourcePath = Join-Path $installDir $hook.Source
$targetPath = Join-Path $hooksDir $hook.Target
if (-not (Test-Path $targetPath)) {
throw "Hook not found: $targetPath"
}
$sourceVer = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($sourcePath).FileVersion
$targetVer = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($targetPath).FileVersion
if ($sourceVer -ne $targetVer) {
throw "Hook version mismatch: $($hook.Target) is $targetVer, expected $sourceVer (from $($hook.Source))"
}
Write-Host " $($hook.Target): $targetVer OK"
}
Write-Host "All hook binary versions match"
}

# =============================================
# Test scenarios
# =============================================
Expand All @@ -192,6 +241,11 @@ jobs:
Unmount-TestRepo
Restart-Service
Assert-PendingUpgrade $false

# Remount and verify all hooks were updated to new version
$null = Mount-TestRepo
Assert-HookVersionsMatch
Unmount-TestRepo
Write-Host "PASS: Staging upgrade completed"
}

Expand All @@ -204,6 +258,11 @@ jobs:
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=false")
Assert-PendingUpgrade $false
Assert-ServiceRunning

# Remount and verify all hooks were updated to new version
$null = Mount-TestRepo
Assert-HookVersionsMatch
Unmount-TestRepo
Write-Host "PASS: Clean upgrade completed"
}

Expand Down Expand Up @@ -274,6 +333,36 @@ jobs:
Write-Host "PASS: Mount safety deferral works correctly"
}

"unmount-all-triggers-upgrade" {
Write-Host "=== Scenario: unmount-all triggers staged upgrade ==="
# Install LKG, mount, staging upgrade with new installer (which
# replaces GVFS.Service.exe in-place with the new version that
# includes PendingUpgradeMonitor). Then unmount via --unmount-all.
# The new service monitors mount process exits and applies the
# upgrade automatically — no pipe message from gvfs.exe needed.
Install-GVFS $lkgInstaller
Assert-ServiceRunning
$mountPid = Mount-TestRepo

Install-GVFS $newInstaller @("/STAGEIFMOUNTED=true")
Assert-MountAlive $mountPid
Assert-PendingUpgrade $true

# Unmount via --unmount-all (uses LKG gvfs.exe — no new pipe msg)
& "$installDir\gvfs.exe" service --unmount-all 2>&1 | Write-Host
if ($LASTEXITCODE -ne 0) { throw "unmount-all failed" }

# The monitor's debounce timer fires 5s after the last mount
# process exits, then applies the upgrade. Wait for completion.
$deadline = (Get-Date).AddSeconds(30)
while ((Test-Path "$installDir\PendingUpgrade") -and (Get-Date) -lt $deadline) {
Start-Sleep -Seconds 2
}

Assert-PendingUpgrade $false
Write-Host "PASS: unmount-all triggers staged upgrade via process monitor"
}

default {
throw "Unknown scenario: ${{ matrix.scenario }}"
}
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,5 @@ ModelManifest.xml
.vscode/

# ProjFS Kext Unit Test coverage results
ProjFS.Mac/CoverageResult.txt
ProjFS.Mac/CoverageResult.txt

68 changes: 68 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,74 @@
<BaseIntermediateOutputPath>$(ProjectOutPath)obj\</BaseIntermediateOutputPath>
</PropertyGroup>

<!--
Static native linking: embed libgit2 directly into AOT-compiled executables.

vcpkg builds a static git2.lib (via the x64-windows-static-aot triplet) which
the NativeAOT linker links into the final executable. This eliminates the
"DLL missing" crash vector for libgit2.

How it works:
1. vcpkg install produces git2.lib + transitive deps (pcre, zlib, etc.)
2. <NativeLibrary> items tell the NativeAOT linker where to find the .lib files
3. <DirectPInvoke> tells the linker to resolve P/Invoke calls at link time
4. libgit2_filename is overridden from "git2-XXXXXXX" (NuGet) to "git2" (vcpkg)

A side benefit of this approach: we can apply patches to libgit2 that haven't
been included in an official release yet, via the overlay port in overlays/.

To rebuild vcpkg libs after overlay changes:
vcpkg install [triplet x64-windows-static-aot] [overlay-triplets=triplets] [overlay-ports=overlays]

See THIRD-PARTY-NOTICES.md for license details on these native libraries.
-->
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
<VcpkgInstalledDir>$(RepoOutPath)vcpkg_installed\static\x64-windows-static-aot\</VcpkgInstalledDir>
</PropertyGroup>

<!-- Set libgit2 library name unconditionally — all builds use vcpkg-sourced libgit2 -->
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<libgit2_filename>git2</libgit2_filename>
</PropertyGroup>

<ItemGroup Condition="'$(PublishAot)' == 'true'">
<DirectPInvoke Include="git2" />

<!-- libgit2 static library and its transitive dependencies -->
<NativeLibrary Include="$(VcpkgInstalledDir)lib\git2.lib" />
<NativeLibrary Include="$(VcpkgInstalledDir)lib\pcre.lib" />
<NativeLibrary Include="$(VcpkgInstalledDir)lib\http_parser.lib" />
<NativeLibrary Include="$(VcpkgInstalledDir)lib\zs.lib" />

<!-- Windows system libraries required by libgit2 (WinHTTP TLS backend) -->
<NativeLibrary Include="winhttp.lib" />
<NativeLibrary Include="crypt32.lib" />
<NativeLibrary Include="rpcrt4.lib" />
<NativeLibrary Include="secur32.lib" />
</ItemGroup>

<!--
Non-AOT projects (tests) need git2.dll for runtime P/Invoke.
Copy from the vcpkg dynamic build output.
-->
<ItemGroup Condition="'$(PublishAot)' != 'true' and '$(MSBuildProjectExtension)' == '.csproj' and '$(IncludeBuildOutput)' != 'false'">
<Content Include="$(RepoOutPath)vcpkg_installed\dynamic\x64-windows-dynamic\bin\git2.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<Link>git2.dll</Link>
</Content>
<Content Include="$(RepoOutPath)vcpkg_installed\dynamic\x64-windows-dynamic\bin\pcre.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<Link>pcre.dll</Link>
</Content>
<Content Include="$(RepoOutPath)vcpkg_installed\dynamic\x64-windows-dynamic\bin\z.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<Link>z.dll</Link>
</Content>
</ItemGroup>

<!-- Native project properties -->
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.vcxproj'">
<Platform>x64</Platform>
Expand Down
69 changes: 69 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,75 @@
<!-- Include custom MSBuild targets/tasks -->
<Import Project="$(MSBuildThisFileDirectory)GVFS\GVFS.MSBuild\GVFS.targets" />

<!--
Auto-restore vcpkg native dependencies when missing.
Modeled after vcpkg's official VcpkgInstallManifestDependencies target
(scripts/buildsystems/msbuild/vcpkg.targets), adapted for .csproj projects
and dual-triplet installs (static-aot + dynamic).

Key differences from official vcpkg.targets:
- Hooks PrepareForBuild instead of ClCompile (which doesn't exist in csproj)
- Installs two triplets (static for AOT linking, dynamic for runtime P/Invoke)
- Outputs to out/vcpkg_installed/ instead of src/vcpkg_installed/
- Discovers vcpkg via VCPKG_INSTALLATION_ROOT, VS-bundled path, or PATH

Stamp file pattern: the target uses Inputs/Outputs so MSBuild skips it entirely
when the manifests haven't changed and the stamp file is up-to-date.
Build.bat also runs vcpkg install upfront, making this a no-op for CLI builds.
-->
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<_VcpkgManifestFile>$(RepoSrcPath)vcpkg.json</_VcpkgManifestFile>
<_VcpkgConfigFile>$(RepoSrcPath)vcpkg-configuration.json</_VcpkgConfigFile>
<_VcpkgStampFile>$(RepoOutPath)vcpkg_installed\.msbuildstamp</_VcpkgStampFile>
</PropertyGroup>

<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<_VcpkgManifestInputs Include="$(_VcpkgManifestFile)" />
<_VcpkgManifestInputs Include="$(_VcpkgConfigFile)" Condition="Exists('$(_VcpkgConfigFile)')" />
</ItemGroup>

<Target Name="_RestoreVcpkgDependencies"
BeforeTargets="PrepareForBuild;ComputeFilesToPublish"
Condition="'$(MSBuildProjectName)' == 'GVFS.Common'"
Inputs="@(_VcpkgManifestInputs)"
Outputs="$(_VcpkgStampFile)">

<!-- Locate vcpkg: VCPKG_INSTALLATION_ROOT > VS-bundled > PATH -->
<PropertyGroup>
<_VcpkgExe Condition="Exists('$(VCPKG_INSTALLATION_ROOT)\vcpkg.exe')">$(VCPKG_INSTALLATION_ROOT)\vcpkg.exe</_VcpkgExe>
<_VcpkgExe Condition="'$(_VcpkgExe)' == '' and Exists('$(VsInstallRoot)\VC\vcpkg\vcpkg.exe')">$(VsInstallRoot)\VC\vcpkg\vcpkg.exe</_VcpkgExe>
<_VcpkgManifestArg>--x-manifest-root="$(RepoSrcPath.TrimEnd('\'))"</_VcpkgManifestArg>
</PropertyGroup>

<!-- For dotnet build (no VsInstallRoot): find VS-bundled vcpkg via vswhere -->
<Exec Command="&quot;$(MSBuildProgramFiles32)\Microsoft Visual Studio\Installer\vswhere.exe&quot; -latest -products * -property installationPath"
ConsoleToMSBuild="true" IgnoreExitCode="true" StandardOutputImportance="low"
Condition="'$(_VcpkgExe)' == ''">
<Output TaskParameter="ConsoleOutput" PropertyName="_VsInstallPath" />
</Exec>
<PropertyGroup Condition="'$(_VcpkgExe)' == '' and '$(_VsInstallPath)' != '' and Exists('$(_VsInstallPath)\VC\vcpkg\vcpkg.exe')">
<_VcpkgExe>$(_VsInstallPath)\VC\vcpkg\vcpkg.exe</_VcpkgExe>
</PropertyGroup>

<!-- Final fallback: bare vcpkg on PATH -->
<PropertyGroup Condition="'$(_VcpkgExe)' == ''">
<_VcpkgExe>vcpkg</_VcpkgExe>
</PropertyGroup>

<Message Importance="high" Text="[vcpkg] Installing native dependencies to $(RepoOutPath)vcpkg_installed\" />

<MakeDir Directories="$(RepoOutPath)vcpkg_installed\" />
<Exec Command="&quot;$(_VcpkgExe)&quot; install --x-wait-for-lock --triplet x64-windows-static-aot --x-install-root=&quot;$(RepoOutPath)vcpkg_installed\static&quot; $(_VcpkgManifestArg)"
StandardOutputImportance="High" StandardErrorImportance="High" />
<Exec Command="&quot;$(_VcpkgExe)&quot; install --x-wait-for-lock --triplet x64-windows-dynamic --x-install-root=&quot;$(RepoOutPath)vcpkg_installed\dynamic&quot; $(_VcpkgManifestArg)"
StandardOutputImportance="High" StandardErrorImportance="High" />

<Error Condition="!Exists('$(RepoOutPath)vcpkg_installed\dynamic\x64-windows-dynamic\bin\git2.dll')"
Text="vcpkg install completed but expected output files are missing. Check vcpkg output above for errors." />

<Touch Files="$(_VcpkgStampFile)" AlwaysCreate="true" />
</Target>

<!--
Copy native C++ hook executables and managed peer executables to GVFS.exe
output directory. In a production install all binaries are co-located in one
Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageVersion Include="SharpZipLib" Version="1.3.3" />

<!-- Git -->
<PackageVersion Include="LibGit2Sharp.NativeBinaries" Version="2.0.322" />
<!-- libgit2 native library is now provided by vcpkg (see overlays/libgit2) -->

<!-- ProjFS -->
<PackageVersion Include="Microsoft.Windows.ProjFS" Version="2.1.0" />
Expand Down
Loading
Loading