From 2b0615ded536a1b893bcf38d8319f433c4c5be5e Mon Sep 17 00:00:00 2001 From: Marc Becker Date: Tue, 1 Sep 2026 18:56:42 +0200 Subject: [PATCH 1/3] config: add test for conditional config inclusion test special conditional global include based on local Git remote value verify unconditional include directive for local scope --- src/Core.Tests/GitConfigurationTests.cs | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Core.Tests/GitConfigurationTests.cs b/src/Core.Tests/GitConfigurationTests.cs index 5005cf4319..2f4b281ded 100644 --- a/src/Core.Tests/GitConfigurationTests.cs +++ b/src/Core.Tests/GitConfigurationTests.cs @@ -656,5 +656,37 @@ public void GitConfiguration_TypedQuery_CanonicalizesValues() // Value should be canonicalized path, not raw "~/example" Assert.NotEqual("~/example", value); } + + [Fact] + public void GitConfiguration_ScopedConfig_ProcessesIncludes() + { + const string configKey = "test.value"; + const string configTrigger = "remote.*.url:http://localhost/**"; + const string configValue = "foo123"; + string repoPath = CreateRepository(out string workDirPath); + + ExecGit(repoPath, workDirPath, $"config --file {workDirPath}/git.config.glob {configKey} {configValue}").AssertSuccess(); + ExecGit(repoPath, workDirPath, $"config --global includeif.hasconfig:{configTrigger}.path {workDirPath}/git.config.glob").AssertSuccess(); + + ExecGit(repoPath, workDirPath, $"config --file {workDirPath}/git.config.loc {configKey} {configValue}").AssertSuccess(); + ExecGit(repoPath, workDirPath, $"config --local include.path {workDirPath}/git.config.loc").AssertSuccess(); + + ExecGit(repoPath, workDirPath, $"config --local remote.test.url http://localhost/my/repo").AssertSuccess(); + + string gitPath = GetGitPath(); + var trace = new NullTrace(); + var trace2 = new NullTrace2(); + var processManager = new TestProcessManager(); + + var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + IGitConfiguration config = git.GetConfiguration(); + + // value of included file set in scopes + foreach (var scope in new GitConfigurationLevel[] { GitConfigurationLevel.Global, GitConfigurationLevel.Local }) + { + Assert.True(config.TryGet(scope, GitConfigurationType.Raw, configKey, out string value)); + Assert.Equal(configValue, value); + } + } } } From 1d8a7c625a74d666b3f6af4bbe5c9d0801bccff4 Mon Sep 17 00:00:00 2001 From: Marc Becker Date: Tue, 1 Sep 2026 19:02:38 +0200 Subject: [PATCH 2/3] config: enable cache for Raw config in Git v2.26 split configuration cache checks for raw and typed values drop `--no-type` for Raw values (default) to support older Git versions --- src/Core/GitConfiguration.cs | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Core/GitConfiguration.cs b/src/Core/GitConfiguration.cs index 83a10d5918..e62f652ff9 100644 --- a/src/Core/GitConfiguration.cs +++ b/src/Core/GitConfiguration.cs @@ -326,11 +326,13 @@ public class GitProcessConfiguration : IGitConfiguration private static readonly GitVersion ConfigListTypeMinVersion = new GitVersion(2, 54, 0); private static readonly GitVersion ConfigListTypeMinVfsBase = new GitVersion(2, 53, 0); private static readonly GitVersion ConfigListTypeMinVfsSuffix = new GitVersion(0, 1); + private static readonly GitVersion ConfigListRawMinVersion = new GitVersion(2, 26, 0); private readonly ITrace _trace; private readonly GitProcess _git; private readonly Dictionary _cache; - private readonly bool _useCache; + private readonly bool _useConfigTypeCache; + private readonly bool _useConfigRawCache; internal GitProcessConfiguration(ITrace trace, GitProcess git) : this(trace, git, useCache: true) { @@ -349,11 +351,23 @@ internal GitProcessConfiguration(ITrace trace, GitProcess git, bool useCache) if (useCache && !SupportsConfigListType(git)) { trace.WriteLine($"Git version {git.Version.OriginalString} does not support 'git config list --type'; config cache disabled"); - useCache = false; + _useConfigTypeCache = false; + } + else + { + _useConfigTypeCache = useCache; } - _useCache = useCache; - _cache = useCache ? new Dictionary() : null; + if (useCache && !SupportsConfigListRaw(git)) + { + trace.WriteLine($"Git version {git.Version.OriginalString} does not support 'git config list --type'; config cache disabled"); + _useConfigRawCache = false; + } + else + { + _useConfigRawCache = useCache; + } + _cache = _useConfigTypeCache || _useConfigRawCache ? new Dictionary() : null; } private static bool SupportsConfigListType(GitProcess git) @@ -381,10 +395,20 @@ private static bool SupportsConfigListType(GitProcess git) return false; } + private static bool SupportsConfigListRaw(GitProcess git) + { + return git.Version >= ConfigListRawMinVersion; + } + + private bool UseConfigCache(GitConfigurationType type) + { + return type != GitConfigurationType.Raw ? _useConfigTypeCache : _useConfigRawCache; + } + private void EnsureCacheLoaded(GitConfigurationType type) { ConfigCache cache; - if (!_useCache || (_cache.TryGetValue(type, out cache) && cache.IsLoaded)) + if (!UseConfigCache(type) || (_cache.TryGetValue(type, out cache) && cache.IsLoaded)) { return; } @@ -400,7 +424,7 @@ private void EnsureCacheLoaded(GitConfigurationType type) switch (type) { case GitConfigurationType.Raw: - typeArg = "--no-type"; + typeArg = ""; break; case GitConfigurationType.Path: @@ -437,7 +461,7 @@ private void EnsureCacheLoaded(GitConfigurationType type) private void InvalidateCache() { - if (_useCache) + if (_useConfigTypeCache ||_useConfigRawCache) { foreach (ConfigCache cache in _cache.Values) { @@ -448,7 +472,7 @@ private void InvalidateCache() public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCallback cb) { - if (_useCache) + if (_useConfigRawCache) { EnsureCacheLoaded(GitConfigurationType.Raw); @@ -530,7 +554,7 @@ public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCa public bool TryGet(GitConfigurationLevel level, GitConfigurationType type, string name, out string value) { - if (_useCache) + if (UseConfigCache(type)) { EnsureCacheLoaded(type); @@ -647,7 +671,7 @@ public void Unset(GitConfigurationLevel level, string name) public IEnumerable GetAll(GitConfigurationLevel level, GitConfigurationType type, string name) { - if (_useCache) + if (UseConfigCache(type)) { EnsureCacheLoaded(type); From c9cf8912bc589adc143225ee323288d418d4a9e6 Mon Sep 17 00:00:00 2001 From: Marc Becker Date: Tue, 1 Sep 2026 19:11:33 +0200 Subject: [PATCH 3/3] config: enable file inclusion in config queries match query results of `ConfigCache` as close as possible --- src/Core/GitConfiguration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Core/GitConfiguration.cs b/src/Core/GitConfiguration.cs index e62f652ff9..1232bb4684 100644 --- a/src/Core/GitConfiguration.cs +++ b/src/Core/GitConfiguration.cs @@ -487,7 +487,7 @@ public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCa // Fall back to original implementation string levelArg = GetLevelFilterArg(level); - using (ChildProcess git = _git.CreateProcess($"config --null {levelArg} --list")) + using (ChildProcess git = _git.CreateProcess($"config --null {levelArg} --includes --list")) { git.Start(Trace2ProcessClass.Git); // To avoid deadlocks, always read the output stream first and then wait @@ -569,7 +569,7 @@ public bool TryGet(GitConfigurationLevel level, GitConfigurationType type, strin // Fall back to individual git config command if cache not available string levelArg = GetLevelFilterArg(level); string typeArg = GetCanonicalizeTypeArg(type); - using (ChildProcess git = _git.CreateProcess($"config --null {levelArg} {typeArg} {QuoteCmdArg(name)}")) + using (ChildProcess git = _git.CreateProcess($"config --null {levelArg} --includes {typeArg} {QuoteCmdArg(name)}")) { git.Start(Trace2ProcessClass.Git); // To avoid deadlocks, always read the output stream first and then wait @@ -691,7 +691,7 @@ public IEnumerable GetAll(GitConfigurationLevel level, GitConfigurationT string levelArg = GetLevelFilterArg(level); string typeArg = GetCanonicalizeTypeArg(type); - var gitArgs = $"config --null {levelArg} {typeArg} --get-all {QuoteCmdArg(name)}"; + var gitArgs = $"config --null {levelArg} --includes {typeArg} --get-all {QuoteCmdArg(name)}"; using (ChildProcess git = _git.CreateProcess(gitArgs)) { @@ -729,7 +729,7 @@ public IEnumerable GetRegex(GitConfigurationLevel level, GitConfiguratio string levelArg = GetLevelFilterArg(level); string typeArg = GetCanonicalizeTypeArg(type); - var gitArgs = $"config --null {levelArg} {typeArg} --get-regex {QuoteCmdArg(nameRegex)}"; + var gitArgs = $"config --null {levelArg} --includes {typeArg} --get-regex {QuoteCmdArg(nameRegex)}"; if (valueRegex != null) { gitArgs += $" {QuoteCmdArg(valueRegex)}";