Skip to content
Open
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
40 changes: 40 additions & 0 deletions .github/workflows/upgrade-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,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 @@ -193,6 +223,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 @@ -205,6 +240,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
47 changes: 39 additions & 8 deletions GVFS/GVFS.Common/FileSystem/HooksInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,27 @@ public static bool TryUpdateHooks(GVFSContext context, out string errorMessage)
}
}

// Update the pre-command and post-command hook loaders (GitHooksLoader copies).
// These are deployed at clone time by InstallHooks but also need updating on
// mount so that upgrading GVFS and remounting refreshes all hooks.
string loaderSourcePath = Path.Combine(ExecutingDirectory, GVFSConstants.DotGit.Hooks.LoaderExecutable);

string precommandHookPath = Path.Combine(
context.Enlistment.WorkingDirectoryBackingRoot,
GVFSConstants.DotGit.Hooks.PreCommandPath + GVFSPlatform.Instance.Constants.ExecutableExtension);
if (!TryUpdateHook(context, GVFSConstants.DotGit.Hooks.PreCommandHookName, loaderSourcePath, precommandHookPath, out errorMessage))
{
return false;
}

string postcommandHookPath = Path.Combine(
context.Enlistment.WorkingDirectoryBackingRoot,
GVFSConstants.DotGit.Hooks.PostCommandPath + GVFSPlatform.Instance.Constants.ExecutableExtension);
if (!TryUpdateHook(context, GVFSConstants.DotGit.Hooks.PostCommandHookName, loaderSourcePath, postcommandHookPath, out errorMessage))
{
return false;
}

return true;
}

Expand Down Expand Up @@ -161,13 +182,23 @@ private static bool TryUpdateHook(
HookData hook,
out string errorMessage)
{
bool copyHook = false;
string enlistmentHookPath = Path.Combine(context.Enlistment.WorkingDirectoryBackingRoot, hook.Path + GVFSPlatform.Instance.Constants.ExecutableExtension);
string installedHookPath = Path.Combine(ExecutingDirectory, hook.ExecutableName);
return TryUpdateHook(context, hook.Name, installedHookPath, enlistmentHookPath, out errorMessage);
}

private static bool TryUpdateHook(
GVFSContext context,
string hookName,
string installedHookPath,
string enlistmentHookPath,
out string errorMessage)
{
bool copyHook = false;

if (!context.FileSystem.FileExists(installedHookPath))
{
errorMessage = hook.ExecutableName + " cannot be found at " + installedHookPath;
errorMessage = Path.GetFileName(installedHookPath) + " cannot be found at " + installedHookPath;
return false;
}

Expand All @@ -179,8 +210,8 @@ private static bool TryUpdateHook(
metadata.Add("Area", "Mount");
metadata.Add(nameof(enlistmentHookPath), enlistmentHookPath);
metadata.Add(nameof(installedHookPath), installedHookPath);
metadata.Add(TracingConstants.MessageKey.WarningMessage, hook.Name + " not found in enlistment, copying from installation folder");
context.Tracer.RelatedWarning(hook.Name + " MissingFromEnlistment", metadata);
metadata.Add(TracingConstants.MessageKey.WarningMessage, hookName + " not found in enlistment, copying from installation folder");
context.Tracer.RelatedWarning(hookName + " MissingFromEnlistment", metadata);
}
else
{
Expand All @@ -197,8 +228,8 @@ private static bool TryUpdateHook(
metadata.Add(nameof(enlistmentHookPath), enlistmentHookPath);
metadata.Add(nameof(installedHookPath), installedHookPath);
metadata.Add("Exception", e.ToString());
context.Tracer.RelatedError(metadata, "Failed to compare " + hook.Name + " version");
errorMessage = "Error comparing " + hook.Name + " versions. " + ConsoleHelper.GetGVFSLogMessage(context.Enlistment.EnlistmentRoot);
context.Tracer.RelatedError(metadata, "Failed to compare " + hookName + " version");
errorMessage = "Error comparing " + hookName + " versions. " + ConsoleHelper.GetGVFSLogMessage(context.Enlistment.EnlistmentRoot);
return false;
}
}
Expand All @@ -216,8 +247,8 @@ private static bool TryUpdateHook(
metadata.Add(nameof(enlistmentHookPath), enlistmentHookPath);
metadata.Add(nameof(installedHookPath), installedHookPath);
metadata.Add("Exception", e.ToString());
context.Tracer.RelatedError(metadata, "Failed to copy " + hook.Name + " to enlistment");
errorMessage = "Error copying " + hook.Name + " to enlistment. " + ConsoleHelper.GetGVFSLogMessage(context.Enlistment.EnlistmentRoot);
context.Tracer.RelatedError(metadata, "Failed to copy " + hookName + " to enlistment");
errorMessage = "Error copying " + hookName + " to enlistment. " + ConsoleHelper.GetGVFSLogMessage(context.Enlistment.EnlistmentRoot);
return false;
}
}
Expand Down
Loading