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()
{