diff --git a/docs/development.md b/docs/development.md index 1c36eff4e6..5ca0de8f2b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -261,9 +261,12 @@ method 0. `exit`: contains current executable's exit code 0. `child_start`: describes a child process that is about to be spawned 0. `child_exit`: describes a child process at exit +0. `cmd_name`: identifies the canonical command and inherited command hierarchy 0. `region_enter`: describes a region (e.g. a timer for a section of code that is interesting) on entry 0. `region_leave`: describes a region on leaving +0. `data`: records a thread- and region-local key/value pair +0. `data_json`: records a thread- and region-local structured JSON value You can read more about each of these format targets in the [corresponding section][trace2-events] of Git's Trace2 API documentation. diff --git a/src/Atlassian.Bitbucket.Tests/Cloud/BitbucketOAuth2ClientTest.cs b/src/Atlassian.Bitbucket.Tests/Cloud/BitbucketOAuth2ClientTest.cs index 0e889ec302..ea8005a0ee 100644 --- a/src/Atlassian.Bitbucket.Tests/Cloud/BitbucketOAuth2ClientTest.cs +++ b/src/Atlassian.Bitbucket.Tests/Cloud/BitbucketOAuth2ClientTest.cs @@ -68,9 +68,8 @@ public async Task BitbucketOAuth2Client_GetAuthorizationCodeAsync_RespectsClient [Fact] public async Task BitbucketOAuth2Client_GetDeviceCodeAsync() { - var trace2 = new NullTrace2(); - var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object, trace2); - await Assert.ThrowsAsync(async () => await client.GetDeviceCodeAsync(scopes, ct)); + var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object); + await Assert.ThrowsAsync(async () => await client.GetDeviceCodeAsync(scopes, ct)); } [Theory] @@ -80,8 +79,7 @@ public async Task BitbucketOAuth2Client_GetDeviceCodeAsync() [InlineData("https", "example.com/", "john", "https://example.com/refresh_token")] public void BitbucketOAuth2Client_GetRefreshTokenServiceName(string protocol, string host, string username, string expectedResult) { - var trace2 = new NullTrace2(); - var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object, trace2); + var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object); var request = new GitRequest(new Dictionary { ["protocol"] = protocol, @@ -102,8 +100,7 @@ private void VerifyAuthorizationCodeResult(OAuth2AuthorizationCodeResult result) private Bitbucket.Cloud.BitbucketOAuth2Client GetBitbucketOAuth2Client() { - var trace2 = new NullTrace2(); - var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object, trace2); + var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object); client.CodeGenerator = codeGenerator.Object; return client; } diff --git a/src/Atlassian.Bitbucket.Tests/DataCenter/BitbucketOAuth2ClientTest.cs b/src/Atlassian.Bitbucket.Tests/DataCenter/BitbucketOAuth2ClientTest.cs index 5931a6a0c7..ec11ac0d42 100644 --- a/src/Atlassian.Bitbucket.Tests/DataCenter/BitbucketOAuth2ClientTest.cs +++ b/src/Atlassian.Bitbucket.Tests/DataCenter/BitbucketOAuth2ClientTest.cs @@ -77,8 +77,7 @@ private void VerifyAuthorizationCodeResult(OAuth2AuthorizationCodeResult result, private Bitbucket.DataCenter.BitbucketOAuth2Client GetBitbucketOAuth2Client() { - var trace2 = new NullTrace2(); - var client = new Bitbucket.DataCenter.BitbucketOAuth2Client(httpClient.Object, settings.Object, trace2); + var client = new Bitbucket.DataCenter.BitbucketOAuth2Client(httpClient.Object, settings.Object); client.CodeGenerator = codeGenerator.Object; return client; } diff --git a/src/Atlassian.Bitbucket/BitbucketAuthentication.cs b/src/Atlassian.Bitbucket/BitbucketAuthentication.cs index b3fe139110..b4e22539bb 100644 --- a/src/Atlassian.Bitbucket/BitbucketAuthentication.cs +++ b/src/Atlassian.Bitbucket/BitbucketAuthentication.cs @@ -72,6 +72,8 @@ public BitbucketAuthentication(ICommandContext context, IRegistry GetCredentialsAsync(Uri targetUri, string userName, AuthenticationModes modes) { + using var _ = Trace2.StartRegion("bitbucket", "get_creds"); + ThrowIfUserInteractionDisabled(); // If we don't have a desktop session/GUI then we cannot offer OAuth since the only @@ -234,12 +236,12 @@ private async Task GetCredentialsViaHelperAsync( { if (!output.TryGetValue("username", out userName)) { - throw new Trace2Exception(Context.Trace2, "Missing username in response"); + throw new Exception("Missing username in response"); } if (!output.TryGetValue("password", out string password)) { - throw new Trace2Exception(Context.Trace2, "Missing password in response"); + throw new Exception("Missing password in response"); } return new CredentialsPromptResult( @@ -250,6 +252,8 @@ private async Task GetCredentialsViaHelperAsync( public async Task CreateOAuthCredentialsAsync(GitRequest request) { + using var _ = Trace2.StartRegion("bitbucket", "oauth_browser"); + ThrowIfUserInteractionDisabled(); var browserOptions = new OAuth2WebBrowserOptions @@ -267,6 +271,8 @@ public async Task CreateOAuthCredentialsAsync(GitRequest requ public async Task RefreshOAuthCredentialsAsync(GitRequest request, string refreshToken) { + using var _ = Trace2.StartRegion("bitbucket", "oauth_refresh"); + var client = _oauth2ClientRegistry.Get(request); return await client.GetTokenByRefreshTokenAsync(refreshToken, CancellationToken.None); } diff --git a/src/Atlassian.Bitbucket/BitbucketHostProvider.cs b/src/Atlassian.Bitbucket/BitbucketHostProvider.cs index 42ed214975..ff35884375 100644 --- a/src/Atlassian.Bitbucket/BitbucketHostProvider.cs +++ b/src/Atlassian.Bitbucket/BitbucketHostProvider.cs @@ -86,7 +86,7 @@ public async Task GetCredentialAsync(GitRequest request) StringComparer.OrdinalIgnoreCase.Equals(request.Protocol, "http") && BitbucketHelper.IsBitbucketOrg(request)) { - throw new Trace2Exception(_context.Trace2, + throw new Exception( "Unencrypted HTTP is not recommended for Bitbucket.org. " + "Ensure the repository remote URL is using HTTPS " + $"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes."); @@ -158,7 +158,7 @@ private async Task GetRefreshedCredentials(GitRequest request, Auth { var message = "User cancelled credential prompt"; _context.Trace.WriteLine(message); - throw new Trace2Exception(_context.Trace2, message); + throw new Exception(message); } switch (result.AuthenticationMode) @@ -191,7 +191,7 @@ private async Task GetRefreshedCredentials(GitRequest request, Auth var message = "Failed to refresh existing OAuth credential using refresh token"; _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message); + Trace2.WriteError(message); // We failed to refresh the AT using the RT; log the refresh failure and fall through to restart // the OAuth authentication flow @@ -317,7 +317,7 @@ public async Task GetSupportedAuthenticationModesAsync(GitR _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message, format); + Trace2.WriteError(message, format); _context.Console.WriteWarning(message); @@ -374,7 +374,7 @@ private async Task ResolveOAuthUserNameAsync(GitRequest request, string return result.Response.UserName; } - throw new Trace2Exception(_context.Trace2, + throw new Exception( $"Failed to resolve username. HTTP: {result.StatusCode}"); } @@ -386,7 +386,7 @@ private async Task ResolveBasicAuthUserNameAsync(GitRequest request, str return result.Response.UserName; } - throw new Trace2Exception(_context.Trace2, + throw new Exception( $"Failed to resolve username. HTTP: {result.StatusCode}"); } @@ -427,7 +427,7 @@ private async Task ValidateCredentialsWork(GitRequest request, ICredential var message = "Failed to validate existing credentials using OAuth"; _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message); + Trace2.WriteError(message); } } @@ -444,7 +444,7 @@ private async Task ValidateCredentialsWork(GitRequest request, ICredential var message = "Failed to validate existing credentials using Basic Auth"; _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message); + Trace2.WriteError(message); return false; } } diff --git a/src/Atlassian.Bitbucket/BitbucketOAuth2Client.cs b/src/Atlassian.Bitbucket/BitbucketOAuth2Client.cs index a7f50876b6..c4daec9e73 100644 --- a/src/Atlassian.Bitbucket/BitbucketOAuth2Client.cs +++ b/src/Atlassian.Bitbucket/BitbucketOAuth2Client.cs @@ -14,8 +14,7 @@ public BitbucketOAuth2Client(HttpClient httpClient, OAuth2ServerEndpoints endpoints, string clientId, Uri redirectUri, - string clientSecret, - ITrace2 trace2) : base(httpClient, endpoints, clientId, trace2, redirectUri, clientSecret, false) + string clientSecret) : base(httpClient, endpoints, clientId, redirectUri, clientSecret, false) { } diff --git a/src/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs b/src/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs index 4b5edbbf74..92592dc0ff 100644 --- a/src/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs +++ b/src/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs @@ -10,9 +10,9 @@ namespace Atlassian.Bitbucket.Cloud { public class BitbucketOAuth2Client : Bitbucket.BitbucketOAuth2Client { - public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings, ITrace2 trace2) + public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings) : base(httpClient, GetEndpoints(), - GetClientId(settings), GetRedirectUri(settings), GetClientSecret(settings), trace2) + GetClientId(settings), GetRedirectUri(settings), GetClientSecret(settings)) { } diff --git a/src/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs b/src/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs index 97abd533cb..5af985cdea 100644 --- a/src/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs +++ b/src/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs @@ -12,9 +12,9 @@ namespace Atlassian.Bitbucket.DataCenter { public class BitbucketOAuth2Client : Bitbucket.BitbucketOAuth2Client { - public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings, ITrace2 trace2) + public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings) : base(httpClient, GetEndpoints(settings), - GetClientId(settings), GetRedirectUri(settings), GetClientSecret(settings), trace2) + GetClientId(settings), GetRedirectUri(settings), GetClientSecret(settings)) { } diff --git a/src/Atlassian.Bitbucket/OAuth2ClientRegistry.cs b/src/Atlassian.Bitbucket/OAuth2ClientRegistry.cs index cb7f1f9c4c..7364d1e9a4 100644 --- a/src/Atlassian.Bitbucket/OAuth2ClientRegistry.cs +++ b/src/Atlassian.Bitbucket/OAuth2ClientRegistry.cs @@ -38,9 +38,9 @@ protected override void ReleaseManagedResources() private HttpClient HttpClient => _httpClient ??= _context.HttpClientFactory.CreateClient(); private Cloud.BitbucketOAuth2Client CloudClient => - _cloudClient ??= new Cloud.BitbucketOAuth2Client(HttpClient, _context.Settings, _context.Trace2); + _cloudClient ??= new Cloud.BitbucketOAuth2Client(HttpClient, _context.Settings); private DataCenter.BitbucketOAuth2Client DataCenterClient => - _dataCenterClient ??= new DataCenter.BitbucketOAuth2Client(HttpClient, _context.Settings, _context.Trace2); + _dataCenterClient ??= new DataCenter.BitbucketOAuth2Client(HttpClient, _context.Settings); } } diff --git a/src/Atlassian.Bitbucket/UI/Commands/CredentialsCommand.cs b/src/Atlassian.Bitbucket/UI/Commands/CredentialsCommand.cs index 0e4b58ceda..d5131d88e1 100644 --- a/src/Atlassian.Bitbucket/UI/Commands/CredentialsCommand.cs +++ b/src/Atlassian.Bitbucket/UI/Commands/CredentialsCommand.cs @@ -44,7 +44,7 @@ private async Task ExecuteAsync(Uri url, string userName, bool showOAuth, b if (!viewModel.WindowResult || viewModel.SelectedMode == AuthenticationModes.None) { - throw new Trace2Exception(Context.Trace2, "User cancelled dialog."); + throw new Exception("User cancelled dialog."); } switch (viewModel.SelectedMode) diff --git a/src/Core.Tests/Authentication/BasicAuthenticationTests.cs b/src/Core.Tests/Authentication/BasicAuthenticationTests.cs index 7e5a42488e..5c7fd04afe 100644 --- a/src/Core.Tests/Authentication/BasicAuthenticationTests.cs +++ b/src/Core.Tests/Authentication/BasicAuthenticationTests.cs @@ -70,7 +70,7 @@ public async Task BasicAuthentication_GetCredentials_NonDesktopSession_NoTermina var basicAuth = new BasicAuthentication(context); - await Assert.ThrowsAsync(() => basicAuth.GetCredentialsAsync(testResource)); + await Assert.ThrowsAsync(() => basicAuth.GetCredentialsAsync(testResource)); } [Fact] diff --git a/src/Core.Tests/Authentication/Entra/EntraAuthenticationTests.cs b/src/Core.Tests/Authentication/Entra/EntraAuthenticationTests.cs index fe2b74ee2b..4ce33ef030 100644 --- a/src/Core.Tests/Authentication/Entra/EntraAuthenticationTests.cs +++ b/src/Core.Tests/Authentication/Entra/EntraAuthenticationTests.cs @@ -25,7 +25,7 @@ public async Task GetTokenForUserAsync_NoInteraction_ThrowsException() }; var entraAuth = new EntraAuthentication(context, config); - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( () => entraAuth.GetTokenForUserAsync(scopes, authority)); } diff --git a/src/Core.Tests/Authentication/OAuth2ClientTests.cs b/src/Core.Tests/Authentication/OAuth2ClientTests.cs index 1ec3eae251..2f6d8a13cc 100644 --- a/src/Core.Tests/Authentication/OAuth2ClientTests.cs +++ b/src/Core.Tests/Authentication/OAuth2ClientTests.cs @@ -36,8 +36,7 @@ public async Task OAuth2Client_GetAuthorizationCodeAsync() IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2AuthorizationCodeResult result = await client.GetAuthorizationCodeAsync(expectedScopes, browser, null, CancellationToken.None); @@ -81,12 +80,10 @@ public async Task OAuth2Client_GetAuthorizationCodeAsync_RedirectUrlOriginalStri var redirectUri = new Uri(expectedRedirectUrl); - var trace2 = new NullTrace2(); OAuth2Client client = new OAuth2Client( new HttpClient(httpHandler), endpoints, TestClientId, - trace2, redirectUri, TestClientSecret); @@ -131,8 +128,7 @@ public async Task OAuth2Client_GetAuthorizationCodeAsync_ExtraQueryParams() IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2AuthorizationCodeResult result = await client.GetAuthorizationCodeAsync(expectedScopes, browser, extraParams, CancellationToken.None); @@ -167,8 +163,7 @@ public async Task OAuth2Client_GetAuthorizationCodeAsync_ExtraQueryParams_Overri IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); await Assert.ThrowsAsync(() => client.GetAuthorizationCodeAsync(expectedScopes, browser, extraParams, CancellationToken.None)); @@ -207,9 +202,8 @@ public async Task OAuth2Client_GetAuthorizationCodeAsync_NonDefaultResponseMode_ IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler); - var trace2 = new NullTrace2(); OAuth2Client client = new OAuth2Client( - new HttpClient(httpHandler), endpoints, TestClientId, trace2, + new HttpClient(httpHandler), endpoints, TestClientId, TestRedirectUri, TestClientSecret, responseMode: responseMode); OAuth2AuthorizationCodeResult result = await client.GetAuthorizationCodeAsync( @@ -246,9 +240,8 @@ public async Task OAuth2Client_GetAuthorizationCodeAsync_DefaultResponseMode_Omi IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler); - var trace2 = new NullTrace2(); OAuth2Client client = new OAuth2Client( - new HttpClient(httpHandler), endpoints, TestClientId, trace2, + new HttpClient(httpHandler), endpoints, TestClientId, TestRedirectUri, TestClientSecret, responseMode: OAuth2ResponseMode.Default); OAuth2AuthorizationCodeResult result = await client.GetAuthorizationCodeAsync( @@ -278,8 +271,7 @@ public async Task OAuth2Client_GetDeviceCodeAsync() server.TokenGenerator.UserCodes.Add(expectedUserCode); server.TokenGenerator.DeviceCodes.Add(expectedDeviceCode); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2DeviceCodeResult result = await client.GetDeviceCodeAsync(expectedScopes, CancellationToken.None); @@ -310,8 +302,7 @@ public async Task OAuth2Client_GetTokenByAuthorizationCodeAsync() server.TokenGenerator.AccessTokens.Add(expectedAccessToken); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); var authCodeResult = new OAuth2AuthorizationCodeResult(authCode, TestRedirectUri); OAuth2TokenResult result = await client.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None); @@ -348,8 +339,7 @@ public async Task OAuth2Client_GetTokenByRefreshTokenAsync() server.TokenGenerator.AccessTokens.Add(expectedAccessToken); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2TokenResult result = await client.GetTokenByRefreshTokenAsync(oldRefreshToken, CancellationToken.None); @@ -387,8 +377,7 @@ public async Task OAuth2Client_GetTokenByDeviceCodeAsync() server.TokenGenerator.AccessTokens.Add(expectedAccessToken); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); var deviceCodeResult = new OAuth2DeviceCodeResult(expectedDeviceCode, expectedUserCode, null, null); @@ -433,8 +422,7 @@ public async Task OAuth2Client_E2E_InteractiveWebFlowAndRefresh() IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2AuthorizationCodeResult authCodeResult = await client.GetAuthorizationCodeAsync( expectedScopes, browser, null, CancellationToken.None); @@ -483,8 +471,7 @@ public async Task OAuth2Client_E2E_DeviceFlowAndRefresh() server.TokenGenerator.AccessTokens.Add(expectedAccessToken1); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken1); - var trace2 = new NullTrace2(); - OAuth2Client client = CreateClient(httpHandler, endpoints, trace2); + OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2DeviceCodeResult deviceResult = await client.GetDeviceCodeAsync(expectedScopes, CancellationToken.None); @@ -517,9 +504,9 @@ public async Task OAuth2Client_E2E_DeviceFlowAndRefresh() RedirectUris = new[] {TestRedirectUri} }; - private static OAuth2Client CreateClient(HttpMessageHandler httpHandler, OAuth2ServerEndpoints endpoints, ITrace2 trace2, IOAuth2CodeGenerator generator = null) + private static OAuth2Client CreateClient(HttpMessageHandler httpHandler, OAuth2ServerEndpoints endpoints, IOAuth2CodeGenerator generator = null) { - return new OAuth2Client(new HttpClient(httpHandler), endpoints, TestClientId, trace2, TestRedirectUri, TestClientSecret) + return new OAuth2Client(new HttpClient(httpHandler), endpoints, TestClientId, TestRedirectUri, TestClientSecret) { CodeGenerator = generator }; diff --git a/src/Core.Tests/ConsoleServiceTests.cs b/src/Core.Tests/ConsoleServiceTests.cs index 0e13cafdd4..91d67fd82c 100644 --- a/src/Core.Tests/ConsoleServiceTests.cs +++ b/src/Core.Tests/ConsoleServiceTests.cs @@ -13,8 +13,8 @@ public void ConsoleService_WriteMethods_RouteToErrorConsoleWriter() { var err = new StringWriter(); var console = new ConsoleService( - AnsiConsoleFactory.CreateHeadless(), - AnsiConsoleFactory.CreateForWriter(err, isRedirected: true)); + AnsiConsoleFactory.CreateHeadless, + () => AnsiConsoleFactory.CreateForWriter(err, isRedirected: true)); console.WriteInfo("info-[marker]"); console.WriteWarning("warn-[marker]"); @@ -38,8 +38,8 @@ public void ConsoleService_WriteFatal_RoutesToAutoFlushStreamWriter() using var sw = new StreamWriter(ms, new UTF8Encoding(false)) { AutoFlush = true, NewLine = "\n" }; var console = new ConsoleService( - AnsiConsoleFactory.CreateHeadless(), - AnsiConsoleFactory.CreateForWriter(sw, isRedirected: true)); + AnsiConsoleFactory.CreateHeadless, + () => AnsiConsoleFactory.CreateForWriter(sw, isRedirected: true)); console.WriteFatal("fatal-marker"); sw.Flush(); diff --git a/src/Core.Tests/GitConfigurationTests.cs b/src/Core.Tests/GitConfigurationTests.cs index 5005cf4319..10ad579c1b 100644 --- a/src/Core.Tests/GitConfigurationTests.cs +++ b/src/Core.Tests/GitConfigurationTests.cs @@ -47,9 +47,8 @@ public void GitProcess_GetConfiguration_ReturnsConfiguration() { string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath); + var git = new GitProcess(trace, processManager, gitPath); var config = git.GetConfiguration(); Assert.NotNull(config); } @@ -71,9 +70,8 @@ public void GitConfiguration_Enumerate_CallbackReturnsTrue_InvokesCallbackForEac string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); var actualVisitedEntries = new List<(string name, string value)>(); @@ -110,9 +108,8 @@ public void GitConfiguration_Enumerate_CallbackReturnsFalse_InvokesCallbackForEa string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); var actualVisitedEntries = new List<(string name, string value)>(); @@ -140,10 +137,9 @@ public void GitConfiguration_TryGet_Name_Exists_ReturnsTrueOutString() ExecGit(repoPath, workDirPath, "config --local user.name john.doe").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); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); bool result = config.TryGet("user.name", false, out string value); @@ -160,9 +156,8 @@ public void GitConfiguration_TryGet_Name_DoesNotExists_ReturnsFalse() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); string randomName = $"{Guid.NewGuid():N}.{Guid.NewGuid():N}"; @@ -180,9 +175,8 @@ public void GitConfiguration_TryGet_IsPath_True_ReturnsCanonicalPath() string homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); bool result = config.TryGet("example.path", true, out string value); @@ -199,9 +193,8 @@ public void GitConfiguration_TryGet_IsPath_False_ReturnsRawConfig() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); bool result = config.TryGet("example.path", false, out string value); @@ -218,9 +211,8 @@ public void GitConfiguration_TryGet_BoolType_ReturnsCanonicalBool() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); bool result = config.TryGet(GitConfigurationLevel.Local, GitConfigurationType.Bool, @@ -238,9 +230,8 @@ public void GitConfiguration_TryGet_BoolWithoutType_ReturnsRawConfig() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); bool result = config.TryGet(GitConfigurationLevel.Local, GitConfigurationType.Raw, @@ -258,10 +249,9 @@ public void GitConfiguration_Get_Name_Exists_ReturnsString() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); string value = config.Get("user.name"); @@ -276,9 +266,8 @@ public void GitConfiguration_Get_Name_DoesNotExists_ThrowsException() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); string randomName = $"{Guid.NewGuid():N}.{Guid.NewGuid():N}"; @@ -292,9 +281,8 @@ public void GitConfiguration_Set_Local_SetsLocalConfig() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath);; + var git = new GitProcess(trace, processManager, gitPath, repoPath);; IGitConfiguration config = git.GetConfiguration(); config.Set(GitConfigurationLevel.Local, "core.foobar", "foo123"); @@ -311,9 +299,8 @@ public void GitConfiguration_Set_All_ThrowsException() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); Assert.Throws(() => @@ -331,9 +318,8 @@ public void GitConfiguration_Unset_Global_UnsetsGlobalConfig() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); config.Unset(GitConfigurationLevel.Global, "core.foobar"); @@ -363,9 +349,8 @@ public void GitConfiguration_Unset_Local_UnsetsLocalConfig() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); config.Unset(GitConfigurationLevel.Local, "core.foobar"); @@ -390,9 +375,8 @@ public void GitConfiguration_Unset_All_ThrowsException() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); Assert.Throws(() => config.Unset(GitConfigurationLevel.All, "core.foobar")); @@ -408,9 +392,8 @@ public void GitConfiguration_UnsetAll_UnsetsAllConfig() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); config.UnsetAll(GitConfigurationLevel.Local, "core.foobar", "foo*"); @@ -428,9 +411,8 @@ public void GitConfiguration_UnsetAll_All_ThrowsException() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); Assert.Throws(() => @@ -446,10 +428,9 @@ public void GitConfiguration_CacheTryGet_ReturnsValueFromCache() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); // First access loads cache @@ -473,10 +454,9 @@ public void GitConfiguration_CacheGetAll_ReturnsAllValuesFromCache() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); var values = new List(config.GetAll("test.multi")); @@ -496,10 +476,9 @@ public void GitConfiguration_CacheEnumerate_EnumeratesFromCache() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); var cacheEntries = new List<(string key, string value)>(); @@ -525,10 +504,9 @@ public void GitConfiguration_CacheInvalidation_SetInvalidatesCache() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); // Load cache with initial value @@ -553,10 +531,9 @@ public void GitConfiguration_CacheInvalidation_AddInvalidatesCache() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); // Load cache @@ -582,10 +559,9 @@ public void GitConfiguration_CacheInvalidation_UnsetInvalidatesCache() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); // Load cache @@ -614,10 +590,9 @@ public void GitConfiguration_CacheLevelFilter_ReturnsOnlyLocalValues() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); // Get local value only @@ -641,10 +616,9 @@ public void GitConfiguration_TypedQuery_CanonicalizesValues() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, repoPath); + var git = new GitProcess(trace, processManager, gitPath, repoPath); IGitConfiguration config = git.GetConfiguration(); // Path type queries use a separate cache loaded with --type=path, diff --git a/src/Core.Tests/GitTests.cs b/src/Core.Tests/GitTests.cs index a6905bb8f5..c71e559bea 100644 --- a/src/Core.Tests/GitTests.cs +++ b/src/Core.Tests/GitTests.cs @@ -13,9 +13,8 @@ public void Git_GetCurrentRepository_NoLocalRepo_ReturnsNull() { string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, Path.GetTempPath()); + var git = new GitProcess(trace, processManager, gitPath, Path.GetTempPath()); string actual = git.GetCurrentRepository(); @@ -29,9 +28,8 @@ public void Git_GetCurrentRepository_LocalRepo_ReturnsNotNull() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, workDirPath); + var git = new GitProcess(trace, processManager, gitPath, workDirPath); string actual = git.GetCurrentRepository(); @@ -43,9 +41,8 @@ public void Git_GetRemotes_NoLocalRepo_ReturnsEmpty() { string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, Path.GetTempPath()); + var git = new GitProcess(trace, processManager, gitPath, Path.GetTempPath()); GitRemote[] remotes = git.GetRemotes().ToArray(); @@ -59,9 +56,8 @@ public void Git_GetRemotes_NoRemotes_ReturnsEmpty() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, workDirPath); + var git = new GitProcess(trace, processManager, gitPath, workDirPath); GitRemote[] remotes = git.GetRemotes().ToArray(); @@ -78,10 +74,9 @@ public void Git_GetRemotes_OneRemote_ReturnsRemote() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, workDirPath); + var git = new GitProcess(trace, processManager, gitPath, workDirPath); GitRemote[] remotes = git.GetRemotes().ToArray(); Assert.Single(remotes); @@ -100,10 +95,9 @@ public void Git_GetRemotes_OneRemoteFetchAndPull_ReturnsRemote() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, workDirPath); + var git = new GitProcess(trace, processManager, gitPath, workDirPath); GitRemote[] remotes = git.GetRemotes().ToArray(); Assert.Single(remotes); @@ -124,10 +118,9 @@ public void Git_GetRemotes_NonHttpRemote_ReturnsRemote(string url) string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, workDirPath); + var git = new GitProcess(trace, processManager, gitPath, workDirPath); GitRemote[] remotes = git.GetRemotes().ToArray(); Assert.Single(remotes); @@ -150,10 +143,9 @@ public void Git_GetRemotes_MultipleRemotes_ReturnsAllRemotes() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, workDirPath); + var git = new GitProcess(trace, processManager, gitPath, workDirPath); GitRemote[] remotes = git.GetRemotes().ToArray(); Assert.Equal(3, remotes.Length); @@ -175,10 +167,9 @@ public void Git_GetRemotes_RemoteNoFetchOnlyPull_ReturnsRemote() string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, workDirPath); + var git = new GitProcess(trace, processManager, gitPath, workDirPath); GitRemote[] remotes = git.GetRemotes().ToArray(); Assert.Single(remotes); @@ -191,10 +182,9 @@ public void Git_Version_ReturnsVersion() { string gitPath = GetGitPath(); var trace = new NullTrace(); - var trace2 = new NullTrace2(); var processManager = new TestProcessManager(); - var git = new GitProcess(trace, trace2, processManager, gitPath, Path.GetTempPath()); + var git = new GitProcess(trace, processManager, gitPath, Path.GetTempPath()); GitVersion version = git.Version; Assert.NotEqual(new GitVersion(), version); diff --git a/src/Core.Tests/HostProviderRegistryTests.cs b/src/Core.Tests/HostProviderRegistryTests.cs index 0d43b1b520..638c8871ba 100644 --- a/src/Core.Tests/HostProviderRegistryTests.cs +++ b/src/Core.Tests/HostProviderRegistryTests.cs @@ -41,7 +41,7 @@ public async Task HostProviderRegistry_GetProvider_NoProviders_ThrowException() var registry = new HostProviderRegistry(context); var request = new GitRequest(new Dictionary()); - await Assert.ThrowsAsync(() => registry.GetProviderAsync(request)); + await Assert.ThrowsAsync(() => registry.GetProviderAsync(request)); } [Fact] diff --git a/src/Core.Tests/HttpClientFactoryTests.cs b/src/Core.Tests/HttpClientFactoryTests.cs index c499e00548..c31e80e8c1 100644 --- a/src/Core.Tests/HttpClientFactoryTests.cs +++ b/src/Core.Tests/HttpClientFactoryTests.cs @@ -11,24 +11,24 @@ namespace GitCredentialManager.Tests { public class HttpClientFactoryTests { - private static readonly IConsoleService TestConsole = new ConsoleService(AnsiConsoleFactory.CreateHeadless(), AnsiConsoleFactory.CreateHeadless()); + private static readonly IConsoleService TestConsole = new ConsoleService(AnsiConsoleFactory.CreateHeadless, AnsiConsoleFactory.CreateHeadless); [Fact] public void HttpClientFactory_GetClient_SetsDefaultHeaders() { - var factory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), TestConsole); + var factory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), TestConsole); HttpClient client = factory.CreateClient(); Assert.NotNull(client); - Assert.Equal(Constants.GetHttpUserAgent(Mock.Of()), client.DefaultRequestHeaders.UserAgent.ToString()); + Assert.Equal(Constants.GetHttpUserAgent(), client.DefaultRequestHeaders.UserAgent.ToString()); Assert.True(client.DefaultRequestHeaders.CacheControl.NoCache); } [Fact] public void HttpClientFactory_GetClient_MultipleCalls_ReturnsNewInstance() { - var factory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), TestConsole); + var factory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), TestConsole); HttpClient client1 = factory.CreateClient(); HttpClient client2 = factory.CreateClient(); @@ -48,7 +48,7 @@ public void HttpClientFactory_TryCreateProxy_NoProxy_ReturnsFalseOutNull() RemoteUri = repoRemoteUri, RepositoryPath = repoPath }; - var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), settings, TestConsole); + var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), settings, TestConsole); bool result = httpFactory.TryCreateProxy(out IWebProxy proxy); @@ -72,7 +72,7 @@ public void HttpClientFactory_TryCreateProxy_ProxyNoCredentials_ReturnsTrueOutPr RepositoryPath = repoPath, ProxyConfiguration = proxyConfig }; - var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), settings, TestConsole); + var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), settings, TestConsole); bool result = httpFactory.TryCreateProxy(out IWebProxy proxy); @@ -105,7 +105,7 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithBypass_ReturnsTrueOutProxy RepositoryPath = repoPath, ProxyConfiguration = proxyConfig }; - var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), settings, TestConsole); + var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), settings, TestConsole); bool result = httpFactory.TryCreateProxy(out IWebProxy proxy); @@ -141,7 +141,7 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithWildcardBypass_ReturnsFals RepositoryPath = repoPath, ProxyConfiguration = proxyConfig }; - var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), settings, TestConsole); + var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), settings, TestConsole); bool result = httpFactory.TryCreateProxy(out IWebProxy proxy); @@ -170,7 +170,7 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithCredentials_ReturnsTrueOut RepositoryPath = repoPath, ProxyConfiguration = proxyConfig }; - var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), settings, TestConsole); + var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), settings, TestConsole); bool result = httpFactory.TryCreateProxy(out IWebProxy proxy); @@ -206,7 +206,7 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithNonEmptyUserAndEmptyPass_R RepositoryPath = repoPath, ProxyConfiguration = proxyConfig }; - var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), settings, TestConsole); + var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), settings, TestConsole); bool result = httpFactory.TryCreateProxy(out IWebProxy proxy); @@ -242,7 +242,7 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithEmptyUserAndNonEmptyPass_R RepositoryPath = repoPath, ProxyConfiguration = proxyConfig }; - var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), settings, TestConsole); + var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), settings, TestConsole); bool result = httpFactory.TryCreateProxy(out IWebProxy proxy); @@ -277,7 +277,7 @@ public void HttpClientFactory_TryCreateProxy_ProxyEmptyUserAndEmptyPass_ReturnsT RepositoryPath = repoPath, ProxyConfiguration = proxyConfig }; - var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), Mock.Of(), settings, TestConsole); + var httpFactory = new HttpClientFactory(Mock.Of(), Mock.Of(), settings, TestConsole); bool result = httpFactory.TryCreateProxy(out IWebProxy proxy); @@ -307,7 +307,7 @@ public void HttpClientFactory_GetClient_ChecksCertBundleOnlyIfEnabled(string cus UseCustomCertificateBundleWithSchannel = useCustomCertBundleWithSchannel }; - var factory = new HttpClientFactory(fileSystemMock.Object, Mock.Of(), Mock.Of(), settings, TestConsole); + var factory = new HttpClientFactory(fileSystemMock.Object, Mock.Of(), settings, TestConsole); HttpClient client = factory.CreateClient(); @@ -337,7 +337,7 @@ public void HttpClientFactory_GetClient_SetCookieOnlyIfEnabled(string cookieFile CustomCookieFilePath = cookieFilePath }; - var factory = new HttpClientFactory(fileSystemMock.Object, Mock.Of(), Mock.Of(), settings, TestConsole); + var factory = new HttpClientFactory(fileSystemMock.Object, Mock.Of(), settings, TestConsole); HttpClient client = factory.CreateClient(); diff --git a/src/Core.Tests/ProcessManagerTests.cs b/src/Core.Tests/ProcessManagerTests.cs deleted file mode 100644 index 8e96c41f0f..0000000000 --- a/src/Core.Tests/ProcessManagerTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using GitCredentialManager; -using Xunit; - -namespace Core.Tests; - -public class ProcessManagerTests -{ - [Theory] - [InlineData("", 0)] - [InlineData("foo", 0)] - [InlineData("foo/bar", 1)] - [InlineData("foo/bar/baz", 2)] - public void CreateSid_Envar_Returns_Expected_Sid(string input, int expected) - { - ProcessManager.Sid = input; - var actual = ProcessManager.GetProcessDepth(); - - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData("", 0)] - [InlineData("foo", 0)] - [InlineData("foo/bar", 1)] - [InlineData("foo/bar/baz", 2)] - public void TryGetProcessDepth_Returns_Expected_Depth(string input, int expected) - { - ProcessManager.Sid = input; - var actual = ProcessManager.GetProcessDepth(); - - Assert.Equal(expected, actual); - } -} \ No newline at end of file diff --git a/src/Core.Tests/TestProcessManager.cs b/src/Core.Tests/TestProcessManager.cs index df54b1bb48..d85a3e3208 100644 --- a/src/Core.Tests/TestProcessManager.cs +++ b/src/Core.Tests/TestProcessManager.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using GitCredentialManager.Tests.Objects; -using Moq; namespace GitCredentialManager.Tests; public class TestProcessManager : IProcessManager { - public ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory) + public ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory, + Trace2ProcessClass @class) { var psi = new ProcessStartInfo(path, args) { @@ -19,11 +16,11 @@ public ChildProcess CreateProcess(string path, string args, bool useShellExecute WorkingDirectory = workingDirectory ?? string.Empty }; - return CreateProcess(psi); + return CreateProcess(psi, @class); } - public ChildProcess CreateProcess(ProcessStartInfo psi) + public ChildProcess CreateProcess(ProcessStartInfo psi, Trace2ProcessClass @class) { - return new ChildProcess(new NullTrace2(), psi); + return new ChildProcess(psi, @class); } } diff --git a/src/Core.Tests/Trace2MessageTests.cs b/src/Core.Tests/Trace2MessageTests.cs index 82c1249ca5..7bf85c44d9 100644 --- a/src/Core.Tests/Trace2MessageTests.cs +++ b/src/Core.Tests/Trace2MessageTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json; using GitCredentialManager; using Xunit; @@ -16,7 +17,7 @@ public class Trace2MessageTests [InlineData(100000.31608, "100000.316080")] public void BuildTimeSpan_Match_Returns_Expected_String(double input, string expected) { - var actual = Trace2Message.BuildTimeSpan(input); + var actual = PerformanceFormatFields.GetTimeSpan(input); Assert.Equal(expected, actual); } @@ -25,7 +26,7 @@ public void BuildRepoSpan_Match_Returns_Expected_String() { var input = 1; var expected = " r1 "; - var actual = Trace2Message.BuildRepoSpan(input); + var actual = PerformanceFormatFields.GetRepoSpan(input); Assert.Equal(expected, actual); } @@ -36,16 +37,15 @@ public void BuildRepoSpan_Match_Returns_Expected_String() [InlineData("foobarbazfoo", " foobarbazfo ")] public void BuildCategorySpan_Match_Returns_Expected_String(string input, string expected) { - var actual = Trace2Message.BuildCategorySpan(input); + var actual = PerformanceFormatFields.GetCategorySpan(input); Assert.Equal(expected, actual); } [Fact] public void Event_Message_Without_Snake_Case_ToJson_Creates_Expected_Json() { - var errorMessage = new ErrorMessage() + var errorMessage = new ErrorMessage { - Event = Trace2Event.Error, Sid = "123", Thread = "main", Time = new DateTimeOffset(), @@ -65,9 +65,8 @@ public void Event_Message_Without_Snake_Case_ToJson_Creates_Expected_Json() [Fact] public void Event_Message_With_Snake_Case_ToJson_Creates_Expected_Json() { - var childStartMessage = new ChildStartMessage() + var childStartMessage = new ChildStartMessage { - Event = Trace2Event.ChildStart, Sid = "123", Thread = "main", Time = new DateTimeOffset(), @@ -75,7 +74,7 @@ public void Event_Message_With_Snake_Case_ToJson_Creates_Expected_Json() Line = 1, Depth = 1, Id = 1, - Classification = Trace2ProcessClass.UIHelper, + Classification = Trace2ProcessClass.UiHelper, UseShell = false, Argv = new List() { "bar", "baz" }, ElapsedTime = 0.05 @@ -86,4 +85,108 @@ public void Event_Message_With_Snake_Case_ToJson_Creates_Expected_Json() Assert.Equal(expected, actual); } + + [Fact] + public void Thread_Events_ToJson_Create_Expected_Json() + { + var startMessage = new ThreadStartMessage + { + Sid = "123", + Thread = "AppMain", + Time = new DateTimeOffset(), + File = "foo.cs", + Line = 1, + Depth = 1 + }; + var exitMessage = new ThreadExitMessage + { + Sid = "123", + Thread = "AppMain", + Time = new DateTimeOffset(), + File = "foo.cs", + Line = 2, + Depth = 1, + RelativeTime = 0.05 + }; + + Assert.Equal( + "{\"event\":\"thread_start\",\"sid\":\"123\",\"thread\":\"AppMain\",\"time\":\"0001-01-01T00:00:00+00:00\",\"file\":\"foo.cs\",\"line\":1,\"depth\":1}", + startMessage.ToJson()); + Assert.Equal( + "{\"event\":\"thread_exit\",\"sid\":\"123\",\"thread\":\"AppMain\",\"time\":\"0001-01-01T00:00:00+00:00\",\"file\":\"foo.cs\",\"line\":2,\"depth\":1,\"t_rel\":0.05}", + exitMessage.ToJson()); + } + + [Fact] + public void Data_Event_ToJson_Creates_Expected_Json() + { + var message = new DataMessage + { + Sid = "123", + Thread = "main", + Time = new DateTimeOffset(), + File = "foo.cs", + Line = 1, + Depth = 1, + ElapsedTime = 0.05, + RelativeTime = 0.01, + Repo = 1, + Nesting = 2, + Category = "index", + Key = "read/cache_nr", + Value = "3552" + }; + + const string expected = "{\"event\":\"data\",\"sid\":\"123\",\"thread\":\"main\",\"time\":\"0001-01-01T00:00:00+00:00\",\"file\":\"foo.cs\",\"line\":1,\"depth\":1,\"t_abs\":0.05,\"t_rel\":0.01,\"repo\":1,\"nesting\":2,\"category\":\"index\",\"key\":\"read/cache_nr\",\"value\":\"3552\"}"; + + Assert.Equal(expected, message.ToJson()); + } + + [Fact] + public void CommandName_Event_ToJson_Creates_Expected_Json() + { + var message = new CommandNameMessage + { + Sid = "123", + Thread = "main", + Time = new DateTimeOffset(), + File = "foo.cs", + Line = 1, + Depth = 1, + Name = "get", + Hierarchy = "git/get" + }; + + const string expected = "{\"event\":\"cmd_name\",\"sid\":\"123\",\"thread\":\"main\",\"time\":\"0001-01-01T00:00:00+00:00\",\"file\":\"foo.cs\",\"line\":1,\"depth\":1,\"name\":\"get\",\"hierarchy\":\"git/get\"}"; + + Assert.Equal(expected, message.ToJson()); + } + + [Fact] + public void DataJson_Event_ToJson_Creates_Expected_Json() + { + using JsonDocument document = JsonDocument.Parse( + "{\"count\":2,\"items\":[\"one\",\"two\"]}"); + + var message = new DataJsonMessage + { + Sid = "123", + Thread = "main", + Time = new DateTimeOffset(), + File = "foo.cs", + Line = 1, + Depth = 1, + ElapsedTime = 0.05, + RelativeTime = 0.01, + Repo = 1, + Nesting = 2, + Category = "index", + Key = "read/statistics", + Value = document.RootElement + }; + + const string expected = "{\"event\":\"data_json\",\"sid\":\"123\",\"thread\":\"main\",\"time\":\"0001-01-01T00:00:00+00:00\",\"file\":\"foo.cs\",\"line\":1,\"depth\":1,\"t_abs\":0.05,\"t_rel\":0.01,\"repo\":1,\"nesting\":2,\"category\":\"index\",\"key\":\"read/statistics\",\"value\":{\"count\":2,\"items\":[\"one\",\"two\"]}}"; + + Assert.Equal(expected, message.ToJson()); + } } diff --git a/src/Core.Tests/Trace2Tests.cs b/src/Core.Tests/Trace2Tests.cs index 38011275db..32932ba7d0 100644 --- a/src/Core.Tests/Trace2Tests.cs +++ b/src/Core.Tests/Trace2Tests.cs @@ -1,3 +1,4 @@ +using System; using Xunit; namespace GitCredentialManager.Tests; @@ -28,4 +29,64 @@ public void TryGetPipeName_Windows_Returns_Expected_Value(string input, string e Assert.True(isSuccessful); Assert.Equal(expected, actual); } + + [Theory] + [InlineData("", 0)] + [InlineData("abc", 0)] + [InlineData("abc/def", 1)] + [InlineData("abc/def/ghi", 2)] + [InlineData("abc/", 1)] + public void GetProcessDepth_ReturnsCorrectDepthh(string sid, int expected) + { + int actual = Trace2.GetProcessDepth(sid); + Assert.Equal(expected, actual); + } + + [Fact] + public void CreateSid_ExistingParentSid_AppendsToExisting() + { + var originalSid = Environment.GetEnvironmentVariable(Trace2.SidEnvar); + + try + { + // Set parent SID + const string parentSid = "0ddfc330-30e9-49f3-86d3-6b34d99d51f4"; + Environment.SetEnvironmentVariable(Trace2.SidEnvar, parentSid); + + string actualSid = Trace2.CreateSid(); + + const string parentPrefix = $"{parentSid}/"; + Assert.StartsWith(parentPrefix, actualSid); + + string rest = actualSid.Substring(parentPrefix.Length); + Assert.False(string.IsNullOrWhiteSpace(rest)); + } + finally + { + // Restore original environment variable for this process + Environment.SetEnvironmentVariable(Trace2.SidEnvar, originalSid); + } + } + + [Fact] + public void CreateSid_NoParentSid_CreatesNew() + { + var originalSid = Environment.GetEnvironmentVariable(Trace2.SidEnvar); + + try + { + // Clear parent SID + Environment.SetEnvironmentVariable(Trace2.SidEnvar, null); + + string actualSid = Trace2.CreateSid(); + + Assert.False(string.IsNullOrWhiteSpace(actualSid)); + Assert.DoesNotContain("/", actualSid); + } + finally + { + // Restore original environment variable for this process + Environment.SetEnvironmentVariable(Trace2.SidEnvar, originalSid); + } + } } diff --git a/src/Core.Tests/WslUtilsTests.cs b/src/Core.Tests/WslUtilsTests.cs index 330e42c846..d0daa44716 100644 --- a/src/Core.Tests/WslUtilsTests.cs +++ b/src/Core.Tests/WslUtilsTests.cs @@ -101,7 +101,7 @@ public void WslUtils_CreateWslProcess() string expectedFileName = WslUtils.GetWslPath(); string expectedArgs = $"--distribution {distribution} --exec {command}"; - ChildProcess process = WslUtils.CreateWslProcess(distribution, command, Mock.Of()); + ChildProcess process = WslUtils.CreateWslProcess(distribution, command); Assert.NotNull(process); Assert.Equal(expectedArgs, process.StartInfo.Arguments); @@ -122,7 +122,7 @@ public void WslUtils_CreateWslProcess_WorkingDirectory() string expectedFileName = WslUtils.GetWslPath(); string expectedArgs = $"--distribution {distribution} --exec {command}"; - ChildProcess process = WslUtils.CreateWslProcess(distribution, command, Mock.Of(), expectedWorkingDirectory); + ChildProcess process = WslUtils.CreateWslProcess(distribution, command, expectedWorkingDirectory); Assert.NotNull(process); Assert.Equal(expectedArgs, process.StartInfo.Arguments); diff --git a/src/Core/Application.cs b/src/Core/Application.cs index 7099cf4b0c..2d13f99991 100644 --- a/src/Core/Application.cs +++ b/src/Core/Application.cs @@ -10,8 +10,6 @@ using GitCredentialManager.Commands; using GitCredentialManager.Diagnostics; using GitCredentialManager.Interop; -using GitCredentialManager.Tty; -using Spectre.Console; namespace GitCredentialManager { @@ -68,57 +66,71 @@ public void RegisterProvider(IHostProvider provider, HostProviderPriority priori protected override async Task RunInternalAsync(string[] args) { - var rootCommand = new RootCommand(); - var diagnoseCommand = new DiagnoseCommand(Context); + Parser parser; + using (Trace2.StartRegion("main", "cmd_setup")) + { + var rootCommand = new RootCommand(); + var diagnoseCommand = new DiagnoseCommand(Context); - // Add common options - var noGuiOption = new Option("--no-ui", "Do not use graphical user interface prompts"); - rootCommand.AddGlobalOption(noGuiOption); + // Add common options + var noGuiOption = new Option("--no-ui", "Do not use graphical user interface prompts"); + rootCommand.AddGlobalOption(noGuiOption); - void NoGuiOptionHandler(InvocationContext context) - { - if (context.ParseResult.HasOption(noGuiOption)) + void NoGuiOptionHandler(InvocationContext context) { - Context.Settings.IsGuiPromptsEnabled = false; + if (context.ParseResult.HasOption(noGuiOption)) + { + Context.Settings.IsGuiPromptsEnabled = false; + } } - } - // Add standard commands - rootCommand.AddCommand(new GetCommand(Context, _providerRegistry)); - rootCommand.AddCommand(new StoreCommand(Context, _providerRegistry)); - rootCommand.AddCommand(new EraseCommand(Context, _providerRegistry)); - rootCommand.AddCommand(new CapabilityCommand(Context)); - rootCommand.AddCommand(new ConfigureCommand(Context, _configurationService)); - rootCommand.AddCommand(new UnconfigureCommand(Context, _configurationService)); - rootCommand.AddCommand(diagnoseCommand); - - // Add any custom provider commands - foreach (ProviderCommand providerCommand in _providerCommands) - { - rootCommand.AddCommand(providerCommand); - } + void Trace2CommandNameHandler(InvocationContext context) + { + Command command = context.ParseResult.CommandResult.Command; + if (!ReferenceEquals(command, rootCommand)) + { + Trace2.WriteCommandName(command.Name); + } + } - // Add any custom provider diagnostic tests - foreach (IDiagnostic providerDiagnostic in _diagnostics) - { - diagnoseCommand.AddDiagnostic(providerDiagnostic); - } + // Add standard commands + rootCommand.AddCommand(new GetCommand(Context, _providerRegistry)); + rootCommand.AddCommand(new StoreCommand(Context, _providerRegistry)); + rootCommand.AddCommand(new EraseCommand(Context, _providerRegistry)); + rootCommand.AddCommand(new CapabilityCommand(Context)); + rootCommand.AddCommand(new ConfigureCommand(Context, _configurationService)); + rootCommand.AddCommand(new UnconfigureCommand(Context, _configurationService)); + rootCommand.AddCommand(diagnoseCommand); + + // Add any custom provider commands + foreach (ProviderCommand providerCommand in _providerCommands) + { + rootCommand.AddCommand(providerCommand); + } + + // Add any custom provider diagnostic tests + foreach (IDiagnostic providerDiagnostic in _diagnostics) + { + diagnoseCommand.AddDiagnostic(providerDiagnostic); + } - // Trace the current version, OS, runtime, and program arguments - PlatformInformation info = PlatformUtils.GetPlatformInformation(Context.Trace2); - Context.Trace.WriteLine($"Version: {Constants.GcmVersion}"); - Context.Trace.WriteLine($"Runtime: {info.ClrVersion}"); - Context.Trace.WriteLine($"Platform: {info.OperatingSystemType} ({info.CpuArchitecture})"); - Context.Trace.WriteLine($"OSVersion: {info.OperatingSystemVersion}"); - Context.Trace.WriteLine($"AppPath: {Context.ApplicationPath}"); - Context.Trace.WriteLine($"InstallDir: {Context.InstallationDirectory}"); - Context.Trace.WriteLine($"Arguments: {string.Join(" ", args)}"); - - var parser = new CommandLineBuilder(rootCommand) - .UseDefaults() - .UseExceptionHandler(OnException) - .AddMiddleware(NoGuiOptionHandler) - .Build(); + // Trace the current version, OS, runtime, and program arguments + PlatformInformation info = PlatformUtils.GetPlatformInformation(); + Context.Trace.WriteLine($"Version: {Constants.GcmVersion}"); + Context.Trace.WriteLine($"Runtime: {info.ClrVersion}"); + Context.Trace.WriteLine($"Platform: {info.OperatingSystemType} ({info.CpuArchitecture})"); + Context.Trace.WriteLine($"OSVersion: {info.OperatingSystemVersion}"); + Context.Trace.WriteLine($"AppPath: {Context.ApplicationPath}"); + Context.Trace.WriteLine($"InstallDir: {Context.InstallationDirectory}"); + Context.Trace.WriteLine($"Arguments: {string.Join(" ", args)}"); + + parser = new CommandLineBuilder(rootCommand) + .UseDefaults() + .UseExceptionHandler(OnException) + .AddMiddleware(NoGuiOptionHandler) + .AddMiddleware(Trace2CommandNameHandler) + .Build(); + } return await parser.InvokeAsync(args); } @@ -183,6 +195,8 @@ internal static bool ContainsInterrupt(Exception ex) private bool WriteException(Exception ex) { + Trace2.WriteError(ex); + // Try and use a nicer format for some well-known exception types switch (ex) { diff --git a/src/Core/Authentication/AuthenticationBase.cs b/src/Core/Authentication/AuthenticationBase.cs index 15e2e11b02..fb177d1129 100644 --- a/src/Core/Authentication/AuthenticationBase.cs +++ b/src/Core/Authentication/AuthenticationBase.cs @@ -46,13 +46,13 @@ protected internal virtual async Task> InvokeHelperA // authentication helper's messages. Context.Trace.Flush(); - var process = ChildProcess.Start(Context.Trace2, procStartInfo, Trace2ProcessClass.UIHelper); + var process = ChildProcess.Start(procStartInfo, Trace2ProcessClass.UiHelper); if (process is null) { var format = "Failed to start helper process: {0} {1}"; var message = string.Format(format, path, args); - throw new Trace2Exception(Context.Trace2, message, format); + throw new Exception(message); } // Kill the process upon a cancellation request @@ -77,7 +77,7 @@ protected internal virtual async Task> InvokeHelperA errorMessage = "Unknown"; } - throw new Trace2Exception(Context.Trace2, $"helper error ({exitCode}): {errorMessage}"); + throw new Exception($"helper error ({exitCode}): {errorMessage}"); } return resultDict; @@ -93,7 +93,7 @@ protected void ThrowIfUserInteractionDisabled() Constants.GitConfiguration.Credential.Interactive); Context.Trace.WriteLine($"{envName} / {cfgName} is false/never; user interactivity has been disabled."); - throw new Trace2InvalidOperationException(Context.Trace2, "Cannot prompt because user interactivity has been disabled."); + throw new InvalidOperationException("Cannot prompt because user interactivity has been disabled."); } } @@ -102,7 +102,7 @@ protected void ThrowIfGuiPromptsDisabled() if (!Context.Settings.IsGuiPromptsEnabled) { Context.Trace.WriteLine($"{Constants.EnvironmentVariables.GitTerminalPrompts} is 0; GUI prompts have been disabled."); - throw new Trace2InvalidOperationException(Context.Trace2, "Cannot show prompt because GUI prompts have been disabled."); + throw new InvalidOperationException("Cannot show prompt because GUI prompts have been disabled."); } } @@ -111,7 +111,7 @@ protected void ThrowIfTerminalPromptsDisabled() if (!Context.Settings.IsTerminalPromptsEnabled) { Context.Trace.WriteLine($"{Constants.EnvironmentVariables.GitTerminalPrompts} is 0; terminal prompts have been disabled."); - throw new Trace2InvalidOperationException(Context.Trace2, "Cannot prompt because terminal prompts have been disabled."); + throw new InvalidOperationException("Cannot prompt because terminal prompts have been disabled."); } } diff --git a/src/Core/Authentication/BasicAuthentication.cs b/src/Core/Authentication/BasicAuthentication.cs index 9a8ac7be1c..455f049efe 100644 --- a/src/Core/Authentication/BasicAuthentication.cs +++ b/src/Core/Authentication/BasicAuthentication.cs @@ -112,12 +112,12 @@ private async Task GetCredentialsViaHelperAsync(string command, str if (!resultDict.TryGetValue("username", out userName)) { - throw new Trace2Exception(Context.Trace2, "Missing 'username' in response"); + throw new Exception("Missing 'username' in response"); } if (!resultDict.TryGetValue("password", out string password)) { - throw new Trace2Exception(Context.Trace2, "Missing 'password' in response"); + throw new Exception("Missing 'password' in response"); } return new GitCredential(userName, password); diff --git a/src/Core/Authentication/Entra/EntraAuthentication.Caching.cs b/src/Core/Authentication/Entra/EntraAuthentication.Caching.cs index 3771ed81cd..8e17b94681 100644 --- a/src/Core/Authentication/Entra/EntraAuthentication.Caching.cs +++ b/src/Core/Authentication/Entra/EntraAuthentication.Caching.cs @@ -29,7 +29,7 @@ private async Task RegisterCacheAsync(ITokenCache cache, StoragePropertiesBuilde if (!PlatformUtils.IsWindows() && !PlatformUtils.IsPosix()) { - string osType = PlatformUtils.GetPlatformInformation(Context.Trace2).OperatingSystemType; + string osType = PlatformUtils.GetPlatformInformation().OperatingSystemType; Context.Trace.WriteLine($"Token cache integration is not supported on {osType}."); return; } @@ -51,7 +51,7 @@ private async Task RegisterCacheAsync(ITokenCache cache, StoragePropertiesBuilde Context.Console.WriteWarning("cannot persist Entra authentication token cache securely!"); Context.Trace.WriteLine(message); Context.Trace.WriteException(ex); - Context.Trace2.WriteError(message); + Trace2.WriteError(message); if (PlatformUtils.IsMacOS()) { diff --git a/src/Core/Authentication/Entra/EntraAuthentication.PublicClient.cs b/src/Core/Authentication/Entra/EntraAuthentication.PublicClient.cs index f0d79e40b6..decca2dd02 100644 --- a/src/Core/Authentication/Entra/EntraAuthentication.PublicClient.cs +++ b/src/Core/Authentication/Entra/EntraAuthentication.PublicClient.cs @@ -116,7 +116,7 @@ public async Task GetInteractionModeAsync(CancellationToken ct return choice; } - throw new Trace2Exception(Context.Trace2, "Missing or invalid interaction_mode in response"); + throw new Exception("Missing or invalid interaction_mode in response"); } // TODO: show prompt in-proc @@ -343,7 +343,7 @@ private async Task UseDefaultAccountAsync(string userName, CancellationTok return str.ToBooleanyOrDefault(false); } - throw new Trace2Exception(Context.Trace2, "Missing use_default_account in response"); + throw new Exception("Missing use_default_account in response"); } var viewModel = new DefaultAccountViewModel(Context.SessionManager) diff --git a/src/Core/Authentication/OAuth/OAuth2Client.cs b/src/Core/Authentication/OAuth/OAuth2Client.cs index b7fe452cb4..1089fae1d0 100644 --- a/src/Core/Authentication/OAuth/OAuth2Client.cs +++ b/src/Core/Authentication/OAuth/OAuth2Client.cs @@ -71,7 +71,6 @@ public class OAuth2Client : IOAuth2Client private readonly OAuth2ServerEndpoints _endpoints; private readonly Uri _redirectUri; private readonly string _clientId; - private readonly ITrace2 _trace2; private readonly string _clientSecret; private readonly bool _addAuthHeader; private readonly OAuth2ResponseMode _responseMode; @@ -81,7 +80,6 @@ public class OAuth2Client : IOAuth2Client public OAuth2Client(HttpClient httpClient, OAuth2ServerEndpoints endpoints, string clientId, - ITrace2 trace2, Uri redirectUri = null, string clientSecret = null, bool addAuthHeader = true, @@ -90,7 +88,6 @@ public OAuth2Client(HttpClient httpClient, _httpClient = httpClient; _endpoints = endpoints; _clientId = clientId; - _trace2 = trace2; _redirectUri = redirectUri; _clientSecret = clientSecret; _addAuthHeader = addAuthHeader; @@ -108,6 +105,8 @@ public IOAuth2CodeGenerator CodeGenerator public async Task GetAuthorizationCodeAsync(IEnumerable scopes, IOAuth2WebBrowser browser, IDictionary extraQueryParams, CancellationToken ct) { + using IDisposable region = Trace2.StartRegion(OAuth2Constants.Trace2Category, "get_authcode"); + string state = CodeGenerator.CreateNonce(); string codeVerifier = CodeGenerator.CreatePkceCodeVerifier(); string codeChallenge = CodeGenerator.CreatePkceCodeChallenge(OAuth2PkceChallengeMethod.Sha256, codeVerifier); @@ -170,27 +169,30 @@ public async Task GetAuthorizationCodeAsync(IEnum // Open the browser at the request URI to start the authorization code grant flow, and // intercept the response parameters delivered to the redirect URI. - IDictionary responseParams = - await browser.GetAuthenticationResponseAsync(authorizationUri, redirectUri, _responseMode, ct); + IDictionary responseParams; + using (Trace2.StartRegion(OAuth2Constants.Trace2Category, "browser")) + { + responseParams = await browser.GetAuthenticationResponseAsync(authorizationUri, redirectUri, _responseMode, ct); + } // Check for errors serious enough we should terminate the flow, such as if the state value returned does // not match the one we passed. This indicates a badly implemented Authorization Server, or worse, some // form of failed MITM or replay attack. if (!responseParams.TryGetValue(OAuth2Constants.AuthorizationGrantResponse.StateParameter, out string replyState)) { - throw new Trace2OAuth2Exception(_trace2, + throw new OAuth2Exception( $"Missing '{OAuth2Constants.AuthorizationGrantResponse.StateParameter}' in response."); } if (!StringComparer.Ordinal.Equals(state, replyState)) { - throw new Trace2OAuth2Exception(_trace2, + throw new OAuth2Exception( $"Invalid '{OAuth2Constants.AuthorizationGrantResponse.StateParameter}' in response; does not match the request."); } // We expect to have the auth code in the response otherwise terminate the flow (we failed authentication for some reason) if (!responseParams.TryGetValue(OAuth2Constants.AuthorizationGrantResponse.AuthorizationCodeParameter, out string authCode)) { - throw new Trace2OAuth2Exception(_trace2, + throw new OAuth2Exception( $"Missing '{OAuth2Constants.AuthorizationGrantResponse.AuthorizationCodeParameter}' in response."); } @@ -199,12 +201,11 @@ public async Task GetAuthorizationCodeAsync(IEnum public async Task GetDeviceCodeAsync(IEnumerable scopes, CancellationToken ct) { - var label = "get device code"; - using IDisposable region = _trace2.CreateRegion(OAuth2Constants.Trace2Category, label); + using IDisposable region = Trace2.StartRegion(OAuth2Constants.Trace2Category, "get_devicecode"); if (_endpoints.DeviceAuthorizationEndpoint is null) { - throw new Trace2InvalidOperationException(_trace2, + throw new InvalidOperationException( "No device authorization endpoint has been configured for this client."); } @@ -237,8 +238,7 @@ public async Task GetDeviceCodeAsync(IEnumerable public async Task GetTokenByAuthorizationCodeAsync(OAuth2AuthorizationCodeResult authorizationCodeResult, CancellationToken ct) { - var label = "get token by auth code"; - using IDisposable region = _trace2.CreateRegion(OAuth2Constants.Trace2Category, label); + using IDisposable region = Trace2.StartRegion(OAuth2Constants.Trace2Category, "token_by_authcode"); var formData = new Dictionary { @@ -276,8 +276,7 @@ public async Task GetTokenByAuthorizationCodeAsync(OAuth2Auth public async Task GetTokenByRefreshTokenAsync(string refreshToken, CancellationToken ct) { - var label = "get token by refresh token"; - using IDisposable region = _trace2.CreateRegion(OAuth2Constants.Trace2Category, label); + using IDisposable region = Trace2.StartRegion(OAuth2Constants.Trace2Category, "token_by_refresh"); var formData = new Dictionary { @@ -309,6 +308,8 @@ public async Task GetTokenByRefreshTokenAsync(string refreshT public async Task GetTokenByDeviceCodeAsync(OAuth2DeviceCodeResult deviceCodeResult, CancellationToken ct) { + using IDisposable region = Trace2.StartRegion(OAuth2Constants.Trace2Category, "token_by_devicecode"); + var formData = new Dictionary { [OAuth2Constants.DeviceAuthorization.GrantTypeParameter] = OAuth2Constants.DeviceAuthorization.DeviceCodeGrantType, @@ -413,13 +414,12 @@ protected Exception CreateExceptionFromResponse(string json) { if (TryCreateExceptionFromResponse(json, out OAuth2Exception exception)) { - _trace2.WriteError(exception.Message); return exception; } var format = "Unknown OAuth error: {0}"; var message = string.Format(format, json); - return new Trace2OAuth2Exception(_trace2, message, format); + return new OAuth2Exception(message); } protected static bool TryDeserializeJson(string json, JsonTypeInfo typeInfo, out T obj) diff --git a/src/Core/Authentication/OAuthAuthentication.cs b/src/Core/Authentication/OAuthAuthentication.cs index 375ee12b23..e7446638ec 100644 --- a/src/Core/Authentication/OAuthAuthentication.cs +++ b/src/Core/Authentication/OAuthAuthentication.cs @@ -157,7 +157,7 @@ private async Task GetAuthenticationModeViaHelperAsync if (!resultDict.TryGetValue("mode", out string responseMode)) { - throw new Trace2Exception(Context.Trace2, "Missing 'mode' in response"); + throw new Exception("Missing 'mode' in response"); } switch (responseMode.ToLowerInvariant()) @@ -169,7 +169,7 @@ private async Task GetAuthenticationModeViaHelperAsync return OAuthAuthenticationModes.DeviceCode; default: - throw new Trace2Exception(Context.Trace2, + throw new Exception( $"Unknown mode value in response '{responseMode}'"); } } @@ -181,7 +181,7 @@ public async Task GetTokenByBrowserAsync(OAuth2Client client, // We require a desktop session to launch the user's default web browser if (!Context.SessionManager.IsDesktopSession) { - throw new Trace2InvalidOperationException(Context.Trace2, + throw new InvalidOperationException( "Browser authentication requires a desktop session"); } @@ -226,7 +226,7 @@ public async Task GetTokenByDeviceCodeAsync(OAuth2Client clie } catch (OperationCanceledException) { - throw new Trace2Exception(Context.Trace2, "User canceled device code authentication"); + throw new Exception("User canceled device code authentication"); } // Close the dialog diff --git a/src/Core/ChildProcess.cs b/src/Core/ChildProcess.cs index 9e86cc53ff..4f38fe8c2c 100644 --- a/src/Core/ChildProcess.cs +++ b/src/Core/ChildProcess.cs @@ -1,56 +1,56 @@ using System; using System.Diagnostics; using System.IO; -using System.Threading.Tasks; +using System.Threading; namespace GitCredentialManager; public class ChildProcess : DisposableObject { - private readonly ITrace2 _trace2; + // Increment with each new child process that is tracked + private static int _nextTrace2Id; + // The child process ID for Trace2 for this instance + private readonly int _trace2Id; + private readonly Trace2ProcessClass _processClass; private DateTimeOffset _startTime; - private DateTimeOffset _exitTime => Process.ExitTime; - private ProcessStartInfo _startInfo => Process.StartInfo; - - private int _id => Process.Id; public ProcessStartInfo StartInfo => Process.StartInfo; public Process Process { get; } + public int Id => Process.Id; public StreamWriter StandardInput => Process.StandardInput; public StreamReader StandardOutput => Process.StandardOutput; public StreamReader StandardError => Process.StandardError; public int ExitCode => Process.ExitCode; - public static ChildProcess Start(ITrace2 trace2, ProcessStartInfo startInfo, Trace2ProcessClass processClass) + public static ChildProcess Start(ProcessStartInfo startInfo, Trace2ProcessClass @class = Trace2ProcessClass.None) { - var childProc = new ChildProcess(trace2, startInfo); - childProc.Start(processClass); + var childProc = new ChildProcess(startInfo, @class); + childProc.Start(); return childProc; } - public ChildProcess(ITrace2 trace2, ProcessStartInfo startInfo) + public ChildProcess(ProcessStartInfo startInfo, Trace2ProcessClass @class = Trace2ProcessClass.None) { - _trace2 = trace2; - Process = new Process() { StartInfo = startInfo }; + _trace2Id = Interlocked.Increment(ref _nextTrace2Id); + _processClass = @class; + Process = new Process + { + StartInfo = startInfo, + EnableRaisingEvents = true + }; Process.Exited += ProcessOnExited; } - public bool Start(Trace2ProcessClass processClass) + public bool Start() { ThrowIfDisposed(); - // Record the time just before the process starts, since: - // (1) There is no event related to Start as there is with Exit. - // (2) Using Process.StartTime causes a race condition that leads - // to an exception if the process finishes executing before the - // variable is passed to Trace2. - _startTime = DateTimeOffset.UtcNow; - _trace2.WriteChildStart( - _startTime, - processClass, - _startInfo.UseShellExecute, - _startInfo.FileName, - _startInfo.Arguments); + _startTime = Trace2.WriteChildStart( + _trace2Id, + _processClass, + Process.StartInfo.UseShellExecute, + Process.StartInfo.FileName, + Process.StartInfo.Arguments); return Process.Start(); } @@ -62,18 +62,24 @@ protected override void ReleaseManagedResources() { Process.Exited -= ProcessOnExited; Process.Dispose(); - base.ReleaseUnmanagedResources(); + base.ReleaseManagedResources(); } private void ProcessOnExited(object sender, EventArgs e) { - if (sender is Process) + if (sender is Process p) { - double elapsedTime = (_exitTime - _startTime).TotalSeconds; - _trace2.WriteChildExit( - elapsedTime, - _id, - Process.ExitCode); + // This event may have been triggered a while after the process + // actually exited, so we should read the exit time from the + // process object, and not compute the current timestamp inproc. + // Note that we continue to use the start time computed and stored + // inproc and *not* the start time recorded by the process object. + // This is because if the process has already exited and cleaned up + // by the operating system by the time we try and read the start time + // we get an error! + var exitTime = p.ExitTime.ToUniversalTime(); + var relativeTime = exitTime - _startTime; + Trace2.WriteChildExit(_trace2Id, relativeTime, p.Id, p.ExitCode); } } } diff --git a/src/Core/CommandContext.cs b/src/Core/CommandContext.cs index 60f330932a..9ca924d8f5 100644 --- a/src/Core/CommandContext.cs +++ b/src/Core/CommandContext.cs @@ -51,11 +51,6 @@ public interface ICommandContext : IDisposable /// ITrace Trace { get; } - /// - /// Application TRACE2 tracing system. - /// - ITrace2 Trace2 { get; } - /// /// File system abstraction (exists mainly for testing). /// @@ -94,12 +89,13 @@ public class CommandContext : DisposableObject, ICommandContext { public CommandContext() { + using var _ = Trace2.StartRegion("cmd_ctx", "create"); + ApplicationPath = GetEntryApplicationPath(); InstallationDirectory = GetInstallationDirectory(); Streams = new StandardStreams(); Trace = new Trace(); - Trace2 = new Trace2(this); Console = new ConsoleService(Streams); if (PlatformUtils.IsWindows()) @@ -107,11 +103,10 @@ public CommandContext() FileSystem = new WindowsFileSystem(); Environment = new WindowsEnvironment(FileSystem); SessionManager = new WindowsSessionManager(Trace, Environment, FileSystem); - ProcessManager = new WindowsProcessManager(Trace2); + ProcessManager = new WindowsProcessManager(); string gitPath = GetGitPath(Environment, FileSystem, Trace); Git = new GitProcess( Trace, - Trace2, ProcessManager, gitPath, FileSystem.GetCurrentDirectory() @@ -123,11 +118,10 @@ public CommandContext() FileSystem = new MacOSFileSystem(); Environment = new MacOSEnvironment(FileSystem); SessionManager = new MacOSSessionManager(Trace, Environment, FileSystem); - ProcessManager = new ProcessManager(Trace2); + ProcessManager = new ProcessManager(); string gitPath = GetGitPath(Environment, FileSystem, Trace); Git = new GitProcess( Trace, - Trace2, ProcessManager, gitPath, FileSystem.GetCurrentDirectory() @@ -139,11 +133,10 @@ public CommandContext() FileSystem = new LinuxFileSystem(); Environment = new PosixEnvironment(FileSystem); SessionManager = new LinuxSessionManager(Trace, Environment, FileSystem); - ProcessManager = new ProcessManager(Trace2); + ProcessManager = new ProcessManager(); string gitPath = GetGitPath(Environment, FileSystem, Trace); Git = new GitProcess( Trace, - Trace2, ProcessManager, gitPath, FileSystem.GetCurrentDirectory() @@ -155,12 +148,14 @@ public CommandContext() throw new PlatformNotSupportedException(); } - HttpClientFactory = new HttpClientFactory(FileSystem, Trace, Trace2, Settings, Console); + HttpClientFactory = new HttpClientFactory(FileSystem, Trace, Settings, Console); CredentialStore = new CredentialStore(this); } private static string GetGitPath(IEnvironment environment, IFileSystem fileSystem, ITrace trace) { + using var _ = Trace2.StartRegion("cmd_ctx", "find_git"); + const string unixGitName = "git"; const string winGitName = "git.exe"; @@ -207,8 +202,6 @@ private static string GetGitPath(IEnvironment environment, IFileSystem fileSyste public ITrace Trace { get; } - public ITrace2 Trace2 { get; } - public IFileSystem FileSystem { get; } public ICredentialStore CredentialStore { get; } diff --git a/src/Core/Commands/CapabilityCommand.cs b/src/Core/Commands/CapabilityCommand.cs index c80607ad3e..6ac5aca197 100644 --- a/src/Core/Commands/CapabilityCommand.cs +++ b/src/Core/Commands/CapabilityCommand.cs @@ -48,6 +48,9 @@ public CapabilityCommand(ICommandContext context) internal void Execute() { + using var _ = Trace2.StartRegion("git_cmd", "run"); + Trace2.WriteData("git_cmd", "name", "capability"); + _context.Trace.WriteLine("Start 'capability' command..."); _context.Streams.Out.WriteLine($"version {ProtocolVersion}"); diff --git a/src/Core/Commands/ConfigurationCommands.cs b/src/Core/Commands/ConfigurationCommands.cs index 1996805c9b..efca06d2c2 100644 --- a/src/Core/Commands/ConfigurationCommands.cs +++ b/src/Core/Commands/ConfigurationCommands.cs @@ -24,13 +24,17 @@ protected ConfigurationCommandBase(ICommandContext context, string name, string protected IConfigurationService ConfigurationService { get; } - internal Task ExecuteAsync(bool system) + internal async Task ExecuteAsync(bool system) { var target = system ? ConfigurationTarget.System : ConfigurationTarget.User; - return ExecuteInternalAsync(target); + using var _ = Trace2.StartRegion("cfg_cmd", "run"); + Trace2.WriteData("cfg_cmd", "name", Name); + Trace2.WriteData("cfg_cmd", "target", target.ToString()); + + await ExecuteInternalAsync(target); } protected abstract Task ExecuteInternalAsync(ConfigurationTarget target); diff --git a/src/Core/Commands/DiagnoseCommand.cs b/src/Core/Commands/DiagnoseCommand.cs index 3603d073b1..a3364f77bd 100644 --- a/src/Core/Commands/DiagnoseCommand.cs +++ b/src/Core/Commands/DiagnoseCommand.cs @@ -45,6 +45,8 @@ public void AddDiagnostic(IDiagnostic diagnostic) private async Task ExecuteAsync(string output) { + using var _ = Trace2.StartRegion("diag_cmd", "run"); + // Don't use IStandardStreams for writing output in this command as we // cannot trust any component on the ICommandContext is working correctly. Console.WriteLine($"Running diagnostics...{Environment.NewLine}"); diff --git a/src/Core/Commands/EraseCommand.cs b/src/Core/Commands/EraseCommand.cs index ab850af5ca..52d109bbd2 100644 --- a/src/Core/Commands/EraseCommand.cs +++ b/src/Core/Commands/EraseCommand.cs @@ -13,9 +13,10 @@ public EraseCommand(ICommandContext context, IHostProviderRegistry hostProviderR IsHidden = true; } - protected override Task ExecuteInternalAsync(GitRequest request, IHostProvider provider) + protected override async Task ExecuteInternalAsync(GitRequest request, IHostProvider provider) { - return provider.EraseCredentialAsync(request); + using var _ = Trace2.StartRegion("git_cmd_erase", "provider_erase"); + await provider.EraseCredentialAsync(request); } } } diff --git a/src/Core/Commands/GetCommand.cs b/src/Core/Commands/GetCommand.cs index c328924de2..551e1feac8 100644 --- a/src/Core/Commands/GetCommand.cs +++ b/src/Core/Commands/GetCommand.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Threading.Tasks; -using GitCredentialManager.Tty; namespace GitCredentialManager.Commands { @@ -21,7 +20,10 @@ protected override async Task ExecuteInternalAsync(GitRequest request, IHostProv GitResponse response; try { - response = await provider.GetCredentialAsync(request); + using (Trace2.StartRegion("git_cmd_get", "provider_get")) + { + response = await provider.GetCredentialAsync(request); + } } catch (Exception ex) when (ex is OperationCanceledException || ex is InterruptedException) { diff --git a/src/Core/Commands/GitCommandBase.cs b/src/Core/Commands/GitCommandBase.cs index f9b56bb8ef..1dfc398a9c 100644 --- a/src/Core/Commands/GitCommandBase.cs +++ b/src/Core/Commands/GitCommandBase.cs @@ -29,11 +29,18 @@ protected GitCommandBase(ICommandContext context, string name, string descriptio internal async Task ExecuteAsync() { + using var _ = Trace2.StartRegion("git_cmd", "run"); + Trace2.WriteData("git_cmd", "name", Name); + Context.Trace.WriteLine($"Start '{Name}' command..."); // Parse standard input arguments - // git-credential treats the keys as case-sensitive; so should we. - IDictionary> inputDict = await Context.Streams.In.ReadMultiDictionaryAsync(StringComparer.Ordinal); + IDictionary> inputDict; + using (Trace2.StartRegion("git_cmd", "parse_input")) + { + // git-credential treats the keys as case-sensitive; so should we. + inputDict = await Context.Streams.In.ReadMultiDictionaryAsync(StringComparer.Ordinal); + } var request = new GitRequest(inputDict); // Validate minimum arguments are present @@ -45,7 +52,14 @@ internal async Task ExecuteAsync() // Determine the host provider Context.Trace.WriteLine("Detecting host provider for request:"); Context.Trace.WriteDictionarySecrets(inputDict, new []{ "password" }, StringComparer.OrdinalIgnoreCase); - IHostProvider provider = await _hostProviderRegistry.GetProviderAsync(request); + IHostProvider provider; + using (Trace2.StartRegion("git_cmd", "resolve_provider")) + { + provider = await _hostProviderRegistry.GetProviderAsync(request); + + Trace2.WriteData("git_cmd", "provider/id", provider.Id); + Trace2.WriteData("git_cmd", "provider/name", provider.Name); + } Context.Trace.WriteLine($"Host provider '{provider.Name}' was selected."); await ExecuteInternalAsync(request, provider); @@ -57,23 +71,23 @@ protected virtual void EnsureMinimumRequest(GitRequest request) { if (request.Protocol is null) { - throw new Trace2InvalidOperationException(Context.Trace2, "Missing 'protocol' request argument"); + throw new InvalidOperationException("Missing 'protocol' request argument"); } if (string.IsNullOrWhiteSpace(request.Protocol)) { - throw new Trace2InvalidOperationException(Context.Trace2, + throw new InvalidOperationException( "Invalid 'protocol' request argument (cannot be empty)"); } if (request.Host is null) { - throw new Trace2InvalidOperationException(Context.Trace2, "Missing 'host' request argument"); + throw new InvalidOperationException("Missing 'host' request argument"); } if (string.IsNullOrWhiteSpace(request.Host)) { - throw new Trace2InvalidOperationException(Context.Trace2, + throw new InvalidOperationException( "Invalid 'host' request argument (cannot be empty)"); } } diff --git a/src/Core/Commands/StoreCommand.cs b/src/Core/Commands/StoreCommand.cs index a4d960d9bf..09043ff74b 100644 --- a/src/Core/Commands/StoreCommand.cs +++ b/src/Core/Commands/StoreCommand.cs @@ -14,9 +14,10 @@ public StoreCommand(ICommandContext context, IHostProviderRegistry hostProviderR IsHidden = true; } - protected override Task ExecuteInternalAsync(GitRequest request, IHostProvider provider) + protected override async Task ExecuteInternalAsync(GitRequest request, IHostProvider provider) { - return provider.StoreCredentialAsync(request); + using var _ = Trace2.StartRegion("git_cmd_store", "provider_store"); + await provider.StoreCredentialAsync(request); } protected override void EnsureMinimumRequest(GitRequest request) @@ -26,12 +27,12 @@ protected override void EnsureMinimumRequest(GitRequest request) // An empty string username/password are valid inputs, so only check for `null` (not provided) if (request.UserName is null) { - throw new Trace2InvalidOperationException(Context.Trace2, "Missing 'username' request argument"); + throw new InvalidOperationException("Missing 'username' request argument"); } if (request.Password is null) { - throw new Trace2InvalidOperationException(Context.Trace2, "Missing 'password' request argument"); + throw new InvalidOperationException("Missing 'password' request argument"); } } } diff --git a/src/Core/ConsoleService.cs b/src/Core/ConsoleService.cs index 5d76223efc..2569b8a1f5 100644 --- a/src/Core/ConsoleService.cs +++ b/src/Core/ConsoleService.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; using GitCredentialManager.Tty; @@ -36,32 +37,32 @@ public interface IConsoleService public class ConsoleService : IConsoleService { - private readonly IAnsiConsole _ttyConsole; - private readonly IAnsiConsole _stderrConsole; + private readonly Lazy _ttyConsole; + private readonly Lazy _stderrConsole; public ConsoleService(IStandardStreams streams) - : this(AnsiConsoleFactory.CreateForTty(), AnsiConsoleFactory.CreateForWriter(streams.Error, streams.IsErrorRedirected)) + : this(AnsiConsoleFactory.CreateForTty, () => AnsiConsoleFactory.CreateForWriter(streams.Error, streams.IsErrorRedirected)) { } - public ConsoleService(IAnsiConsole ttyConsole, IAnsiConsole stderrConsole) + public ConsoleService(Func ttyConsoleFunc, Func stderrConsoleFunc) { - _ttyConsole = ttyConsole; - _stderrConsole = stderrConsole; + _ttyConsole = new Lazy(ttyConsoleFunc); + _stderrConsole = new Lazy(stderrConsoleFunc); } - public void WriteInfo(string message) => _stderrConsole.MarkupLine($"[blue]info:[/] {Markup.Escape(message)}"); + public void WriteInfo(string message) => _stderrConsole.Value.MarkupLine($"[blue]info:[/] {Markup.Escape(message)}"); - public void WriteWarning(string message) => _stderrConsole.MarkupLine($"[yellow]warning:[/] {Markup.Escape(message)}"); + public void WriteWarning(string message) => _stderrConsole.Value.MarkupLine($"[yellow]warning:[/] {Markup.Escape(message)}"); - public void WriteError(string message) => _stderrConsole.MarkupLine($"[red]error:[/] {Markup.Escape(message)}"); + public void WriteError(string message) => _stderrConsole.Value.MarkupLine($"[red]error:[/] {Markup.Escape(message)}"); - public void WriteFatal(string message) => _stderrConsole.MarkupLine($"[red]fatal:[/] {Markup.Escape(message)}"); + public void WriteFatal(string message) => _stderrConsole.Value.MarkupLine($"[red]fatal:[/] {Markup.Escape(message)}"); - public void WriteLine(string message) => _stderrConsole.WriteLine(message); + public void WriteLine(string message) => _stderrConsole.Value.WriteLine(message); public T ShowPrompt(IPrompt prompt) => - prompt.Show(_ttyConsole); + prompt.Show(_ttyConsole.Value); public Task ShowPromptAsync(IPrompt prompt, CancellationToken ct = default) => - prompt.ShowAsync(_ttyConsole, ct); + prompt.ShowAsync(_ttyConsole.Value, ct); } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 667ff8b0ba..cff7a1a1bd 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -314,9 +314,9 @@ public static Version GcmVersion /// Get the HTTP user-agent for Git Credential Manager. /// /// User-agent string for HTTP requests. - public static string GetHttpUserAgent(ITrace2 trace2) + public static string GetHttpUserAgent() { - PlatformInformation info = PlatformUtils.GetPlatformInformation(trace2); + PlatformInformation info = PlatformUtils.GetPlatformInformation(); string osType = info.OperatingSystemType; string cpuArch = info.CpuArchitecture; string clrVersion = info.ClrVersion; diff --git a/src/Core/CredentialStore.cs b/src/Core/CredentialStore.cs index 95d26df320..29b9d04c5c 100644 --- a/src/Core/CredentialStore.cs +++ b/src/Core/CredentialStore.cs @@ -27,24 +27,28 @@ public CredentialStore(ICommandContext context) public IList GetAccounts(string service) { + using var _ = Trace2.StartRegion("cred_store", "get_accounts"); EnsureBackingStore(); return _backingStore.GetAccounts(service); } public ICredential Get(string service, string account) { + using var _ = Trace2.StartRegion("cred_store", "get"); EnsureBackingStore(); return _backingStore.Get(service, account); } public void AddOrUpdate(string service, string account, string secret) { + using var _ = Trace2.StartRegion("cred_store", "add"); EnsureBackingStore(); _backingStore.AddOrUpdate(service, account, secret); } public bool Remove(string service, string account) { + using var _ = Trace2.StartRegion("cred_store", "remove"); EnsureBackingStore(); return _backingStore.Remove(service, account); } @@ -58,11 +62,17 @@ private void EnsureBackingStore() return; } + using var _ = Trace2.StartRegion("cred_store", "init"); + string ns = _context.Settings.CredentialNamespace; - string credStoreName = _context.Settings.CredentialBackingStore?.ToLowerInvariant() - ?? GetDefaultStore(); + string credStoreName = _context.Settings.CredentialBackingStore?.ToLowerInvariant(); + string defaultStore = GetDefaultStore(); + + Trace2.WriteData("cred_store", "store/configured", credStoreName); + Trace2.WriteData("cred_store", "store/default", defaultStore); + Trace2.WriteData("cred_store", "store/namespace", ns); - switch (credStoreName) + switch (credStoreName ?? defaultStore) { case StoreNames.WindowsCredentialManager: ValidateWindowsCredentialManager(); @@ -86,7 +96,7 @@ private void EnsureBackingStore() case StoreNames.Gpg: ValidateGpgPass(out string gpgStoreRoot, out string gpgExec); - IGpg gpg = new Gpg(gpgExec, _context.SessionManager, _context.ProcessManager, _context.Trace2); + IGpg gpg = new Gpg(gpgExec, _context.SessionManager, _context.ProcessManager); _backingStore = new GpgPassCredentialStore(_context.FileSystem, gpg, gpgStoreRoot, ns); break; @@ -109,7 +119,6 @@ private void EnsureBackingStore() sb.AppendLine(string.IsNullOrWhiteSpace(credStoreName) ? "No credential store has been selected." : $"Unknown credential store '{credStoreName}'."); - _context.Trace2.WriteError(sb.ToString()); sb.AppendFormat( "{3}Set the {0} environment variable or the {1}.{2} Git configuration setting to one of the following options:{3}{3}", Constants.EnvironmentVariables.GcmCredentialStore, @@ -182,7 +191,6 @@ private void ValidateWindowsCredentialManager() if (!PlatformUtils.IsWindows()) { var message = $"Can only use the '{StoreNames.WindowsCredentialManager}' credential store on Windows."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -191,7 +199,6 @@ private void ValidateWindowsCredentialManager() if (!WindowsCredentialManager.CanPersist()) { var message = $"Unable to persist credentials with the '{StoreNames.WindowsCredentialManager}' credential store."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -203,7 +210,6 @@ private void ValidateDpapi(out string storeRoot) if (!PlatformUtils.IsWindows()) { var message = $"Can only use the '{StoreNames.Dpapi}' credential store on Windows."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -226,7 +232,6 @@ private void ValidateMacOSKeychain() if (!PlatformUtils.IsMacOS()) { var message = $"Can only use the '{StoreNames.MacOSKeychain}' credential store on macOS."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -238,7 +243,6 @@ private void ValidateSecretService() if (!PlatformUtils.IsLinux()) { var message = $"Can only use the '{StoreNames.SecretService}' credential store on Linux."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -247,7 +251,6 @@ private void ValidateSecretService() if (!_context.SessionManager.IsDesktopSession) { var message = $"Cannot use the '{StoreNames.SecretService}' credential backing store without a graphical interface present."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -259,7 +262,6 @@ private void ValidateGpgPass(out string storeRoot, out string execPath) if (!PlatformUtils.IsPosix()) { var message = $"Can only use the '{StoreNames.Gpg}' credential store on POSIX systems."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -274,7 +276,6 @@ private void ValidateGpgPass(out string storeRoot, out string execPath) !_context.Environment.Variables.ContainsKey("SSH_TTY")) { var message = "GPG_TTY is not set; add `export GPG_TTY=$(tty)` to your profile."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -298,7 +299,6 @@ private void ValidateCredentialCache(out string options) if (PlatformUtils.IsWindows()) { var message = $"Can not use the '{StoreNames.Cache}' credential store on Windows due to lack of UNIX socket support in Git for Windows."; - _context.Trace2.WriteError(message); throw new Exception(message + Environment.NewLine + $"See {Constants.HelpUrls.GcmCredentialStores} for more information." ); @@ -345,7 +345,7 @@ private string GetGpgPath() var format = "GPG executable does not exist with path '{0}'"; var message = string.Format(format, gpgPath); - throw new Trace2Exception(_context.Trace2, message, format); + throw new Exception(message); } // If no explicit GPG path is specified, mimic the way `pass` diff --git a/src/Core/Diagnostics/EnvironmentDiagnostic.cs b/src/Core/Diagnostics/EnvironmentDiagnostic.cs index dbec71f02b..d0d63c386c 100644 --- a/src/Core/Diagnostics/EnvironmentDiagnostic.cs +++ b/src/Core/Diagnostics/EnvironmentDiagnostic.cs @@ -15,7 +15,7 @@ public EnvironmentDiagnostic(ICommandContext commandContext) protected override Task RunInternalAsync(StringBuilder log, IList additionalFiles) { - PlatformInformation platformInfo = PlatformUtils.GetPlatformInformation(CommandContext.Trace2); + PlatformInformation platformInfo = PlatformUtils.GetPlatformInformation(); log.AppendLine($"OSType: {platformInfo.OperatingSystemType}"); log.AppendLine($"OSVersion: {platformInfo.OperatingSystemVersion}"); diff --git a/src/Core/Diagnostics/GitDiagnostic.cs b/src/Core/Diagnostics/GitDiagnostic.cs index 74c4c76b34..f97ac60790 100644 --- a/src/Core/Diagnostics/GitDiagnostic.cs +++ b/src/Core/Diagnostics/GitDiagnostic.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics; using System.Text; using System.Threading.Tasks; @@ -32,7 +31,7 @@ protected override Task RunInternalAsync(StringBuilder log, IList log.Append("Listing all Git configuration..."); ChildProcess configProc = CommandContext.Git.CreateProcess("config --list --show-origin"); - configProc.Start(Trace2ProcessClass.Git); + configProc.Start(); // To avoid deadlocks, always read the output stream first and then wait // TODO: don't read in all the data at once; stream it string gitConfig = configProc.StandardOutput.ReadToEnd().TrimEnd(); diff --git a/src/Core/GenericHostProvider.cs b/src/Core/GenericHostProvider.cs index ab17405b69..3480b9b439 100644 --- a/src/Core/GenericHostProvider.cs +++ b/src/Core/GenericHostProvider.cs @@ -166,7 +166,7 @@ public async Task GenerateCredentialAsync(GitRequest request) _context.Trace.WriteLine($"\tDefaultUserName = {oauthConfig.DefaultUserName}"); return new GitResponse( - await GetOAuthAccessToken(uri, request.UserName, oauthConfig, _context.Trace2) + await GetOAuthAccessToken(uri, request.UserName, oauthConfig) ); } // Try detecting WIA for this remote, if permitted @@ -231,7 +231,7 @@ await _basicAuth.GetCredentialsAsync(uri.AbsoluteUri, null) } else { - string osType = PlatformUtils.GetPlatformInformation(_context.Trace2).OperatingSystemType; + string osType = PlatformUtils.GetPlatformInformation().OperatingSystemType; _context.Trace.WriteLine($"Skipping check for Windows Integrated Authentication on {osType}."); } } @@ -264,7 +264,7 @@ private void EnableNtlmSupport(Uri uri) } } - private async Task GetOAuthAccessToken(Uri remoteUri, string userName, GenericOAuthConfig config, ITrace2 trace2) + private async Task GetOAuthAccessToken(Uri remoteUri, string userName, GenericOAuthConfig config) { // TODO: Determined user info from a webcall? ID token? Need OIDC support string oauthUser = userName ?? config.DefaultUserName; @@ -273,7 +273,6 @@ private async Task GetOAuthAccessToken(Uri remoteUri, string userNa HttpClient, config.Endpoints, config.ClientId, - trace2, config.RedirectUri, config.ClientSecret, config.UseAuthHeader, @@ -355,7 +354,7 @@ private async Task GetOAuthAccessToken(Uri remoteUri, string userNa break; default: - throw new Trace2Exception(_context.Trace2, "No authentication mode selected!"); + throw new Exception("No authentication mode selected!"); } // Store the refresh token if we have one diff --git a/src/Core/Git.cs b/src/Core/Git.cs index 82588357cd..37bd25487f 100644 --- a/src/Core/Git.cs +++ b/src/Core/Git.cs @@ -71,20 +71,17 @@ public GitRemote(string name, string fetchUrl, string pushUrl) public class GitProcess : IGit { private readonly ITrace _trace; - private readonly ITrace2 _trace2; private readonly IProcessManager _processManager; private readonly string _gitPath; private readonly string _workingDirectory; - public GitProcess(ITrace trace, ITrace2 trace2, IProcessManager processManager, string gitPath, string workingDirectory = null) + public GitProcess(ITrace trace, IProcessManager processManager, string gitPath, string workingDirectory = null) { EnsureArgument.NotNull(trace, nameof(trace)); - EnsureArgument.NotNull(trace2, nameof(trace2)); EnsureArgument.NotNull(processManager, nameof(processManager)); EnsureArgument.NotNullOrWhiteSpace(gitPath, nameof(gitPath)); _trace = trace; - _trace2 = trace2; _processManager = processManager; _gitPath = gitPath; _workingDirectory = workingDirectory; @@ -97,23 +94,18 @@ public GitVersion Version { if (_version is null) { + string data; using (var git = CreateProcess("version")) { - git.Start(Trace2ProcessClass.Git); + git.Start(); - string data = git.StandardOutput.ReadToEnd(); + data = git.StandardOutput.ReadToEnd(); git.WaitForExit(); - - Match match = Regex.Match(data, @"^git version (?'value'.*)"); - if (match.Success) - { - _version = new GitVersion(match.Groups["value"].Value); - } - else - { - _version = new GitVersion(); - } } + Match match = Regex.Match(data, @"^git version (?'value'.*)"); + _version = match.Success + ? new GitVersion(match.Groups["value"].Value) + : new GitVersion(); } return _version; @@ -145,7 +137,7 @@ private string GetCurrentRepositoryInternal(bool suppressStreams) git.StartInfo.RedirectStandardError = true; } - git.Start(Trace2ProcessClass.Git); + git.Start(); // Drain and throw away stderr asynchronously to avoid a deadlock // if the child process fills the stderr pipe buffer. @@ -167,7 +159,7 @@ private string GetCurrentRepositoryInternal(bool suppressStreams) default: var message = "Failed to get current Git repository"; _trace.WriteLine($"{message} (exit={git.ExitCode})"); - throw CreateGitException(git, message, _trace2); + throw CreateGitException(git, message); } } } @@ -178,7 +170,7 @@ public IEnumerable GetRemotes() { // Redirect stderr so we can check for 'not a git repository' errors git.StartInfo.RedirectStandardError = true; - git.Start(Trace2ProcessClass.Git); + git.Start(); // To avoid deadlocks, always read the output stream first and then wait // TODO: don't read in all the data at once; stream it string data = git.StandardOutput.ReadToEnd(); @@ -194,7 +186,7 @@ public IEnumerable GetRemotes() default: var message = "Failed to enumerate Git remotes"; _trace.WriteLine($"{message} (exit={git.ExitCode})"); - throw CreateGitException(git, message, _trace2); + throw CreateGitException(git, message); } string[] lines = data.Split('\n'); @@ -222,7 +214,8 @@ public IEnumerable GetRemotes() public ChildProcess CreateProcess(string args) { - return _processManager.CreateProcess(_gitPath, args, false, _workingDirectory); + return _processManager.CreateProcess( + _gitPath, args, false, _workingDirectory, Trace2ProcessClass.Git); } // This code was originally copied from @@ -242,12 +235,12 @@ public async Task> InvokeHelperAsync(string args, ID UseShellExecute = false }; - var process = _processManager.CreateProcess(procStartInfo); - if (!process.Start(Trace2ProcessClass.Git)) + var process = _processManager.CreateProcess(procStartInfo, Trace2ProcessClass.Git); + if (!process.Start()) { var format = "Failed to start Git helper '{0}'"; var message = string.Format(format, args); - throw new Trace2Exception(_trace2, message, format); + throw new Exception(message); } if (!(standardInput is null)) @@ -276,15 +269,12 @@ public async Task> InvokeHelperAsync(string args, ID return resultDict; } - public static GitException CreateGitException(ChildProcess git, string message, ITrace2 trace2 = null) + public static GitException CreateGitException(ChildProcess git, string message) { var gitMessage = git.StartInfo.RedirectStandardError ? git.StandardError.ReadToEnd() : null; - if (trace2 != null) - throw new Trace2GitException(trace2, message, git.ExitCode, gitMessage); - throw new GitException(message, gitMessage, git.ExitCode); } } diff --git a/src/Core/GitConfiguration.cs b/src/Core/GitConfiguration.cs index 83a10d5918..9da4bc0289 100644 --- a/src/Core/GitConfiguration.cs +++ b/src/Core/GitConfiguration.cs @@ -332,7 +332,8 @@ public class GitProcessConfiguration : IGitConfiguration private readonly Dictionary _cache; private readonly bool _useCache; - internal GitProcessConfiguration(ITrace trace, GitProcess git) : this(trace, git, useCache: true) + internal GitProcessConfiguration(ITrace trace, GitProcess git) + : this(trace, git, useCache: true) { } @@ -389,6 +390,9 @@ private void EnsureCacheLoaded(GitConfigurationType type) return; } + using IDisposable region = Trace2.StartRegion("git_config", "load_cache"); + Trace2.WriteData("git_config", "type", type.ToString().ToLowerInvariant()); + if (cache == null) { cache = new ConfigCache(); @@ -417,7 +421,7 @@ private void EnsureCacheLoaded(GitConfigurationType type) using (ChildProcess git = _git.CreateProcess($"config list --show-scope -z {typeArg}")) { - git.Start(Trace2ProcessClass.Git); + git.Start(); // To avoid deadlocks, always read the output stream first and then wait string data = git.StandardOutput.ReadToEnd(); git.WaitForExit(); @@ -448,6 +452,9 @@ private void InvalidateCache() public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCallback cb) { + using IDisposable region = Trace2.StartRegion("git_config", "enumerate"); + Trace2.WriteData("git_config", "scope", level.ToString().ToLowerInvariant()); + if (_useCache) { EnsureCacheLoaded(GitConfigurationType.Raw); @@ -465,7 +472,7 @@ public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCa string levelArg = GetLevelFilterArg(level); using (ChildProcess git = _git.CreateProcess($"config --null {levelArg} --list")) { - git.Start(Trace2ProcessClass.Git); + git.Start(); // To avoid deadlocks, always read the output stream first and then wait // TODO: don't read in all the data at once; stream it string data = git.StandardOutput.ReadToEnd(); @@ -543,11 +550,14 @@ public bool TryGet(GitConfigurationLevel level, GitConfigurationType type, strin } // Fall back to individual git config command if cache not available + using var _ = Trace2.StartRegion("git_config", "get"); + Trace2.WriteData("git_config", "scope", level.ToString().ToLowerInvariant()); + Trace2.WriteData("git_config", "type", type.ToString().ToLowerInvariant()); string levelArg = GetLevelFilterArg(level); string typeArg = GetCanonicalizeTypeArg(type); using (ChildProcess git = _git.CreateProcess($"config --null {levelArg} {typeArg} {QuoteCmdArg(name)}")) { - git.Start(Trace2ProcessClass.Git); + git.Start(); // To avoid deadlocks, always read the output stream first and then wait // TODO: don't read in all the data at once; stream it string data = git.StandardOutput.ReadToEnd(); @@ -580,12 +590,15 @@ public bool TryGet(GitConfigurationLevel level, GitConfigurationType type, strin public void Set(GitConfigurationLevel level, string name, string value) { + using IDisposable region = Trace2.StartRegion("git_config", "set"); + Trace2.WriteData("git_config", "scope", level.ToString().ToLowerInvariant()); + EnsureSpecificLevel(level); string levelArg = GetLevelFilterArg(level); using (ChildProcess git = _git.CreateProcess($"config {levelArg} {QuoteCmdArg(name)} {QuoteCmdArg(value)}")) { - git.Start(Trace2ProcessClass.Git); + git.Start(); git.WaitForExit(); switch (git.ExitCode) @@ -602,12 +615,15 @@ public void Set(GitConfigurationLevel level, string name, string value) public void Add(GitConfigurationLevel level, string name, string value) { + using IDisposable region = Trace2.StartRegion("git_config", "add"); + Trace2.WriteData("git_config", "scope", level.ToString().ToLowerInvariant()); + EnsureSpecificLevel(level); string levelArg = GetLevelFilterArg(level); using (ChildProcess git = _git.CreateProcess($"config {levelArg} --add {QuoteCmdArg(name)} {QuoteCmdArg(value)}")) { - git.Start(Trace2ProcessClass.Git); + git.Start(); git.WaitForExit(); switch (git.ExitCode) @@ -624,12 +640,15 @@ public void Add(GitConfigurationLevel level, string name, string value) public void Unset(GitConfigurationLevel level, string name) { + using IDisposable region = Trace2.StartRegion("git_config", "unset"); + Trace2.WriteData("git_config", "scope", level.ToString().ToLowerInvariant()); + EnsureSpecificLevel(level); string levelArg = GetLevelFilterArg(level); using (ChildProcess git = _git.CreateProcess($"config {levelArg} --unset {QuoteCmdArg(name)}")) { - git.Start(Trace2ProcessClass.Git); + git.Start(); git.WaitForExit(); switch (git.ExitCode) @@ -647,6 +666,10 @@ public void Unset(GitConfigurationLevel level, string name) public IEnumerable GetAll(GitConfigurationLevel level, GitConfigurationType type, string name) { + using IDisposable region = Trace2.StartRegion("git_config", "get_all"); + Trace2.WriteData("git_config", "scope", level.ToString().ToLowerInvariant()); + Trace2.WriteData("git_config", "type", type.ToString().ToLowerInvariant()); + if (_useCache) { EnsureCacheLoaded(type); @@ -671,7 +694,7 @@ public IEnumerable GetAll(GitConfigurationLevel level, GitConfigurationT using (ChildProcess git = _git.CreateProcess(gitArgs)) { - git.Start(Trace2ProcessClass.Git); + git.Start(); // To avoid deadlocks, always read the output stream first and then wait // TODO: don't read in all the data at once; stream it string data = git.StandardOutput.ReadToEnd(); @@ -702,6 +725,10 @@ public IEnumerable GetAll(GitConfigurationLevel level, GitConfigurationT public IEnumerable GetRegex(GitConfigurationLevel level, GitConfigurationType type, string nameRegex, string valueRegex) { + using IDisposable region = Trace2.StartRegion("git_config", "get_regex"); + Trace2.WriteData("git_config", "scope", level.ToString().ToLowerInvariant()); + Trace2.WriteData("git_config", "type", type.ToString().ToLowerInvariant()); + string levelArg = GetLevelFilterArg(level); string typeArg = GetCanonicalizeTypeArg(type); @@ -713,7 +740,7 @@ public IEnumerable GetRegex(GitConfigurationLevel level, GitConfiguratio using (ChildProcess git = _git.CreateProcess(gitArgs)) { - git.Start(Trace2ProcessClass.Git); + git.Start(); // To avoid deadlocks, always read the output stream first and then wait // TODO: don't read in all the data at once; stream it string data = git.StandardOutput.ReadToEnd(); @@ -744,6 +771,8 @@ public IEnumerable GetRegex(GitConfigurationLevel level, GitConfiguratio public void ReplaceAll(GitConfigurationLevel level, string name, string valueRegex, string value) { + using IDisposable region = Trace2.StartRegion("git_config", "replace_all"); + EnsureSpecificLevel(level); string levelArg = GetLevelFilterArg(level); @@ -755,7 +784,7 @@ public void ReplaceAll(GitConfigurationLevel level, string name, string valueReg using (ChildProcess git = _git.CreateProcess(gitArgs)) { - git.Start(Trace2ProcessClass.Git); + git.Start(); git.WaitForExit(); switch (git.ExitCode) @@ -772,6 +801,8 @@ public void ReplaceAll(GitConfigurationLevel level, string name, string valueReg public void UnsetAll(GitConfigurationLevel level, string name, string valueRegex) { + using IDisposable region = Trace2.StartRegion("git_config", "unset_all"); + EnsureSpecificLevel(level); string levelArg = GetLevelFilterArg(level); @@ -783,7 +814,7 @@ public void UnsetAll(GitConfigurationLevel level, string name, string valueRegex using (ChildProcess git = _git.CreateProcess(gitArgs)) { - git.Start(Trace2ProcessClass.Git); + git.Start(); git.WaitForExit(); switch (git.ExitCode) diff --git a/src/Core/Gpg.cs b/src/Core/Gpg.cs index 686cf0db98..d2812b1dab 100644 --- a/src/Core/Gpg.cs +++ b/src/Core/Gpg.cs @@ -15,18 +15,15 @@ public class Gpg : IGpg private readonly string _gpgPath; private readonly ISessionManager _sessionManager; private readonly IProcessManager _processManager; - private readonly ITrace2 _trace2; - public Gpg(string gpgPath, ISessionManager sessionManager, IProcessManager processManager, ITrace2 trace2) + public Gpg(string gpgPath, ISessionManager sessionManager, IProcessManager processManager) { EnsureArgument.NotNullOrWhiteSpace(gpgPath, nameof(gpgPath)); EnsureArgument.NotNull(sessionManager, nameof(sessionManager)); - EnsureArgument.NotNull(trace2, nameof(trace2)); _gpgPath = gpgPath; _sessionManager = sessionManager; _processManager = processManager; - _trace2 = trace2; } public string DecryptFile(string path) @@ -42,11 +39,11 @@ public string DecryptFile(string path) PrepareEnvironment(psi); - using (var gpg = _processManager.CreateProcess(psi)) + using (var gpg = _processManager.CreateProcess(psi, Trace2ProcessClass.Other)) { - if (!gpg.Start(Trace2ProcessClass.Other)) + if (!gpg.Start()) { - throw new Trace2Exception(_trace2, "Failed to start gpg."); + throw new Exception("Failed to start gpg."); } gpg.WaitForExit(); @@ -57,7 +54,7 @@ public string DecryptFile(string path) string stderr = gpg.StandardError.ReadToEnd(); var format = "Failed to decrypt file '{0}' with gpg. exit={1}, out={2}, err={3}"; var message = string.Format(format, path, gpg.ExitCode, stdout, stderr); - throw new Trace2Exception(_trace2, message, format); + throw new Exception(message); } return gpg.StandardOutput.ReadToEnd(); @@ -76,11 +73,11 @@ public void EncryptFile(string path, string gpgId, string contents) PrepareEnvironment(psi); - using (var gpg = _processManager.CreateProcess(psi)) + using (var gpg = _processManager.CreateProcess(psi, Trace2ProcessClass.Other)) { - if (!gpg.Start(Trace2ProcessClass.Other)) + if (!gpg.Start()) { - throw new Trace2Exception(_trace2, "Failed to start gpg."); + throw new Exception("Failed to start gpg."); } gpg.StandardInput.Write(contents); @@ -94,7 +91,7 @@ public void EncryptFile(string path, string gpgId, string contents) string stderr = gpg.StandardError.ReadToEnd(); var format = "Failed to encrypt file '{0}' with gpg. exit={1}, out={2}, err={3}"; var message = string.Format(format, path, gpg.ExitCode, stdout, stderr); - throw new Trace2Exception(_trace2, message, format); + throw new Exception(message); } } } diff --git a/src/Core/HostProviderRegistry.cs b/src/Core/HostProviderRegistry.cs index e702b5e687..7bdc4658df 100644 --- a/src/Core/HostProviderRegistry.cs +++ b/src/Core/HostProviderRegistry.cs @@ -152,7 +152,7 @@ public async Task GetProviderAsync(GitRequest request) var uri = request.GetRemoteUri(); if (uri is null) { - throw new Trace2Exception(_context.Trace2, "Unable to detect host provider without a remote URL"); + throw new Exception("Unable to detect host provider without a remote URL"); } // We can only probe HTTP(S) URLs - for SMTP, IMAP, etc we cannot do network probing @@ -244,7 +244,7 @@ await MatchProviderAsync(HostProviderPriority.Low, canProbeUri) ?? var message = "Failed to set host provider!"; _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message); + Trace2.WriteError(message); _context.Console.WriteWarning("failed to remember result of host provider detection!"); _context.Console.WriteWarning($"try setting this manually: `git config --global {keyName} {match.Id}`"); diff --git a/src/Core/HttpClientFactory.cs b/src/Core/HttpClientFactory.cs index e9b4cfdd09..81d511c873 100644 --- a/src/Core/HttpClientFactory.cs +++ b/src/Core/HttpClientFactory.cs @@ -38,11 +38,10 @@ public class HttpClientFactory : IHttpClientFactory { private readonly IFileSystem _fileSystem; private readonly ITrace _trace; - private readonly ITrace2 _trace2; private readonly ISettings _settings; private readonly IConsoleService _console; - public HttpClientFactory(IFileSystem fileSystem, ITrace trace, ITrace2 trace2, ISettings settings, IConsoleService console) + public HttpClientFactory(IFileSystem fileSystem, ITrace trace, ISettings settings, IConsoleService console) { EnsureArgument.NotNull(fileSystem, nameof(fileSystem)); EnsureArgument.NotNull(trace, nameof(trace)); @@ -51,7 +50,6 @@ public HttpClientFactory(IFileSystem fileSystem, ITrace trace, ITrace2 trace2, I _fileSystem = fileSystem; _trace = trace; - _trace2 = trace2; _settings = settings; _console = console; } @@ -114,7 +112,7 @@ public HttpClient CreateClient() { var format = "Custom certificate bundle not found at path: {0}"; var message = string.Format(format, certBundlePath); - throw new Trace2FileNotFoundException(_trace2, message, format, certBundlePath); + throw new FileNotFoundException(message, certBundlePath); } Func validationCallback = (cert, chain, errors) => @@ -209,7 +207,7 @@ public HttpClient CreateClient() var client = new HttpClient(handler); // Add default headers - client.DefaultRequestHeaders.UserAgent.ParseAdd(Constants.GetHttpUserAgent(_trace2)); + client.DefaultRequestHeaders.UserAgent.ParseAdd(Constants.GetHttpUserAgent()); client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true @@ -295,7 +293,7 @@ public bool TryCreateProxy(out IWebProxy proxy) "Failed to convert proxy bypass hosts to regular expressions; ignoring bypass list"; _trace.WriteLine(message); _trace.WriteException(ex); - _trace2.WriteError(message); + Trace2.WriteError(message); dict["bypass"] = "<< failed to convert >>"; } } diff --git a/src/Core/Interop/Windows/Native/Win32Error.cs b/src/Core/Interop/Windows/Native/Win32Error.cs index f6a170bda6..f5c4363398 100644 --- a/src/Core/Interop/Windows/Native/Win32Error.cs +++ b/src/Core/Interop/Windows/Native/Win32Error.cs @@ -97,18 +97,6 @@ public static int GetLastError(bool success) return Marshal.GetLastWin32Error(); } - /// - /// Throw an if is not true. - /// - /// The application's TRACE2 tracer. - /// Windows API return code. - /// Default error message. - /// Throw if is not true. - public static void ThrowIfError(ITrace2 trace2, bool succeeded, string defaultErrorMessage = "Unknown error.") - { - ThrowIfError(GetLastError(succeeded), defaultErrorMessage, trace2); - } - /// /// Throw an if is not true. /// @@ -125,9 +113,8 @@ public static void ThrowIfError(bool succeeded, string defaultErrorMessage = "Un /// /// Windows API error code. /// Default error message. - /// The application's TRACE2 tracer. /// Throw if is not . - public static void ThrowIfError(int error, string defaultErrorMessage = "Unknown error.", ITrace2 trace2 = null) + public static void ThrowIfError(int error, string defaultErrorMessage = "Unknown error.") { switch (error) { @@ -136,8 +123,6 @@ public static void ThrowIfError(int error, string defaultErrorMessage = "Unknown default: // The Win32Exception constructor will automatically get the human-readable // message for the error code. - if (trace2 != null) - throw new Trace2InteropException(trace2, defaultErrorMessage, new Win32Exception(error)); throw new InteropException(defaultErrorMessage, new Win32Exception(error)); } } diff --git a/src/Core/Interop/Windows/WindowsProcessManager.cs b/src/Core/Interop/Windows/WindowsProcessManager.cs index 0192274110..0b87fb2d80 100644 --- a/src/Core/Interop/Windows/WindowsProcessManager.cs +++ b/src/Core/Interop/Windows/WindowsProcessManager.cs @@ -5,20 +5,21 @@ namespace GitCredentialManager.Interop.Windows; [SupportedOSPlatform("windows")] public class WindowsProcessManager : ProcessManager { - public WindowsProcessManager(ITrace2 trace2) : base(trace2) + public WindowsProcessManager() { PlatformUtils.EnsureWindows(); } - public override ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory) + public override ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory, + Trace2ProcessClass @class = Trace2ProcessClass.None) { // If we're asked to start a WSL executable we must launch via the wsl.exe command tool if (!useShellExecute && WslUtils.IsWslPath(path)) { string wslPath = WslUtils.ConvertToDistroPath(path, out string distro); - return WslUtils.CreateWslProcess(distro, $"{wslPath} {args}", Trace2, workingDirectory); + return WslUtils.CreateWslProcess(distro, $"{wslPath} {args}", workingDirectory, @class); } - return base.CreateProcess(path, args, useShellExecute, workingDirectory); + return base.CreateProcess(path, args, useShellExecute, workingDirectory, @class); } } diff --git a/src/Core/PlatformUtils.cs b/src/Core/PlatformUtils.cs index 4081b15f8c..cba2c4e3c3 100644 --- a/src/Core/PlatformUtils.cs +++ b/src/Core/PlatformUtils.cs @@ -14,10 +14,10 @@ public static class PlatformUtils /// Get information about the current platform (OS and CLR details). /// /// Platform information. - public static PlatformInformation GetPlatformInformation(ITrace2 trace2) + public static PlatformInformation GetPlatformInformation() { string osType = GetOSType(); - string osVersion = GetOSVersion(trace2); + string osVersion = GetOSVersion(); string cpuArch = GetCpuArchitecture(); string clrVersion = RuntimeInformation.FrameworkDescription; @@ -353,7 +353,7 @@ private static string GetOSType() private static string _linuxDistroVersion; - private static string GetOSVersion(ITrace2 trace2) + private static string GetOSVersion() { // // Since .NET 5 we can use Environment.OSVersion because it was updated to @@ -429,9 +429,9 @@ string GetLinuxDistroVersion() RedirectStandardOutput = true }; - using (var uname = new ChildProcess(trace2, psi)) + using (var uname = new ChildProcess(psi, Trace2ProcessClass.Other)) { - uname.Start(Trace2ProcessClass.Other); + uname.Start(); uname.Process.WaitForExit(); if (uname.ExitCode == 0) diff --git a/src/Core/ProcessManager.cs b/src/Core/ProcessManager.cs index 4c5988c4df..c78a10fdc2 100644 --- a/src/Core/ProcessManager.cs +++ b/src/Core/ProcessManager.cs @@ -14,35 +14,25 @@ public interface IProcessManager /// True to resolve using the OS shell, false to use as an absolute file path. /// /// Working directory for the new process. + /// TRACE2 process class. /// object ready to start. - ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory); + ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory, + Trace2ProcessClass @class = Trace2ProcessClass.None); /// /// Create a process ready to start. /// /// Process start info. + /// TRACE2 process class. /// object ready to start. - ChildProcess CreateProcess(ProcessStartInfo psi); + ChildProcess CreateProcess(ProcessStartInfo psi, Trace2ProcessClass @class = Trace2ProcessClass.None); } public class ProcessManager : IProcessManager { - private const string SidEnvar = "GIT_TRACE2_PARENT_SID"; - - protected readonly ITrace2 Trace2; - - public static string Sid { get; internal set; } - - public static int Depth { get; internal set; } - - public ProcessManager(ITrace2 trace2) - { - EnsureArgument.NotNull(trace2, nameof(trace2)); - - Trace2 = trace2; - } - - public virtual ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory) + public virtual ChildProcess CreateProcess( + string path, string args, bool useShellExecute, string workingDirectory, + Trace2ProcessClass @class = Trace2ProcessClass.None) { var psi = new ProcessStartInfo(path, args) { @@ -53,54 +43,12 @@ public virtual ChildProcess CreateProcess(string path, string args, bool useShel WorkingDirectory = workingDirectory ?? string.Empty }; - return CreateProcess(psi); - } - - public virtual ChildProcess CreateProcess(ProcessStartInfo psi) - { - return new ChildProcess(Trace2, psi); + return CreateProcess(psi, @class); } - /// - /// Create a TRACE2 "session id" (sid) for this process. - /// - public static void CreateSid() + public virtual ChildProcess CreateProcess( + ProcessStartInfo psi, Trace2ProcessClass @class = Trace2ProcessClass.None) { - Sid = Environment.GetEnvironmentVariable(SidEnvar); - - if (!string.IsNullOrEmpty(Sid)) - { - // Use trim to ensure no accidental leading or trailing slashes - Sid = $"{Sid.Trim('/')}/{Guid.NewGuid():D}"; - // Only check for process depth if there is a parent. - // If there is not a parent, depth defaults to 0. - Depth = GetProcessDepth(); - } - else - { - // We are the root process; create our own 'root' SID - Sid = Guid.NewGuid().ToString("D"); - } - - Environment.SetEnvironmentVariable(SidEnvar, Sid); - } - - /// - /// Get "depth" of current process relative to top-level GCM process. - /// - /// Depth of current process. - internal static int GetProcessDepth() - { - char processSeparator = '/'; - - int count = 0; - // Use AsSpan() for slight performance bump over traditional foreach loop. - foreach (var c in Sid.AsSpan()) - { - if (c == processSeparator) - count++; - } - - return count; + return new ChildProcess(psi, @class); } } diff --git a/src/Core/Settings.cs b/src/Core/Settings.cs index 15d22bb4a9..ae787ef191 100644 --- a/src/Core/Settings.cs +++ b/src/Core/Settings.cs @@ -198,11 +198,6 @@ public interface ISettings : IDisposable /// bool AllowUnsafeRemotes { get; } - /// - /// Get TRACE2 settings. - /// - /// TRACE2 settings object. - Trace2Settings GetTrace2Settings(); } public class ProxyConfiguration @@ -595,31 +590,6 @@ public bool UseSoftwareRendering KnownGitCfg.Credential.AllowUnsafeRemotes, out string str) && str.ToBooleanyOrDefault(false); - public Trace2Settings GetTrace2Settings() - { - var settings = new Trace2Settings(); - - if (TryGetSetting(Constants.EnvironmentVariables.GitTrace2Event, KnownGitCfg.Trace2.SectionName, - Constants.GitConfiguration.Trace2.EventTarget, out string value)) - { - settings.FormatTargetsAndValues.Add(Trace2FormatTarget.Event, value); - } - - if (TryGetSetting(Constants.EnvironmentVariables.GitTrace2Normal, KnownGitCfg.Trace2.SectionName, - Constants.GitConfiguration.Trace2.NormalTarget, out value)) - { - settings.FormatTargetsAndValues.Add(Trace2FormatTarget.Normal, value); - } - - if (TryGetSetting(Constants.EnvironmentVariables.GitTrace2Performance, KnownGitCfg.Trace2.SectionName, - Constants.GitConfiguration.Trace2.PerformanceTarget, out value)) - { - settings.FormatTargetsAndValues.Add(Trace2FormatTarget.Performance, value); - } - - return settings; - } - public bool IsSecretTracingEnabled => TryGetSetting(KnownEnvars.GcmTraceSecrets, KnownGitCfg.Credential.SectionName, diff --git a/src/Core/Trace2.cs b/src/Core/Trace2.cs deleted file mode 100644 index ebce213da2..0000000000 --- a/src/Core/Trace2.cs +++ /dev/null @@ -1,676 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Pipes; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading; - -namespace GitCredentialManager; - -/// -/// The different event types tracked in the TRACE2 tracing -/// system. -/// -public enum Trace2Event -{ - [JsonStringEnumMemberName("version")] - Version = 0, - [JsonStringEnumMemberName("start")] - Start = 1, - [JsonStringEnumMemberName("exit")] - Exit = 2, - [JsonStringEnumMemberName("child_start")] - ChildStart = 3, - [JsonStringEnumMemberName("child_exit")] - ChildExit = 4, - [JsonStringEnumMemberName("error")] - Error = 5, - [JsonStringEnumMemberName("region_enter")] - RegionEnter = 6, - [JsonStringEnumMemberName("region_leave")] - RegionLeave = 7, -} - -/// -/// Classifications of processes invoked by GCM. -/// -public enum Trace2ProcessClass -{ - [JsonStringEnumMemberName("none")] - None = 0, - [JsonStringEnumMemberName("ui_helper")] - UIHelper = 1, - [JsonStringEnumMemberName("git")] - Git = 2, - [JsonStringEnumMemberName("other")] - Other = 3 -} - -/// -/// Stores various TRACE2 format targets user has enabled. -/// Check for supported formats. -/// -public class Trace2Settings -{ - public IDictionary FormatTargetsAndValues { get; set; } = - new Dictionary(); -} - -/// -/// Specifies a "text span" (i.e. space between two pipes) for the performance format target. -/// -public class PerformanceFormatSpan -{ - public int Size { get; set; } - - public int BeginPadding { get; set; } - - public int EndPadding { get; set; } -} - -/// -/// Class that manages regions. -/// -public class Region : DisposableObject -{ - private readonly ITrace2 _trace2; - private readonly string _category; - private readonly string _label; - private readonly string _filePath; - private readonly int _lineNumber; - private readonly string _message; - private readonly DateTimeOffset _startTime; - - public Region(ITrace2 trace2, string category, string label, string filePath, int lineNumber, string message = "") - { - _trace2 = trace2; - _category = category; - _label = label; - _filePath = filePath; - _lineNumber = lineNumber; - _message = message; - - _startTime = DateTimeOffset.UtcNow; - - _trace2.WriteRegionEnter(_category, _label, _message, _filePath, _lineNumber); - } - - protected override void ReleaseManagedResources() - { - double relativeTime = (DateTimeOffset.UtcNow - _startTime).TotalSeconds; - _trace2.WriteRegionLeave(relativeTime, _category, _label, _message, _filePath, _lineNumber); - } -} - -/// -/// Represents the application's TRACE2 tracing system. -/// -public interface ITrace2 : IDisposable -{ - /// - /// Initialize TRACE2 tracing by initializing multi-use fields and setting up any configured target formats. - /// - /// Approximate time calling application began executing. - void Initialize(DateTimeOffset startTime); - - /// - /// Write Version and Start events. - /// - /// The path to the application. - /// Args passed to the application (if applicable). - /// Path of the file this method is called from. - /// Line number of file this method is called from. - void Start(string appPath, - string[] args, - [System.Runtime.CompilerServices.CallerFilePath] string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0); - - /// - /// Write Exit event and dispose of writers. - /// - /// The exit code of the GCM application. - /// Path of the file this method is called from. - /// Line number of file this method is called from. - void Stop(int exitCode, - [System.Runtime.CompilerServices.CallerFilePath] string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0); - - /// - /// Writes information related to startup of child process to trace writer. - /// - /// Time at which child process began executing. - /// Process classification. - /// Specifies whether or not OS shell was used to start the process. - /// Name of application running in child process. - /// Arguments specific to the child process. - /// The child process's session id. - /// Path of the file this method is called from. - /// Line number of file this method is called from. - void WriteChildStart(DateTimeOffset startTime, - Trace2ProcessClass processClass, - bool useShell, - string appName, - string argv, - [System.Runtime.CompilerServices.CallerFilePath] - string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] - int lineNumber = 0); - - /// - /// Writes information related to exit of child process to trace writer. - /// - /// Runtime of child process. - /// Id of exiting process. - /// Process exit code. - /// Path of the file this method is called from. - /// Line number of file this method is called from. - void WriteChildExit( - double relativeTime, - int pid, - int code, - [System.Runtime.CompilerServices.CallerFilePath] - string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] - int lineNumber = 0); - - /// - /// Writes an error as a message to the trace writer. - /// - /// The error message to write. - /// The error format string. - /// Path of the file this method is called from. - /// Line number of file this method is called from. - void WriteError( - string errorMessage, - string parameterizedMessage = null, - [System.Runtime.CompilerServices.CallerFilePath] - string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] - int lineNumber = 0); - - /// - /// Creates a region and manages entry/leaving. - /// - /// Category of region. - /// Description of region. - /// Message associated with entering region. - /// Path of the file this method is called from. - /// Line number of file this method is called from. - Region CreateRegion( - string category, - string label, - string message = "", - [System.Runtime.CompilerServices.CallerFilePath] - string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] - int lineNumber = 0); - - /// - /// Writes a region enter message to the trace writer. - /// - /// Category of region. - /// Description of region. - /// Message associated with entering region. - /// Path of the file this method is called from. - /// Line number of file this method is called from. - void WriteRegionEnter( - string category, - string label, - string message = "", - [System.Runtime.CompilerServices.CallerFilePath] - string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] - int lineNumber = 0); - - /// - /// Writes a region leave message to the trace writer. - /// - /// Time of region execution. - /// Category of region. - /// Description of region. - /// Message associated with entering region. - /// Path of the file this method is called from. - /// Line number of file this method is called from. - void WriteRegionLeave( - double relativeTime, - string category, - string label, - string message = "", - [System.Runtime.CompilerServices.CallerFilePath] - string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] - int lineNumber = 0); -} - -public class Trace2 : DisposableObject, ITrace2 -{ - private readonly ICommandContext _commandContext; - private readonly object _writersLock = new object(); - private readonly Encoding _utf8NoBomEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - private readonly List _writers = new List(); - - private const string GitSidVariable = "GIT_TRACE2_PARENT_SID"; - - private DateTimeOffset _applicationStartTime; - private Trace2Settings _settings; - private string _sid; - - private bool _initialized; - // Increment with each new child process that is tracked - private int _childProcCounter = 0; - - public Trace2(ICommandContext commandContext) - { - _commandContext = commandContext; - } - - public void Initialize(DateTimeOffset startTime) - { - if (_initialized) - { - return; - } - - _applicationStartTime = startTime; - _settings = _commandContext.Settings.GetTrace2Settings(); - _sid = ProcessManager.Sid; - - InitializeWriters(); - - _initialized = true; - } - - public void Start(string appPath, - string[] args, - string filePath, - int lineNumber) - { - if (!AssemblyUtils.TryGetAssemblyVersion(out string version)) - { - // A version is required for TRACE2, so if this call fails - // manually set the version. - version = "0.0.0"; - } - WriteVersion(version, filePath, lineNumber); - WriteStart(appPath, args, filePath, lineNumber); - } - - public void Stop(int exitCode, string filePath, int lineNumber) - { - WriteExit(exitCode, filePath, lineNumber); - } - - public void WriteChildStart(DateTimeOffset startTime, - Trace2ProcessClass processClass, - bool useShell, - string appName, - string argv, - string filePath = "", - int lineNumber = 0) - { - // Some child processes are started before TRACE2 can be initialized. - // Since certain dependencies are not available until initialization, - // we must immediately return if this method is invoked prior to - // initialization. - if (!_initialized) - { - return; - } - - // Always add name of the application the process is executing - var procArgs = new List() - { - Path.GetFileName(appName) - }; - - // If the process has arguments, append them. - if (!string.IsNullOrEmpty(argv)) - { - procArgs.AddRange(argv.Split(' ')); - } - - WriteMessage(new ChildStartMessage() - { - Event = Trace2Event.ChildStart, - Sid = _sid, - Time = startTime, - Thread = BuildThreadName(), - File = Path.GetFileName(filePath), - Line = lineNumber, - Id = ++_childProcCounter, - Classification = processClass, - UseShell = useShell, - Argv = procArgs, - ElapsedTime = (DateTimeOffset.UtcNow - _applicationStartTime).TotalSeconds, - Depth = ProcessManager.Depth, - }); - } - - public void WriteChildExit( - double relativeTime, - int pid, - int code, - string filePath = "", - int lineNumber = 0) - { - // Some child processes are started before TRACE2 can be initialized. - // Since certain dependencies are not available until initialization, - // we must immediately return if this method is invoked prior to - // initialization. - if (!_initialized) - { - return; - } - - WriteMessage(new ChildExitMessage() - { - Event = Trace2Event.ChildExit, - Sid = _sid, - Time = DateTimeOffset.UtcNow, - Thread = BuildThreadName(), - File = Path.GetFileName(filePath), - Line = lineNumber, - Id = _childProcCounter, - Pid = pid, - Code = code, - ElapsedTime = (DateTimeOffset.UtcNow - _applicationStartTime).TotalSeconds, - RelativeTime = relativeTime, - Depth = ProcessManager.Depth - }); - } - - public void WriteError( - string errorMessage, - string parameterizedMessage = null, - [System.Runtime.CompilerServices.CallerFilePath] string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0) - { - // It is possible for an error to be thrown before TRACE2 can be initialized. - // Since certain dependencies are not available until initialization, - // we must immediately return if this method is invoked prior to - // initialization. - if (!_initialized) - { - return; - } - - WriteMessage(new ErrorMessage() - { - Event = Trace2Event.Error, - Sid = _sid, - Time = DateTimeOffset.UtcNow, - Thread = BuildThreadName(), - File = Path.GetFileName(filePath), - Line = lineNumber, - Message = errorMessage, - ParameterizedMessage = parameterizedMessage ?? errorMessage, - Depth = ProcessManager.Depth - }); - } - - public Region CreateRegion( - string category, - string label, - string message, - string filePath, - int lineNumber) - { - return new Region(this, category, label, filePath, lineNumber, message); - } - - public void WriteRegionEnter( - string category, - string label, - string message = "", - string filePath = "", - int lineNumber = 0) - { - WriteMessage(new RegionEnterMessage() - { - Event = Trace2Event.RegionEnter, - Sid = _sid, - Time = DateTimeOffset.UtcNow, - Category = category, - Label = label, - Message = message == "" ? label : message, - Thread = BuildThreadName(), - File = Path.GetFileName(filePath), - Line = lineNumber, - ElapsedTime = (DateTimeOffset.UtcNow - _applicationStartTime).TotalSeconds, - Depth = ProcessManager.Depth - }); - } - - public void WriteRegionLeave( - double relativeTime, - string category, - string label, - string message = "", - string filePath = "", - int lineNumber = 0) - { - WriteMessage(new RegionLeaveMessage() - { - Event = Trace2Event.RegionLeave, - Sid = _sid, - Time = DateTimeOffset.UtcNow, - Category = category, - Label = label, - Message = message == "" ? label : message, - Thread = BuildThreadName(), - File = Path.GetFileName(filePath), - Line = lineNumber, - ElapsedTime = (DateTimeOffset.UtcNow - _applicationStartTime).TotalSeconds, - RelativeTime = relativeTime, - Depth = ProcessManager.Depth - }); - } - - protected override void ReleaseManagedResources() - { - lock (_writersLock) - { - try - { - for (int i = _writers.Count - 1; i >= 0; i--) - { - using (_writers[i]) - { - _writers.RemoveAt(i); - } - } - } - catch - { - /* squelch */ - } - } - - base.ReleaseManagedResources(); - } - - internal static bool TryGetPipeName(string eventTarget, out string name) - { - // Use prefixes to determine whether target is a named pipe/socket - if (eventTarget.StartsWith("af_unix:", StringComparison.OrdinalIgnoreCase) || - eventTarget.StartsWith(@"\\.\pipe\", StringComparison.OrdinalIgnoreCase) || - eventTarget.StartsWith("//./pipe/", StringComparison.OrdinalIgnoreCase)) - { - name = PlatformUtils.IsWindows() - ? eventTarget.Replace('/', '\\') - .TrimUntilIndexOf(@"\\.\pipe\") - : eventTarget.Replace("af_unix:dgram:", "") - .Replace("af_unix:stream:", "") - .Replace("af_unix:", ""); - return true; - } - - name = ""; - return false; - } - - private void InitializeWriters() - { - // Set up the correct writer for every enabled format target. - foreach (var formatTarget in _settings.FormatTargetsAndValues) - { - if (TryGetPipeName(formatTarget.Value, out string name)) // Write to named pipe/socket - { - AddWriter(new Trace2CollectorWriter(formatTarget.Key, ( - () => new NamedPipeClientStream(".", name, - PipeDirection.Out, - PipeOptions.Asynchronous) - ) - )); - } - else if (formatTarget.Value.IsTruthy()) // Write to stderr - { - AddWriter(new Trace2StreamWriter(formatTarget.Key, _commandContext.Streams.Error)); - } - else if (Path.IsPathRooted(formatTarget.Value)) // Write to file - { - try - { - AddWriter(new Trace2FileWriter(formatTarget.Key, formatTarget.Value)); - } - catch (Exception ex) - { - Console.Error.WriteLine($"warning: unable to trace to file '{formatTarget.Value}': {ex.Message}"); - } - } - } - } - - private void WriteVersion( - string gcmVersion, - string filePath, - int lineNumber, - string eventFormatVersion = "3") - { - EnsureArgument.NotNull(gcmVersion, nameof(gcmVersion)); - - WriteMessage(new VersionMessage() - { - Event = Trace2Event.Version, - Sid = _sid, - Time = DateTimeOffset.UtcNow, - Thread = BuildThreadName(), - File = Path.GetFileName(filePath), - Line = lineNumber, - Evt = eventFormatVersion, - Exe = gcmVersion - }); - } - - private void WriteStart( - string appPath, - string[] args, - string filePath, - int lineNumber) - { - // Prepend GCM exe to arguments - var argv = new List() - { - Path.GetFileName(appPath), - }; - - if (args.Length > 0) - { - argv.AddRange(args); - } - - WriteMessage(new StartMessage() - { - Event = Trace2Event.Start, - Sid = _sid, - Time = DateTimeOffset.UtcNow, - Thread = BuildThreadName(), - File = Path.GetFileName(filePath), - Line = lineNumber, - Argv = argv, - ElapsedTime = (DateTimeOffset.UtcNow - _applicationStartTime).TotalSeconds - }); - } - - private void WriteExit(int code, string filePath = "", int lineNumber = 0) - { - EnsureArgument.NotNull(code, nameof(code)); - - WriteMessage(new ExitMessage() - { - Event = Trace2Event.Exit, - Sid = _sid, - Time = DateTimeOffset.UtcNow, - Thread = BuildThreadName(), - File = Path.GetFileName(filePath), - Line = lineNumber, - Code = code, - ElapsedTime = (DateTimeOffset.UtcNow - _applicationStartTime).TotalSeconds - }); - } - - private void AddWriter(ITrace2Writer writer) - { - ThrowIfDisposed(); - - lock (_writersLock) - { - // Try not to add the same writer more than once - if (_writers.Contains(writer)) - return; - - _writers.Add(writer); - } - } - - private void WriteMessage(Trace2Message message) - { - ThrowIfDisposed(); - - if (!_initialized) - { - return; - } - - lock (_writersLock) - { - if (_writers.Count == 0) - { - return; - } - - foreach (var writer in _writers) - { - if (!writer.Failed) - { - writer.Write(message); - } - } - } - } - - private static string BuildThreadName() - { - // If this is the entry thread, call it "main", per Trace2 convention - if (Thread.CurrentThread.ManagedThreadId == 1) - { - return "main"; - } - - // If this is a thread pool thread, name it as such - if (Thread.CurrentThread.IsThreadPoolThread) - { - return $"thread_pool_{Environment.CurrentManagedThreadId}"; - } - - // Otherwise, if the thread is named, use it! - if (!string.IsNullOrEmpty(Thread.CurrentThread.Name)) - { - return Thread.CurrentThread.Name; - } - - // We don't know what this thread is! - return string.Empty; - } -} diff --git a/src/Core/Trace2Exception.cs b/src/Core/Trace2Exception.cs deleted file mode 100644 index 292ec15161..0000000000 --- a/src/Core/Trace2Exception.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.ComponentModel; -using System.IO; -using GitCredentialManager.Authentication.OAuth; -using GitCredentialManager.Interop; - -namespace GitCredentialManager; - -public class Trace2Exception : Exception -{ - public Trace2Exception(ITrace2 trace2, string message) : base(message) - { - trace2.WriteError(message); - } - - public Trace2Exception(ITrace2 trace2, string message, string messageFormat) : base(message) - { - trace2.WriteError(message, messageFormat); - } -} - -public class Trace2InvalidOperationException : InvalidOperationException -{ - public Trace2InvalidOperationException(ITrace2 trace2, string message) : base(message) - { - trace2.WriteError(message); - } -} - -public class Trace2OAuth2Exception : OAuth2Exception -{ - public Trace2OAuth2Exception(ITrace2 trace2, string message) : base(message) - { - trace2.WriteError(message); - } - - public Trace2OAuth2Exception(ITrace2 trace2, string message, string messageFormat) : base(message) - { - trace2.WriteError(message, messageFormat); - } -} - -public class Trace2InteropException : InteropException -{ - public Trace2InteropException(ITrace2 trace2, string message, int errorCode) : base(message, errorCode) - { - trace2.WriteError($"message: {message} error code: {errorCode}"); - } - - public Trace2InteropException(ITrace2 trace2, string message, Win32Exception ex) : base(message, ex) - { - trace2.WriteError(message); - } -} - -public class Trace2GitException : GitException -{ - public Trace2GitException(ITrace2 trace2, string message, int errorCode, string gitMessage) : - base(message, gitMessage, errorCode) - { - var format = $"message: '{message}' error code: '{errorCode}' git message: '{{0}}'"; - var traceMessage = string.Format(format, gitMessage); - - trace2.WriteError(traceMessage, format); - } -} - -public class Trace2FileNotFoundException : FileNotFoundException -{ - public Trace2FileNotFoundException(ITrace2 trace2, string message, string messageFormat, string fileName) : - base(message, fileName) - { - trace2.WriteError(message, messageFormat); - } -} diff --git a/src/Core/Trace2Message.cs b/src/Core/Trace2Message.cs deleted file mode 100644 index 175fd8bf02..0000000000 --- a/src/Core/Trace2Message.cs +++ /dev/null @@ -1,539 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace GitCredentialManager; - -[JsonSerializable(typeof(VersionMessage))] -[JsonSerializable(typeof(StartMessage))] -[JsonSerializable(typeof(ExitMessage))] -[JsonSerializable(typeof(ChildStartMessage))] -[JsonSerializable(typeof(ChildExitMessage))] -[JsonSerializable(typeof(ErrorMessage))] -[JsonSerializable(typeof(RegionEnterMessage))] -[JsonSerializable(typeof(RegionLeaveMessage))] -[JsonSourceGenerationOptions( - UseStringEnumConverter = true, - PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower, - PropertyNameCaseInsensitive = true -)] -public partial class Trace2JsonContext : JsonSerializerContext; - -public abstract class Trace2Message -{ - private const int SourceColumnMaxWidth = 23; - private const string NormalPerfTimeFormat = "HH:mm:ss.ffffff"; - - protected const string EmptyPerformanceSpan = "| | | | "; - - [JsonPropertyName("event")] - [JsonPropertyOrder(1)] - public Trace2Event Event { get; set; } - - [JsonPropertyName("sid")] - [JsonPropertyOrder(2)] - public string Sid { get; set; } - - [JsonPropertyName("thread")] - [JsonPropertyOrder(3)] - public string Thread { get; set; } - - [JsonPropertyName("time")] - [JsonPropertyOrder(4)] - public DateTimeOffset Time { get; set; } - - [JsonPropertyName("file")] - [JsonPropertyOrder(5)] - public string File { get; set; } - - [JsonPropertyName("line")] - [JsonPropertyOrder(6)] - public int Line { get; set; } - - [JsonPropertyName("depth")] - [JsonPropertyOrder(7)] - public int Depth { get; set; } - - public abstract string ToJson(); - - public abstract string ToNormalString(); - - public abstract string ToPerformanceString(); - - protected abstract string BuildPerformanceSpan(); - - protected string BuildNormalString() - { - string message = GetEventMessage(Trace2FormatTarget.Normal); - - // The normal format uses local time rather than UTC time. - string time = Time.ToLocalTime().ToString(NormalPerfTimeFormat); - string source = GetSource(); - - // Git's TRACE2 normal format is: - // [