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
22 changes: 22 additions & 0 deletions GVFS/GVFS.Common/FileSystem/HooksInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,28 @@ public static bool TryUpdateHooks(GVFSContext context, out string errorMessage)
return false;
}

// Refresh the corresponding .hooks text files. These hold the
// absolute path of GVFS.Hooks.exe that the loader execs at hook
// time, and were originally written at clone time pointing at
// wherever GVFS was installed back then. If GVFS has moved
// (system-to-user migration, version-junction swap, hand-edited
// install), those paths go stale and the loader exits non-zero
// on every git invocation that fires a hook - making the
// enlistment unrecoverable through normal mount. Refreshing on
// every mount makes us self-healing against install-location
// drift, and is a no-op when paths are already current.
string precommandBasePath = Path.Combine(context.Enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Hooks.PreCommandPath);
if (!GVFSPlatform.Instance.TryInstallGitCommandHooks(context, ExecutingDirectory, GVFSConstants.DotGit.Hooks.PreCommandHookName, precommandBasePath, out errorMessage))
{
return false;
}

string postcommandBasePath = Path.Combine(context.Enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Hooks.PostCommandPath);
if (!GVFSPlatform.Instance.TryInstallGitCommandHooks(context, ExecutingDirectory, GVFSConstants.DotGit.Hooks.PostCommandHookName, postcommandBasePath, out errorMessage))
{
return false;
}

return true;
}

Expand Down
66 changes: 47 additions & 19 deletions GVFS/GVFS.Common/Git/GitProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ public virtual bool TryDeleteCredential(ITracer tracer, string repoUrl, string u
Result result = this.InvokeGitAgainstDotGitFolder(
GenerateCredentialVerbCommand("reject"),
stdin => stdin.Write(stdinConfig),
null);
null,
usePreCommandHook: false);

