diff --git a/GVFS/GVFS.Common/FileSystem/HooksInstaller.cs b/GVFS/GVFS.Common/FileSystem/HooksInstaller.cs index bdf6a03cb..407918c67 100644 --- a/GVFS/GVFS.Common/FileSystem/HooksInstaller.cs +++ b/GVFS/GVFS.Common/FileSystem/HooksInstaller.cs @@ -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; } diff --git a/GVFS/GVFS.Common/Git/GitProcess.cs b/GVFS/GVFS.Common/Git/GitProcess.cs index b818fd915..623a52f1f 100644 --- a/GVFS/GVFS.Common/Git/GitProcess.cs +++ b/GVFS/GVFS.Common/Git/GitProcess.cs @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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; } @@ -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) @@ -384,7 +405,8 @@ public Result SetInFileConfig(string configFile, string settingName, string valu public bool TryGetConfigUrlMatch(string section, string repositoryUrl, out Dictionary 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; @@ -399,7 +421,8 @@ public bool TryGetAllConfig(bool localOnly, out Dictionary