From 7612b287a7a6ce39949ce94a795b0f979413187c Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Wed, 4 Nov 2026 12:21:21 -0800 Subject: [PATCH] Read gvfs.prefetch-use-idx from the merged git config BlobPrefetcher read gvfs.prefetch-use-idx with GitProcess.GetFromLocalConfig and parsed the value with bool.TryParse. Two problems follow: - A value in the global or system git config had no effect. Only the repository's local config enabled the pack-index object existence checker. - Valid git boolean values such as "yes", "on", and "1" were rejected, and the setting silently fell back to the default. Other gvfs.* feature settings (for example gvfs.prefetch-offload, gvfs.trust-pack-indexes, and gvfs.show-hydration-status) read the merged config chain through LibGit2Repo.GetConfigBoolOrDefault. Use the same helper here. It applies git boolean parsing, and it returns the default and logs a warning if the value is invalid or the read fails. Add functional tests that set gvfs.prefetch-use-idx to "yes" only in a temporary global config. One test confirms that prefetch uses the pack-index object existence checker. The other test also sets the key to "false" in the local config and confirms that the local value takes precedence. Assisted-by: Claude Opus 5.5 Signed-off-by: Tyrie Vella --- GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs | 21 ++--- .../EnlistmentPerFixture/PrefetchVerbTests.cs | 84 ++++++++++++++++++- 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs b/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs index de47a37621..47cd4cba31 100644 --- a/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs +++ b/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs @@ -404,22 +404,11 @@ private Func CreateObjectExistenceCheckerFactory(out ID { sharedCheckerOwner = null; - bool usePackIdx = false; - try - { - GitProcess git = new GitProcess(this.Enlistment); - GitProcess.ConfigResult configResult = git.GetFromLocalConfig(GVFSConstants.GitConfig.PrefetchUseIdx); - if (!configResult.TryParseAsString(out string value, out string _) || - string.IsNullOrEmpty(value) || - !bool.TryParse(value, out usePackIdx)) - { - usePackIdx = GVFSConstants.GitConfig.PrefetchUseIdxDefault; - } - } - catch (Exception ex) - { - this.Tracer.RelatedWarning("Failed to read {0} config: {1}", GVFSConstants.GitConfig.PrefetchUseIdx, ex.Message); - } + bool usePackIdx = LibGit2Repo.GetConfigBoolOrDefault( + this.Tracer, + this.Enlistment.WorkingDirectoryBackingRoot, + GVFSConstants.GitConfig.PrefetchUseIdx, + GVFSConstants.GitConfig.PrefetchUseIdxDefault); if (usePackIdx) { diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchVerbTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchVerbTests.cs index 2df774bdef..c10da43fa4 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchVerbTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchVerbTests.cs @@ -4,6 +4,7 @@ using GVFS.Tests.Should; using NUnit.Framework; using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -156,6 +157,80 @@ public void InvalidRetrySettingsDoNotStopAMountedEnlistment() } } + [TestCase(null, "pack-index")] + [TestCase("false", "revparse")] + public void PrefetchUseIdxIsReadFromMergedConfig(string localValue, string expectedChecker) + { + string[] keys = new[] { "gvfs.prefetch-use-idx", "gvfs.prefetch-offload" }; + Dictionary previous = this.ReadLocalConfig(keys); + string homeDirectory = Path.Combine(this.Enlistment.EnlistmentRoot, "global-git-config-" + Guid.NewGuid().ToString("N")); + string globalConfigPath = Path.Combine(homeDirectory, ".gitconfig"); + try + { + Directory.CreateDirectory(homeDirectory); + if (localValue == null) + { + this.UnsetLocalConfig(keys[0]); + } + else + { + this.SetLocalConfig(keys[0], localValue); + } + + this.SetLocalConfig(keys[1], "false"); + + ProcessResult originalGlobals = GitProcess.InvokeProcess(this.Enlistment.EnlistmentRoot, "var GIT_CONFIG_GLOBAL"); + originalGlobals.ExitCode.ShouldEqual(0, originalGlobals.Errors); + foreach (string originalGlobal in originalGlobals.Output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) + { + GitProcess.InvokeProcess(this.Enlistment.EnlistmentRoot, $"config --file \"{globalConfigPath}\" --add include.path \"{originalGlobal}\"") + .ExitCode.ShouldEqual(0); + } + + // Use a git boolean value that is not "true" to check git boolean parsing. + GitProcess.InvokeProcess(this.Enlistment.EnlistmentRoot, $"config --file \"{globalConfigPath}\" {keys[0]} yes") + .ExitCode.ShouldEqual(0); + + ProcessStartInfo startInfo = new ProcessStartInfo(GVFSTestConfig.PathToGVFS) + { + Arguments = $"prefetch --files {Path.Combine("GVFS", "GVFS", "Program.cs")}", + WorkingDirectory = this.Enlistment.RepoRoot, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + }; + + // libgit2 finds the global config through HOME, and git.exe uses GIT_CONFIG_GLOBAL. + // The temporary global config includes the original global config files, so + // credential settings stay available to the child process. + startInfo.Environment["HOME"] = homeDirectory; + startInfo.Environment["GIT_CONFIG_GLOBAL"] = globalConfigPath; + + ProcessResult result = ProcessHelper.Run(startInfo); + result.ExitCode.ShouldEqual(0, result.Output + result.Errors); + string log = Directory.GetFiles(Path.Combine(this.Enlistment.DotGVFSRoot, "logs"), "gvfs_prefetch_*.log") + .OrderByDescending(File.GetLastWriteTimeUtc) + .Select(File.ReadAllText) + .First(); + log.ShouldContain($"Prefetch: Using {expectedChecker} object existence checker"); + } + finally + { + try + { + this.RestoreLocalConfig(previous); + } + finally + { + if (Directory.Exists(homeDirectory)) + { + Directory.Delete(homeDirectory, recursive: true); + } + } + } + } + private Dictionary ReadLocalConfig(IEnumerable keys) { Dictionary values = new Dictionary(); @@ -175,8 +250,7 @@ private void RestoreLocalConfig(Dictionary values) { if (setting.Value == null) { - ProcessResult result = GitProcess.InvokeProcess(this.Enlistment.RepoRoot, "config --local --unset-all " + setting.Key); - (result.ExitCode == 0 || result.ExitCode == 5).ShouldEqual(true, result.Errors); + this.UnsetLocalConfig(setting.Key); } else { @@ -185,6 +259,12 @@ private void RestoreLocalConfig(Dictionary values) } } + private void UnsetLocalConfig(string key) + { + ProcessResult result = GitProcess.InvokeProcess(this.Enlistment.RepoRoot, "config --local --unset-all " + key); + (result.ExitCode == 0 || result.ExitCode == 5).ShouldEqual(true, result.Errors); + } + private void SetLocalConfig(string key, string value) { ProcessResult result = GitProcess.InvokeProcess(