From e9a8194c2a552c49669c257f72f7b184d991e343 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 20:52:49 +0000 Subject: [PATCH 01/24] build(deps): bump actions/setup-dotnet from 4.0.0 to 4.0.1 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/continuous-integration.yml | 6 +++--- .github/workflows/release.yml | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dc252805a..745027d8b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1b83a990d..27834c10e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -100,7 +100,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2440cface..b87cbec74 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -150,7 +150,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -190,7 +190,7 @@ jobs: # The Azure Code Signing action overrides the .NET version, so we reset it. - name: Set up .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -236,7 +236,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -314,7 +314,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -387,7 +387,7 @@ jobs: path: signed - name: Set up .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -491,7 +491,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x @@ -561,7 +561,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.0.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: 8.0.x From 5ea56d6b17b704681675148faa86c6ba6459d232 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 10 Jul 2024 12:12:05 -0700 Subject: [PATCH 02/24] homebrew: remove no longer needed workflow The `git-credential-manager` cask is part of the 'autobump' for the public tap of Homebrew. We no longer need to maintain our own publishing workflow. Error message when we try to publish our own package bump: ```log Error: Whoops, the git-credential-manager cask has its version update pull requests automatically opened by BrewTestBot every ~3 hours! We'd still love your contributions, though, so try another one that's not in the autobump list: https://github.com/Homebrew/homebrew-cask/blob/master/.github/autobump.txt ``` --- .github/workflows/release-homebrew.yaml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .github/workflows/release-homebrew.yaml diff --git a/.github/workflows/release-homebrew.yaml b/.github/workflows/release-homebrew.yaml deleted file mode 100644 index a27735d36..000000000 --- a/.github/workflows/release-homebrew.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: "release-homebrew" -on: - release: - types: [released] - -jobs: - release: - runs-on: macos-latest - environment: release - env: - HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_TOKEN }} - steps: - - name: Open PR against homebrew/homebrew-cask - run: | - # Get latest version - version=$(curl --silent "https://github.com/repos/git-ecosystem/git-credential-manager/releases/latest" | - grep '"tag_name":' | - sed -E 's/.*"v([0-9\.]+).*/\1/') - - # Ensure local Homebrew repository is up to date - cd "$(brew --repository homebrew/cask)" - git pull - - # Open PR to update to latest version - brew bump-cask-pr git-credential-manager --version $version --no-audit --no-browse From 7886272abb137e46166db20999605eacc1e62861 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Thu, 18 Jul 2024 02:35:36 +0900 Subject: [PATCH 03/24] docs: update generic-oauth.md overriden -> overridden --- docs/generic-oauth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-oauth.md b/docs/generic-oauth.md index 08b735ccb..92ad6dc5c 100644 --- a/docs/generic-oauth.md +++ b/docs/generic-oauth.md @@ -101,7 +101,7 @@ In order to use special characters you need to URL encode the values; for example `@` becomes `%40`. By default GCM uses the value `OAUTH-USER` unless specified in the remote URL, -or overriden using the `credential..oauthDefaultUserName` configuration. +or overridden using the `credential..oauthDefaultUserName` configuration. #### Include client authentication in headers From 62b9c3d5f8698f4b64804524321844bd741e125c Mon Sep 17 00:00:00 2001 From: "Michael J. Lyons (XBOX)" Date: Wed, 17 Jul 2024 14:04:42 -0700 Subject: [PATCH 04/24] Add method for sending X5C --- VERSION | 2 +- .../Authentication/MicrosoftAuthentication.cs | 15 ++++++++++++++- .../Microsoft.AzureRepos/AzureDevOpsConstants.cs | 2 ++ .../AzureReposHostProvider.cs | 8 ++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 27c7c7b9d..3a6d2147d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.1.0 +2.5.1.1 diff --git a/src/shared/Core/Authentication/MicrosoftAuthentication.cs b/src/shared/Core/Authentication/MicrosoftAuthentication.cs index b39cc1a73..c64ce4b9c 100644 --- a/src/shared/Core/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Core/Authentication/MicrosoftAuthentication.cs @@ -92,6 +92,11 @@ public class ServicePrincipalIdentity /// If both and are set, the certificate will be used. /// public string ClientSecret { get; set; } + + /// + /// Whether the authentication should send X5C + /// + public bool SendX5C { get; set; } } public interface IMicrosoftAuthenticationResult @@ -269,7 +274,15 @@ public async Task GetTokenForServicePrincipalAsy try { - AuthenticationResult result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); + var tokenBuilder = app.AcquireTokenForClient(scopes); + + if (sp.SendX5C) + { + tokenBuilder = tokenBuilder.WithSendX5C(true); + } + + AuthenticationResult result = await tokenBuilder.ExecuteAsync(); + return new MsalResult(result); } catch (Exception ex) diff --git a/src/shared/Microsoft.AzureRepos/AzureDevOpsConstants.cs b/src/shared/Microsoft.AzureRepos/AzureDevOpsConstants.cs index c46f08c33..a282d4eff 100644 --- a/src/shared/Microsoft.AzureRepos/AzureDevOpsConstants.cs +++ b/src/shared/Microsoft.AzureRepos/AzureDevOpsConstants.cs @@ -44,6 +44,7 @@ public static class EnvironmentVariables public const string ServicePrincipalId = "GCM_AZREPOS_SERVICE_PRINCIPAL"; public const string ServicePrincipalSecret = "GCM_AZREPOS_SP_SECRET"; public const string ServicePrincipalCertificateThumbprint = "GCM_AZREPOS_SP_CERT_THUMBPRINT"; + public const string ServicePrincipalCertificateSendX5C = "GCM_AZREPOS_SP_CERT_SEND_X5C"; public const string ManagedIdentity = "GCM_AZREPOS_MANAGEDIDENTITY"; } @@ -59,6 +60,7 @@ public static class Credential public const string ServicePrincipal = "azreposServicePrincipal"; public const string ServicePrincipalSecret = "azreposServicePrincipalSecret"; public const string ServicePrincipalCertificateThumbprint = "azreposServicePrincipalCertificateThumbprint"; + public const string ServicePrincipalCertificateSendX5C = "azreposServicePrincipalCertificateSendX5C"; public const string ManagedIdentity = "azreposManagedIdentity"; } } diff --git a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs index 55b1449d7..cdbf16133 100644 --- a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs +++ b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs @@ -549,6 +549,14 @@ private bool UseServicePrincipal(out ServicePrincipalIdentity sp) if (hasCertThumbprint) { + bool hasX5CSetting = _context.Settings.TryGetSetting( + AzureDevOpsConstants.EnvironmentVariables.ServicePrincipalCertificateSendX5C, + Constants.GitConfiguration.Credential.SectionName, + AzureDevOpsConstants.GitConfiguration.Credential.ServicePrincipalCertificateSendX5C, + out string certHasX5C); + + sp.SendX5C = !hasX5CSetting || certHasX5C == "false"; + X509Certificate2 cert = X509Utils.GetCertificateByThumbprint(certThumbprint); if (cert is null) { From ece43792a8335393a3611d1e9ad529edd7b7501c Mon Sep 17 00:00:00 2001 From: "Michael J. Lyons (XBOX)" Date: Thu, 18 Jul 2024 06:18:56 -0700 Subject: [PATCH 05/24] Debugging updates --- .../Core/Authentication/MicrosoftAuthentication.cs | 12 +++--------- .../Microsoft.AzureRepos/AzureReposHostProvider.cs | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/shared/Core/Authentication/MicrosoftAuthentication.cs b/src/shared/Core/Authentication/MicrosoftAuthentication.cs index c64ce4b9c..12bccf5fe 100644 --- a/src/shared/Core/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Core/Authentication/MicrosoftAuthentication.cs @@ -274,20 +274,14 @@ public async Task GetTokenForServicePrincipalAsy try { - var tokenBuilder = app.AcquireTokenForClient(scopes); - - if (sp.SendX5C) - { - tokenBuilder = tokenBuilder.WithSendX5C(true); - } - - AuthenticationResult result = await tokenBuilder.ExecuteAsync(); + Context.Trace.WriteLine($"Sending with X5C: '{sp.SendX5C}'."); + AuthenticationResult result = await app.AcquireTokenForClient(scopes).WithSendX5C(sp.SendX5C).ExecuteAsync();; return new MsalResult(result); } catch (Exception ex) { - Context.Trace.WriteLine($"Failed to acquire token for service principal '{sp.TenantId}/{sp.TenantId}'."); + Context.Trace.WriteLine($"Failed to acquire token for service principal '{sp.TenantId}/{sp.Id}'."); Context.Trace.WriteException(ex); throw; } diff --git a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs index cdbf16133..9bd6c72a1 100644 --- a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs +++ b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs @@ -555,7 +555,7 @@ private bool UseServicePrincipal(out ServicePrincipalIdentity sp) AzureDevOpsConstants.GitConfiguration.Credential.ServicePrincipalCertificateSendX5C, out string certHasX5C); - sp.SendX5C = !hasX5CSetting || certHasX5C == "false"; + sp.SendX5C = !hasX5CSetting || certHasX5C != "false"; X509Certificate2 cert = X509Utils.GetCertificateByThumbprint(certThumbprint); if (cert is null) From 52c1be973c5182e336e50599ee0377143760e466 Mon Sep 17 00:00:00 2001 From: "Michael J. Lyons (XBOX)" Date: Thu, 18 Jul 2024 16:51:16 -0700 Subject: [PATCH 06/24] Revert version change --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3a6d2147d..27c7c7b9d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.1.1 +2.5.1.0 From 55d626240ff4b427a04082f4e33a5e34e0d46ed8 Mon Sep 17 00:00:00 2001 From: "Michael J. Lyons (XBOX)" Date: Thu, 18 Jul 2024 17:50:09 -0700 Subject: [PATCH 07/24] Apply code review feedback to use more appropriate helper functions in parsing a boolean config option --- src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs index 9bd6c72a1..1d5c649d0 100644 --- a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs +++ b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs @@ -549,13 +549,11 @@ private bool UseServicePrincipal(out ServicePrincipalIdentity sp) if (hasCertThumbprint) { - bool hasX5CSetting = _context.Settings.TryGetSetting( + sp.SendX5C = _context.Settings.TryGetSetting( AzureDevOpsConstants.EnvironmentVariables.ServicePrincipalCertificateSendX5C, Constants.GitConfiguration.Credential.SectionName, AzureDevOpsConstants.GitConfiguration.Credential.ServicePrincipalCertificateSendX5C, - out string certHasX5C); - - sp.SendX5C = !hasX5CSetting || certHasX5C != "false"; + out string certHasX5CStr) && certHasX5CStr.ToBooleanyOrDefault(false); X509Certificate2 cert = X509Utils.GetCertificateByThumbprint(certThumbprint); if (cert is null) From ab05752af4e3709c9059f9d00f1502ebfe9084f6 Mon Sep 17 00:00:00 2001 From: "Michael J. Lyons (XBOX)" Date: Thu, 18 Jul 2024 17:50:46 -0700 Subject: [PATCH 08/24] Update configuration and environment documentation to document the new x5c MSAL claim option --- docs/azrepos-misp.md | 3 +++ docs/configuration.md | 22 ++++++++++++++++++++++ docs/environment.md | 28 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/docs/azrepos-misp.md b/docs/azrepos-misp.md index 60a3c3e2b..9d11dbfa6 100644 --- a/docs/azrepos-misp.md +++ b/docs/azrepos-misp.md @@ -108,6 +108,7 @@ Type|Git Configuration|Environment Variable -|-|- Client Secret|[`credential.azreposServicePrincipalSecret`][gcm-sp-secret-config]|[`GCM_AZREPOS_SP_SECRET`][gcm-sp-secret-env] Certificate|[`credential.azreposServicePrincipalCertificateThumbprint`][gcm-sp-cert-config]|[`GCM_AZREPOS_SP_CERT_THUMBPRINT`][gcm-sp-cert-env] +Send X5C|[`credential.azreposServicePrincipalCertificateSendX5C`][gcm-sp-cert-x5c-config]|[`GCM_AZREPOS_SP_CERT_SEND_X5C`][gcm-sp-cert-x5c-env] The value for these options should be the client secret or the thumbrint of the certificate that is associated with the Service Principal. @@ -126,4 +127,6 @@ current user or the local machine. [gcm-sp-secret-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalsecret [gcm-sp-secret-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_SECRET [gcm-sp-cert-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalcertificatethumbprint +[gcm-sp-cert-x5c-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalcertificatesendx5c [gcm-sp-cert-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_CERT_THUMBPRINT +[gcm-sp-cert-x5c-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_CERT_SEND_X5C diff --git a/docs/configuration.md b/docs/configuration.md index 2439c9297..f6993dfd8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -858,6 +858,7 @@ You must also set at least one authentication mechanism if you set this value: - [credential.azreposServicePrincipalSecret][credential-azrepos-sp-secret] - [credential.azreposServicePrincipalCertificateThumbprint][credential-azrepos-sp-cert-thumbprint] +- [credential.azreposServicePrincipalCertificateSendX5C][credential-azrepos-sp-cert-x5c] For more information about service principals, see the Azure DevOps [documentation][azrepos-sp-mid]. @@ -904,6 +905,25 @@ git config --global credential.azreposServicePrincipalCertificateThumbprint "9b6 --- +### credential.azreposServicePrincipalCertificateSendX5C + +When using a certificate for [service principal][service-principal] authentication, this configuration +specifies whether the X5C claim should be should be sent to the STS. Sending the x5c +enables application developers to achieve easy certificate rollover in Azure AD: +this method will send the public certificate to Azure AD along with the token request, +so that Azure AD can use it to validate the subject name based on a trusted issuer +policy. This saves the application admin from the need to explicitly manage the +certificate rollover. For details see [https://aka.ms/msal-net-sni](https://aka.ms/msal-net-sni). + +#### Example + +```shell +git config --global credential.azreposServicePrincipalCertificateSendX5C true +``` +**Also see: [GCM_AZREPOS_SP_CERT_SEND_X5C][gcm-azrepos-sp-cert-x5c]** + +--- + ### trace2.normalTarget Turns on Trace2 Normal Format tracing - see [Git's Trace2 Normal Format @@ -1034,6 +1054,8 @@ Defaults to disabled. [credential-azrepos-sp]: #credentialazreposserviceprincipal [credential-azrepos-sp-secret]: #credentialazreposserviceprincipalsecret [credential-azrepos-sp-cert-thumbprint]: #credentialazreposserviceprincipalcertificatethumbprint +[credential-azrepos-sp-cert-x5c]: #credentialazreposserviceprincipalcertificatesendx5c [gcm-azrepos-service-principal]: environment.md#GCM_AZREPOS_SERVICE_PRINCIPAL [gcm-azrepos-sp-secret]: environment.md#GCM_AZREPOS_SP_SECRET [gcm-azrepos-sp-cert-thumbprint]: environment.md#GCM_AZREPOS_SP_CERT_THUMBPRINT +[gcm-azrepos-sp-cert-x5c]: environment.md#GCM_AZREPOS_SP_CERT_SEND_X5C diff --git a/docs/environment.md b/docs/environment.md index 18f3f05fe..edda0d714 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -1039,6 +1039,32 @@ export GCM_AZREPOS_SP_CERT_THUMBPRINT="9b6555292e4ea21cbc2ebd23e66e2f91ebbe92dc" --- +### GCM_AZREPOS_SP_CERT_SEND_X5C + +When using a certificate for service principal authentication, this configuration +specifies whether the X5C claim should be should be sent to the STS. Sending the x5c +enables application developers to achieve easy certificate rollover in Azure AD: +this method will send the public certificate to Azure AD along with the token request, +so that Azure AD can use it to validate the subject name based on a trusted issuer +policy. This saves the application admin from the need to explicitly manage the +certificate rollover. For details see [https://aka.ms/msal-net-sni](https://aka.ms/msal-net-sni). + +#### Windows + +```batch +SET GCM_AZREPOS_SP_CERT_SEND_X5C="true" +``` + +#### macOS/Linux + +```bash +export GCM_AZREPOS_SP_CERT_SEND_X5C="true" +``` + +**Also see: [credential.azreposServicePrincipalCertificateSendX5C][credential-azrepos-sp-cert-x5c]** + +--- + ### GIT_TRACE2 Turns on Trace2 Normal Format tracing - see [Git's Trace2 Normal Format @@ -1184,6 +1210,8 @@ Defaults to disabled. [gcm-azrepos-sp]: #gcm_azrepos_service_principal [gcm-azrepos-sp-secret]: #gcm_azrepos_sp_secret [gcm-azrepos-sp-cert-thumbprint]: #gcm_azrepos_sp_cert_thumbprint +[gcm-azrepos-sp-cert-x5c]: #gcm_azrepos_sp_cert_send_x5c [credential-azrepos-sp]: configuration.md#credentialazreposserviceprincipal [credential-azrepos-sp-secret]: configuration.md#credentialazreposserviceprincipalsecret [credential-azrepos-sp-cert-thumbprint]: configuration.md#credentialazreposserviceprincipalcertificatethumbprint +[credential-azrepos-sp-cert-x5c]: configuration.md#credentialazreposserviceprincipalcertificatesendx5c From 650f296b6196b1ecde696d19aaa9119d76cdb97e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 20:31:48 +0000 Subject: [PATCH 09/24] build(deps): bump azure/trusted-signing-action from 0.3.20 to 0.4.0 Bumps [azure/trusted-signing-action](https://github.com/azure/trusted-signing-action) from 0.3.20 to 0.4.0. - [Release notes](https://github.com/azure/trusted-signing-action/releases) - [Commits](https://github.com/azure/trusted-signing-action/compare/v0.3.20...v0.4.0) --- updated-dependencies: - dependency-name: azure/trusted-signing-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2440cface..a719a8c99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -177,7 +177,7 @@ jobs: subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Sign payload files with Azure Code Signing - uses: azure/trusted-signing-action@v0.3.20 + uses: azure/trusted-signing-action@v0.4.0 with: endpoint: https://wus2.codesigning.azure.net/ trusted-signing-account-name: git-fundamentals-signing @@ -204,7 +204,7 @@ jobs: -Destination $env:GITHUB_WORKSPACE\installers - name: Sign installers with Azure Code Signing - uses: azure/trusted-signing-action@v0.3.20 + uses: azure/trusted-signing-action@v0.4.0 with: endpoint: https://wus2.codesigning.azure.net/ trusted-signing-account-name: git-fundamentals-signing From e388474191c6e760be5f60bf4789f037938fec37 Mon Sep 17 00:00:00 2001 From: Jeff Guerra <18237845+jeffreyguerra@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:26:06 -0700 Subject: [PATCH 10/24] Update SECURITY.md Updating the security policy for this open source project. --- SECURITY.md | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 994a657e2..dd9a42d6f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,8 +1,32 @@ -# Security +Thanks for helping make GitHub safe for everyone. -If you discover a security issue in this repo, please submit it through the -[GitHub Security Bug Bounty][hackerone-github] +## Security -Thanks for helping make GitHub products safe for everyone. +GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). + +Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. + +## Reporting Security Issues + +If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please send an email to opensource-security[@]github.com. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + + * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Policy + +See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor) -[hackerone-github]: https://hackerone.com/github From 71b5d062b5130dffc5425ea400ee1e624fb4dd28 Mon Sep 17 00:00:00 2001 From: Igor Pitsyn Date: Wed, 4 Sep 2024 12:09:57 -0400 Subject: [PATCH 11/24] Make GPG Pass store path configurable by git config --- VERSION | 2 +- src/shared/Core/Constants.cs | 1 + src/shared/Core/CredentialStore.cs | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 27c7c7b9d..012037873 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.1.0 +2.5.101.0 diff --git a/src/shared/Core/Constants.cs b/src/shared/Core/Constants.cs index ac609adaa..210c991bc 100644 --- a/src/shared/Core/Constants.cs +++ b/src/shared/Core/Constants.cs @@ -162,6 +162,7 @@ public static class Credential public const string DevUseLegacyUiHelpers = "devUseLegacyUiHelpers"; public const string MsAuthUseDefaultAccount = "msauthUseDefaultAccount"; public const string GuiSoftwareRendering = "guiSoftwareRendering"; + public const string GpgPassStorePath = "gpgPassStorePath"; public const string OAuthAuthenticationModes = "oauthAuthModes"; public const string OAuthClientId = "oauthClientId"; diff --git a/src/shared/Core/CredentialStore.cs b/src/shared/Core/CredentialStore.cs index a0c1ed861..83f915d1e 100644 --- a/src/shared/Core/CredentialStore.cs +++ b/src/shared/Core/CredentialStore.cs @@ -276,7 +276,8 @@ private void ValidateGpgPass(out string storeRoot, out string execPath) // Check for a redirected pass store location if (!_context.Settings.TryGetSetting( GpgPassCredentialStore.PasswordStoreDirEnvar, - null, null, + Constants.GitConfiguration.Credential.SectionName, + Constants.GitConfiguration.Credential.GpgPassStorePath, out storeRoot)) { // Use default store root at ~/.password-store From 0b3d84627f028843db117a705889239840f23824 Mon Sep 17 00:00:00 2001 From: Igor Pitsyn Date: Mon, 9 Sep 2024 10:43:25 -0400 Subject: [PATCH 12/24] Changes according to PR review --- VERSION | 2 +- docs/configuration.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 012037873..27c7c7b9d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.101.0 +2.5.1.0 diff --git a/docs/configuration.md b/docs/configuration.md index f6993dfd8..a4fecf395 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -633,6 +633,24 @@ git config --global credential.dpapiStorePath D:\credentials --- +### credential.gpgPassStorePath + +Specify a custom directory to store GPG-encrypted [pass][pass]-compatible credential files +in when [`credential.credentialStore`][credential-credentialstore] is set to `gpg`. + +Defaults to the value `~/.password-store` or `%USERPROFILE%\.password-store`. + +#### Example + +```shell +git config --global credential.gpgPassStorePath /mnt/external-drive/.password-store +``` + +**Note:** Location of the password store used by [pass][pass] can be overridden by the +`PASSWORD_STORE_DIR` environment variable, see the [man page][pass-man] for details. + +--- + ### credential.msauthFlow Specify which authentication flow should be used when performing Microsoft @@ -1042,6 +1060,7 @@ Defaults to disabled. [provider-migrate]: migration.md#gcm_authority [cache-options]: https://git-scm.com/docs/git-credential-cache#_options [pass]: https://www.passwordstore.org/ +[pass-man]: https://git.zx2c4.com/password-store/about/ [trace2-normal-docs]: https://git-scm.com/docs/api-trace2#_the_normal_format_target [trace2-normal-env]: environment.md#GIT_TRACE2 [trace2-event-docs]: https://git-scm.com/docs/api-trace2#_the_event_format_target From 4a460b419bbc8d147b268f704d9a876708a7614c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 11:38:15 +0200 Subject: [PATCH 13/24] Fix build in Visual Studio Building in Visual Studio causes some tasks to be run concurrently that would be run in a specific order in MSBuild instead. The symptom would look like this: MSB4044: The "GenerateWindowsAppManifest" task was not given a value for the required parameter "Version". Let's help Visual Studio realize that there are certain dependencies between the `GetVersion` and the `GenerateWindowsAppManifest` task. Reported by Michael J. Lyons. Signed-off-by: Johannes Schindelin --- Directory.Build.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 72d4712e7..7ec523390 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -6,7 +6,7 @@ - + @@ -21,6 +21,7 @@ Date: Mon, 30 Sep 2024 12:24:13 +0200 Subject: [PATCH 14/24] Update the InnoSetup dependency to v6.3.1 There is actually v6.3.3 already, but it does not seem to have propagated to nuget.org yet. While at it, use a centrally-defined property instead of repeating the version number several times. Signed-off-by: Johannes Schindelin --- src/windows/Installer.Windows/Installer.Windows.csproj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/windows/Installer.Windows/Installer.Windows.csproj b/src/windows/Installer.Windows/Installer.Windows.csproj index 99253c445..bbd49a291 100644 --- a/src/windows/Installer.Windows/Installer.Windows.csproj +++ b/src/windows/Installer.Windows/Installer.Windows.csproj @@ -7,6 +7,7 @@ false false $(PlatformOutPath)Installer.Windows\bin\$(Configuration)\net472\win-x86 + 6.3.1 @@ -18,7 +19,7 @@ - + @@ -26,8 +27,8 @@ - "$(NuGetPackageRoot)Tools.InnoSetup\6.0.5\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=system "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)" - "$(NuGetPackageRoot)Tools.InnoSetup\6.0.5\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=user "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)" + "$(NuGetPackageRoot)Tools.InnoSetup\$(InnoSetupVersion)\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=system "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)" + "$(NuGetPackageRoot)Tools.InnoSetup\$(InnoSetupVersion)\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=user "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)" From c6d28d378623601fbcef9c48b707aa512dfd7777 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 12:52:07 +0200 Subject: [PATCH 15/24] installer: do require Windows 7 SP1 or later Previously, we only required Windows 7, but that is not recommended by InnoSetup. Let's do enforce at least SP1 of that Windows version, which is past its end-of-life, anyway. Signed-off-by: Johannes Schindelin --- src/windows/Installer.Windows/Setup.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/Installer.Windows/Setup.iss b/src/windows/Installer.Windows/Setup.iss index f3ce31cbb..9fc8ff674 100644 --- a/src/windows/Installer.Windows/Setup.iss +++ b/src/windows/Installer.Windows/Setup.iss @@ -73,7 +73,7 @@ OutputBaseFilename={#GcmSetupExe}-win-{#GcmArch}-{#GcmVersionSimple} DefaultDirName={autopf}\{#GcmShortName} Compression=lzma2 SolidCompression=yes -MinVersion=6.1.7600 +MinVersion=6.1sp1 DisableDirPage=yes UninstallDisplayIcon={app}\{#GcmExe} SetupIconFile={#GcmAssets}\gcmicon.ico From 5441da03bf0240ffa9304aeae1d3df9d65011f0f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 12:58:09 +0200 Subject: [PATCH 16/24] installer: avoid using the deprecated `ParseVersion()` function It has been renamed to `GetVersionComponents()` (leaving a deprecated shim in place of the original name). Signed-off-by: Johannes Schindelin --- src/windows/Installer.Windows/Setup.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/Installer.Windows/Setup.iss b/src/windows/Installer.Windows/Setup.iss index 9fc8ff674..ae7167892 100644 --- a/src/windows/Installer.Windows/Setup.iss +++ b/src/windows/Installer.Windows/Setup.iss @@ -51,7 +51,7 @@ #define VerMinor #define VerBuild #define VerRevision -#expr ParseVersion(PayloadDir + "\" + GcmExe, VerMajor, VerMinor, VerBuild, VerRevision) +#expr GetVersionComponents(PayloadDir + "\" + GcmExe, VerMajor, VerMinor, VerBuild, VerRevision) #define GcmVersionSimple str(VerMajor) + "." + str(VerMinor) + "." + str(VerBuild) #define GcmVersion str(GcmVersionSimple) + "." + str(VerRevision) From cf935a76af43d0e39ca6578d88f2bdb8bed93ec4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 12:59:21 +0200 Subject: [PATCH 17/24] installer: avoid running the `UninstallRun` entry more than once Under certain circumstances, it is possible for `[UninstallRun]` entries to be run multiple times. To avoid that, we now use a `RunOnceId`. Signed-off-by: Johannes Schindelin --- src/windows/Installer.Windows/Setup.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/Installer.Windows/Setup.iss b/src/windows/Installer.Windows/Setup.iss index ae7167892..f03d16c9b 100644 --- a/src/windows/Installer.Windows/Setup.iss +++ b/src/windows/Installer.Windows/Setup.iss @@ -100,7 +100,7 @@ Name: full; Description: "Full installation"; Flags: iscustom; Filename: "{app}\{#GcmExe}"; Parameters: "configure {#GcmConfigureCmdArgs}"; Flags: runhidden [UninstallRun] -Filename: "{app}\{#GcmExe}"; Parameters: "unconfigure {#GcmConfigureCmdArgs}"; Flags: runhidden +Filename: "{app}\{#GcmExe}"; Parameters: "unconfigure {#GcmConfigureCmdArgs}"; Flags: runhidden; RunOnceId: "unconfigure" [Files] Source: "{#PayloadDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs From a749c9217aa977efb49f2ba351e62254c1225076 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 11:46:16 +0200 Subject: [PATCH 18/24] Update the remaining dependencies Visual Studio pointed out that a couple of dependencies were in need of being updated. After already upgrading InnoSetup over the preceding commits, this here commit does _almost_ what Visual Studio suggested. The only exception is that we continue to define the `System.Text.Json` version centrally, in `Directory.Build.props`, which Visual Studio did not know how to update (and therefore wanted to add the dependency individually to seven `.csproj` files instead). Signed-off-by: Johannes Schindelin --- Directory.Build.props | 2 +- .../Atlassian.Bitbucket.Tests.csproj | 11 +++++++---- src/shared/Core.Tests/Core.Tests.csproj | 11 +++++++---- src/shared/Core/Core.csproj | 18 +++++++++--------- src/shared/GitHub.Tests/GitHub.Tests.csproj | 11 +++++++---- src/shared/GitLab.Tests/GitLab.Tests.csproj | 11 +++++++---- .../Microsoft.AzureRepos.Tests.csproj | 11 +++++++---- .../TestInfrastructure.csproj | 6 +++--- 8 files changed, 48 insertions(+), 33 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8c94238ca..5c0d87bdb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,7 +28,7 @@ - 7.0.2 + 8.0.4 diff --git a/src/shared/Atlassian.Bitbucket.Tests/Atlassian.Bitbucket.Tests.csproj b/src/shared/Atlassian.Bitbucket.Tests/Atlassian.Bitbucket.Tests.csproj index d5d08797c..9e768b91c 100644 --- a/src/shared/Atlassian.Bitbucket.Tests/Atlassian.Bitbucket.Tests.csproj +++ b/src/shared/Atlassian.Bitbucket.Tests/Atlassian.Bitbucket.Tests.csproj @@ -8,13 +8,16 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/shared/Core.Tests/Core.Tests.csproj b/src/shared/Core.Tests/Core.Tests.csproj index db045a83b..e3ae7e6b0 100644 --- a/src/shared/Core.Tests/Core.Tests.csproj +++ b/src/shared/Core.Tests/Core.Tests.csproj @@ -9,13 +9,16 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/shared/Core/Core.csproj b/src/shared/Core/Core.csproj index fb3a189d3..f2804177b 100644 --- a/src/shared/Core/Core.csproj +++ b/src/shared/Core/Core.csproj @@ -13,25 +13,25 @@ - - + + - + - - + + - - - + + + - + diff --git a/src/shared/GitHub.Tests/GitHub.Tests.csproj b/src/shared/GitHub.Tests/GitHub.Tests.csproj index 1b892075e..0574e00d1 100644 --- a/src/shared/GitHub.Tests/GitHub.Tests.csproj +++ b/src/shared/GitHub.Tests/GitHub.Tests.csproj @@ -8,13 +8,16 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/shared/GitLab.Tests/GitLab.Tests.csproj b/src/shared/GitLab.Tests/GitLab.Tests.csproj index 253c36db4..098878aec 100644 --- a/src/shared/GitLab.Tests/GitLab.Tests.csproj +++ b/src/shared/GitLab.Tests/GitLab.Tests.csproj @@ -8,13 +8,16 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/shared/Microsoft.AzureRepos.Tests/Microsoft.AzureRepos.Tests.csproj b/src/shared/Microsoft.AzureRepos.Tests/Microsoft.AzureRepos.Tests.csproj index 01ec9d411..1c673bcc9 100644 --- a/src/shared/Microsoft.AzureRepos.Tests/Microsoft.AzureRepos.Tests.csproj +++ b/src/shared/Microsoft.AzureRepos.Tests/Microsoft.AzureRepos.Tests.csproj @@ -8,13 +8,16 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/shared/TestInfrastructure/TestInfrastructure.csproj b/src/shared/TestInfrastructure/TestInfrastructure.csproj index 569b64cd5..63f6fee89 100644 --- a/src/shared/TestInfrastructure/TestInfrastructure.csproj +++ b/src/shared/TestInfrastructure/TestInfrastructure.csproj @@ -9,9 +9,9 @@ - - - + + + From 6f13e76a2730162dbb2cb33502f979f02aa75e25 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 11:50:21 +0200 Subject: [PATCH 19/24] Properly await `Assert.ThrowsAsync()` calls Pointed out by Visual Studio. Signed-off-by: Johannes Schindelin --- .../Core.Tests/Authentication/BasicAuthenticationTests.cs | 8 ++++---- src/shared/Core.Tests/HostProviderRegistryTests.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs b/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs index 446d5c9bf..18f477382 100644 --- a/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs +++ b/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs @@ -12,12 +12,12 @@ namespace GitCredentialManager.Tests.Authentication public class BasicAuthenticationTests { [Fact] - public void BasicAuthentication_GetCredentials_NullResource_ThrowsException() + public async Task BasicAuthentication_GetCredentials_NullResource_ThrowsException() { var context = new TestCommandContext(); var basicAuth = new BasicAuthentication(context); - Assert.ThrowsAsync(() => basicAuth.GetCredentialsAsync(null)); + await Assert.ThrowsAsync(() => basicAuth.GetCredentialsAsync(null)); } [Fact] @@ -58,7 +58,7 @@ public async Task BasicAuthentication_GetCredentials_NonDesktopSession_Resource_ } [Fact] - public void BasicAuthentication_GetCredentials_NonDesktopSession_NoTerminalPrompts_ThrowsException() + public async Task BasicAuthentication_GetCredentials_NonDesktopSession_NoTerminalPrompts_ThrowsException() { const string testResource = "https://example.com"; @@ -70,7 +70,7 @@ public void BasicAuthentication_GetCredentials_NonDesktopSession_NoTerminalPromp var basicAuth = new BasicAuthentication(context); - Assert.ThrowsAsync(() => basicAuth.GetCredentialsAsync(testResource)); + await Assert.ThrowsAsync(() => basicAuth.GetCredentialsAsync(testResource)); } [Fact] diff --git a/src/shared/Core.Tests/HostProviderRegistryTests.cs b/src/shared/Core.Tests/HostProviderRegistryTests.cs index 8bae48d33..c8345a5ca 100644 --- a/src/shared/Core.Tests/HostProviderRegistryTests.cs +++ b/src/shared/Core.Tests/HostProviderRegistryTests.cs @@ -35,13 +35,13 @@ public void HostProviderRegistry_Register_AutoAuthorityId_ThrowException() } [Fact] - public void HostProviderRegistry_GetProvider_NoProviders_ThrowException() + public async Task HostProviderRegistry_GetProvider_NoProviders_ThrowException() { var context = new TestCommandContext(); var registry = new HostProviderRegistry(context); var input = new InputArguments(new Dictionary()); - Assert.ThrowsAsync(() => registry.GetProviderAsync(input)); + await Assert.ThrowsAsync(() => registry.GetProviderAsync(input)); } [Fact] From da12fc9ce77b6e66abf7160077c30700fab36a71 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 11:53:56 +0200 Subject: [PATCH 20/24] Use `Assert.Fail(message)` as appropriate Visual Studio pointed out that this coding pattern is preferred to `Assert.True(false, message)`. Signed-off-by: Johannes Schindelin --- src/shared/Core.Tests/HostProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/Core.Tests/HostProviderTests.cs b/src/shared/Core.Tests/HostProviderTests.cs index 64dd444d9..60d0cfba8 100644 --- a/src/shared/Core.Tests/HostProviderTests.cs +++ b/src/shared/Core.Tests/HostProviderTests.cs @@ -30,7 +30,7 @@ public async Task HostProvider_GetCredentialAsync_CredentialExists_ReturnsExisti IsSupportedFunc = _ => true, GenerateCredentialFunc = _ => { - Assert.True(false, "Should never be called"); + Assert.Fail("Should never be called"); return null; }, }; From 48d06a759aa8fe77c5f51ba10af06cbd01c7c830 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 11:51:01 +0200 Subject: [PATCH 21/24] Use preferred `Assert.*` patterns Visual Studio pointed out a couple of instances where `Assert.Equal(0, X.Count)` was used instead of `Assert.Empty(X)`, and similarly `Assert.Equal(1, X.Count)` instead of `Assert.Single(X)`. Let's accept the suggested fixes and thereby address the last remaining warnings when building in Visual Studio. Signed-off-by: Johannes Schindelin --- src/shared/Core.Tests/ApplicationTests.cs | 2 +- src/shared/Core.Tests/CurlCookieTests.cs | 2 +- src/shared/Core.Tests/HostProviderRegistryTests.cs | 4 ++-- src/shared/Core.Tests/IniFileTests.cs | 6 +++--- src/shared/Core.Tests/StreamExtensionsTests.cs | 10 +++++----- .../Objects/TestHttpMessageHandler.cs | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/shared/Core.Tests/ApplicationTests.cs b/src/shared/Core.Tests/ApplicationTests.cs index d62782f10..f983e8d54 100644 --- a/src/shared/Core.Tests/ApplicationTests.cs +++ b/src/shared/Core.Tests/ApplicationTests.cs @@ -244,7 +244,7 @@ public async Task Application_UnconfigureAsync_EmptyAndGcmWithOthersBefore_Remov Assert.Single(context.Git.Configuration.Global); Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues)); - Assert.Equal(1, actualValues.Count); + Assert.Single(actualValues); Assert.Equal(beforeHelper, actualValues[0]); } diff --git a/src/shared/Core.Tests/CurlCookieTests.cs b/src/shared/Core.Tests/CurlCookieTests.cs index 811bdd2c2..3d161d76d 100644 --- a/src/shared/Core.Tests/CurlCookieTests.cs +++ b/src/shared/Core.Tests/CurlCookieTests.cs @@ -37,7 +37,7 @@ public void CurlCookieParser_Parse_MissingFields_SkipsInvalidLines() IList actual = parser.Parse(content); - Assert.Equal(1, actual.Count); + Assert.Single(actual); AssertCookie(actual[0], ".example.com", "/path/here", true, 0, "cookie1", "value1"); } diff --git a/src/shared/Core.Tests/HostProviderRegistryTests.cs b/src/shared/Core.Tests/HostProviderRegistryTests.cs index c8345a5ca..5406ba918 100644 --- a/src/shared/Core.Tests/HostProviderRegistryTests.cs +++ b/src/shared/Core.Tests/HostProviderRegistryTests.cs @@ -119,7 +119,7 @@ public async Task HostProviderRegistry_GetProvider_Auto_HasProviders_DynamicMatc Assert.Same(providerMock.Object, result); Assert.True(context.Git.Configuration.Global.TryGetValue(configKey, out IList config)); - Assert.Equal(1, config.Count); + Assert.Single(config); Assert.Equal(providerId, config[0]); } @@ -148,7 +148,7 @@ public async Task HostProviderRegistry_GetProvider_Auto_HasProviders_DynamicMatc Assert.Same(providerMock.Object, result); Assert.True(context.Git.Configuration.Global.TryGetValue(configKey, out IList config)); - Assert.Equal(1, config.Count); + Assert.Single(config); Assert.Equal(providerId, config[0]); } diff --git a/src/shared/Core.Tests/IniFileTests.cs b/src/shared/Core.Tests/IniFileTests.cs index a661547d4..2cbbf8aa0 100644 --- a/src/shared/Core.Tests/IniFileTests.cs +++ b/src/shared/Core.Tests/IniFileTests.cs @@ -79,7 +79,7 @@ recovery tests] Assert.Equal(6, ini.Sections.Count); AssertSection(ini, "one", out IniSection one); - Assert.Equal(1, one.Properties.Count); + Assert.Single(one.Properties); AssertProperty(one, "foo", "123"); AssertSection(ini, "two", out IniSection twoA); @@ -88,7 +88,7 @@ recovery tests] AssertProperty(twoA, "widget", "Hello, World!"); AssertSection(ini, "two", "subsection name", out IniSection twoB); - Assert.Equal(1, twoB.Properties.Count); + Assert.Single(twoB.Properties); AssertProperty(twoB, "foo", "this is different"); AssertSection(ini, "three", out IniSection three); @@ -97,7 +97,7 @@ recovery tests] AssertProperty(three, "empty", ""); AssertSection(ini, "four", out IniSection four); - Assert.Equal(0, four.Properties.Count); + Assert.Empty(four.Properties); AssertSection(ini, "five", out IniSection five); Assert.Equal(3, five.Properties.Count); diff --git a/src/shared/Core.Tests/StreamExtensionsTests.cs b/src/shared/Core.Tests/StreamExtensionsTests.cs index 56a7c762b..09153ad26 100644 --- a/src/shared/Core.Tests/StreamExtensionsTests.cs +++ b/src/shared/Core.Tests/StreamExtensionsTests.cs @@ -21,7 +21,7 @@ public void StreamExtensions_ReadDictionary_EmptyString_ReturnsEmptyDictionary() var output = ReadStringStream(input, StreamExtensions.ReadDictionary); Assert.NotNull(output); - Assert.Equal(0, output.Count); + Assert.Empty(output); } [Fact] @@ -73,7 +73,7 @@ public void StreamExtensions_ReadDictionary_CaseInsensitive_ReturnsDictionaryWit var output = ReadStringStream(input, x => StreamExtensions.ReadDictionary(x, StringComparer.OrdinalIgnoreCase)); Assert.NotNull(output); - Assert.Equal(1, output.Count); + Assert.Single(output); AssertDictionary("2", "a", output); } @@ -197,7 +197,7 @@ public void StreamExtensions_ReadMultiDictionary_EmptyString_ReturnsEmptyDiction var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary); Assert.NotNull(output); - Assert.Equal(0, output.Count); + Assert.Empty(output); } [Fact] @@ -250,7 +250,7 @@ public void StreamExtensions_ReadMultiDictionary_CaseInsensitive_ReturnsDictiona var output = ReadStringStream(input, x => StreamExtensions.ReadMultiDictionary(x, StringComparer.OrdinalIgnoreCase)); Assert.NotNull(output); - Assert.Equal(1, output.Count); + Assert.Single(output); AssertMultiDictionary(new[] { "2" }, "a", output); } @@ -262,7 +262,7 @@ public void StreamExtensions_ReadMultiDictionary_EmptyString_ReturnsKeyWithEmpty var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary); Assert.NotNull(output); - Assert.Equal(1, output.Count); + Assert.Single(output); AssertMultiDictionary(new[] { String.Empty, }, "a", output); } diff --git a/src/shared/TestInfrastructure/Objects/TestHttpMessageHandler.cs b/src/shared/TestInfrastructure/Objects/TestHttpMessageHandler.cs index 0a2c46e18..afe2ee77b 100644 --- a/src/shared/TestInfrastructure/Objects/TestHttpMessageHandler.cs +++ b/src/shared/TestInfrastructure/Objects/TestHttpMessageHandler.cs @@ -65,7 +65,7 @@ public void AssertRequest(HttpMethod method, Uri uri, int expectedNumberOfCalls) public void AssertNoRequests() { - Assert.Equal(0, _requestCounts.Count); + Assert.Empty(_requestCounts); } #region HttpMessageHandler From 8bfe765a1b650f243c08eda713941140b8892b79 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 12:50:46 +0200 Subject: [PATCH 22/24] Use the `Trace2Exception` It seems that (probably due to updated dependencies), there is a problem where the `Trace2Exception` no longer inherits from `InvalidOperationException`. Let's use the former, then. Signed-off-by: Johannes Schindelin --- .../Core.Tests/Authentication/BasicAuthenticationTests.cs | 2 +- src/shared/Core.Tests/HostProviderRegistryTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs b/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs index 18f477382..ba42b3b05 100644 --- a/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs +++ b/src/shared/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/shared/Core.Tests/HostProviderRegistryTests.cs b/src/shared/Core.Tests/HostProviderRegistryTests.cs index 5406ba918..33725758d 100644 --- a/src/shared/Core.Tests/HostProviderRegistryTests.cs +++ b/src/shared/Core.Tests/HostProviderRegistryTests.cs @@ -41,7 +41,7 @@ public async Task HostProviderRegistry_GetProvider_NoProviders_ThrowException() var registry = new HostProviderRegistry(context); var input = new InputArguments(new Dictionary()); - await Assert.ThrowsAsync(() => registry.GetProviderAsync(input)); + await Assert.ThrowsAsync(() => registry.GetProviderAsync(input)); } [Fact] From 1a774c1c2bf637bc3589b24c78fe58103499b0db Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Sep 2024 13:12:29 +0200 Subject: [PATCH 23/24] Replace broken `PlatformFact()` constructs We just upgraded XUnit to a newer version, which unfortunately no longer works with the `PlatformFact()` constructs of `Xunit.SkippableFact` even though we updated to the latest version, v1.4.13. It might have something to do with the fact that that package has not been updated since July 9th, 2024. Happily, XUnit has grown equivalent features in the meantime that we can use instead. So let's use those XUnit-native constructs instead. Note that we still cannot drop the `SkippableFact` dependency altogether because we need it in the `MacOSKeychain_ReadWriteDelete` test case. It is needed to work around a flaky test that is caused by semi-random broken states of macOS' key-chain, and that can only be detected while the test case is running (and hence _needs_ `AssertEx.Skip()`, which in turn requires `Xunit.Skip.If()` that is provided only via the `SkippableFact` package and there is no equivalent native XUnit functionality). Helped-by: Matthew Cheetham Signed-off-by: Johannes Schindelin --- src/shared/Core.Tests/EnvironmentTests.cs | 22 ++--- .../Core.Tests/GenericHostProviderTests.cs | 4 +- .../Interop/Linux/LinuxFileSystemTests.cs | 12 +-- .../Linux/SecretServiceCollectionTests.cs | 6 +- .../Interop/MacOS/MacOSFileSystemTests.cs | 12 +-- .../Interop/MacOS/MacOSKeychainTests.cs | 6 +- .../Posix/GnuPassCredentialStoreTests.cs | 6 +- .../Interop/Posix/PosixFileSystemTests.cs | 4 +- .../Windows/DpapiCredentialStoreTests.cs | 8 +- .../Windows/WindowsCredentialManagerTests.cs | 26 +++--- .../Interop/Windows/WindowsFileSystemTests.cs | 10 +-- src/shared/Core.Tests/Trace2Tests.cs | 4 +- src/shared/Core.Tests/WslUtilsTests.cs | 4 +- .../AzureReposHostProviderTests.cs | 10 +-- .../TestInfrastructure/PlatformAttributes.cs | 87 +++++++++++-------- 15 files changed, 120 insertions(+), 101 deletions(-) diff --git a/src/shared/Core.Tests/EnvironmentTests.cs b/src/shared/Core.Tests/EnvironmentTests.cs index bd7a8c99b..d9b7cb67c 100644 --- a/src/shared/Core.Tests/EnvironmentTests.cs +++ b/src/shared/Core.Tests/EnvironmentTests.cs @@ -15,7 +15,7 @@ public class EnvironmentTests private const string PosixPathVar = "/home/john.doe/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"; private const string PosixExecName = "foo"; - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsEnvironment_TryLocateExecutable_NotExists_ReturnFalse() { var fs = new TestFileSystem(); @@ -28,7 +28,7 @@ public void WindowsEnvironment_TryLocateExecutable_NotExists_ReturnFalse() Assert.Null(actualPath); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsEnvironment_TryLocateExecutable_Exists_ReturnTrueAndPath() { string expectedPath = @"C:\Windows\system32\foo.exe"; @@ -48,7 +48,7 @@ public void WindowsEnvironment_TryLocateExecutable_Exists_ReturnTrueAndPath() Assert.Equal(expectedPath, actualPath); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsEnvironment_TryLocateExecutable_ExistsMultiple_ReturnTrueAndFirstPath() { string expectedPath = @"C:\Users\john.doe\bin\foo.exe"; @@ -70,7 +70,7 @@ public void WindowsEnvironment_TryLocateExecutable_ExistsMultiple_ReturnTrueAndF Assert.Equal(expectedPath, actualPath); } - [PlatformFact(Platforms.Posix)] + [PosixFact] public void PosixEnvironment_TryLocateExecutable_NotExists_ReturnFalse() { var fs = new TestFileSystem(); @@ -83,7 +83,7 @@ public void PosixEnvironment_TryLocateExecutable_NotExists_ReturnFalse() Assert.Null(actualPath); } - [PlatformFact(Platforms.Posix)] + [PosixFact] public void PosixEnvironment_TryLocateExecutable_Exists_ReturnTrueAndPath() { string expectedPath = "/usr/local/bin/foo"; @@ -103,7 +103,7 @@ public void PosixEnvironment_TryLocateExecutable_Exists_ReturnTrueAndPath() Assert.Equal(expectedPath, actualPath); } - [PlatformFact(Platforms.Posix)] + [PosixFact] public void PosixEnvironment_TryLocateExecutable_ExistsMultiple_ReturnTrueAndFirstPath() { string expectedPath = "/home/john.doe/bin/foo"; @@ -125,7 +125,7 @@ public void PosixEnvironment_TryLocateExecutable_ExistsMultiple_ReturnTrueAndFir Assert.Equal(expectedPath, actualPath); } - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public void MacOSEnvironment_TryLocateExecutable_Paths_Are_Ignored() { List pathsToIgnore = new List() @@ -150,8 +150,8 @@ public void MacOSEnvironment_TryLocateExecutable_Paths_Are_Ignored() Assert.True(actualResult); Assert.Equal(expectedPath, actualPath); } - - [PlatformFact(Platforms.Posix)] + + [PosixFact] public void PosixEnvironment_SetEnvironmentVariable_Sets_Expected_Value() { var variable = "FOO_BAR"; @@ -166,8 +166,8 @@ public void PosixEnvironment_SetEnvironmentVariable_Sets_Expected_Value() Assert.Contains(env.Variables, item => item.Key.Equals(variable) && item.Value.Equals(value)); } - - [PlatformFact(Platforms.Windows)] + + [WindowsFact] public void WindowsEnvironment_SetEnvironmentVariable_Sets_Expected_Value() { var variable = "FOO_BAR"; diff --git a/src/shared/Core.Tests/GenericHostProviderTests.cs b/src/shared/Core.Tests/GenericHostProviderTests.cs index 39ed85cfe..8f9594b06 100644 --- a/src/shared/Core.Tests/GenericHostProviderTests.cs +++ b/src/shared/Core.Tests/GenericHostProviderTests.cs @@ -169,13 +169,13 @@ public async Task GenericHostProvider_CreateCredentialAsync_NonHttpProtocol_Retu basicAuthMock.Verify(x => x.GetCredentialsAsync(It.IsAny(), It.IsAny()), Times.Once); } - [PlatformFact(Platforms.Posix)] + [PosixFact] public async Task GenericHostProvider_CreateCredentialAsync_NonWindows_WiaSupported_ReturnsBasicCredential() { await TestCreateCredentialAsync_ReturnsBasicCredential(wiaSupported: true); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public async Task GenericHostProvider_CreateCredentialAsync_Windows_WiaSupported_ReturnsEmptyCredential() { await TestCreateCredentialAsync_ReturnsEmptyCredential(wiaSupported: true); diff --git a/src/shared/Core.Tests/Interop/Linux/LinuxFileSystemTests.cs b/src/shared/Core.Tests/Interop/Linux/LinuxFileSystemTests.cs index 4f283294c..2cd6d3cc1 100644 --- a/src/shared/Core.Tests/Interop/Linux/LinuxFileSystemTests.cs +++ b/src/shared/Core.Tests/Interop/Linux/LinuxFileSystemTests.cs @@ -7,7 +7,7 @@ namespace GitCredentialManager.Tests.Interop.Linux { public class LinuxFileSystemTests { - [PlatformFact(Platforms.Linux)] + [LinuxFact] public static void LinuxFileSystem_IsSamePath_SamePath_ReturnsTrue() { var fs = new LinuxFileSystem(); @@ -18,7 +18,7 @@ public static void LinuxFileSystem_IsSamePath_SamePath_ReturnsTrue() Assert.True(fs.IsSamePath(fileA, fileA)); } - [PlatformFact(Platforms.Linux)] + [LinuxFact] public static void LinuxFileSystem_IsSamePath_DifferentFile_ReturnsFalse() { var fs = new LinuxFileSystem(); @@ -31,7 +31,7 @@ public static void LinuxFileSystem_IsSamePath_DifferentFile_ReturnsFalse() Assert.False(fs.IsSamePath(fileB, fileA)); } - [PlatformFact(Platforms.Linux)] + [LinuxFact] public static void LinuxFileSystem_IsSamePath_SameFileDifferentCase_ReturnsFalse() { var fs = new LinuxFileSystem(); @@ -44,7 +44,7 @@ public static void LinuxFileSystem_IsSamePath_SameFileDifferentCase_ReturnsFalse Assert.False(fs.IsSamePath(fileA2, fileA1)); } - [PlatformFact(Platforms.Linux)] + [LinuxFact] public static void LinuxFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue() { var fs = new LinuxFileSystem(); @@ -58,7 +58,7 @@ public static void LinuxFileSystem_IsSamePath_SameFileDifferentPathNormalization Assert.True(fs.IsSamePath(fileA2, fileA1)); } - [PlatformFact(Platforms.Linux)] + [LinuxFact] public static void LinuxFileSystem_IsSamePath_SameFileViaSymlink_ReturnsTrue() { var fs = new LinuxFileSystem(); @@ -71,7 +71,7 @@ public static void LinuxFileSystem_IsSamePath_SameFileViaSymlink_ReturnsTrue() Assert.True(fs.IsSamePath(fileA2, fileA1)); } - [PlatformFact(Platforms.Linux)] + [LinuxFact] public static void LinuxFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue() { var fs = new LinuxFileSystem(); diff --git a/src/shared/Core.Tests/Interop/Linux/SecretServiceCollectionTests.cs b/src/shared/Core.Tests/Interop/Linux/SecretServiceCollectionTests.cs index 1237e2907..8cc6c7272 100644 --- a/src/shared/Core.Tests/Interop/Linux/SecretServiceCollectionTests.cs +++ b/src/shared/Core.Tests/Interop/Linux/SecretServiceCollectionTests.cs @@ -8,7 +8,7 @@ public class SecretServiceCollectionTests { private const string TestNamespace = "git-test"; - [PlatformFact(Platforms.Linux, Skip = "Cannot run headless")] + [LinuxFact(Skip = "Cannot run headless")] public void SecretServiceCollection_ReadWriteDelete() { var collection = new SecretServiceCollection(TestNamespace); @@ -37,7 +37,7 @@ public void SecretServiceCollection_ReadWriteDelete() } } - [PlatformFact(Platforms.Linux, Skip = "Cannot run headless")] + [LinuxFact(Skip = "Cannot run headless")] public void SecretServiceCollection_Get_NotFound_ReturnsNull() { var collection = new SecretServiceCollection(TestNamespace); @@ -49,7 +49,7 @@ public void SecretServiceCollection_Get_NotFound_ReturnsNull() Assert.Null(credential); } - [PlatformFact(Platforms.Linux, Skip = "Cannot run headless")] + [LinuxFact(Skip = "Cannot run headless")] public void SecretServiceCollection_Remove_NotFound_ReturnsFalse() { var collection = new SecretServiceCollection(TestNamespace); diff --git a/src/shared/Core.Tests/Interop/MacOS/MacOSFileSystemTests.cs b/src/shared/Core.Tests/Interop/MacOS/MacOSFileSystemTests.cs index 54916103a..85643f8fd 100644 --- a/src/shared/Core.Tests/Interop/MacOS/MacOSFileSystemTests.cs +++ b/src/shared/Core.Tests/Interop/MacOS/MacOSFileSystemTests.cs @@ -7,7 +7,7 @@ namespace GitCredentialManager.Tests.Interop.MacOS { public class MacOSFileSystemTests { - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public static void MacOSFileSystem_IsSamePath_SamePath_ReturnsTrue() { var fs = new MacOSFileSystem(); @@ -18,7 +18,7 @@ public static void MacOSFileSystem_IsSamePath_SamePath_ReturnsTrue() Assert.True(fs.IsSamePath(fileA, fileA)); } - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public static void MacOSFileSystem_IsSamePath_DifferentFile_ReturnsFalse() { var fs = new MacOSFileSystem(); @@ -31,7 +31,7 @@ public static void MacOSFileSystem_IsSamePath_DifferentFile_ReturnsFalse() Assert.False(fs.IsSamePath(fileB, fileA)); } - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public static void MacOSFileSystem_IsSamePath_SameFileDifferentCase_ReturnsTrue() { var fs = new MacOSFileSystem(); @@ -44,7 +44,7 @@ public static void MacOSFileSystem_IsSamePath_SameFileDifferentCase_ReturnsTrue( Assert.True(fs.IsSamePath(fileA2, fileA1)); } - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public static void MacOSFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue() { var fs = new MacOSFileSystem(); @@ -58,7 +58,7 @@ public static void MacOSFileSystem_IsSamePath_SameFileDifferentPathNormalization Assert.True(fs.IsSamePath(fileA2, fileA1)); } - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public static void MacOSFileSystem_IsSamePath_SameFileViaSymlink_ReturnsTrue() { var fs = new MacOSFileSystem(); @@ -71,7 +71,7 @@ public static void MacOSFileSystem_IsSamePath_SameFileViaSymlink_ReturnsTrue() Assert.True(fs.IsSamePath(fileA2, fileA1)); } - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public static void MacOSFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue() { var fs = new MacOSFileSystem(); diff --git a/src/shared/Core.Tests/Interop/MacOS/MacOSKeychainTests.cs b/src/shared/Core.Tests/Interop/MacOS/MacOSKeychainTests.cs index 8ad1caead..e9c517d88 100644 --- a/src/shared/Core.Tests/Interop/MacOS/MacOSKeychainTests.cs +++ b/src/shared/Core.Tests/Interop/MacOS/MacOSKeychainTests.cs @@ -10,7 +10,7 @@ public class MacOSKeychainTests { private const string TestNamespace = "git-test"; - [SkippablePlatformFact(Platforms.MacOS)] + [MacOSFact] public void MacOSKeychain_ReadWriteDelete() { var keychain = new MacOSKeychain(TestNamespace); @@ -52,7 +52,7 @@ public void MacOSKeychain_ReadWriteDelete() } } - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public void MacOSKeychain_Get_NotFound_ReturnsNull() { var keychain = new MacOSKeychain(TestNamespace); @@ -64,7 +64,7 @@ public void MacOSKeychain_Get_NotFound_ReturnsNull() Assert.Null(credential); } - [PlatformFact(Platforms.MacOS)] + [MacOSFact] public void MacOSKeychain_Remove_NotFound_ReturnsFalse() { var keychain = new MacOSKeychain(TestNamespace); diff --git a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs index 423868bd7..7ff80f03d 100644 --- a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs +++ b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs @@ -11,7 +11,7 @@ public class GnuPassCredentialStoreTests { private const string TestNamespace = "git-test"; - [PlatformFact(Platforms.Posix)] + [PosixFact] public void GnuPassCredentialStore_ReadWriteDelete() { var fs = new TestFileSystem(); @@ -54,7 +54,7 @@ public void GnuPassCredentialStore_ReadWriteDelete() } } - [PlatformFact(Platforms.Posix)] + [PosixFact] public void GnuPassCredentialStore_Get_NotFound_ReturnsNull() { var fs = new TestFileSystem(); @@ -70,7 +70,7 @@ public void GnuPassCredentialStore_Get_NotFound_ReturnsNull() Assert.Null(credential); } - [PlatformFact(Platforms.Posix)] + [PosixFact] public void GnuPassCredentialStore_Remove_NotFound_ReturnsFalse() { var fs = new TestFileSystem(); diff --git a/src/shared/Core.Tests/Interop/Posix/PosixFileSystemTests.cs b/src/shared/Core.Tests/Interop/Posix/PosixFileSystemTests.cs index 56dc3a4e3..607eb2e66 100644 --- a/src/shared/Core.Tests/Interop/Posix/PosixFileSystemTests.cs +++ b/src/shared/Core.Tests/Interop/Posix/PosixFileSystemTests.cs @@ -7,7 +7,7 @@ namespace GitCredentialManager.Tests.Interop.Posix { public class PosixFileSystemTests { - [PlatformFact(Platforms.Posix)] + [PosixFact] public void PosixFileSystem_ResolveSymlinks_FileLinks() { string baseDir = GetTempDirectory(); @@ -19,7 +19,7 @@ public void PosixFileSystem_ResolveSymlinks_FileLinks() Assert.Equal(realPath, actual); } - [PlatformFact(Platforms.Posix)] + [PosixFact] public void PosixFileSystem_ResolveSymlinks_DirectoryLinks() { // diff --git a/src/shared/Core.Tests/Interop/Windows/DpapiCredentialStoreTests.cs b/src/shared/Core.Tests/Interop/Windows/DpapiCredentialStoreTests.cs index 202924f69..bb7026903 100644 --- a/src/shared/Core.Tests/Interop/Windows/DpapiCredentialStoreTests.cs +++ b/src/shared/Core.Tests/Interop/Windows/DpapiCredentialStoreTests.cs @@ -13,7 +13,7 @@ public class DpapiCredentialStoreTests private const string TestStoreRoot = @"C:\dpapi_store"; private const string TestNamespace = "git-test"; - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void DpapiCredentialStore_AddOrUpdate_CreatesUTF8ProtectedFile() { var fs = new TestFileSystem(); @@ -49,7 +49,7 @@ public void DpapiCredentialStore_AddOrUpdate_CreatesUTF8ProtectedFile() Assert.True(string.IsNullOrWhiteSpace(lines[3])); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void DpapiCredentialStore_Get_KeyNotFound_ReturnsNull() { var fs = new TestFileSystem(); @@ -62,7 +62,7 @@ public void DpapiCredentialStore_Get_KeyNotFound_ReturnsNull() Assert.Null(credential); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void DpapiCredentialStore_Get_ReadProtectedFile() { var fs = new TestFileSystem(); @@ -97,7 +97,7 @@ public void DpapiCredentialStore_Get_ReadProtectedFile() Assert.Equal(userName, credential.Account); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void DpapiCredentialStore_Remove_KeyNotFound_ReturnsFalse() { var fs = new TestFileSystem(); diff --git a/src/shared/Core.Tests/Interop/Windows/WindowsCredentialManagerTests.cs b/src/shared/Core.Tests/Interop/Windows/WindowsCredentialManagerTests.cs index b7b5cef62..ba4659ec0 100644 --- a/src/shared/Core.Tests/Interop/Windows/WindowsCredentialManagerTests.cs +++ b/src/shared/Core.Tests/Interop/Windows/WindowsCredentialManagerTests.cs @@ -9,7 +9,7 @@ public class WindowsCredentialManagerTests { private const string TestNamespace = "git-test"; - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_ReadWriteDelete() { var credManager = new WindowsCredentialManager(TestNamespace); @@ -45,7 +45,7 @@ public void WindowsCredentialManager_ReadWriteDelete() } } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_AddOrUpdate_UsernameWithAtCharacter() { var credManager = new WindowsCredentialManager(TestNamespace); @@ -81,7 +81,7 @@ public void WindowsCredentialManager_AddOrUpdate_UsernameWithAtCharacter() } } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_Get_KeyNotFound_ReturnsNull() { var credManager = new WindowsCredentialManager(TestNamespace); @@ -93,7 +93,7 @@ public void WindowsCredentialManager_Get_KeyNotFound_ReturnsNull() Assert.Null(credential); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_Remove_KeyNotFound_ReturnsFalse() { var credManager = new WindowsCredentialManager(TestNamespace); @@ -105,7 +105,7 @@ public void WindowsCredentialManager_Remove_KeyNotFound_ReturnsFalse() Assert.False(result); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_AddOrUpdate_TargetNameAlreadyExists_CreatesWithUserInTargetName() { var credManager = new WindowsCredentialManager(TestNamespace); @@ -155,7 +155,7 @@ public void WindowsCredentialManager_AddOrUpdate_TargetNameAlreadyExists_Creates } } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_AddOrUpdate_TargetNameAlreadyExistsAndUserWithAtCharacter_CreatesWithEscapedUserInTargetName() { var credManager = new WindowsCredentialManager(TestNamespace); @@ -235,7 +235,7 @@ public void WindowsCredentialManager_RemoveUriUserInfo(string input, string expe Assert.Equal(expected, actual); } - [PlatformTheory(Platforms.Windows)] + [WindowsTheory] [InlineData("https://example.com", null, "https://example.com", "alice", true)] [InlineData("https://example.com", "alice", "https://example.com", "alice", true)] [InlineData("https://example.com", null, "https://example.com:443", "alice", true)] @@ -270,7 +270,7 @@ public void WindowsCredentialManager_IsMatch( Assert.Equal(expected, actual); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_IsMatch_NoNamespace_NotMatched() { var win32Cred = new Win32Credential @@ -286,7 +286,7 @@ public void WindowsCredentialManager_IsMatch_NoNamespace_NotMatched() Assert.False(result); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_IsMatch_DifferentNamespace_NotMatched() { var win32Cred = new Win32Credential @@ -302,7 +302,7 @@ public void WindowsCredentialManager_IsMatch_DifferentNamespace_NotMatched() Assert.False(result); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_IsMatch_CaseSensitiveNamespace_NotMatched() { var win32Cred = new Win32Credential @@ -318,7 +318,7 @@ public void WindowsCredentialManager_IsMatch_CaseSensitiveNamespace_NotMatched() Assert.False(result); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WindowsCredentialManager_IsMatch_NoNamespaceInQuery_IsMatched() { var win32Cred = new Win32Credential @@ -334,7 +334,7 @@ public void WindowsCredentialManager_IsMatch_NoNamespaceInQuery_IsMatched() Assert.True(result); } - [PlatformTheory(Platforms.Windows)] + [WindowsTheory] [InlineData("https://example.com", null, "https://example.com")] [InlineData("https://example.com", "bob", "https://bob@example.com")] [InlineData("https://example.com", "bob@id.example.com", "https://bob_id.example.com@example.com")] // @ in user @@ -355,7 +355,7 @@ public void WindowsCredentialManager_CreateTargetName(string service, string acc Assert.Equal(fullExpected, actual); } - [PlatformTheory(Platforms.Windows)] + [WindowsTheory] [InlineData(TestNamespace, "https://example.com", null, $"{TestNamespace}:https://example.com")] [InlineData(null, "https://example.com", null, "https://example.com")] [InlineData("", "https://example.com", null, "https://example.com")] diff --git a/src/shared/Core.Tests/Interop/Windows/WindowsFileSystemTests.cs b/src/shared/Core.Tests/Interop/Windows/WindowsFileSystemTests.cs index a94a76597..be89bc1fc 100644 --- a/src/shared/Core.Tests/Interop/Windows/WindowsFileSystemTests.cs +++ b/src/shared/Core.Tests/Interop/Windows/WindowsFileSystemTests.cs @@ -7,7 +7,7 @@ namespace GitCredentialManager.Tests.Interop.Windows { public class WindowsFileSystemTests { - [PlatformFact(Platforms.Windows)] + [WindowsFact] public static void WindowsFileSystem_IsSamePath_SamePath_ReturnsTrue() { var fs = new WindowsFileSystem(); @@ -18,7 +18,7 @@ public static void WindowsFileSystem_IsSamePath_SamePath_ReturnsTrue() Assert.True(fs.IsSamePath(fileA, fileA)); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public static void WindowsFileSystem_IsSamePath_DifferentFile_ReturnsFalse() { var fs = new WindowsFileSystem(); @@ -31,7 +31,7 @@ public static void WindowsFileSystem_IsSamePath_DifferentFile_ReturnsFalse() Assert.False(fs.IsSamePath(fileB, fileA)); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public static void WindowsFileSystem_IsSamePath_SameFileDifferentCase_ReturnsTrue() { var fs = new WindowsFileSystem(); @@ -44,7 +44,7 @@ public static void WindowsFileSystem_IsSamePath_SameFileDifferentCase_ReturnsTru Assert.True(fs.IsSamePath(fileA2, fileA1)); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public static void WindowsFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue() { var fs = new WindowsFileSystem(); @@ -58,7 +58,7 @@ public static void WindowsFileSystem_IsSamePath_SameFileDifferentPathNormalizati Assert.True(fs.IsSamePath(fileA2, fileA1)); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public static void WindowsFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue() { var fs = new WindowsFileSystem(); diff --git a/src/shared/Core.Tests/Trace2Tests.cs b/src/shared/Core.Tests/Trace2Tests.cs index 60d89ac2f..26df5ab98 100644 --- a/src/shared/Core.Tests/Trace2Tests.cs +++ b/src/shared/Core.Tests/Trace2Tests.cs @@ -4,7 +4,7 @@ namespace GitCredentialManager.Tests; public class Trace2Tests { - [PlatformTheory(Platforms.Posix)] + [PosixTheory] [InlineData("af_unix:foo", "foo")] [InlineData("af_unix:stream:foo-bar", "foo-bar")] [InlineData("af_unix:dgram:foo-bar-baz", "foo-bar-baz")] @@ -16,7 +16,7 @@ public void TryGetPipeName_Posix_Returns_Expected_Value(string input, string exp Assert.Matches(actual, expected); } - [PlatformTheory(Platforms.Windows)] + [WindowsTheory] [InlineData("\\\\.\\pipe\\git-foo", "git-foo")] [InlineData("\\\\.\\pipe\\git-foo-bar", "git-foo-bar")] [InlineData("\\\\.\\pipe\\foo\\git-bar", "git-bar")] diff --git a/src/shared/Core.Tests/WslUtilsTests.cs b/src/shared/Core.Tests/WslUtilsTests.cs index 722a8654d..330e42c84 100644 --- a/src/shared/Core.Tests/WslUtilsTests.cs +++ b/src/shared/Core.Tests/WslUtilsTests.cs @@ -92,7 +92,7 @@ public void WslUtils_ConvertToDistroPath_Invalid_ThrowsException(string path) Assert.Throws(() => WslUtils.ConvertToDistroPath(path, out _)); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WslUtils_CreateWslProcess() { const string distribution = "ubuntu"; @@ -112,7 +112,7 @@ public void WslUtils_CreateWslProcess() Assert.False(process.StartInfo.UseShellExecute); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public void WslUtils_CreateWslProcess_WorkingDirectory() { const string distribution = "ubuntu"; diff --git a/src/shared/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs b/src/shared/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs index e8da8d79c..e379aaa3b 100644 --- a/src/shared/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs +++ b/src/shared/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs @@ -705,7 +705,7 @@ public async Task AzureReposHostProvider_UnconfigureAsync_UseHttpPathSet_Removes Assert.Empty(context.Git.Configuration.Global); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public async Task AzureReposHostProvider_UnconfigureAsync_System_Windows_UseHttpPathSetAndManagerHelper_DoesNotRemoveEntry() { var context = new TestCommandContext(); @@ -721,7 +721,7 @@ public async Task AzureReposHostProvider_UnconfigureAsync_System_Windows_UseHttp Assert.Equal("true", actualValues[0]); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public async Task AzureReposHostProvider_UnconfigureAsync_System_Windows_UseHttpPathSetAndManagerCoreHelper_DoesNotRemoveEntry() { var context = new TestCommandContext(); @@ -737,7 +737,7 @@ public async Task AzureReposHostProvider_UnconfigureAsync_System_Windows_UseHttp Assert.Equal("true", actualValues[0]); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public async Task AzureReposHostProvider_UnconfigureAsync_System_Windows_UseHttpPathSetNoManagerCoreHelper_RemovesEntry() { var context = new TestCommandContext(); @@ -750,7 +750,7 @@ public async Task AzureReposHostProvider_UnconfigureAsync_System_Windows_UseHttp Assert.Empty(context.Git.Configuration.System); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public async Task AzureReposHostProvider_UnconfigureAsync_User_Windows_UseHttpPathSetAndManagerHelper_RemovesEntry() { var context = new TestCommandContext(); @@ -764,7 +764,7 @@ public async Task AzureReposHostProvider_UnconfigureAsync_User_Windows_UseHttpPa Assert.False(context.Git.Configuration.Global.TryGetValue(AzDevUseHttpPathKey, out _)); } - [PlatformFact(Platforms.Windows)] + [WindowsFact] public async Task AzureReposHostProvider_UnconfigureAsync_User_Windows_UseHttpPathSetAndManagerCoreHelper_RemovesEntry() { var context = new TestCommandContext(); diff --git a/src/shared/TestInfrastructure/PlatformAttributes.cs b/src/shared/TestInfrastructure/PlatformAttributes.cs index 24217602d..50d9d6c34 100644 --- a/src/shared/TestInfrastructure/PlatformAttributes.cs +++ b/src/shared/TestInfrastructure/PlatformAttributes.cs @@ -1,76 +1,95 @@ -using System; using System.Runtime.InteropServices; using Xunit; namespace GitCredentialManager.Tests { - public class PlatformFactAttribute : FactAttribute + public class WindowsFactAttribute : FactAttribute { - public PlatformFactAttribute(Platforms platforms) + public WindowsFactAttribute() { - if (!XunitHelpers.IsSupportedPlatform(platforms)) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { Skip = "Test not supported on this platform."; } } } - public class PlatformTheoryAttribute : TheoryAttribute + public class MacOSFactAttribute : FactAttribute { - public PlatformTheoryAttribute(Platforms platforms) + public MacOSFactAttribute() { - if (!XunitHelpers.IsSupportedPlatform(platforms)) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { Skip = "Test not supported on this platform."; } } } - public class SkippablePlatformFactAttribute : SkippableFactAttribute + public class LinuxFactAttribute : FactAttribute { - public SkippablePlatformFactAttribute(Platforms platforms) + public LinuxFactAttribute() { - Xunit.Skip.IfNot( - XunitHelpers.IsSupportedPlatform(platforms), - "Test not supported on this platform." - ); + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Skip = "Test not supported on this platform."; + } } } - public class SkippablePlatformTheoryAttribute : SkippableTheoryAttribute + public class PosixFactAttribute : FactAttribute { - public SkippablePlatformTheoryAttribute(Platforms platforms) + public PosixFactAttribute() { - Xunit.Skip.IfNot( - XunitHelpers.IsSupportedPlatform(platforms), - "Test not supported on this platform." - ); + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && + !RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Skip = "Test not supported on this platform."; + } } } - internal static class XunitHelpers + public class WindowsTheoryAttribute : TheoryAttribute { - public static bool IsSupportedPlatform(Platforms platforms) + public WindowsTheoryAttribute() { - if (platforms.HasFlag(Platforms.Windows) && RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || - platforms.HasFlag(Platforms.MacOS) && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || - platforms.HasFlag(Platforms.Linux) && RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return true; + Skip = "Test not supported on this platform."; } + } + } - return false; + public class MacOSTheoryAttribute : TheoryAttribute + { + public MacOSTheoryAttribute() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Skip = "Test not supported on this platform."; + } } } - [Flags] - public enum Platforms + public class LinuxTheoryAttribute : TheoryAttribute { - None = 0, - Windows = 1 << 0, - MacOS = 1 << 2, - Linux = 1 << 3, - Posix = MacOS | Linux, - All = Windows | Posix + public LinuxTheoryAttribute() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Skip = "Test not supported on this platform."; + } + } + } + + public class PosixTheoryAttribute : TheoryAttribute + { + public PosixTheoryAttribute() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && + !RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Skip = "Test not supported on this platform."; + } + } } } From c16a9b786fcad7946e944c17fb1be3140f20e8dc Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 30 Sep 2024 14:10:45 +0100 Subject: [PATCH 24/24] VERSION: bump version to 2.6.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 27c7c7b9d..82f00d533 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.1.0 +2.6.0.0