From cac7c5dd00024a25cf417b48be409dbbb14300dd Mon Sep 17 00:00:00 2001 From: xping-admin Date: Sat, 16 May 2026 21:41:12 +0200 Subject: [PATCH] feat: implement fallback to global git config for user name retrieval --- samples/Directory.Build.props | 3 +- .../Internals/EnvironmentDetector.cs | 30 ++++++++++++++++- .../Environment/EnvironmentDetectorTests.cs | 33 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index b6ad718..81af6f6 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -10,7 +10,8 @@ false - $(NoWarn);CA2007;CS1591;CA1515;CA1816;CA1062 + + $(NoWarn);CA2007;CS1591;CA1515;CA1816;CA1062;CA5394 false diff --git a/src/Xping.Sdk.Core/Services/Environment/Internals/EnvironmentDetector.cs b/src/Xping.Sdk.Core/Services/Environment/Internals/EnvironmentDetector.cs index 82b5e28..39e358d 100644 --- a/src/Xping.Sdk.Core/Services/Environment/Internals/EnvironmentDetector.cs +++ b/src/Xping.Sdk.Core/Services/Environment/Internals/EnvironmentDetector.cs @@ -732,10 +732,38 @@ private static void CollectLocalGitMetadata(Dictionary propertie } private static string? ReadGitConfigUserName(string gitDir) + { + // Check local repo config first, then fall back to global git config (~/.gitconfig or + // XDG_CONFIG_HOME/git/config), which is where most users set their name. + string localConfig = Path.Combine(gitDir, "config"); + string? result = ReadUserNameFromFile(localConfig); + if (result is not null) + return result; + + // Global config: $HOME/.gitconfig + string? home = System.Environment.GetEnvironmentVariable("HOME") + ?? System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile); + if (!string.IsNullOrEmpty(home)) + { + result = ReadUserNameFromFile(Path.Combine(home, ".gitconfig")); + if (result is not null) + return result; + + // XDG-compliant location: $XDG_CONFIG_HOME/git/config (defaults to ~/.config/git/config) + string xdgConfigHome = System.Environment.GetEnvironmentVariable("XDG_CONFIG_HOME") + ?? Path.Combine(home, ".config"); + result = ReadUserNameFromFile(Path.Combine(xdgConfigHome, "git", "config")); + if (result is not null) + return result; + } + + return null; + } + + private static string? ReadUserNameFromFile(string configPath) { try { - string configPath = Path.Combine(gitDir, "config"); if (!File.Exists(configPath)) return null; diff --git a/tests/Xping.Sdk.Core.Tests/Services/Environment/EnvironmentDetectorTests.cs b/tests/Xping.Sdk.Core.Tests/Services/Environment/EnvironmentDetectorTests.cs index 3542c06..6997f6f 100644 --- a/tests/Xping.Sdk.Core.Tests/Services/Environment/EnvironmentDetectorTests.cs +++ b/tests/Xping.Sdk.Core.Tests/Services/Environment/EnvironmentDetectorTests.cs @@ -172,6 +172,39 @@ public async Task BuildEnvironmentInfoAsync_InsideGitRepositoryWithUserConfig_Se Assert.Equal("Jane Doe", info.CustomProperties["Git.Actor"]); } + [Fact] + public async Task BuildEnvironmentInfoAsync_NoLocalUserConfig_FallsBackToGlobalGitConfig() + { + using var clearedCiVariables = ClearEnvironmentVariables(_environmentVariables); + using var tempGit = new TempGitDirectory(); + tempGit.WriteHead("ref: refs/heads/main"); + tempGit.WriteRef("main", "0000000000000000000000000000000000000001"); + // No local [user] in .git/config — only write an unrelated section + tempGit.WriteConfig("[core]\n\trepositoryformatversion = 0\n"); + using var dirRestorer = new WorkingDirectoryRestorer(tempGit.WorkingDirectory); + + // Create a temporary HOME directory that contains only a .gitconfig with the user name + string tempHome = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempHome); + string? originalHome = System.Environment.GetEnvironmentVariable("HOME"); + try + { + await File.WriteAllTextAsync(Path.Combine(tempHome, ".gitconfig"), + "[user]\n\tname = Global Author\n\temail = global@example.com\n"); + System.Environment.SetEnvironmentVariable("HOME", tempHome); + + IEnvironmentDetector detector = CreateDetector(new XpingConfiguration { CollectLocalGitAuthor = true }); + EnvironmentInfo info = await detector.BuildEnvironmentInfoAsync(); + + Assert.Equal("Global Author", info.CustomProperties["Git.Actor"]); + } + finally + { + System.Environment.SetEnvironmentVariable("HOME", originalHome); + try { Directory.Delete(tempHome, recursive: true); } catch { /* best effort */ } + } + } + [Fact] public async Task BuildEnvironmentInfoAsync_WithUserConfigButAuthorCollectionDisabled_OmitsActor() {