if (result.ExitCodeIsFailure)
{
Expand All @@ -218,7 +219,8 @@ public virtual bool TryStoreCredential(ITracer tracer, string repoUrl, string us
Result result = this.InvokeGitAgainstDotGitFolder(
GenerateCredentialVerbCommand("approve"),
stdin => stdin.Write(stdinConfig),
null);
null,
usePreCommandHook: false);

if (result.ExitCodeIsFailure)
{
Expand Down Expand Up @@ -249,10 +251,13 @@ public virtual bool TryGetCertificatePassword(

using (ITracer activity = tracer.StartActivity("TryGetCertificatePassword", EventLevel.Informational))
{
// See GetFromConfig for why pre-command hook is disabled
// for bootstrap-time git operations.
Result gitCredentialOutput = this.InvokeGitAgainstDotGitFolder(
"credential fill",
stdin => stdin.Write("protocol=cert\npath=" + certificatePath + "\nusername=\n\n"),
parseStdOutLine: null);
parseStdOutLine: null,
usePreCommandHook: false);

if (gitCredentialOutput.ExitCodeIsFailure)
{
Expand Down Expand Up @@ -300,10 +305,13 @@ public virtual bool TryGetCredential(

using (ITracer activity = tracer.StartActivity(nameof(this.TryGetCredential), EventLevel.Informational))
{
// See GetFromConfig for why pre-command hook is disabled
// for bootstrap-time git operations.
Result gitCredentialOutput = this.InvokeGitAgainstDotGitFolder(
GenerateCredentialVerbCommand("fill"),
stdin => stdin.Write($"url={repoUrl}\n\n"),
parseStdOutLine: null);
parseStdOutLine: null,
usePreCommandHook: false);

if (gitCredentialOutput.ExitCodeIsFailure)
{
Expand Down Expand Up @@ -336,7 +344,10 @@ public virtual bool TryGetCredential(

public bool IsValidRepo()
{
Result result = this.InvokeGitAgainstDotGitFolder("rev-parse --show-toplevel");
// Mount-time bootstrap check - skip pre-command hook so a broken
// hook config in the enlistment can be detected and repaired
// rather than blocking the mount that would fix it.
Result result = this.InvokeGitAgainstDotGitFolder("rev-parse --show-toplevel", usePreCommandHook: false);
return result.ExitCodeIsSuccess;
}

Expand All @@ -352,24 +363,34 @@ public Result GetCurrentBranchName()

public void DeleteFromLocalConfig(string settingName)
{
this.InvokeGitAgainstDotGitFolder("config --local --unset-all " + settingName);
// git config operations never need the pre-command hook (no
// working-tree mutation). Skipping it also keeps mount bootstrap
// robust against a stale hook config that TryUpdateHooks will
// repair shortly. See GetFromConfig for the longer rationale.
this.InvokeGitAgainstDotGitFolder("config --local --unset-all " + settingName, usePreCommandHook: false);
}

public Result SetInLocalConfig(string settingName, string value, bool replaceAll = false)
{
return this.InvokeGitAgainstDotGitFolder(string.Format(
"config --local {0} \"{1}\" \"{2}\"",
replaceAll ? "--replace-all " : string.Empty,
settingName,
value));
// See DeleteFromLocalConfig for why pre-command hook is disabled.
return this.InvokeGitAgainstDotGitFolder(
string.Format(
"config --local {0} \"{1}\" \"{2}\"",
replaceAll ? "--replace-all " : string.Empty,
settingName,
value),
usePreCommandHook: false);
}

public Result AddInLocalConfig(string settingName, string value)
{
return this.InvokeGitAgainstDotGitFolder(string.Format(
"config --local --add {0} {1}",
settingName,
value));
// See DeleteFromLocalConfig for why pre-command hook is disabled.
return this.InvokeGitAgainstDotGitFolder(
string.Format(
"config --local --add {0} {1}",
settingName,
value),
usePreCommandHook: false);
}

public Result SetInFileConfig(string configFile, string settingName, string value, bool replaceAll = false)
Expand All @@ -384,7 +405,8 @@ public Result SetInFileConfig(string configFile, string settingName, string valu

public bool TryGetConfigUrlMatch(string section, string repositoryUrl, out Dictionary<string, GitConfigSetting> configSettings)
{
Result result = this.InvokeGitAgainstDotGitFolder($"config --get-urlmatch {section} {repositoryUrl}");
// See GetFromConfig for why pre-command hook is disabled.
Result result = this.InvokeGitAgainstDotGitFolder($"config --get-urlmatch {section} {repositoryUrl}", usePreCommandHook: false);
if (result.ExitCodeIsFailure)
{
configSettings = null;
Expand All @@ -399,7 +421,8 @@ public bool TryGetAllConfig(bool localOnly, out Dictionary<string, GitConfigSett
{
configSettings = null;
string localParameter = localOnly ? "--local" : string.Empty;
ConfigResult result = new ConfigResult(this.InvokeGitAgainstDotGitFolder("config --list " + localParameter), "--list");
// See GetFromConfig for why pre-command hook is disabled.
ConfigResult result = new ConfigResult(this.InvokeGitAgainstDotGitFolder("config --list " + localParameter, usePreCommandHook: false), "--list");

if (result.TryParseAsString(out string output, out string _, string.Empty))
{
Expand All @@ -425,15 +448,20 @@ public virtual ConfigResult GetFromConfig(string settingName, bool forceOutsideE
fileSystem = fileSystem ?? new PhysicalFileSystem();

// This method is called at clone time, so the physical repo may not exist yet.
// Pre-command hook never applies to `git config` reads (no working tree
// mutation happens), and skipping the hook makes us robust to a broken
// hook config in the enlistment - which is exactly what we'd be trying
// to repair via TryUpdateHooks at mount time.
return
fileSystem.DirectoryExists(this.workingDirectoryRoot) && !forceOutsideEnlistment
? new ConfigResult(this.InvokeGitAgainstDotGitFolder(command), settingName)
? new ConfigResult(this.InvokeGitAgainstDotGitFolder(command, usePreCommandHook: false), settingName)
: new ConfigResult(this.InvokeGitOutsideEnlistment(command), settingName);
}

public ConfigResult GetFromLocalConfig(string settingName)
{
return new ConfigResult(this.InvokeGitAgainstDotGitFolder("config --local " + settingName), settingName);
// See GetFromConfig above for why pre-command hook is disabled here.
return new ConfigResult(this.InvokeGitAgainstDotGitFolder("config --local " + settingName, usePreCommandHook: false), settingName);
}

/// <summary>
Expand Down
Loading