From f600e92b42b83984d92e9662c9e513beefb51ad4 Mon Sep 17 00:00:00 2001 From: M Hickford Date: Fri, 23 Feb 2024 22:29:47 +0000 Subject: [PATCH 01/84] Omit GitLab client secret Secret is superfluous for GitLab PKCE https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-with-proof-key-for-code-exchange-pkce --- src/shared/GitLab/GitLabConstants.cs | 1 - src/shared/GitLab/GitLabOAuth2Client.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/GitLab/GitLabConstants.cs b/src/shared/GitLab/GitLabConstants.cs index a686ece7a..69f1f9b9e 100644 --- a/src/shared/GitLab/GitLabConstants.cs +++ b/src/shared/GitLab/GitLabConstants.cs @@ -10,7 +10,6 @@ public static class GitLabConstants // Owned by https://gitlab.com/gitcredentialmanager public const string OAuthClientId = "172b9f227872b5dde33f4d9b1db06a6a5515ae79508e7a00c973c85ce490671e"; - public const string OAuthClientSecret = "7da92770d1447508601e4ba026bc5eb655c8268e818cd609889cc9bae2023f39"; public static readonly Uri OAuthRedirectUri = new Uri("http://127.0.0.1/"); // https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow diff --git a/src/shared/GitLab/GitLabOAuth2Client.cs b/src/shared/GitLab/GitLabOAuth2Client.cs index 3b146aaeb..ba72f5b41 100644 --- a/src/shared/GitLab/GitLabOAuth2Client.cs +++ b/src/shared/GitLab/GitLabOAuth2Client.cs @@ -59,7 +59,8 @@ private static string GetClientSecret(ISettings settings) return clientSecret; } - return GitLabConstants.OAuthClientSecret; + // no secret necessary + return null; } } } From 5d98ecef1ddac612219f2e267f9f15ceaa121054 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:53:57 +0000 Subject: [PATCH 02/84] build(deps): bump DavidAnson/markdownlint-cli2-action Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 16.0.0 to 17.0.0. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/b4c9feab76d8025d1e83c653fa3990936df0e6c8...db43aef879112c3119a410d69f66701e0d530809) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 4cf4c4b70..17835c897 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: DavidAnson/markdownlint-cli2-action@b4c9feab76d8025d1e83c653fa3990936df0e6c8 + - uses: DavidAnson/markdownlint-cli2-action@db43aef879112c3119a410d69f66701e0d530809 with: globs: | "**/*.md" From bc4dfa90f4d7298ddcb1d394b7d0ceb3a4cb3892 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:06:55 +0100 Subject: [PATCH 03/84] docs: update required dotnet-sdk version in install.md Update the version notation because since the release of git-credential-manager version 2.5.0, the sdk version required for installation is .NET 8. --- docs/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index 4268858bb..5ae7b44d5 100644 --- a/docs/install.md +++ b/docs/install.md @@ -210,7 +210,7 @@ the preferred install method for Linux because you can use it to install on any distribution][dotnet-supported-distributions]. You can also use this method on macOS if you so choose. -**Note:** Make sure you have installed [version 7.0 of the .NET +**Note:** Make sure you have installed [version 8.0 of the .NET SDK][dotnet-install] before attempting to run the following `dotnet tool` commands. After installing, you will also need to follow the output instructions to add the tools directory to your `PATH`. From 32d205b8ea623dfdb8d663a1ff8b2d5da3a8903b Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 7 Oct 2024 12:47:13 +0100 Subject: [PATCH 04/84] settings: add allow unsafe remotes option Add a new setting that allows users to express an explicit consent to using unsafe remote URLs (such as those using HTTP rather than HTTPS). --- docs/configuration.md | 19 ++++++++++++ docs/environment.md | 29 ++++++++++++++++++- docs/netconfig.md | 18 ++++++++++++ src/shared/Core/Constants.cs | 3 ++ src/shared/Core/Settings.cs | 11 +++++++ .../Objects/TestSettings.cs | 4 +++ 6 files changed, 83 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index a4fecf395..6a38098e3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -255,6 +255,24 @@ Defaults to false (use hardware acceleration where available). --- +### credential.allowUnsafeRemotes + +Allow transmitting credentials to unsafe remote URLs such as unencrypted HTTP +URLs. This setting is not recommended for general use and should only be used +when necessary. + +Defaults false (disallow unsafe remote URLs). + +#### Example + +```shell +git config --global credential.allowUnsafeRemotes true +``` + +**Also see: [GCM_ALLOW_UNSAFE_REMOTES][gcm-allow-unsafe-remotes]** + +--- + ### credential.autoDetectTimeout Set the maximum length of time, in milliseconds, that GCM should wait for a @@ -1024,6 +1042,7 @@ Defaults to disabled. [envars]: environment.md [freedesktop-ss]: https://specifications.freedesktop.org/secret-service/ [gcm-allow-windowsauth]: environment.md#GCM_ALLOW_WINDOWSAUTH +[gcm-allow-unsafe-remotes]: environment.md#GCM_ALLOW_UNSAFE_REMOTES [gcm-authority]: environment.md#GCM_AUTHORITY-deprecated [gcm-autodetect-timeout]: environment.md#GCM_AUTODETECT_TIMEOUT [gcm-azrepos-credentialtype]: environment.md#GCM_AZREPOS_CREDENTIALTYPE diff --git a/docs/environment.md b/docs/environment.md index edda0d714..293a86ae0 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -302,6 +302,32 @@ Defaults to false (use hardware acceleration where available). --- +### GCM_ALLOW_UNSAFE_REMOTES + +Allow transmitting credentials to unsafe remote URLs such as unencrypted HTTP +URLs. This setting is not recommended for general use and should only be used +when necessary. + +Defaults false (disallow unsafe remote URLs). + +#### Example + +##### Windows + +```batch +SET GCM_ALLOW_UNSAFE_REMOTES=true +``` + +##### macOS/Linux + +```bash +export GCM_ALLOW_UNSAFE_REMOTES=true +``` + +**Also see: [credential.allowUnsafeRemotes][credential-allowunsaferemotes]** + +--- + ### GCM_AUTODETECT_TIMEOUT Set the maximum length of time, in milliseconds, that GCM should wait for a @@ -1153,7 +1179,8 @@ Defaults to disabled. [autodetect]: autodetect.md [azure-access-tokens]: azrepos-users-and-tokens.md [configuration]: configuration.md -[credential-allowwindowsauth]: environment.md#credentialallowWindowsAuth +[credential-allowwindowsauth]: configuration.md#credentialallowwindowsauth +[credential-allowunsaferemotes]: configuration.md#credentialallowunsaferemotes [credential-authority]: configuration.md#credentialauthority-deprecated [credential-autodetecttimeout]: configuration.md#credentialautodetecttimeout [credential-azrepos-credential-type]: configuration.md#credentialazreposcredentialtype diff --git a/docs/netconfig.md b/docs/netconfig.md index cf312336f..920344f15 100644 --- a/docs/netconfig.md +++ b/docs/netconfig.md @@ -191,6 +191,22 @@ network traffic inspection tool such as [Telerik Fiddler][telerik-fiddler]. If you are using such tools please consult their documentation for trusting the proxy root certificates. +--- + +## Unsafe Remote URLs + +If you are using a remote URL that is not considered safe, such as unencrypted +HTTP (remote URLs that start with `http://`), host providers may prevent you +from authenticating with your credentials. + +In this case, you should consider using a HTTPS (starting with `https://`) +remote URL to ensure your credentials are transmitted securely. + +If you accept the risks associated with using an unsafe remote URL, you can +configure GCM to allow the use of unsafe remote URLS by setting the environment +variable [`GCM_ALLOW_UNSAFE_REMOTES`][unsafe-envar], or by using the Git +configuration option [`credential.allowUnsafeRemotes`][unsafe-config] to `true`. + [environment]: environment.md [configuration]: configuration.md [git-http-proxy]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpproxy @@ -212,3 +228,5 @@ proxy root certificates. [git-ssl-no-verify]: https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_networking [git-http-ssl-verify]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpsslVerify [telerik-fiddler]: https://www.telerik.com/fiddler +[unsafe-envar]: environment.md#gcm_allow_unsafe_remotes +[unsafe-config]: configuration.md#credentialallowunsaferemotes diff --git a/src/shared/Core/Constants.cs b/src/shared/Core/Constants.cs index 210c991bc..41ccb990c 100644 --- a/src/shared/Core/Constants.cs +++ b/src/shared/Core/Constants.cs @@ -119,6 +119,7 @@ public static class EnvironmentVariables public const string OAuthDefaultUserName = "GCM_OAUTH_DEFAULT_USERNAME"; public const string GcmDevUseLegacyUiHelpers = "GCM_DEV_USELEGACYUIHELPERS"; public const string GcmGuiSoftwareRendering = "GCM_GUI_SOFTWARE_RENDERING"; + public const string GcmAllowUnsafeRemotes = "GCM_ALLOW_UNSAFE_REMOTES"; } public static class Http @@ -163,6 +164,7 @@ public static class Credential public const string MsAuthUseDefaultAccount = "msauthUseDefaultAccount"; public const string GuiSoftwareRendering = "guiSoftwareRendering"; public const string GpgPassStorePath = "gpgPassStorePath"; + public const string AllowUnsafeRemotes = "allowUnsafeRemotes"; public const string OAuthAuthenticationModes = "oauthAuthModes"; public const string OAuthClientId = "oauthClientId"; @@ -226,6 +228,7 @@ public static class HelpUrls public const string GcmAutoDetect = "https://aka.ms/gcm/autodetect"; public const string GcmDefaultAccount = "https://aka.ms/gcm/defaultaccount"; public const string GcmMultipleUsers = "https://aka.ms/gcm/multipleusers"; + public const string GcmUnsafeRemotes = "https://aka.ms/gcm/unsaferemotes"; } private static Version _gcmVersion; diff --git a/src/shared/Core/Settings.cs b/src/shared/Core/Settings.cs index 2aa71edf4..0e24ce9a3 100644 --- a/src/shared/Core/Settings.cs +++ b/src/shared/Core/Settings.cs @@ -189,6 +189,11 @@ public interface ISettings : IDisposable /// bool UseSoftwareRendering { get; } + /// + /// Permit the use of unsafe remotes URLs such as regular HTTP. + /// + bool AllowUnsafeRemotes { get; } + /// /// Get TRACE2 settings. /// @@ -580,6 +585,12 @@ public bool UseSoftwareRendering } } + public bool AllowUnsafeRemotes => + TryGetSetting(KnownEnvars.GcmAllowUnsafeRemotes, + KnownGitCfg.Credential.SectionName, + KnownGitCfg.Credential.AllowUnsafeRemotes, + out string str) && str.ToBooleanyOrDefault(false); + public Trace2Settings GetTrace2Settings() { var settings = new Trace2Settings(); diff --git a/src/shared/TestInfrastructure/Objects/TestSettings.cs b/src/shared/TestInfrastructure/Objects/TestSettings.cs index f14bf6cc9..3e67e39b0 100644 --- a/src/shared/TestInfrastructure/Objects/TestSettings.cs +++ b/src/shared/TestInfrastructure/Objects/TestSettings.cs @@ -53,6 +53,8 @@ public class TestSettings : ISettings public bool UseMsAuthDefaultAccount { get; set; } + public bool AllowUnsafeRemotes { get; set; } = false; + public Trace2Settings GetTrace2Settings() { return new Trace2Settings() @@ -189,6 +191,8 @@ ProxyConfiguration ISettings.GetProxyConfiguration() bool ISettings.UseSoftwareRendering => false; + bool ISettings.AllowUnsafeRemotes => AllowUnsafeRemotes; + #endregion #region IDisposable From 2fbe3d615d6fb8013e14e55a837a9c8e95742b16 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 7 Oct 2024 12:48:29 +0100 Subject: [PATCH 05/84] bitbucket: support GCM_ALLOW_UNSAFE_REMOTES option --- .../Atlassian.Bitbucket/BitbucketHostProvider.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs b/src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs index 35472682c..286398de9 100644 --- a/src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs +++ b/src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs @@ -55,8 +55,8 @@ public bool IsSupported(InputArguments input) return false; } - // We do not support unencrypted HTTP communications to Bitbucket, - // but we report `true` here for HTTP so that we can show a helpful + // We do not recommend unencrypted HTTP communications to Bitbucket, but it is possible. + // Therefore, we report `true` here for HTTP so that we can show a helpful // error message for the user in `GetCredentialAsync`. return (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") || StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "https")) && @@ -81,11 +81,14 @@ public bool IsSupported(HttpResponseMessage response) public async Task GetCredentialAsync(InputArguments input) { // We should not allow unencrypted communication and should inform the user - if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") - && BitbucketHelper.IsBitbucketOrg(input)) + if (!_context.Settings.AllowUnsafeRemotes && + StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") && + BitbucketHelper.IsBitbucketOrg(input)) { throw new Trace2Exception(_context.Trace2, - "Unencrypted HTTP is not supported for Bitbucket.org. Ensure the repository remote URL is using HTTPS."); + "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."); } var authModes = await GetSupportedAuthenticationModesAsync(input); From 6b87cc7f21b11da541e1b2c194ec06975d5d1265 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 7 Oct 2024 12:48:45 +0100 Subject: [PATCH 06/84] github: support GCM_ALLOW_UNSAFE_REMOTES option --- src/shared/GitHub/GitHubHostProvider.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/shared/GitHub/GitHubHostProvider.cs b/src/shared/GitHub/GitHubHostProvider.cs index 918e859a0..21d29f651 100644 --- a/src/shared/GitHub/GitHubHostProvider.cs +++ b/src/shared/GitHub/GitHubHostProvider.cs @@ -285,10 +285,13 @@ public virtual Task EraseCredentialAsync(InputArguments input) ThrowIfDisposed(); // We should not allow unencrypted communication and should inform the user - if (StringComparer.OrdinalIgnoreCase.Equals(remoteUri.Scheme, "http")) + if (!_context.Settings.AllowUnsafeRemotes && + StringComparer.OrdinalIgnoreCase.Equals(remoteUri.Scheme, "http")) { throw new Trace2Exception(_context.Trace2, - "Unencrypted HTTP is not supported for GitHub. Ensure the repository remote URL is using HTTPS."); + "Unencrypted HTTP is not recommended for GitHub. " + + "Ensure the repository remote URL is using HTTPS " + + $"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes."); } string service = GetServiceName(remoteUri); From f2652f3bad072ce8bcdcdf3615068a9cf815aeaa Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 7 Oct 2024 12:49:00 +0100 Subject: [PATCH 07/84] gitlab: support GCM_ALLOW_UNSAFE_REMOTES option --- src/shared/GitLab/GitLabHostProvider.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/shared/GitLab/GitLabHostProvider.cs b/src/shared/GitLab/GitLabHostProvider.cs index eda6e2f0f..6cda3c0e1 100644 --- a/src/shared/GitLab/GitLabHostProvider.cs +++ b/src/shared/GitLab/GitLabHostProvider.cs @@ -95,10 +95,13 @@ public override async Task GenerateCredentialAsync(InputArguments i ThrowIfDisposed(); // We should not allow unencrypted communication and should inform the user - if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")) + if (!Context.Settings.AllowUnsafeRemotes && + StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")) { throw new Trace2Exception(Context.Trace2, - "Unencrypted HTTP is not supported for GitHub. Ensure the repository remote URL is using HTTPS."); + "Unencrypted HTTP is not recommended for GitLab. " + + "Ensure the repository remote URL is using HTTPS " + + $"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes."); } Uri remoteUri = input.GetRemoteUri(); From 7a613f3c2b8064d973b8733411ee1262993cda39 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 7 Oct 2024 12:49:16 +0100 Subject: [PATCH 08/84] azrepos: support GCM_ALLOW_UNSAFE_REMOTES option --- .../AzureReposHostProvider.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs index 1d5c649d0..525704886 100644 --- a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs +++ b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs @@ -59,7 +59,7 @@ public bool IsSupported(InputArguments input) return false; } - // We do not support unencrypted HTTP communications to Azure Repos, + // We do not recommend unencrypted HTTP communications to Azure Repos, // but we report `true` here for HTTP so that we can show a helpful // error message for the user in `CreateCredentialAsync`. return input.TryGetHostAndPort(out string hostName, out _) @@ -208,16 +208,22 @@ protected override void ReleaseManagedResources() base.ReleaseManagedResources(); } - private async Task GeneratePersonalAccessTokenAsync(InputArguments input) + private void ThrowIfUnsafeRemote(InputArguments input) { - ThrowIfDisposed(); - - // We should not allow unencrypted communication and should inform the user - if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")) + if (!_context.Settings.AllowUnsafeRemotes && + StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")) { throw new Trace2Exception(_context.Trace2, - "Unencrypted HTTP is not supported for Azure Repos. Ensure the repository remote URL is using HTTPS."); + "Unencrypted HTTP is not recommended for Azure Repos. " + + "Ensure the repository remote URL is using HTTPS " + + $"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes."); } + } + + private async Task GeneratePersonalAccessTokenAsync(InputArguments input) + { + ThrowIfDisposed(); + ThrowIfUnsafeRemote(input); Uri remoteUserUri = input.GetRemoteUri(includeUser: true); Uri orgUri = UriHelpers.CreateOrganizationUri(remoteUserUri, out _); @@ -257,16 +263,11 @@ private async Task GeneratePersonalAccessTokenAsync(InputArguments private async Task GetAzureAccessTokenAsync(InputArguments input) { + ThrowIfUnsafeRemote(input); + Uri remoteWithUserUri = input.GetRemoteUri(includeUser: true); string userName = input.UserName; - // We should not allow unencrypted communication and should inform the user - if (StringComparer.OrdinalIgnoreCase.Equals(remoteWithUserUri.Scheme, "http")) - { - throw new Trace2Exception(_context.Trace2, - "Unencrypted HTTP is not supported for Azure Repos. Ensure the repository remote URL is using HTTPS."); - } - Uri orgUri = UriHelpers.CreateOrganizationUri(remoteWithUserUri, out string orgName); _context.Trace.WriteLine($"Determining Microsoft Authentication authority for Azure DevOps organization '{orgName}'..."); From fc067e8c00d95d1ac520d78744bb962829567552 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 7 Oct 2024 12:49:30 +0100 Subject: [PATCH 09/84] generic: support GCM_ALLOW_UNSAFE_REMOTES option Note that we only emit a warning for the generic host provider rather than failing-fast like the other providers do. This is because we never blocked HTTP remotes previously in the generic provider (which is often used for localhost, custom hosts, etc) and don't want to break any existing scenarios or scripts. The new option can be used to dismiss this warning message. --- src/shared/Core/GenericHostProvider.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/shared/Core/GenericHostProvider.cs b/src/shared/Core/GenericHostProvider.cs index 447e465d5..9f087ca5b 100644 --- a/src/shared/Core/GenericHostProvider.cs +++ b/src/shared/Core/GenericHostProvider.cs @@ -54,6 +54,17 @@ public override async Task GenerateCredentialAsync(InputArguments i { ThrowIfDisposed(); + // We only want to *warn* about HTTP remotes for the generic provider because it supports all protocols + // and, historically, we never blocked HTTP remotes in this provider. + // The user can always set the 'GCM_ALLOW_UNSAFE' setting to silence the warning. + if (!Context.Settings.AllowUnsafeRemotes && + StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")) + { + Context.Streams.Error.WriteLine( + "warning: use of unencrypted HTTP remote URLs is not recommended; " + + $"see {Constants.HelpUrls.GcmUnsafeRemotes} for more information."); + } + Uri uri = input.GetRemoteUri(); // Determine the if the host supports Windows Integration Authentication (WIA) or OAuth From 004b19e6046080b0191e74fd79ab6dfc9984f453 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 7 Oct 2024 14:51:51 +0200 Subject: [PATCH 10/84] docs: update Secret Service links The links to FreeDesktop's Secret Service specifications has changed, it would seem. Signed-off-by: Johannes Schindelin --- docs/configuration.md | 2 +- docs/credstores.md | 2 +- docs/environment.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a4fecf395..27939e701 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1022,7 +1022,7 @@ Defaults to disabled. [devbox]: https://azure.microsoft.com/en-us/products/dev-box [enterprise-config]: enterprise-config.md [envars]: environment.md -[freedesktop-ss]: https://specifications.freedesktop.org/secret-service/ +[freedesktop-ss]: https://specifications.freedesktop.org/secret-service-spec/ [gcm-allow-windowsauth]: environment.md#GCM_ALLOW_WINDOWSAUTH [gcm-authority]: environment.md#GCM_AUTHORITY-deprecated [gcm-autodetect-timeout]: environment.md#GCM_AUTODETECT_TIMEOUT diff --git a/docs/credstores.md b/docs/credstores.md index 157eaf930..2964f5645 100644 --- a/docs/credstores.md +++ b/docs/credstores.md @@ -257,7 +257,7 @@ that you take with you and use full-disk encryption. [cmdkey]: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/cmdkey [credential-store]: configuration.md#credentialcredentialstore [credential-cache]: https://git-scm.com/docs/git-credential-cache -[freedesktop-secret-service]: https://specifications.freedesktop.org/secret-service/ +[freedesktop-secret-service]: https://specifications.freedesktop.org/secret-service-spec/ [gcm-credential-store]: environment.md#GCM_CREDENTIAL_STORE [git-credential-store]: https://git-scm.com/docs/git-credential-store [mac-keychain-management]: https://support.apple.com/en-gb/guide/mac-help/mchlf375f392/mac diff --git a/docs/environment.md b/docs/environment.md index edda0d714..05c0ea8fd 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -1182,7 +1182,7 @@ Defaults to disabled. [credential-trace-msauth]: configuration.md#credentialtracemsauth [default-values]: enterprise-config.md [devbox]: https://azure.microsoft.com/en-us/products/dev-box -[freedesktop-ss]: https://specifications.freedesktop.org/secret-service/ +[freedesktop-ss]: https://specifications.freedesktop.org/secret-service-spec/ [gcm]: usage.md [gcm-interactive]: #gcm_interactive [gcm-credential-store]: #gcm_credential_store From 180a9e46f71792efea9898181c897fc151460f2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 20:52:26 +0000 Subject: [PATCH 11/84] build(deps): bump lycheeverse/lychee-action from 1.9.3 to 2.0.0 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.9.3 to 2.0.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/c053181aa0c3d17606addfe97a9075a32723548a...7da8ec1fc4e01b5a12062ac6c589c10a4ce70d67) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 4cf4c4b70..84bc2725b 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -35,7 +35,7 @@ jobs: - name: Run link checker # For any troubleshooting, see: # https://github.com/lycheeverse/lychee/blob/master/docs/TROUBLESHOOTING.md - uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a + uses: lycheeverse/lychee-action@7da8ec1fc4e01b5a12062ac6c589c10a4ce70d67 with: # user-agent: if a user agent is not specified, some websites (e.g. From 535ed76ec2eababbb92c8db6636dc28579150803 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:07:55 +0000 Subject: [PATCH 12/84] build(deps): bump lycheeverse/lychee-action from 2.0.0 to 2.0.2 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.0.0 to 2.0.2. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/7da8ec1fc4e01b5a12062ac6c589c10a4ce70d67...7cd0af4c74a61395d455af97419279d86aafaede) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 84bc2725b..3bbee52e8 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -35,7 +35,7 @@ jobs: - name: Run link checker # For any troubleshooting, see: # https://github.com/lycheeverse/lychee/blob/master/docs/TROUBLESHOOTING.md - uses: lycheeverse/lychee-action@7da8ec1fc4e01b5a12062ac6c589c10a4ce70d67 + uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede with: # user-agent: if a user agent is not specified, some websites (e.g. From a96afbb254675544a1f60cf7a6865b540785deac Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 15 Oct 2024 12:10:42 +0100 Subject: [PATCH 13/84] credstore: add no-op credential storage option Add a null/no-op credential store option that, as the name suggests, does nothing. This can be useful if the user wants to use another credential helper, configured in-front of GCM via Git, to store credentials. Example config: ```ini [credential] credentialStore = none helper = /bin/my-awesome-helper helper = /usr/local/bin/git-credential-manager ``` In this example, the `my-awesome-helper` will be consulted first to retrieve existing credentials before GCM, and will be asked to store any credentials generated by GCM. --- docs/configuration.md | 1 + docs/credstores.md | 26 ++++++++++++++++++++++++++ docs/environment.md | 1 + src/shared/Core/Constants.cs | 1 + src/shared/Core/CredentialStore.cs | 7 +++++++ src/shared/Core/NullCredentialStore.cs | 19 +++++++++++++++++++ 6 files changed, 55 insertions(+) create mode 100644 src/shared/Core/NullCredentialStore.cs diff --git a/docs/configuration.md b/docs/configuration.md index f843b839a..ba978ef30 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -585,6 +585,7 @@ _(unset)_|Windows: `wincredman`, macOS: `keychain`, Linux: _(none)_|- `gpg`|Use GPG to store encrypted files that are compatible with the [pass][pass] (requires GPG and `pass` to initialize the store).|macOS, Linux `cache`|Git's built-in [credential cache][credential-cache].|macOS, Linux `plaintext`|Store credentials in plaintext files (**UNSECURE**). Customize the plaintext store location with [`credential.plaintextStorePath`][credential-plaintextstorepath].|Windows, macOS, Linux +`none`|Do not store credentials via GCM.|Windows, macOS, Linux #### Example diff --git a/docs/credstores.md b/docs/credstores.md index 2964f5645..43811dc30 100644 --- a/docs/credstores.md +++ b/docs/credstores.md @@ -9,6 +9,7 @@ There are several options for storing credentials that GCM supports: - GPG/[`pass`][passwordstore] compatible files - Git's built-in [credential cache][credential-cache] - Plaintext files +- Passthrough/no-op (no credential store) The default credential stores on macOS and Windows are the macOS Keychain and the Windows Credential Manager, respectively. @@ -251,6 +252,31 @@ permissions on this directory such that no other users or applications can access files within. If possible, use a path that exists on an external volume that you take with you and use full-disk encryption. +## Passthrough/no-op (no credential store) + +**Available on:** _Windows, macOS, Linux_ + +**:warning: .** + +```batch +SET GCM_CREDENTIAL_STORE="none" +``` + +or + +```shell +git config --global credential.credentialStore none +``` + +This option disables the internal credential store. All operations to store or +retrieve credentials will do nothing, and will return success. This is useful if +you want to use a different credential store, chained in sequence via Git +configuration, and don't want GCM to store credentials. + +Note that you'll want to ensure that another credential helper is placed before +GCM in the `credential.helper` Git configuration or else you will be prompted to +enter your credentials every time you interact with a remote repository. + [access-windows-credential-manager]: https://support.microsoft.com/en-us/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0 [aws-cloudshell]: https://aws.amazon.com/cloudshell/ [azure-cloudshell]: https://docs.microsoft.com/azure/cloud-shell/overview diff --git a/docs/environment.md b/docs/environment.md index 1973132ea..f321caa6c 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -716,6 +716,7 @@ _(unset)_|Windows: `wincredman`, macOS: `keychain`, Linux: _(none)_|- `gpg`|Use GPG to store encrypted files that are compatible with the [`pass` utility][passwordstore] (requires GPG and `pass` to initialize the store).|macOS, Linux `cache`|Git's built-in [credential cache][git-credential-cache].|Windows, macOS, Linux `plaintext`|Store credentials in plaintext files (**UNSECURE**). Customize the plaintext store location with [`GCM_PLAINTEXT_STORE_PATH`][gcm-plaintext-store-path].|Windows, macOS, Linux +`none`|Do not store credentials via GCM.|Windows, macOS, Linux #### Windows diff --git a/src/shared/Core/Constants.cs b/src/shared/Core/Constants.cs index 41ccb990c..191fcc83d 100644 --- a/src/shared/Core/Constants.cs +++ b/src/shared/Core/Constants.cs @@ -38,6 +38,7 @@ public static class CredentialStoreNames public const string SecretService = "secretservice"; public const string Plaintext = "plaintext"; public const string Cache = "cache"; + public const string None = "none"; } public static class RegexPatterns diff --git a/src/shared/Core/CredentialStore.cs b/src/shared/Core/CredentialStore.cs index 83f915d1e..11dc83818 100644 --- a/src/shared/Core/CredentialStore.cs +++ b/src/shared/Core/CredentialStore.cs @@ -100,6 +100,10 @@ private void EnsureBackingStore() _backingStore = new PlaintextCredentialStore(_context.FileSystem, plainStoreRoot, ns); break; + case StoreNames.None: + _backingStore = new NullCredentialStore(); + break; + default: var sb = new StringBuilder(); sb.AppendLine(string.IsNullOrWhiteSpace(credStoreName) @@ -168,6 +172,9 @@ private static void AppendAvailableStoreList(StringBuilder sb) sb.AppendFormat(" {1,-13} : store credentials in plain-text files (UNSECURE){0}", Environment.NewLine, StoreNames.Plaintext); + + sb.AppendFormat(" {1, -13} : disable internal credential storage{0}", + Environment.NewLine, StoreNames.None); } private void ValidateWindowsCredentialManager() diff --git a/src/shared/Core/NullCredentialStore.cs b/src/shared/Core/NullCredentialStore.cs new file mode 100644 index 000000000..fac92f47c --- /dev/null +++ b/src/shared/Core/NullCredentialStore.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace GitCredentialManager; + +/// +/// Credential store that does nothing. This is useful when you want to disable internal credential storage +/// and only use another helper configured in Git to store credentials. +/// +public class NullCredentialStore : ICredentialStore +{ + public IList GetAccounts(string service) => Array.Empty(); + + public ICredential Get(string service, string account) => null; + + public void AddOrUpdate(string service, string account, string secret) { } + + public bool Remove(string service, string account) => false; +} From 178a7d0864733da357075f3a1419564b61a76cfd Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 21 Oct 2024 10:18:37 +0200 Subject: [PATCH 14/84] ci: move to "more official" tgagor/centos There used to be two separate images, `tgagor/centos` and `tgagor/centos-stream`, relating to the CentOS and the CentOS Stream distribution, respectively. However, CentOS ceased to exist, and CentOS Stream is the only remaining actively-maintained project of the two. As per https://hub.docker.com/r/tgagor/centos-stream: Moved to new repo I created new repo for both stream and non stream, variants. I push some images here, but it's better to switch to: https://hub.docker.com/r/tgagor/centos Essentially, the CentOS Stream images are now available as `tgagor/centos`. So let's drop the `tgagor/centos-stream` one. Signed-off-by: Johannes Schindelin --- .github/workflows/validate-install-from-source.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/validate-install-from-source.yml b/.github/workflows/validate-install-from-source.yml index ca57d2daf..4b67b00ae 100644 --- a/.github/workflows/validate-install-from-source.yml +++ b/.github/workflows/validate-install-from-source.yml @@ -21,7 +21,6 @@ jobs: # tgagor is a contributor who pushes updated images weekly, which should # be sufficient for our validation needs. - image: tgagor/centos - - image: tgagor/centos-stream - image: redhat/ubi8 - image: alpine - image: alpine:3.14.10 From 2dece79f0bf3c4d16da7b30833bd21fd271bee03 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 21 Oct 2024 10:48:02 +0200 Subject: [PATCH 15/84] install-from-source: avoid using `which` before it is installed The `which` executable must often be installed because it is missing from many a Docker image. Therefore, it won't _really_ work if one checks `which which` to figure out whether `which` is installed. Let's avoid this by using `type`, which is a shell builtin for most shells. The `type` utility is specified in the POSIX standard, as per https://pubs.opengroup.org/onlinepubs/9699919799/utilities/type.html, yet neither command-line options nor output is standardized. The only thing we _can_ rely on is the exit status. Note: _Technically_, this poses a change of behavior, as `which` resolves only to executables that are on the `PATH` while `type` will also happily report shell builtins. However, this is a net improvement: If running the script in, say, BusyBox, where many of the common utilities (including `which`!) are shell builtins, we would like to avoid forcefully installing the packages without need. Signed-off-by: Johannes Schindelin --- src/linux/Packaging.Linux/install-from-source.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/Packaging.Linux/install-from-source.sh b/src/linux/Packaging.Linux/install-from-source.sh index be6ea1579..40259eded 100755 --- a/src/linux/Packaging.Linux/install-from-source.sh +++ b/src/linux/Packaging.Linux/install-from-source.sh @@ -63,7 +63,7 @@ install_packages() { for package in $packages; do # Ensure we don't stomp on existing installations. - if [ ! -z $(which $package) ]; then + if type $package >/dev/null 2>&1; then continue fi From 89adecefada837d99c9c10a32be2a9c84a7fb9fc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 21 Oct 2024 10:56:17 +0200 Subject: [PATCH 16/84] install-from-source(mariner): awk is required to make dotnet-install.sh work The dotnet-install.sh script expects `awk` to be present, which is not installed by default in Mariner Linux. Signed-off-by: Johannes Schindelin --- src/linux/Packaging.Linux/install-from-source.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/Packaging.Linux/install-from-source.sh b/src/linux/Packaging.Linux/install-from-source.sh index 40259eded..0126d1ddf 100755 --- a/src/linux/Packaging.Linux/install-from-source.sh +++ b/src/linux/Packaging.Linux/install-from-source.sh @@ -228,7 +228,7 @@ case "$distribution" in $sudo_cmd tdnf update -y # Install dotnet/GCM dependencies. - install_packages tdnf install "curl git krb5-libs libicu openssl-libs zlib findutils which bash" + install_packages tdnf install "curl git krb5-libs libicu openssl-libs zlib findutils which bash awk" ensure_dotnet_installed ;; From 7b721ea32fab8dadcebe0c988c74f9dc0a67742b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 21 Oct 2024 14:50:48 +0200 Subject: [PATCH 17/84] install-from-source(mariner): ensure that CA certificates are installed This seems to be necessary to avoid problems with the `curl` calls when `dotnet-install.sh` tries to download the `dotnet-sdk` TAR archive: dotnet-install: Attempting to download using aka.ms link https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.403/dotnet-sdk-8.0.403-linux-x64.tar.gz curl: (60) SSL certificate problem: unable to get local issuer certificate More details here: https://curl.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above. Signed-off-by: Johannes Schindelin --- src/linux/Packaging.Linux/install-from-source.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/Packaging.Linux/install-from-source.sh b/src/linux/Packaging.Linux/install-from-source.sh index 0126d1ddf..8cf60251c 100755 --- a/src/linux/Packaging.Linux/install-from-source.sh +++ b/src/linux/Packaging.Linux/install-from-source.sh @@ -228,7 +228,7 @@ case "$distribution" in $sudo_cmd tdnf update -y # Install dotnet/GCM dependencies. - install_packages tdnf install "curl git krb5-libs libicu openssl-libs zlib findutils which bash awk" + install_packages tdnf install "curl ca-certificates git krb5-libs libicu openssl-libs zlib findutils which bash awk" ensure_dotnet_installed ;; From 41a26cf6dc4f5459835df6cdad0b6d6a43e81502 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 21 Oct 2024 10:25:14 +0200 Subject: [PATCH 18/84] ci: also verify that installation works on Mariner and Arch Linux These currently work, too, and we probably want to keep it that way. Signed-off-by: Johannes Schindelin --- .github/workflows/validate-install-from-source.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/validate-install-from-source.yml b/.github/workflows/validate-install-from-source.yml index 4b67b00ae..2b1fd7696 100644 --- a/.github/workflows/validate-install-from-source.yml +++ b/.github/workflows/validate-install-from-source.yml @@ -27,6 +27,8 @@ jobs: - image: opensuse/leap - image: opensuse/tumbleweed - image: registry.suse.com/suse/sle15:15.4.27.11.31 + - image: archlinux + - image: mcr.microsoft.com/cbl-mariner/base/core:2.0 container: ${{matrix.vector.image}} steps: - run: | @@ -34,6 +36,9 @@ jobs: zypper -n install tar gzip elif [[ ${{matrix.vector.image}} == *"centos"* ]]; then dnf install which -y + elif [[ ${{matrix.vector.image}} == *"mariner"* ]]; then + GNUPGHOME=/root/.gnupg tdnf update -y && + GNUPGHOME=/root/.gnupg tdnf install tar -y # needed for `actions/checkout` fi - uses: actions/checkout@v4 From 4431516e718e9a4448ec1d14002e2d45030f95e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:38:55 +0000 Subject: [PATCH 19/84] build(deps): bump azure/trusted-signing-action from 0.4.0 to 0.5.0 Bumps [azure/trusted-signing-action](https://github.com/azure/trusted-signing-action) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/azure/trusted-signing-action/releases) - [Commits](https://github.com/azure/trusted-signing-action/compare/v0.4.0...v0.5.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 9f578e109..50c08c793 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.4.0 + uses: azure/trusted-signing-action@v0.5.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.4.0 + uses: azure/trusted-signing-action@v0.5.0 with: endpoint: https://wus2.codesigning.azure.net/ trusted-signing-account-name: git-fundamentals-signing From ca7a0d6839ef5f247a293d9fbdca06d831e2c654 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:08:32 +0000 Subject: [PATCH 20/84] build(deps): bump actions/setup-dotnet from 4.0.1 to 4.1.0 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.0.1 to 4.1.0. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4.0.1...v4.1.0) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-minor ... 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 745027d8b..8ce40ad8b 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.1 + uses: actions/setup-dotnet@v4.1.0 with: dotnet-version: 8.0.x diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 27834c10e..610574f80 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.1 + uses: actions/setup-dotnet@v4.1.0 with: dotnet-version: 8.0.x @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.0.1 + uses: actions/setup-dotnet@v4.1.0 with: dotnet-version: 8.0.x @@ -100,7 +100,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.0.1 + uses: actions/setup-dotnet@v4.1.0 with: dotnet-version: 8.0.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f578e109..58db506ca 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.1 + uses: actions/setup-dotnet@v4.1.0 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.1 + uses: actions/setup-dotnet@v4.1.0 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.1 + uses: actions/setup-dotnet@v4.1.0 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.1 + uses: actions/setup-dotnet@v4.1.0 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.1 + uses: actions/setup-dotnet@v4.1.0 with: dotnet-version: 8.0.x @@ -387,7 +387,7 @@ jobs: path: signed - name: Set up .NET - uses: actions/setup-dotnet@v4.0.1 + uses: actions/setup-dotnet@v4.1.0 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.1 + uses: actions/setup-dotnet@v4.1.0 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.1 + uses: actions/setup-dotnet@v4.1.0 with: dotnet-version: 8.0.x From 557937a919dd358bb3db01ab361b2cf92f26bbc5 Mon Sep 17 00:00:00 2001 From: JaoSchmidt Date: Sun, 27 Oct 2024 22:41:57 -0300 Subject: [PATCH 21/84] fix wrong bash if-else syntax --- src/linux/Packaging.Linux/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/Packaging.Linux/build.sh b/src/linux/Packaging.Linux/build.sh index 6672857d2..9db7ad5e3 100755 --- a/src/linux/Packaging.Linux/build.sh +++ b/src/linux/Packaging.Linux/build.sh @@ -41,7 +41,7 @@ esac done # Ensure install prefix exists -if [! -d "$INSTALL_PREFIX" ]; then +if [ ! -d "$INSTALL_PREFIX" ]; then mkdir -p "$INSTALL_PREFIX" fi From 61e4fa4c328c232445618dd4ae7a0c5f31776fc5 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 29 Oct 2024 11:31:13 +0000 Subject: [PATCH 22/84] streams: only consider LF and CRLF as newlines Git only considers LF (`\n`) and CRLF (`\r\n`) as valid line endings for the Git credential protocol. Lone carriage-returns should be treated as part of the value, and not a delimiter in the credential protocol. Override the behaviour of the standard `StreamReader`'s `ReadLineAsync` method to only break on LF or CRLF, in alignment with Git. Note that we also override the non-async `ReadLine` method too as this is also implemented separatley in the base class from the async version. We must also make allowances for .NET Framework where the override of `ReadLineAsync` that takes a `CancellationToken` does not exist. --- src/shared/Core.Tests/GitStreamReaderTests.cs | 193 ++++++++++++++++++ src/shared/Core/GitStreamReader.cs | 70 +++++++ src/shared/Core/StandardStreams.cs | 2 +- 3 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 src/shared/Core.Tests/GitStreamReaderTests.cs create mode 100644 src/shared/Core/GitStreamReader.cs diff --git a/src/shared/Core.Tests/GitStreamReaderTests.cs b/src/shared/Core.Tests/GitStreamReaderTests.cs new file mode 100644 index 000000000..bf656d102 --- /dev/null +++ b/src/shared/Core.Tests/GitStreamReaderTests.cs @@ -0,0 +1,193 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace GitCredentialManager.Tests; + +public class GitStreamReaderTests +{ + #region ReadLineAsync + + [Fact] + public async Task GitStreamReader_ReadLineAsync_LF() + { + // hello\n + // world\n + + byte[] buffer = Encoding.UTF8.GetBytes("hello\nworld\n"); + using var stream = new MemoryStream(buffer); + var reader = new GitStreamReader(stream, Encoding.UTF8); + + string actual1 = await reader.ReadLineAsync(); + string actual2 = await reader.ReadLineAsync(); + string actual3 = await reader.ReadLineAsync(); + + Assert.Equal("hello", actual1); + Assert.Equal("world", actual2); + Assert.Null(actual3); + } + + [Fact] + public async Task GitStreamReader_ReadLineAsync_CR() + { + // hello\rworld\r + + byte[] buffer = Encoding.UTF8.GetBytes("hello\rworld\r"); + using var stream = new MemoryStream(buffer); + var reader = new GitStreamReader(stream, Encoding.UTF8); + + string actual1 = await reader.ReadLineAsync(); + string actual2 = await reader.ReadLineAsync(); + + Assert.Equal("hello\rworld\r", actual1); + Assert.Null(actual2); + } + + [Fact] + public async Task GitStreamReader_ReadLineAsync_CRLF() + { + // hello\r\n + // world\r\n + + byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\r\n"); + using var stream = new MemoryStream(buffer); + var reader = new GitStreamReader(stream, Encoding.UTF8); + + string actual1 = await reader.ReadLineAsync(); + string actual2 = await reader.ReadLineAsync(); + string actual3 = await reader.ReadLineAsync(); + + Assert.Equal("hello", actual1); + Assert.Equal("world", actual2); + Assert.Null(actual3); + } + + [Fact] + public async Task GitStreamReader_ReadLineAsync_Mixed() + { + // hello\r\n + // world\rthis\n + // is\n + // a\n + // \rmixed\rnewline\r\n + // \n + // string\n + + byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\rthis\nis\na\n\rmixed\rnewline\r\n\nstring\n"); + using var stream = new MemoryStream(buffer); + var reader = new GitStreamReader(stream, Encoding.UTF8); + + string actual1 = await reader.ReadLineAsync(); + string actual2 = await reader.ReadLineAsync(); + string actual3 = await reader.ReadLineAsync(); + string actual4 = await reader.ReadLineAsync(); + string actual5 = await reader.ReadLineAsync(); + string actual6 = await reader.ReadLineAsync(); + string actual7 = await reader.ReadLineAsync(); + string actual8 = await reader.ReadLineAsync(); + + Assert.Equal("hello", actual1); + Assert.Equal("world\rthis", actual2); + Assert.Equal("is", actual3); + Assert.Equal("a", actual4); + Assert.Equal("\rmixed\rnewline", actual5); + Assert.Equal("", actual6); + Assert.Equal("string", actual7); + Assert.Null(actual8); + } + + #endregion + + #region ReadLine + + [Fact] + public void GitStreamReader_ReadLine_LF() + { + // hello\n + // world\n + + byte[] buffer = Encoding.UTF8.GetBytes("hello\nworld\n"); + using var stream = new MemoryStream(buffer); + var reader = new GitStreamReader(stream, Encoding.UTF8); + + string actual1 = reader.ReadLine(); + string actual2 = reader.ReadLine(); + string actual3 = reader.ReadLine(); + + Assert.Equal("hello", actual1); + Assert.Equal("world", actual2); + Assert.Null(actual3); + } + + [Fact] + public void GitStreamReader_ReadLine_CR() + { + // hello\rworld\r + + byte[] buffer = Encoding.UTF8.GetBytes("hello\rworld\r"); + using var stream = new MemoryStream(buffer); + var reader = new GitStreamReader(stream, Encoding.UTF8); + + string actual1 = reader.ReadLine(); + string actual2 = reader.ReadLine(); + + Assert.Equal("hello\rworld\r", actual1); + Assert.Null(actual2); + } + + [Fact] + public void GitStreamReader_ReadLine_CRLF() + { + // hello\r\n + // world\r\n + + byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\r\n"); + using var stream = new MemoryStream(buffer); + var reader = new GitStreamReader(stream, Encoding.UTF8); + + string actual1 = reader.ReadLine(); + string actual2 = reader.ReadLine(); + string actual3 = reader.ReadLine(); + + Assert.Equal("hello", actual1); + Assert.Equal("world", actual2); + Assert.Null(actual3); + } + + [Fact] + public void GitStreamReader_ReadLine_Mixed() + { + // hello\r\n + // world\rthis\n + // is\n + // a\n + // \rmixed\rnewline\r\n + // \n + // string\n + + byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\rthis\nis\na\n\rmixed\rnewline\r\n\nstring\n"); + using var stream = new MemoryStream(buffer); + var reader = new GitStreamReader(stream, Encoding.UTF8); + + string actual1 = reader.ReadLine(); + string actual2 = reader.ReadLine(); + string actual3 = reader.ReadLine(); + string actual4 = reader.ReadLine(); + string actual5 = reader.ReadLine(); + string actual6 = reader.ReadLine(); + string actual7 = reader.ReadLine(); + string actual8 = reader.ReadLine(); + + Assert.Equal("hello", actual1); + Assert.Equal("world\rthis", actual2); + Assert.Equal("is", actual3); + Assert.Equal("a", actual4); + Assert.Equal("\rmixed\rnewline", actual5); + Assert.Equal("", actual6); + Assert.Equal("string", actual7); + Assert.Null(actual8); + } + + #endregion +} diff --git a/src/shared/Core/GitStreamReader.cs b/src/shared/Core/GitStreamReader.cs new file mode 100644 index 000000000..6512b2efc --- /dev/null +++ b/src/shared/Core/GitStreamReader.cs @@ -0,0 +1,70 @@ +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace GitCredentialManager; + +/// +/// StreamReader that does NOT consider a lone carriage-return as a new-line character, +/// only a line-feed or carriage-return immediately followed by a line-feed. +/// +/// The only major operating system that uses a lone carriage-return as a new-line character +/// is the classic Macintosh OS (before OS X), which is not supported by Git. +/// +public class GitStreamReader : StreamReader +{ + public GitStreamReader(Stream stream, Encoding encoding) : base(stream, encoding) { } + + public override string ReadLine() + { +#if NETFRAMEWORK + return ReadLineAsync().ConfigureAwait(false).GetAwaiter().GetResult(); +#else + return ReadLineAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); +#endif + } + +#if NETFRAMEWORK + public override async Task ReadLineAsync() +#else + public override async ValueTask ReadLineAsync(CancellationToken cancellationToken) +#endif + { + int nr; + var sb = new StringBuilder(); + var buffer = new char[1]; + bool lastWasCR = false; + + while ((nr = await base.ReadAsync(buffer, 0, 1).ConfigureAwait(false)) > 0) + { + char c = buffer[0]; + + // Only treat a line-feed as a new-line character. + // Carriage-returns alone are NOT considered new-line characters. + if (c == '\n') + { + if (lastWasCR) + { + // If the last character was a carriage-return we should remove it from the string builder + // since together with this line-feed it is considered a new-line character. + sb.Length--; + } + + // We have a new-line character, so we should stop reading. + break; + } + + lastWasCR = c == '\r'; + + sb.Append(c); + } + + if (sb.Length == 0 && nr == 0) + { + return null; + } + + return sb.ToString(); + } +} diff --git a/src/shared/Core/StandardStreams.cs b/src/shared/Core/StandardStreams.cs index d0b3042b0..45f9f6cc7 100644 --- a/src/shared/Core/StandardStreams.cs +++ b/src/shared/Core/StandardStreams.cs @@ -39,7 +39,7 @@ public TextReader In { if (_stdIn == null) { - _stdIn = new StreamReader(Console.OpenStandardInput(), EncodingEx.UTF8NoBom); + _stdIn = new GitStreamReader(Console.OpenStandardInput(), EncodingEx.UTF8NoBom); } return _stdIn; From 99e2f7f60e7364fe807e7925f361a81f3c47bd1b Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 30 Oct 2024 09:50:25 +0000 Subject: [PATCH 23/84] release.yml: use gatewatcher mac app certificate Use Gatewatcher to provision the Application certificate and password secrets in our release workflow. --- .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 3a2e708ca..59e7c8bc1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,8 +57,8 @@ jobs: - name: Set up signing/notarization infrastructure env: - A1: ${{ secrets.APPLICATION_CERTIFICATE_BASE64 }} - A2: ${{ secrets.APPLICATION_CERTIFICATE_PASSWORD }} + A1: ${{ secrets.GATEWATCHER_DEVELOPER_ID_CERT }} + A2: ${{ secrets.GATEWATCHER_DEVELOPER_ID_PASSWORD }} I1: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }} I2: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }} N1: ${{ secrets.APPLE_TEAM_ID }} From 786ab03440ddc82e807a97c0e540f5247e44cec6 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 30 Oct 2024 09:52:19 +0000 Subject: [PATCH 24/84] VERSION: bump to 2.6.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 82f00d533..cfad4122e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.0.0 +2.6.1.0 From 628acd0e500eee6506590ce1dc7f7b8f681cb54c Mon Sep 17 00:00:00 2001 From: theofficialgman <28281419+theofficialgman@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:38:31 -0400 Subject: [PATCH 25/84] add support for Linux arm64 and Linux arm Co-Authored-By: Matthew John Cheetham --- .github/workflows/continuous-integration.yml | 10 ++++- .github/workflows/release.yml | 38 ++++++++++++------- README.md | 4 +- docs/development.md | 6 +++ .../Packaging.Linux/Packaging.Linux.csproj | 4 +- src/linux/Packaging.Linux/build.sh | 32 ++++++++++++++-- src/linux/Packaging.Linux/layout.sh | 9 ++++- src/linux/Packaging.Linux/pack.sh | 33 ++++++++++++---- .../Git-Credential-Manager.csproj | 2 +- 9 files changed, 105 insertions(+), 33 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1b83a990d..7f7c28a9a 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -54,6 +54,9 @@ jobs: linux: name: Linux runs-on: ubuntu-latest + strategy: + matrix: + runtime: [ linux-x64, linux-arm64, linux-arm ] steps: - uses: actions/checkout@v4 @@ -67,7 +70,10 @@ jobs: run: dotnet restore - name: Build - run: dotnet build --configuration LinuxRelease + run: | + dotnet build src/linux/Packaging.Linux/*.csproj \ + --configuration=Release --no-self-contained \ + --runtime=${{ matrix.runtime }} - name: Test run: | @@ -82,7 +88,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: linux-x64 + name: ${{ matrix.runtime }} path: | artifacts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 28858d7c1..b8a2560af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -232,6 +232,9 @@ jobs: runs-on: ubuntu-latest environment: release needs: prereqs + strategy: + matrix: + runtime: [ linux-x64, linux-arm64, linux-arm ] steps: - uses: actions/checkout@v4 @@ -241,7 +244,10 @@ jobs: dotnet-version: 8.0.x - name: Build - run: dotnet build --configuration=LinuxRelease + run: | + dotnet build src/linux/Packaging.Linux/*.csproj \ + --configuration=LinuxRelease --no-self-contained \ + --runtime=${{ matrix.runtime }} - name: Run Linux unit tests run: | @@ -286,18 +292,18 @@ jobs: run: | # Sign Debian package version=${{ needs.prereqs.outputs.version }} - mv out/linux/Packaging.Linux/Release/deb/gcm-linux_amd64.$version.deb . - debsigs --sign=origin --verify --check gcm-linux_amd64.$version.deb + mv out/linux/Packaging.Linux/Release/deb/gcm-${{ matrix.runtime }}.$version.deb . + debsigs --sign=origin --verify --check gcm-${{ matrix.runtime }}.$version.deb # Generate tarball signature file mv -v out/linux/Packaging.Linux/Release/tar/* . - gpg --batch --yes --armor --output gcm-linux_amd64.$version.tar.gz.asc \ - --detach-sig gcm-linux_amd64.$version.tar.gz + gpg --batch --yes --armor --output gcm-${{ matrix.runtime }}.$version.tar.gz.asc \ + --detach-sig gcm-${{ matrix.runtime }}.$version.tar.gz - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: linux-artifacts + name: ${{ matrix.runtime }}-artifacts path: | ./*.deb ./*.asc @@ -486,9 +492,9 @@ jobs: matrix: component: - os: ubuntu-latest - artifact: linux-artifacts + artifact: linux-x64-artifacts command: git-credential-manager - description: linux + description: linux-x64 - os: macos-latest artifact: macos-osx-x64-artifacts command: git-credential-manager @@ -530,15 +536,15 @@ jobs: Start-Process -Wait -FilePath "$exePath" -ArgumentList "/SILENT /VERYSILENT /NORESTART" } - - name: Install Linux (Debian package) - if: contains(matrix.component.description, 'linux') + - name: Install Linux x64 (Debian package) + if: contains(matrix.component.description, 'linux-x64') run: | debpath=$(find ./*.deb) sudo apt install $debpath "${{ matrix.component.command }}" configure - - name: Install Linux (tarball) - if: contains(matrix.component.description, 'linux') + - name: Install Linux x64 (tarball) + if: contains(matrix.component.description, 'linux-x64') run: | # Ensure we find only the source tarball, not the symbols tarpath=$(find . -name '*[[:digit:]].tar.gz') @@ -618,7 +624,9 @@ jobs: az keyvault secret show --name "$GPG_PUBLIC_KEY_SECRET_NAME" \ --vault-name "$AZURE_VAULT" --query "value" \ | sed -e 's/^"//' -e 's/"$//' | base64 -d >gcm-public.asc - mv gcm-public.asc linux-artifacts + cp gcm-public.asc linux-x64-artifacts/ + cp gcm-public.asc linux-arm64-artifacts/ + mv gcm-public.asc linux-arm-artifacts - uses: actions/github-script@v7 with: @@ -675,7 +683,9 @@ jobs: uploadDirectoryToRelease('osx-payload-and-symbols'), // Upload Linux artifacts - uploadDirectoryToRelease('linux-artifacts'), + uploadDirectoryToRelease('linux-x64-artifacts'), + uploadDirectoryToRelease('linux-arm64-artifacts'), + uploadDirectoryToRelease('linux-arm-artifacts'), // Upload .NET tool package uploadDirectoryToRelease('dotnet-tool-sign'), diff --git a/README.md b/README.md index 18c9b1309..6c6aa1535 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ Basic HTTP authentication support|✓|✓|✓ Proxy support|✓|✓|✓ `amd64` support|✓|✓|✓ `x86` support|✓|_N/A_|✗ -`arm64` support|best effort|✓|best effort, no packages -`armhf` support|_N/A_|_N/A_|best effort, no packages +`arm64` support|best effort|✓|✓ +`armhf` support|_N/A_|_N/A_|✓ (\*) GCM guarantees support only for [the Linux distributions that are officially supported by dotnet][dotnet-distributions]. diff --git a/docs/development.md b/docs/development.md index 7729556f9..0242d68b8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -54,6 +54,12 @@ To build from the command line, run: dotnet build -c LinuxDebug ``` +If you want to build for a specific architecture, you can provide `linux-x64` or `linux-arm64` or `linux-arm` as the runtime: + +```shell +dotnet build -c LinuxDebug -r linux-arm64 +``` + You can find a copy of the Debian package (.deb) file in `out/linux/Packaging.Linux/deb/Debug`. The flat binaries can also be found in `out/linux/Packaging.Linux/payload/Debug`. diff --git a/src/linux/Packaging.Linux/Packaging.Linux.csproj b/src/linux/Packaging.Linux/Packaging.Linux.csproj index 8b9755c78..ddfb31500 100644 --- a/src/linux/Packaging.Linux/Packaging.Linux.csproj +++ b/src/linux/Packaging.Linux/Packaging.Linux.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/src/linux/Packaging.Linux/build.sh b/src/linux/Packaging.Linux/build.sh index 6672857d2..88f1b0359 100755 --- a/src/linux/Packaging.Linux/build.sh +++ b/src/linux/Packaging.Linux/build.sh @@ -30,6 +30,10 @@ case "$i" in INSTALL_FROM_SOURCE="${i#*=}" shift # past argument=value ;; + --runtime=*) + RUNTIME="${i#*=}" + shift # past argument=value + ;; --install-prefix=*) INSTALL_PREFIX="${i#*=}" shift # past argument=value @@ -41,10 +45,32 @@ esac done # Ensure install prefix exists -if [! -d "$INSTALL_PREFIX" ]; then +if [ ! -d "$INSTALL_PREFIX" ]; then mkdir -p "$INSTALL_PREFIX" fi +# Fall back to host architecture if no explicit runtime is given. +if test -z "$RUNTIME"; then + HOST_ARCH="`dpkg-architecture -q DEB_HOST_ARCH`" + + case $HOST_ARCH in + amd64) + RUNTIME="linux-x64" + ;; + arm64) + RUNTIME="linux-arm64" + ;; + armhf) + RUNTIME="linux-arm" + ;; + *) + die "Could not determine host architecture!" + ;; + esac +fi + +echo "Building for runtime ${RUNTIME}" + # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" if [ -z "$VERSION" ]; then @@ -56,7 +82,7 @@ PAYLOAD="$OUTDIR/payload" SYMBOLS="$OUTDIR/payload.sym" # Lay out payload -"$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" || exit 1 +"$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" --runtime="$RUNTIME" || exit 1 if [ $INSTALL_FROM_SOURCE = true ]; then echo "Installing to $INSTALL_PREFIX" @@ -79,7 +105,7 @@ if [ $INSTALL_FROM_SOURCE = true ]; then echo "Install complete." else # Pack - "$INSTALLER_SRC/pack.sh" --configuration="$CONFIGURATION" --payload="$PAYLOAD" --symbols="$SYMBOLS" --version="$VERSION" || exit 1 + "$INSTALLER_SRC/pack.sh" --configuration="$CONFIGURATION" --runtime="$RUNTIME" --payload="$PAYLOAD" --symbols="$SYMBOLS" --version="$VERSION" || exit 1 fi echo "Build of Packaging.Linux complete." diff --git a/src/linux/Packaging.Linux/layout.sh b/src/linux/Packaging.Linux/layout.sh index 6679c39ca..9355eaa02 100755 --- a/src/linux/Packaging.Linux/layout.sh +++ b/src/linux/Packaging.Linux/layout.sh @@ -23,6 +23,10 @@ case "$i" in CONFIGURATION="${i#*=}" shift # past argument=value ;; + --runtime=*) + RUNTIME="${i#*=}" + shift # past argument=value + ;; *) # unknown option ;; @@ -39,7 +43,10 @@ PROJ_OUT="$OUT/linux/Packaging.Linux" # Build parameters FRAMEWORK=net8.0 -RUNTIME=linux-x64 + +if [ -z "$RUNTIME" ]; then + die "--runtime was not set" +fi # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" diff --git a/src/linux/Packaging.Linux/pack.sh b/src/linux/Packaging.Linux/pack.sh index 14d26aee5..817704f76 100755 --- a/src/linux/Packaging.Linux/pack.sh +++ b/src/linux/Packaging.Linux/pack.sh @@ -28,6 +28,10 @@ case "$i" in SYMBOLS="${i#*=}" shift # past argument=value ;; + --runtime=*) + RUNTIME="${i#*=}" + shift # past argument=value + ;; --configuration=*) CONFIGURATION="${i#*=}" shift # past argument=value @@ -51,20 +55,17 @@ fi if [ -z "$SYMBOLS" ]; then die "--symbols was not set" fi - -ARCH="`dpkg-architecture -q DEB_HOST_ARCH`" - -if test -z "$ARCH"; then - die "Could not determine host architecture!" +if [ -z "$RUNTIME" ]; then + die "--runtime was not set" fi TAROUT="$PROJ_OUT/$CONFIGURATION/tar/" -TARBALL="$TAROUT/gcm-linux_$ARCH.$VERSION.tar.gz" -SYMTARBALL="$TAROUT/gcm-linux_$ARCH.$VERSION-symbols.tar.gz" +TARBALL="$TAROUT/gcm-$RUNTIME.$VERSION.tar.gz" +SYMTARBALL="$TAROUT/gcm-$RUNTIME.$VERSION-symbols.tar.gz" DEBOUT="$PROJ_OUT/$CONFIGURATION/deb" DEBROOT="$DEBOUT/root" -DEBPKG="$DEBOUT/gcm-linux_$ARCH.$VERSION.deb" +DEBPKG="$DEBOUT/gcm-$RUNTIME.$VERSION.deb" mkdir -p "$DEBROOT" # Set full read, write, execute permissions for owner and just read and execute permissions for group and other @@ -99,6 +100,22 @@ INSTALL_TO="$DEBROOT/usr/local/share/gcm-core/" LINK_TO="$DEBROOT/usr/local/bin/" mkdir -p "$DEBROOT/DEBIAN" "$INSTALL_TO" "$LINK_TO" || exit 1 +# Determine architecture for debian control file from the runtime architecture +case $RUNTIME in + linux-x64) + ARCH="amd64" + ;; + linux-arm64) + ARCH="arm64" + ;; + linux-arm) + ARCH="armhf" + ;; + *) + die "Incompatible runtime architecture given for pack.sh" + ;; +esac + # make the debian control file # this is purposefully not indented, see # https://stackoverflow.com/questions/9349616/bash-eof-in-if-statement diff --git a/src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj b/src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj index 2b594e3eb..456adf547 100644 --- a/src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj +++ b/src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj @@ -4,7 +4,7 @@ Exe net8.0 net472;net8.0 - win-x86;osx-x64;linux-x64;osx-arm64 + win-x86;osx-x64;linux-x64;osx-arm64;linux-arm64;linux-arm x86 git-credential-manager GitCredentialManager From 5cd01b6e7aeccc05afdc17eae2104e8cf7a2c67b Mon Sep 17 00:00:00 2001 From: theofficialgman <28281419+theofficialgman@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:17:18 -0500 Subject: [PATCH 26/84] don't require runtime to be set to install from source --- src/linux/Packaging.Linux/build.sh | 22 ++-------------------- src/linux/Packaging.Linux/layout.sh | 27 ++++++++++++++++----------- src/linux/Packaging.Linux/pack.sh | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/linux/Packaging.Linux/build.sh b/src/linux/Packaging.Linux/build.sh index 88f1b0359..62352a7e8 100755 --- a/src/linux/Packaging.Linux/build.sh +++ b/src/linux/Packaging.Linux/build.sh @@ -49,28 +49,10 @@ if [ ! -d "$INSTALL_PREFIX" ]; then mkdir -p "$INSTALL_PREFIX" fi -# Fall back to host architecture if no explicit runtime is given. -if test -z "$RUNTIME"; then - HOST_ARCH="`dpkg-architecture -q DEB_HOST_ARCH`" - - case $HOST_ARCH in - amd64) - RUNTIME="linux-x64" - ;; - arm64) - RUNTIME="linux-arm64" - ;; - armhf) - RUNTIME="linux-arm" - ;; - *) - die "Could not determine host architecture!" - ;; - esac +if [ ! -z "$RUNTIME" ]; then + echo "Building for runtime ${RUNTIME}" fi -echo "Building for runtime ${RUNTIME}" - # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" if [ -z "$VERSION" ]; then diff --git a/src/linux/Packaging.Linux/layout.sh b/src/linux/Packaging.Linux/layout.sh index 9355eaa02..ccf031156 100755 --- a/src/linux/Packaging.Linux/layout.sh +++ b/src/linux/Packaging.Linux/layout.sh @@ -44,10 +44,6 @@ PROJ_OUT="$OUT/linux/Packaging.Linux" # Build parameters FRAMEWORK=net8.0 -if [ -z "$RUNTIME" ]; then - die "--runtime was not set" -fi - # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" @@ -76,13 +72,22 @@ fi # Publish core application executables echo "Publishing core application..." -$DOTNET_ROOT/dotnet publish "$GCM_SRC" \ - --configuration="$CONFIGURATION" \ - --framework="$FRAMEWORK" \ - --runtime="$RUNTIME" \ - --self-contained \ - -p:PublishSingleFile=true \ - --output="$(make_absolute "$PAYLOAD")" || exit 1 +if [ -z "$RUNTIME" ]; then + $DOTNET_ROOT/dotnet publish "$GCM_SRC" \ + --configuration="$CONFIGURATION" \ + --framework="$FRAMEWORK" \ + --self-contained \ + -p:PublishSingleFile=true \ + --output="$(make_absolute "$PAYLOAD")" || exit 1 +else + $DOTNET_ROOT/dotnet publish "$GCM_SRC" \ + --configuration="$CONFIGURATION" \ + --framework="$FRAMEWORK" \ + --runtime="$RUNTIME" \ + --self-contained \ + -p:PublishSingleFile=true \ + --output="$(make_absolute "$PAYLOAD")" || exit 1 +fi # Collect symbols echo "Collecting managed symbols..." diff --git a/src/linux/Packaging.Linux/pack.sh b/src/linux/Packaging.Linux/pack.sh index 817704f76..4cf5aaea7 100755 --- a/src/linux/Packaging.Linux/pack.sh +++ b/src/linux/Packaging.Linux/pack.sh @@ -100,6 +100,26 @@ INSTALL_TO="$DEBROOT/usr/local/share/gcm-core/" LINK_TO="$DEBROOT/usr/local/bin/" mkdir -p "$DEBROOT/DEBIAN" "$INSTALL_TO" "$LINK_TO" || exit 1 +# Fall back to host architecture if no explicit runtime is given. +if test -z "$RUNTIME"; then + HOST_ARCH="`dpkg-architecture -q DEB_HOST_ARCH`" + + case $HOST_ARCH in + amd64) + RUNTIME="linux-x64" + ;; + arm64) + RUNTIME="linux-arm64" + ;; + armhf) + RUNTIME="linux-arm" + ;; + *) + die "Could not determine host architecture!" + ;; + esac +fi + # Determine architecture for debian control file from the runtime architecture case $RUNTIME in linux-x64) From 47b731ed05ce3e927a17b11a19aa199bd8e28bef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:10:35 +0000 Subject: [PATCH 27/84] build(deps): bump lycheeverse/lychee-action from 2.0.2 to 2.1.0 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.0.2 to 2.1.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/7cd0af4c74a61395d455af97419279d86aafaede...f81112d0d2814ded911bd23e3beaa9dda9093915) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 7146eb679..dce42cd58 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -35,7 +35,7 @@ jobs: - name: Run link checker # For any troubleshooting, see: # https://github.com/lycheeverse/lychee/blob/master/docs/TROUBLESHOOTING.md - uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede + uses: lycheeverse/lychee-action@f81112d0d2814ded911bd23e3beaa9dda9093915 with: # user-agent: if a user agent is not specified, some websites (e.g. From b378f2a63637ae647e60d04035c34e8b3dd544e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 20:06:34 +0000 Subject: [PATCH 28/84] build(deps): bump DavidAnson/markdownlint-cli2-action Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 17.0.0 to 18.0.0. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/db43aef879112c3119a410d69f66701e0d530809...eb5ca3ab411449c66620fe7f1b3c9e10547144b0) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 7146eb679..afb1688d2 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: DavidAnson/markdownlint-cli2-action@db43aef879112c3119a410d69f66701e0d530809 + - uses: DavidAnson/markdownlint-cli2-action@eb5ca3ab411449c66620fe7f1b3c9e10547144b0 with: globs: | "**/*.md" From 235d63649e820a26cb1c8d8f028b790a847d51fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:14:20 +0000 Subject: [PATCH 29/84] build(deps): bump actions/setup-dotnet from 4.1.0 to 4.2.0 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4.1.0...v4.2.0) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-minor ... 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 8ce40ad8b..9f8170d53 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.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index b0ffeb2d8..b8da48e7b 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.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x @@ -62,7 +62,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x @@ -106,7 +106,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e020c425..47469eb43 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.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x @@ -150,7 +150,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 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.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x @@ -239,7 +239,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x @@ -320,7 +320,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x @@ -393,7 +393,7 @@ jobs: path: signed - name: Set up .NET - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x @@ -497,7 +497,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x @@ -567,7 +567,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 8.0.x From 5f6d32ae4e43694b0b7e329d5c87fcf4d7a1348a Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Fri, 24 Jan 2025 11:56:59 +0000 Subject: [PATCH 30/84] macospreferences: add class to read macOS app preferences --- .../Interop/MacOS/MacOSPreferencesTests.cs | 66 ++++++++++ .../Core/Interop/MacOS/MacOSKeychain.cs | 33 ++--- .../Core/Interop/MacOS/MacOSPreferences.cs | 96 ++++++++++++++ .../Interop/MacOS/Native/CoreFoundation.cs | 119 ++++++++++++++++++ src/shared/TestInfrastructure/TestUtils.cs | 38 ++++++ 5 files changed, 327 insertions(+), 25 deletions(-) create mode 100644 src/shared/Core.Tests/Interop/MacOS/MacOSPreferencesTests.cs create mode 100644 src/shared/Core/Interop/MacOS/MacOSPreferences.cs diff --git a/src/shared/Core.Tests/Interop/MacOS/MacOSPreferencesTests.cs b/src/shared/Core.Tests/Interop/MacOS/MacOSPreferencesTests.cs new file mode 100644 index 000000000..0efb14471 --- /dev/null +++ b/src/shared/Core.Tests/Interop/MacOS/MacOSPreferencesTests.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using GitCredentialManager.Interop.MacOS; +using static GitCredentialManager.Tests.TestUtils; + +namespace GitCredentialManager.Tests.Interop.MacOS; + +public class MacOSPreferencesTests +{ + private const string TestAppId = "com.example.gcm-test"; + private const string DefaultsPath = "/usr/bin/defaults"; + + [MacOSFact] + public async Task MacOSPreferences_ReadPreferences() + { + try + { + await SetupTestPreferencesAsync(); + + var pref = new MacOSPreferences(TestAppId); + + // Exists + string stringValue = pref.GetString("myString"); + int? intValue = pref.GetInteger("myInt"); + IDictionary dictValue = pref.GetDictionary("myDict"); + + Assert.NotNull(stringValue); + Assert.Equal("this is a string", stringValue); + Assert.NotNull(intValue); + Assert.Equal(42, intValue); + Assert.NotNull(dictValue); + Assert.Equal(2, dictValue.Count); + Assert.Equal("value1", dictValue["dict-k1"]); + Assert.Equal("value2", dictValue["dict-k2"]); + + // Does not exist + string missingString = pref.GetString("missingString"); + int? missingInt = pref.GetInteger("missingInt"); + IDictionary missingDict = pref.GetDictionary("missingDict"); + + Assert.Null(missingString); + Assert.Null(missingInt); + Assert.Null(missingDict); + } + finally + { + await CleanupTestPreferencesAsync(); + } + } + + private static async Task SetupTestPreferencesAsync() + { + // Using the defaults command set up preferences for the test app + await RunCommandAsync(DefaultsPath, $"write {TestAppId} myString \"this is a string\""); + await RunCommandAsync(DefaultsPath, $"write {TestAppId} myInt -int 42"); + await RunCommandAsync(DefaultsPath, $"write {TestAppId} myDict -dict dict-k1 value1 dict-k2 value2"); + } + + private static async Task CleanupTestPreferencesAsync() + { + // Delete the test app preferences + // defaults delete com.example.gcm-test + await RunCommandAsync(DefaultsPath, $"delete {TestAppId}"); + } +} diff --git a/src/shared/Core/Interop/MacOS/MacOSKeychain.cs b/src/shared/Core/Interop/MacOS/MacOSKeychain.cs index b024be129..9335e136d 100644 --- a/src/shared/Core/Interop/MacOS/MacOSKeychain.cs +++ b/src/shared/Core/Interop/MacOS/MacOSKeychain.cs @@ -302,35 +302,18 @@ private static string GetStringAttribute(IntPtr dict, IntPtr key) return null; } - IntPtr buffer = IntPtr.Zero; - try + if (CFDictionaryGetValueIfPresent(dict, key, out IntPtr value) && value != IntPtr.Zero) { - if (CFDictionaryGetValueIfPresent(dict, key, out IntPtr value) && value != IntPtr.Zero) + if (CFGetTypeID(value) == CFStringGetTypeID()) { - if (CFGetTypeID(value) == CFStringGetTypeID()) - { - int stringLength = (int)CFStringGetLength(value); - int bufferSize = stringLength + 1; - buffer = Marshal.AllocHGlobal(bufferSize); - if (CFStringGetCString(value, buffer, bufferSize, CFStringEncoding.kCFStringEncodingUTF8)) - { - return Marshal.PtrToStringAuto(buffer, stringLength); - } - } - - if (CFGetTypeID(value) == CFDataGetTypeID()) - { - int length = CFDataGetLength(value); - IntPtr ptr = CFDataGetBytePtr(value); - return Marshal.PtrToStringAuto(ptr, length); - } + return CFStringToString(value); } - } - finally - { - if (buffer != IntPtr.Zero) + + if (CFGetTypeID(value) == CFDataGetTypeID()) { - Marshal.FreeHGlobal(buffer); + int length = CFDataGetLength(value); + IntPtr ptr = CFDataGetBytePtr(value); + return Marshal.PtrToStringAuto(ptr, length); } } diff --git a/src/shared/Core/Interop/MacOS/MacOSPreferences.cs b/src/shared/Core/Interop/MacOS/MacOSPreferences.cs new file mode 100644 index 000000000..f866b30a8 --- /dev/null +++ b/src/shared/Core/Interop/MacOS/MacOSPreferences.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using GitCredentialManager.Interop.MacOS.Native; +using static GitCredentialManager.Interop.MacOS.Native.CoreFoundation; + +namespace GitCredentialManager.Interop.MacOS; + +public class MacOSPreferences +{ + private readonly string _appId; + + public MacOSPreferences(string appId) + { + EnsureArgument.NotNull(appId, nameof(appId)); + + _appId = appId; + } + + /// + /// Return a typed value from the app preferences. + /// + /// Preference name. + /// Thrown if the preference is not a string. + /// + /// or null if the preference with the given key does not exist. + /// + public string GetString(string key) + { + return TryGet(key, CFStringToString, out string value) + ? value + : null; + } + + /// + /// Return a typed value from the app preferences. + /// + /// Preference name. + /// Thrown if the preference is not an integer. + /// + /// or null if the preference with the given key does not exist. + /// + public int? GetInteger(string key) + { + return TryGet(key, CFNumberToInt32, out int value) + ? value + : null; + } + + /// + /// Return a typed value from the app preferences. + /// + /// Preference name. + /// Thrown if the preference is not a dictionary. + /// + /// or null if the preference with the given key does not exist. + /// + public IDictionary GetDictionary(string key) + { + return TryGet(key, CFDictionaryToDictionary, out IDictionary value) + ? value + : null; + } + + private bool TryGet(string key, Func converter, out T value) + { + IntPtr cfValue = IntPtr.Zero; + IntPtr keyPtr = IntPtr.Zero; + IntPtr appIdPtr = CreateAppIdPtr(); + + try + { + keyPtr = CFStringCreateWithCString(IntPtr.Zero, key, CFStringEncoding.kCFStringEncodingUTF8); + cfValue = CFPreferencesCopyAppValue(keyPtr, appIdPtr); + + if (cfValue == IntPtr.Zero) + { + value = default; + return false; + } + + value = converter(cfValue); + return true; + } + finally + { + if (cfValue != IntPtr.Zero) CFRelease(cfValue); + if (keyPtr != IntPtr.Zero) CFRelease(keyPtr); + if (appIdPtr != IntPtr.Zero) CFRelease(appIdPtr); + } + } + + private IntPtr CreateAppIdPtr() + { + return CFStringCreateWithCString(IntPtr.Zero, _appId, CFStringEncoding.kCFStringEncodingUTF8); + } +} diff --git a/src/shared/Core/Interop/MacOS/Native/CoreFoundation.cs b/src/shared/Core/Interop/MacOS/Native/CoreFoundation.cs index 0f32a383b..9cab2ca8f 100644 --- a/src/shared/Core/Interop/MacOS/Native/CoreFoundation.cs +++ b/src/shared/Core/Interop/MacOS/Native/CoreFoundation.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using static GitCredentialManager.Interop.MacOS.Native.LibSystem; @@ -55,6 +56,9 @@ public static extern void CFDictionaryAddValue( public static extern IntPtr CFStringCreateWithBytes(IntPtr alloc, byte[] bytes, long numBytes, CFStringEncoding encoding, bool isExternalRepresentation); + [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CFStringCreateWithCString(IntPtr alloc, string cStr, CFStringEncoding encoding); + [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern long CFStringGetLength(IntPtr theString); @@ -82,15 +86,130 @@ public static extern IntPtr CFStringCreateWithBytes(IntPtr alloc, byte[] bytes, [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern int CFArrayGetTypeID(); + [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern int CFNumberGetTypeID(); + [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr CFDataGetBytePtr(IntPtr theData); [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern int CFDataGetLength(IntPtr theData); + + [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CFPreferencesCopyAppValue(IntPtr key, IntPtr appID); + + [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern bool CFNumberGetValue(IntPtr number, CFNumberType theType, out IntPtr valuePtr); + + [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CFDictionaryGetKeysAndValues(IntPtr theDict, IntPtr[] keys, IntPtr[] values); + + [DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern long CFDictionaryGetCount(IntPtr theDict); + + public static string CFStringToString(IntPtr cfString) + { + if (cfString == IntPtr.Zero) + { + throw new ArgumentNullException(nameof(cfString)); + } + + if (CFGetTypeID(cfString) != CFStringGetTypeID()) + { + throw new InvalidOperationException("Object is not a CFString."); + } + + long length = CFStringGetLength(cfString); + IntPtr buffer = Marshal.AllocHGlobal((int)length + 1); + + try + { + if (!CFStringGetCString(cfString, buffer, length + 1, CFStringEncoding.kCFStringEncodingUTF8)) + { + throw new InvalidOperationException("Failed to convert CFString to C string."); + } + + return Marshal.PtrToStringAnsi(buffer); + } + finally + { + Marshal.FreeHGlobal(buffer); + } + } + + public static int CFNumberToInt32(IntPtr cfNumber) + { + if (cfNumber == IntPtr.Zero) + { + throw new ArgumentNullException(nameof(cfNumber)); + } + + if (CFGetTypeID(cfNumber) != CFNumberGetTypeID()) + { + throw new InvalidOperationException("Object is not a CFNumber."); + } + + if (!CFNumberGetValue(cfNumber, CFNumberType.kCFNumberIntType, out IntPtr valuePtr)) + { + throw new InvalidOperationException("Failed to convert CFNumber to Int32."); + } + + return valuePtr.ToInt32(); + } + + public static IDictionary CFDictionaryToDictionary(IntPtr cfDict) + { + if (cfDict == IntPtr.Zero) + { + throw new ArgumentNullException(nameof(cfDict)); + } + + if (CFGetTypeID(cfDict) != CFDictionaryGetTypeID()) + { + throw new InvalidOperationException("Object is not a CFDictionary."); + } + + int count = (int)CFDictionaryGetCount(cfDict); + var keys = new IntPtr[count]; + var values = new IntPtr[count]; + + CFDictionaryGetKeysAndValues(cfDict, keys, values); + + var dict = new Dictionary(capacity: count); + for (int i = 0; i < count; i++) + { + string keyStr = CFStringToString(keys[i])!; + string valueStr = CFStringToString(values[i]); + + dict[keyStr] = valueStr; + } + + return dict; + } } public enum CFStringEncoding { kCFStringEncodingUTF8 = 0x08000100, } + + public enum CFNumberType + { + kCFNumberSInt8Type = 1, + kCFNumberSInt16Type = 2, + kCFNumberSInt32Type = 3, + kCFNumberSInt64Type = 4, + kCFNumberFloat32Type = 5, + kCFNumberFloat64Type = 6, + kCFNumberCharType = 7, + kCFNumberShortType = 8, + kCFNumberIntType = 9, + kCFNumberLongType = 10, + kCFNumberLongLongType = 11, + kCFNumberFloatType = 12, + kCFNumberDoubleType = 13, + kCFNumberCFIndexType = 14, + kCFNumberNSIntegerType = 15, + kCFNumberCGFloatType = 16 + } } diff --git a/src/shared/TestInfrastructure/TestUtils.cs b/src/shared/TestInfrastructure/TestUtils.cs index c547856d7..000b8e75e 100644 --- a/src/shared/TestInfrastructure/TestUtils.cs +++ b/src/shared/TestInfrastructure/TestUtils.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics; using System.IO; +using System.Threading.Tasks; namespace GitCredentialManager.Tests { @@ -87,5 +89,41 @@ public static string GetUuid(int length = -1) return uuid.Substring(0, length); } + + public static async Task RunCommandAsync(string filePath, string arguments, string workingDirectory = null) + { + using var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = filePath, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory + } + }; + + process.Start(); + + string output = await process.StandardOutput.ReadToEndAsync(); + string error = await process.StandardError.ReadToEndAsync(); + + await process.WaitForExitAsync(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException( + $"Command `{filePath} {arguments}` failed with exit code {process.ExitCode}." + + Environment.NewLine + + $"Output: {output}" + + Environment.NewLine + + $"Error: {error}"); + } + + return output; + } } } From b05317f74562e087b56ce8e7fdfc44e33c400589 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Fri, 24 Jan 2025 12:07:18 +0000 Subject: [PATCH 31/84] macossettings: implement default settings for macOS --- docs/enterprise-config.md | 33 ++++++++- src/shared/Core/CommandContext.cs | 2 +- src/shared/Core/Constants.cs | 1 + .../Core/Interop/MacOS/MacOSSettings.cs | 67 +++++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/shared/Core/Interop/MacOS/MacOSSettings.cs diff --git a/docs/enterprise-config.md b/docs/enterprise-config.md index bfdc7e302..97544a33f 100644 --- a/docs/enterprise-config.md +++ b/docs/enterprise-config.md @@ -55,7 +55,38 @@ those of the [Git configuration][config] settings. The type of each registry key can be either `REG_SZ` (string) or `REG_DWORD` (integer). -## macOS/Linux +## macOS + +Default settings values come from macOS's preferences system. Configuration +profiles can be deployed to devices using a compatible Mobile Device Management +(MDM) solution. + +Configuration for Git Credential Manager must take the form of a dictionary, set +for the domain `git-credential-manager` under the key `configuration`. For +example: + +```shell +defaults write git-credential-manager configuration -dict-add +``` + +..where `` is the name of the settings from the [Git configuration][config] +reference, and `` is the desired value. + +All values in the `configuration` dictionary must be strings. For boolean values +use `true` or `false`, and for integer values use the number in string form. + +To read the current configuration: + +```console +$ defaults read git-credential-manager configuration +{ + = ; + ... + = ; +} +``` + +## Linux Default configuration setting stores has not been implemented. diff --git a/src/shared/Core/CommandContext.cs b/src/shared/Core/CommandContext.cs index 712db32e1..d3ef1dbf6 100644 --- a/src/shared/Core/CommandContext.cs +++ b/src/shared/Core/CommandContext.cs @@ -131,7 +131,7 @@ public CommandContext() gitPath, FileSystem.GetCurrentDirectory() ); - Settings = new Settings(Environment, Git); + Settings = new MacOSSettings(Environment, Git, Trace); } else if (PlatformUtils.IsLinux()) { diff --git a/src/shared/Core/Constants.cs b/src/shared/Core/Constants.cs index 191fcc83d..4777b0cf8 100644 --- a/src/shared/Core/Constants.cs +++ b/src/shared/Core/Constants.cs @@ -16,6 +16,7 @@ public static class Constants public const string GcmDataDirectoryName = ".gcm"; + public const string MacOSBundleId = "git-credential-manager"; public static readonly Guid DevBoxPartnerId = new("e3171dd9-9a5f-e5be-b36c-cc7c4f3f3bcf"); /// diff --git a/src/shared/Core/Interop/MacOS/MacOSSettings.cs b/src/shared/Core/Interop/MacOS/MacOSSettings.cs new file mode 100644 index 000000000..3ef2c8247 --- /dev/null +++ b/src/shared/Core/Interop/MacOS/MacOSSettings.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; + +namespace GitCredentialManager.Interop.MacOS +{ + /// + /// Reads settings from Git configuration, environment variables, and defaults from the system. + /// + public class MacOSSettings : Settings + { + private readonly ITrace _trace; + + public MacOSSettings(IEnvironment environment, IGit git, ITrace trace) + : base(environment, git) + { + EnsureArgument.NotNull(trace, nameof(trace)); + _trace = trace; + + PlatformUtils.EnsureMacOS(); + } + + protected override bool TryGetExternalDefault(string section, string scope, string property, out string value) + { + value = null; + + try + { + // Check for app default preferences for our bundle ID. + // Defaults can be deployed system administrators via device management profiles. + var prefs = new MacOSPreferences(Constants.MacOSBundleId); + IDictionary dict = prefs.GetDictionary("configuration"); + + if (dict is null) + { + // No configuration key exists + return false; + } + + // Wrap the raw dictionary in one configured with the Git configuration key comparer. + // This means we can use the same key comparison rules as Git in our configuration plist dict, + // That is, sections and names are insensitive to case, but the scope is case-sensitive. + var config = new Dictionary(dict, GitConfigurationKeyComparer.Instance); + + string name = string.IsNullOrWhiteSpace(scope) + ? $"{section}.{property}" + : $"{section}.{scope}.{property}"; + + if (!config.TryGetValue(name, out value)) + { + // No property exists + return false; + } + + _trace.WriteLine($"Default setting found in app preferences: {name}={value}"); + return true; + } + catch (Exception ex) + { + // Reading defaults is not critical to the operation of the application + // so we can ignore any errors and just log the failure. + _trace.WriteLine("Failed to read default setting from app preferences."); + _trace.WriteException(ex); + return false; + } + } + } +} From 7f34d7dcba8d0e2e9b857905d9d2a2f15e37bec7 Mon Sep 17 00:00:00 2001 From: Marc Becker Date: Thu, 13 Feb 2025 21:29:57 +0100 Subject: [PATCH 32/84] fix(generic): save new refresh_token value actually replace the deprecated token with the new and checked value --- src/shared/Core/GenericHostProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/Core/GenericHostProvider.cs b/src/shared/Core/GenericHostProvider.cs index 9f087ca5b..19e1d6733 100644 --- a/src/shared/Core/GenericHostProvider.cs +++ b/src/shared/Core/GenericHostProvider.cs @@ -161,7 +161,7 @@ private async Task GetOAuthAccessToken(Uri remoteUri, string userNa // Store new refresh token if we have been given one if (!string.IsNullOrWhiteSpace(refreshResult.RefreshToken)) { - Context.CredentialStore.AddOrUpdate(refreshService, refreshToken.Account, refreshToken.Password); + Context.CredentialStore.AddOrUpdate(refreshService, refreshToken.Account, refreshResult.RefreshToken); } // Return the new access token From 82d77846788ec171e958a0b2b5cce6185a6780e2 Mon Sep 17 00:00:00 2001 From: hii-jririe Date: Tue, 1 Apr 2025 16:40:48 -0400 Subject: [PATCH 33/84] Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c6aa1535..b9dcff451 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ supported by dotnet][dotnet-distributions]. ## Supported Git versions Git Credential Manager tries to be compatible with the broadest set of Git -versions (within reason). However there are some know problematic releases of +versions (within reason). However there are some known problematic releases of Git that are not compatible. - Git 1.x From d4e2f59030b0862a7d0d01b96fad454f27d5932e Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 30 Apr 2025 15:17:44 +0100 Subject: [PATCH 34/84] trace2: fix pipe/socket name parsing There are a few bugs in the way we read a pipe or socket name for the TRACE2 event target. We do not correctly trim the \\.\pipe\ prefix on Windows, if present. On Unix we do not correctly strip the af_unix: prefix correctly if a socket file path contains a ':'. Let's fix this! Signed-off-by: Matthew John Cheetham --- src/shared/Core.Tests/Trace2Tests.cs | 11 ++++++----- src/shared/Core/Trace2.cs | 13 ++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/shared/Core.Tests/Trace2Tests.cs b/src/shared/Core.Tests/Trace2Tests.cs index 26df5ab98..38011275d 100644 --- a/src/shared/Core.Tests/Trace2Tests.cs +++ b/src/shared/Core.Tests/Trace2Tests.cs @@ -6,25 +6,26 @@ public class Trace2Tests { [PosixTheory] [InlineData("af_unix:foo", "foo")] - [InlineData("af_unix:stream:foo-bar", "foo-bar")] - [InlineData("af_unix:dgram:foo-bar-baz", "foo-bar-baz")] + [InlineData("af_unix:foo/bar", "foo/bar")] + [InlineData("af_unix:stream:foo/bar", "foo/bar")] + [InlineData("af_unix:dgram:foo/bar/baz", "foo/bar/baz")] public void TryGetPipeName_Posix_Returns_Expected_Value(string input, string expected) { var isSuccessful = Trace2.TryGetPipeName(input, out var actual); Assert.True(isSuccessful); - Assert.Matches(actual, expected); + Assert.Equal(actual, expected); } [WindowsTheory] [InlineData("\\\\.\\pipe\\git-foo", "git-foo")] [InlineData("\\\\.\\pipe\\git-foo-bar", "git-foo-bar")] - [InlineData("\\\\.\\pipe\\foo\\git-bar", "git-bar")] + [InlineData("\\\\.\\pipe\\foo\\git-bar", "foo\\git-bar")] public void TryGetPipeName_Windows_Returns_Expected_Value(string input, string expected) { var isSuccessful = Trace2.TryGetPipeName(input, out var actual); Assert.True(isSuccessful); - Assert.Matches(actual, expected); + Assert.Equal(expected, actual); } } diff --git a/src/shared/Core/Trace2.cs b/src/shared/Core/Trace2.cs index d8eba64b5..535812ea8 100644 --- a/src/shared/Core/Trace2.cs +++ b/src/shared/Core/Trace2.cs @@ -480,13 +480,16 @@ protected override void ReleaseManagedResources() internal static bool TryGetPipeName(string eventTarget, out string name) { // Use prefixes to determine whether target is a named pipe/socket - if (eventTarget.Contains("af_unix:", StringComparison.OrdinalIgnoreCase) || - eventTarget.Contains("\\\\.\\pipe\\", StringComparison.OrdinalIgnoreCase) || - eventTarget.Contains("/./pipe/", StringComparison.OrdinalIgnoreCase)) + if (eventTarget.StartsWith("af_unix:", StringComparison.OrdinalIgnoreCase) || + eventTarget.StartsWith(@"\\.\pipe\", StringComparison.OrdinalIgnoreCase) || + eventTarget.StartsWith("//./pipe/", StringComparison.OrdinalIgnoreCase)) { name = PlatformUtils.IsWindows() - ? eventTarget.TrimUntilLastIndexOf("\\") - : eventTarget.TrimUntilLastIndexOf(":"); + ? eventTarget.Replace('/', '\\') + .TrimUntilIndexOf(@"\\.\pipe\") + : eventTarget.Replace("af_unix:dgram:", "") + .Replace("af_unix:stream:", "") + .Replace("af_unix:", ""); return true; } From 480b32c515cb6f05359765471657962a94901437 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 30 Apr 2025 15:19:47 +0100 Subject: [PATCH 35/84] trace2: use 'fmt' for the formatted message event field In a message that is formatted/parameterised, we incorrectly use the field name of 'format' rather than 'fmt'. Fix this. Signed-off-by: Matthew John Cheetham --- src/shared/Core.Tests/Trace2MessageTests.cs | 2 +- src/shared/Core/Trace2Message.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/Core.Tests/Trace2MessageTests.cs b/src/shared/Core.Tests/Trace2MessageTests.cs index 82b161744..7e29a641f 100644 --- a/src/shared/Core.Tests/Trace2MessageTests.cs +++ b/src/shared/Core.Tests/Trace2MessageTests.cs @@ -54,7 +54,7 @@ public void Event_Message_Without_Snake_Case_ToJson_Creates_Expected_Json() ParameterizedMessage = "baz" }; - var expected = "{\"event\":\"error\",\"sid\":\"123\",\"thread\":\"main\",\"time\":\"0001-01-01T00:00:00+00:00\",\"file\":\"foo.cs\",\"line\":1,\"depth\":1,\"msg\":\"bar\",\"format\":\"baz\"}"; + var expected = "{\"event\":\"error\",\"sid\":\"123\",\"thread\":\"main\",\"time\":\"0001-01-01T00:00:00+00:00\",\"file\":\"foo.cs\",\"line\":1,\"depth\":1,\"msg\":\"bar\",\"fmt\":\"baz\"}"; var actual = errorMessage.ToJson(); Assert.Equal(expected, actual); diff --git a/src/shared/Core/Trace2Message.cs b/src/shared/Core/Trace2Message.cs index cbbe48288..14327031f 100644 --- a/src/shared/Core/Trace2Message.cs +++ b/src/shared/Core/Trace2Message.cs @@ -409,7 +409,7 @@ public class ErrorMessage : Trace2Message [JsonPropertyOrder(8)] public string Message { get; set; } - [JsonPropertyName("format")] + [JsonPropertyName("fmt")] [JsonPropertyOrder(9)] public string ParameterizedMessage { get; set; } From 0c3edf5f33028f835728bdf31475e2c77ab1e54d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 00:56:34 +0000 Subject: [PATCH 36/84] build(deps): bump azure/trusted-signing-action from 0.5.0 to 0.5.9 Bumps [azure/trusted-signing-action](https://github.com/azure/trusted-signing-action) from 0.5.0 to 0.5.9. - [Release notes](https://github.com/azure/trusted-signing-action/releases) - [Commits](https://github.com/azure/trusted-signing-action/compare/v0.5.0...v0.5.9) --- updated-dependencies: - dependency-name: azure/trusted-signing-action dependency-version: 0.5.9 dependency-type: direct:production update-type: version-update:semver-patch ... 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 c8b32151f..9d937dcd5 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.5.0 + uses: azure/trusted-signing-action@v0.5.9 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.5.0 + uses: azure/trusted-signing-action@v0.5.9 with: endpoint: https://wus2.codesigning.azure.net/ trusted-signing-account-name: git-fundamentals-signing From fb394378d497b0d15219f4f52c48bfe11d6b12d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:16:09 +0000 Subject: [PATCH 37/84] build(deps): bump actions/download-artifact from 4 to 5 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c8b32151f..7b84b91a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -344,7 +344,7 @@ jobs: - uses: actions/checkout@v4 - name: Download payload - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: tmp.dotnet-tool-build @@ -387,7 +387,7 @@ jobs: - uses: actions/checkout@v4 - name: Download signed payload - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: dotnet-tool-payload-sign path: signed @@ -419,7 +419,7 @@ jobs: - uses: actions/checkout@v4 - name: Download unsigned package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: tmp.dotnet-tool-package-unsigned path: nupkg @@ -502,7 +502,7 @@ jobs: dotnet-version: 8.0.x - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: ${{ matrix.component.artifact }} @@ -572,7 +572,7 @@ jobs: dotnet-version: 8.0.x - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 - name: Archive macOS payload and symbols run: | From 0dfd32b1c1ee5fa3ec0b485e51a5cd0e5daffe48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:31:15 +0000 Subject: [PATCH 38/84] build(deps): bump lycheeverse/lychee-action from 2.1.0 to 2.6.1 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.1.0 to 2.6.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/f81112d0d2814ded911bd23e3beaa9dda9093915...885c65f3dc543b57c898c8099f4e08c8afd178a2) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 5f60867eb..738f8f369 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -35,7 +35,7 @@ jobs: - name: Run link checker # For any troubleshooting, see: # https://github.com/lycheeverse/lychee/blob/master/docs/TROUBLESHOOTING.md - uses: lycheeverse/lychee-action@f81112d0d2814ded911bd23e3beaa9dda9093915 + uses: lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2 with: # user-agent: if a user agent is not specified, some websites (e.g. From 1bd0bafac9d9973b2d5713129ef27743298615e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:38:06 +0000 Subject: [PATCH 39/84] build(deps): bump actions/setup-dotnet from 4.2.0 to 5.0.0 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.2.0 to 5.0.0. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4.2.0...v5.0.0) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... 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 9f8170d53..86386f25e 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.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index b8da48e7b..dbd2cbc1d 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.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x @@ -62,7 +62,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x @@ -106,7 +106,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c8b32151f..62c8cc31f 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.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x @@ -150,7 +150,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v5.0.0 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.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x @@ -239,7 +239,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x @@ -320,7 +320,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x @@ -393,7 +393,7 @@ jobs: path: signed - name: Set up .NET - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x @@ -497,7 +497,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x @@ -567,7 +567,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up .NET - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v5.0.0 with: dotnet-version: 8.0.x From 7347c6fa95c3abe4eff08b8254803db4e83b51e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:00:15 +0000 Subject: [PATCH 40/84] Bump System.Text.Json from 8.0.4 to 8.0.5 --- updated-dependencies: - dependency-name: System.Text.Json dependency-version: 8.0.5 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5c0d87bdb..e7ed76eb9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,7 +28,7 @@ - 8.0.4 + 8.0.5 From 203768ba4c17fa44069fc1964495643fee98334c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 20:31:00 +0000 Subject: [PATCH 41/84] build(deps): bump DavidAnson/markdownlint-cli2-action Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 18.0.0 to 20.0.0. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/eb5ca3ab411449c66620fe7f1b3c9e10547144b0...992badcdf24e3b8eb7e87ff9287fe931bcb00c6e) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: 20.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 738f8f369..3520dae9f 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: DavidAnson/markdownlint-cli2-action@eb5ca3ab411449c66620fe7f1b3c9e10547144b0 + - uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e with: globs: | "**/*.md" From f5e006bdd1615edbb3f9f7d2a75c442d24534bfa Mon Sep 17 00:00:00 2001 From: Alexandre Khoury Date: Mon, 22 Sep 2025 16:08:47 +0300 Subject: [PATCH 42/84] Remove libopenssl1_1 dependency --- src/linux/Packaging.Linux/install-from-source.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/Packaging.Linux/install-from-source.sh b/src/linux/Packaging.Linux/install-from-source.sh index 8cf60251c..19ccb6b61 100755 --- a/src/linux/Packaging.Linux/install-from-source.sh +++ b/src/linux/Packaging.Linux/install-from-source.sh @@ -208,7 +208,7 @@ case "$distribution" in $sudo_cmd zypper -n update # Install dotnet/GCM dependencies. - install_packages zypper install "curl git find krb5 libicu libopenssl1_1" + install_packages zypper install "curl git find krb5 libicu" ensure_dotnet_installed ;; From b4347950a6464444f5bddff056be64e902e2329e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:03:31 +0000 Subject: [PATCH 43/84] build(deps): bump actions/github-script from 7 to 8 Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/maintainer-absence.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maintainer-absence.yml b/.github/workflows/maintainer-absence.yml index 433cb0f7e..20e6694e7 100644 --- a/.github/workflows/maintainer-absence.yml +++ b/.github/workflows/maintainer-absence.yml @@ -18,7 +18,7 @@ jobs: name: create-issue runs-on: ubuntu-latest steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@v8 with: script: | const startDate = new Date('${{ github.event.inputs.startDate }}'); diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5706c7806..a29ddb86f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -608,7 +608,7 @@ jobs: cp gcm-public.asc linux-arm64-artifacts/ mv gcm-public.asc linux-arm-artifacts - - uses: actions/github-script@v7 + - uses: actions/github-script@v8 with: script: | const fs = require('fs'); From 93a454bd5ca0fab0f0ffc61963ee9ca316dc1fb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:03:37 +0000 Subject: [PATCH 44/84] build(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/continuous-integration.yml | 6 +++--- .github/workflows/lint-docs.yml | 4 ++-- .github/workflows/release.yml | 20 +++++++++---------- .../validate-install-from-source.yml | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 86386f25e..f0867a571 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,7 +22,7 @@ jobs: language: [ 'csharp' ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v5.0.0 diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index dbd2cbc1d..b699d86a3 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,7 +16,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v5.0.0 @@ -59,7 +59,7 @@ jobs: runtime: [ linux-x64, linux-arm64, linux-arm ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v5.0.0 @@ -103,7 +103,7 @@ jobs: runtime: [ osx-x64, osx-arm64 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v5.0.0 diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 3520dae9f..15b1c7d16 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -18,7 +18,7 @@ jobs: name: Lint markdown files runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e with: @@ -30,7 +30,7 @@ jobs: name: Check for broken links runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run link checker # For any troubleshooting, see: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5706c7806..1f8ca2404 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: outputs: version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set version run: echo "version=$(cat VERSION | sed -E 's/.[0-9]+$//')" >> $GITHUB_OUTPUT @@ -32,7 +32,7 @@ jobs: matrix: runtime: [ osx-x64, osx-arm64 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up .NET uses: actions/setup-dotnet@v5.0.0 @@ -147,7 +147,7 @@ jobs: environment: release needs: prereqs steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up .NET uses: actions/setup-dotnet@v5.0.0 @@ -236,7 +236,7 @@ jobs: matrix: runtime: [ linux-x64, linux-arm64, linux-arm ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up .NET uses: actions/setup-dotnet@v5.0.0 @@ -317,7 +317,7 @@ jobs: runs-on: ubuntu-latest needs: prereqs steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up .NET uses: actions/setup-dotnet@v5.0.0 @@ -341,7 +341,7 @@ jobs: environment: release needs: dotnet-tool-build steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Download payload uses: actions/download-artifact@v5 @@ -384,7 +384,7 @@ jobs: runs-on: ubuntu-latest needs: [ prereqs, dotnet-tool-payload-sign ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Download signed payload uses: actions/download-artifact@v5 @@ -416,7 +416,7 @@ jobs: environment: release needs: dotnet-tool-pack steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Download unsigned package uses: actions/download-artifact@v5 @@ -494,7 +494,7 @@ jobs: runs-on: ${{ matrix.component.os }} needs: [ create-macos-artifacts, create-windows-artifacts, create-linux-artifacts, dotnet-tool-sign ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up .NET uses: actions/setup-dotnet@v5.0.0 @@ -564,7 +564,7 @@ jobs: environment: release needs: [ prereqs, validate ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up .NET uses: actions/setup-dotnet@v5.0.0 diff --git a/.github/workflows/validate-install-from-source.yml b/.github/workflows/validate-install-from-source.yml index 2b1fd7696..ead91c3d4 100644 --- a/.github/workflows/validate-install-from-source.yml +++ b/.github/workflows/validate-install-from-source.yml @@ -41,7 +41,7 @@ jobs: GNUPGHOME=/root/.gnupg tdnf install tar -y # needed for `actions/checkout` fi - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: | sh "${GITHUB_WORKSPACE}/src/linux/Packaging.Linux/install-from-source.sh" -y From f3c4ffbbed861c73ffd1d7a9bd6203da189c3bde Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Sep 2025 09:21:41 +0200 Subject: [PATCH 45/84] validate-install-from-source: bump Alpine LTS to latest non-EOL one The Alpine container we use still is at version v3.14 (which, from a numerophile's point of view is a quite pleasing number). However, this Alpine version is past its support, as pointed out in https://github.com/actions/checkout/issues/2246#issuecomment-3182183831 One fallout is that since I upgraded Git Credential Manager's GitHub workflows to require Node.JS 24, we are now greeted with this error: Run actions/checkout@v5 /usr/bin/docker exec 7d1dbaed0040c61df8247f411d8220c2fb587aa0a7536d22ed12caff60b592da sh -c "cat /etc/*release | grep ^ID" Error relocating /__e/node24_alpine/bin/node: pthread_getname_np: symbol not found For details about this run, see https://github.com/git-ecosystem/git-credential-manager/actions/runs/17937675613/job/51006830230 Let's switch to the oldest, just _barely_ supported Alpine version (https://alpinelinux.org/releases/ says that v3.19 will be out of support this November). Signed-off-by: Johannes Schindelin --- .github/workflows/validate-install-from-source.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-install-from-source.yml b/.github/workflows/validate-install-from-source.yml index ead91c3d4..542a07c7c 100644 --- a/.github/workflows/validate-install-from-source.yml +++ b/.github/workflows/validate-install-from-source.yml @@ -23,7 +23,7 @@ jobs: - image: tgagor/centos - image: redhat/ubi8 - image: alpine - - image: alpine:3.14.10 + - image: alpine:3.19.8 - image: opensuse/leap - image: opensuse/tumbleweed - image: registry.suse.com/suse/sle15:15.4.27.11.31 From 9d3bdd1bb7ec12e1bbd5ec228be79a9c01b14675 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 23 Sep 2025 10:22:32 +0100 Subject: [PATCH 46/84] CODEOWNERS: add CODEOWNERS file The git-client team is a code owner of all code by default. Also add specific code owners for GitHub and Azure Repos provider code. Signed-off-by: Matthew John Cheetham --- CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..87c4b2935 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,5 @@ +* @git-ecosystem/git-client +/src/shared/Microsoft.AzureRepos/ @git-ecosystem/git-client @git-ecosystem/gcm-azure-maintainers +/src/shared/Microsoft.AzureRepos.Tests/ @git-ecosystem/git-client @git-ecosystem/gcm-azure-maintainers +/src/shared/GitHub/ @git-ecosystem/git-client @git-ecosystem/hubbers +/src/shared/GitHub.Tests/ @git-ecosystem/git-client @git-ecosystem/hubbers From dfa3b4993e4c4e196aa8c20ab112263d10001788 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 23 Sep 2025 11:46:19 +0100 Subject: [PATCH 47/84] windows/layout.ps1: clean up layout script Clean up the Windows payload layout script to avoid errors when removing files that don't exist. Also make the script parameters use normal casing rules. Signed-off-by: Matthew John Cheetham --- src/windows/Installer.Windows/layout.ps1 | 48 ++++++++++++------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/windows/Installer.Windows/layout.ps1 b/src/windows/Installer.Windows/layout.ps1 index 070c9bf49..818ee01c6 100644 --- a/src/windows/Installer.Windows/layout.ps1 +++ b/src/windows/Installer.Windows/layout.ps1 @@ -1,20 +1,22 @@ # Inputs -param ([Parameter(Mandatory)] $CONFIGURATION, [Parameter(Mandatory)] $OUTPUT, $SYMBOLOUTPUT) +param ([Parameter(Mandatory)] $Configuration, [Parameter(Mandatory)] $Output, $SymbolOutput) -Write-Output "Output: $OUTPUT" +Write-Output "Output: $Output" # Directories -$THISDIR = $pwd.path -$ROOT = (Get-Item $THISDIR).parent.parent.parent.FullName -$SRC = "$ROOT/src" -$GCM_SRC = "$SRC/shared/Git-Credential-Manager" +$THISDIR = $PSScriptRoot +$ROOT = (Get-Item $THISDIR).Parent.Parent.Parent.FullName +$SRC = "$ROOT\src" +$GCM_SRC = "$SRC\shared\Git-Credential-Manager" # Perform pre-execution checks -$PAYLOAD = "$OUTPUT" -if ($SYMBOLOUTPUT) +$PAYLOAD = "$Output" +if ($SymbolOutput) +{ + $SYMBOLS = "$SymbolOutput" +} +else { - $SYMBOLS = "$SYMBOLOUTPUT" -} else { $SYMBOLS = "$PAYLOAD.sym" } @@ -32,37 +34,37 @@ if (Test-Path -Path $SYMBOLS) } # Ensure payload and symbol directories exist -mkdir -p "$PAYLOAD","$SYMBOLS" +mkdir -p "$PAYLOAD","$SYMBOLS" | Out-Null # Publish core application executables Write-Output "Publishing core application..." dotnet publish "$GCM_SRC" ` --framework net472 ` - --configuration "$CONFIGURATION" ` + --configuration "$Configuration" ` --runtime win-x86 ` --output "$PAYLOAD" # Delete libraries that are not needed for Windows but find their way # into the publish output. -Remove-Item -Path "$PAYLOAD/*.dylib" -Force +Remove-Item -Path "$PAYLOAD/*.dylib" -Force -ErrorAction Ignore # Delete extraneous files that get included for other architectures # We only care about x86 as the core GCM executable is only targeting x86 -Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -Remove-Item -Path "$PAYLOAD/arm64/" -Recurse -Force -Remove-Item -Path "$PAYLOAD/x64/" -Recurse -Force -Remove-Item -Path "$PAYLOAD/musl-x64/" -Recurse -Force -Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/" -Recurse -Force -Remove-Item -Path "$PAYLOAD/runtimes/win-x64/" -Recurse -Force +Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore +Remove-Item -Path "$PAYLOAD/arm64/" -Recurse -Force -ErrorAction Ignore +Remove-Item -Path "$PAYLOAD/x64/" -Recurse -Force -ErrorAction Ignore +Remove-Item -Path "$PAYLOAD/musl-x64/" -Recurse -Force -ErrorAction Ignore +Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/" -Recurse -Force -ErrorAction Ignore +Remove-Item -Path "$PAYLOAD/runtimes/win-x64/" -Recurse -Force -ErrorAction Ignore # The Avalonia and MSAL binaries in these directories are already included in # the $PAYLOAD directory directly, so we can delete these extra copies. -Remove-Item -Path "$PAYLOAD/x86/libSkiaSharp.dll" -Recurse -Force -Remove-Item -Path "$PAYLOAD/x86/libHarfBuzzSharp.dll" -Recurse -Force -Remove-Item -Path "$PAYLOAD/runtimes/win-x86/native/msalruntime_x86.dll" -Recurse -Force +Remove-Item -Path "$PAYLOAD/x86/libSkiaSharp.dll" -Recurse -Force -ErrorAction Ignore +Remove-Item -Path "$PAYLOAD/x86/libHarfBuzzSharp.dll" -Recurse -Force -ErrorAction Ignore +Remove-Item -Path "$PAYLOAD/runtimes/win-x86/native/msalruntime_x86.dll" -Recurse -Force -ErrorAction Ignore # Delete localized resource assemblies - we don't localize the core GCM assembly anyway -Get-ChildItem "$PAYLOAD" -Recurse -Include "*.resources.dll" | Remove-Item -Force +Get-ChildItem "$PAYLOAD" -Recurse -Include "*.resources.dll" | Remove-Item -Force -ErrorAction Ignore # Delete any empty directories Get-ChildItem "$PAYLOAD" -Recurse -Directory ` From 09d797ee74c6895f28f681f3906e22add949fa06 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 23 Sep 2025 11:56:23 +0100 Subject: [PATCH 48/84] .azure-pipelines/release.yml: add Windows release pipeline Add a release pipeline for Windows using Azure Pipelines. This pipeline is currently incomplete; a stub so we can link up Azure Pipelines to this YAML during development. This pipeline uses internal Microsoft 1ES templates. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .azure-pipelines/release.yml diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml new file mode 100644 index 000000000..c2c644db8 --- /dev/null +++ b/.azure-pipelines/release.yml @@ -0,0 +1,32 @@ +name: Release-$(Date:yyyyMMdd)$(Rev:.r) +trigger: none +pr: none + +resources: + repositories: + - repository: 1ESPipelines + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +parameters: + - name: 'esrp' + type: boolean + default: false + displayName: 'Enable ESRP code signing' + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelines + parameters: + stages: + - stage: windows + displayName: 'Windows' + jobs: + - job: win_x86_build + displayName: 'Windows Build and Sign (x86)' + pool: + name: GitClient-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows + steps: + - checkout: self From 1b39410641d57f10840d91d7c35058ec2c419d43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 20:14:00 +0000 Subject: [PATCH 49/84] build(deps): bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f0867a571..7ec8fbe4f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -31,7 +31,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} @@ -39,4 +39,4 @@ jobs: dotnet build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 From 83dff1517c20294691e69a690fa712583bfac33d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:04:10 +0000 Subject: [PATCH 50/84] build(deps): bump azure/trusted-signing-action from 0.5.9 to 0.5.10 Bumps [azure/trusted-signing-action](https://github.com/azure/trusted-signing-action) from 0.5.9 to 0.5.10. - [Release notes](https://github.com/azure/trusted-signing-action/releases) - [Commits](https://github.com/azure/trusted-signing-action/compare/v0.5.9...v0.5.10) --- updated-dependencies: - dependency-name: azure/trusted-signing-action dependency-version: 0.5.10 dependency-type: direct:production update-type: version-update:semver-patch ... 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 21cb0cffd..388d5b37f 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.5.9 + uses: azure/trusted-signing-action@v0.5.10 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.5.9 + uses: azure/trusted-signing-action@v0.5.10 with: endpoint: https://wus2.codesigning.azure.net/ trusted-signing-account-name: git-fundamentals-signing From 3c26a994bf033e861fa5f37f229bd6cdc26b703f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:03:30 +0000 Subject: [PATCH 51/84] build(deps): bump actions/download-artifact from 5 to 6 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 388d5b37f..50e4fca9b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -344,7 +344,7 @@ jobs: - uses: actions/checkout@v5 - name: Download payload - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: tmp.dotnet-tool-build @@ -387,7 +387,7 @@ jobs: - uses: actions/checkout@v5 - name: Download signed payload - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: dotnet-tool-payload-sign path: signed @@ -419,7 +419,7 @@ jobs: - uses: actions/checkout@v5 - name: Download unsigned package - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: tmp.dotnet-tool-package-unsigned path: nupkg @@ -502,7 +502,7 @@ jobs: dotnet-version: 8.0.x - name: Download artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: ${{ matrix.component.artifact }} @@ -572,7 +572,7 @@ jobs: dotnet-version: 8.0.x - name: Download artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 - name: Archive macOS payload and symbols run: | From 3684e3761272476d5858946d5ab0676ad83b8965 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:03:34 +0000 Subject: [PATCH 52/84] build(deps): bump actions/upload-artifact from 4 to 5 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/continuous-integration.yml | 6 +++--- .github/workflows/release.yml | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index b699d86a3..d3f304490 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -42,7 +42,7 @@ jobs: mv out/windows/Installer.Windows/bin/Release/net472/gcm*.exe artifacts/ - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: win-x86 path: | @@ -86,7 +86,7 @@ jobs: mv out/linux/Packaging.Linux/Release/tar/*.tar.gz artifacts/ - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.runtime }} path: | @@ -131,7 +131,7 @@ jobs: mv out/osx/Installer.Mac/pkg/Release/gcm*.pkg artifacts/ - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.runtime }} path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 388d5b37f..d707598ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -130,7 +130,7 @@ jobs: --keychain-profile="$N4" - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: macos-${{ matrix.runtime }}-artifacts path: | @@ -216,7 +216,7 @@ jobs: timestamp-digest: SHA256 - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: windows-artifacts path: | @@ -301,7 +301,7 @@ jobs: --detach-sig gcm-${{ matrix.runtime }}.$version.tar.gz - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.runtime }}-artifacts path: | @@ -329,7 +329,7 @@ jobs: src/shared/DotnetTool/layout.sh --configuration=Release - name: Upload .NET tool artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: tmp.dotnet-tool-build path: | @@ -373,7 +373,7 @@ jobs: mv images payload.sym payload -t dotnet-tool-payload-sign - name: Upload signed payload - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: dotnet-tool-payload-sign path: | @@ -404,7 +404,7 @@ jobs: --publish-dir=$(pwd)/signed - name: Upload unsigned package - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: tmp.dotnet-tool-package-unsigned path: | @@ -456,7 +456,7 @@ jobs: mv $cert .\nuget-signing.cer - name: Publish signed package and certificate - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: dotnet-tool-sign path: | From d6c18909228174a636f0d8614a8137ca8fdb7ddb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:04:09 +0000 Subject: [PATCH 53/84] build(deps): bump lycheeverse/lychee-action from 2.6.1 to 2.7.0 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.6.1 to 2.7.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/885c65f3dc543b57c898c8099f4e08c8afd178a2...a8c4c7cb88f0c7386610c35eb25108e448569cb0) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-version: 2.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 15b1c7d16..7ba06c156 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -35,7 +35,7 @@ jobs: - name: Run link checker # For any troubleshooting, see: # https://github.com/lycheeverse/lychee/blob/master/docs/TROUBLESHOOTING.md - uses: lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2 + uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 with: # user-agent: if a user agent is not specified, some websites (e.g. From a0cf8b6d7b9ae2f538a09394c00e273a9aee9581 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Thu, 6 Nov 2025 08:32:34 +0000 Subject: [PATCH 54/84] global.json: add global.json file to lock SDK ver Add a global.json file to the project to make it clearer that we should be using _at least_ version 8.x of the .NET SDK. This can help when building locally without a valid .NET Runtime version installed, to make it clear what the issue is. We set the roll-forward policy to `latestMajor` meaning we will look for any SDK major version from the set version forward. In this case, since we set 8.0, this will match 8.x, 9.x, 10.x etc. When we move to a newer runtime target (probably `net10.0`) we'll want to bump this minimum SDK version to 10.0. Signed-off-by: Matthew John Cheetham --- global.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 000000000..5cc6b13a6 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "rollForward": "latestMajor", + "version": "8.0" + } +} + From e295b94b18c8fa4c94e961e33a4461d0f5a38f9f Mon Sep 17 00:00:00 2001 From: khanhkhanhlele Date: Fri, 7 Nov 2025 18:12:26 +0700 Subject: [PATCH 55/84] Fix typos in some files --- build/GetVersion.cs | 2 +- src/linux/Packaging.Linux/pack.sh | 2 +- src/osx/Installer.Mac/dist.sh | 2 +- src/osx/Installer.Mac/pack.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/GetVersion.cs b/build/GetVersion.cs index 2b3473641..0078c4c12 100644 --- a/build/GetVersion.cs +++ b/build/GetVersion.cs @@ -34,7 +34,7 @@ public override bool Execute() // The main version number we use for GCM contains the first three // components. // The assembly and file version numbers contain all components, as - // ommitting the revision portion from these properties causes + // omitting the revision portion from these properties causes // runtime failures on Windows. Version = $"{fullVersion.Major}.{fullVersion.Minor}.{fullVersion.Build}"; AssemblyVersion = FileVersion = fullVersion.ToString(); diff --git a/src/linux/Packaging.Linux/pack.sh b/src/linux/Packaging.Linux/pack.sh index 4cf5aaea7..e69783bb6 100755 --- a/src/linux/Packaging.Linux/pack.sh +++ b/src/linux/Packaging.Linux/pack.sh @@ -76,7 +76,7 @@ echo "Packing Packaging.Linux..." # Cleanup any old archive files if [ -e "$TAROUT" ]; then - echo "Deleteing old archive '$TAROUT'..." + echo "Deleting old archive '$TAROUT'..." rm "$TAROUT" fi diff --git a/src/osx/Installer.Mac/dist.sh b/src/osx/Installer.Mac/dist.sh index f26761e26..185da7248 100755 --- a/src/osx/Installer.Mac/dist.sh +++ b/src/osx/Installer.Mac/dist.sh @@ -82,7 +82,7 @@ fi # Cleanup any old package if [ -e "$DISTOUT" ]; then - echo "Deleteing old product package '$DISTOUT'..." + echo "Deleting old product package '$DISTOUT'..." rm "$DISTOUT" fi diff --git a/src/osx/Installer.Mac/pack.sh b/src/osx/Installer.Mac/pack.sh index b58f4ce5a..77c6c623e 100755 --- a/src/osx/Installer.Mac/pack.sh +++ b/src/osx/Installer.Mac/pack.sh @@ -52,7 +52,7 @@ fi # Cleanup any old component if [ -e "$PKGOUT" ]; then - echo "Deleteing old component '$PKGOUT'..." + echo "Deleting old component '$PKGOUT'..." rm "$PKGOUT" fi From 7610bdbddbd89e51f8d659c5b1ea2e3fba2dcfdb Mon Sep 17 00:00:00 2001 From: Ridvan Gundogmus Date: Mon, 10 Nov 2025 11:21:56 +0100 Subject: [PATCH 56/84] Update Git for Windows screenshot link --- docs/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index 5ae7b44d5..731ffa7cc 100644 --- a/docs/install.md +++ b/docs/install.md @@ -241,7 +241,7 @@ dotnet tool uninstall -g git-credential-manager [gcm-credstores]: credstores.md [gcm-wsl]: wsl.md [git-for-windows]: https://gitforwindows.org/ -[git-for-windows-screenshot]: https://user-images.githubusercontent.com/5658207/140082529-1ac133c1-0922-4a24-af03-067e27b3988b.png +[git-for-windows-gcm-screenshot]: ./img/git-for-windows-gcm-screenshot.png [latest-release]: https://github.com/git-ecosystem/git-credential-manager/releases/latest [linux-uninstall]: linux-fromsrc-uninstall.md [linux-validate-gpg-debian]: ./linux-validate-gpg.md#debian-package From 0b7f2caa45a249679901230eb2de257fdb55f40f Mon Sep 17 00:00:00 2001 From: Ridvan Gundogmus Date: Mon, 10 Nov 2025 11:22:41 +0100 Subject: [PATCH 57/84] Add files via upload --- docs/img/git-for-windows-gcm-screenshot.png | Bin 0 -> 24298 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/img/git-for-windows-gcm-screenshot.png diff --git a/docs/img/git-for-windows-gcm-screenshot.png b/docs/img/git-for-windows-gcm-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..aaf60b682fb600ce296832055f87109c4f05cc87 GIT binary patch literal 24298 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 Date: Mon, 10 Nov 2025 11:26:08 +0100 Subject: [PATCH 58/84] Fix image link for Git for Windows GCM screenshot Updated image link for Git for Windows GCM installation. --- docs/install.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/install.md b/docs/install.md index 731ffa7cc..9fa7da4ac 100644 --- a/docs/install.md +++ b/docs/install.md @@ -151,7 +151,7 @@ manager. GCM is included with [Git for Windows][git-for-windows]. During installation you will be asked to select a credential helper, with GCM listed as the default. -![image][git-for-windows-screenshot] +![image][git-for-windows-gcm-screenshot] --- @@ -241,7 +241,7 @@ dotnet tool uninstall -g git-credential-manager [gcm-credstores]: credstores.md [gcm-wsl]: wsl.md [git-for-windows]: https://gitforwindows.org/ -[git-for-windows-gcm-screenshot]: ./img/git-for-windows-gcm-screenshot.png +[git-for-windows-gcm-screenshot]: img/git-for-windows-gcm-screenshot.png [latest-release]: https://github.com/git-ecosystem/git-credential-manager/releases/latest [linux-uninstall]: linux-fromsrc-uninstall.md [linux-validate-gpg-debian]: ./linux-validate-gpg.md#debian-package From 3ab48cd52e06310eb33a5b4bc5580a95719b839e Mon Sep 17 00:00:00 2001 From: Ridvan Gundogmus Date: Tue, 11 Nov 2025 12:55:33 +0100 Subject: [PATCH 59/84] Fix interactive prompt handling Fix interactive prompt failures in non-interactive environments, add /dev/tty fallback --- .../Packaging.Linux/install-from-source.sh | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/linux/Packaging.Linux/install-from-source.sh b/src/linux/Packaging.Linux/install-from-source.sh index 19ccb6b61..4e640aa07 100755 --- a/src/linux/Packaging.Linux/install-from-source.sh +++ b/src/linux/Packaging.Linux/install-from-source.sh @@ -40,18 +40,26 @@ if [ -z $is_ci ]; then Git Credential Manager is licensed under the MIT License: https://aka.ms/gcm/license" - while true; do - read -p "Do you want to continue? [Y/n] " yn - case $yn in - [Yy]*|"") - break - ;; - [Nn]*) - exit - ;; - *) - echo "Please answer yes or no." - ;; + while true; do + # Display prompt once before reading input + printf "Do you want to continue? [Y/n] " + + # Prefer reading from the controlling terminal (TTY) when available, + # so that input works even if the script is piped (e.g. curl URL | sh) + if [ -r /dev/tty ]; then + read yn < /dev/tty + # If no TTY is available, attempt to read from standard input (stdin) + elif ! read yn; then + # If input is not possible via TTY or stdin, assume a non-interactive environment + # and abort with guidance for automated usage + echo "Interactive prompt unavailable in this environment. Use 'sh -s -- -y' for automated install." + exit 1 + fi + + case "$yn" in + [Yy]*|"") break ;; + [Nn]*) exit ;; + *) echo "Please answer yes or no." ;; esac done fi From defa6909c80a7568d73244d6ab1c871d411916df Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 23 Sep 2025 17:05:15 +0100 Subject: [PATCH 60/84] linux/{pack,layout}.sh: allow specification of output dir Allow a caller of pack.sh and layout.sh to specify the location of the payload and symbols. Signed-off-by: Matthew John Cheetham --- src/linux/Packaging.Linux/build.sh | 2 +- src/linux/Packaging.Linux/layout.sh | 17 +++++++++++++---- src/linux/Packaging.Linux/pack.sh | 12 ++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/linux/Packaging.Linux/build.sh b/src/linux/Packaging.Linux/build.sh index 62352a7e8..4a77eff69 100755 --- a/src/linux/Packaging.Linux/build.sh +++ b/src/linux/Packaging.Linux/build.sh @@ -64,7 +64,7 @@ PAYLOAD="$OUTDIR/payload" SYMBOLS="$OUTDIR/payload.sym" # Lay out payload -"$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" --runtime="$RUNTIME" || exit 1 +"$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" --runtime="$RUNTIME" --output="$PAYLOAD" --symbol-output="$SYMBOLS" || exit 1 if [ $INSTALL_FROM_SOURCE = true ]; then echo "Installing to $INSTALL_PREFIX" diff --git a/src/linux/Packaging.Linux/layout.sh b/src/linux/Packaging.Linux/layout.sh index ccf031156..fe3a0f2b8 100755 --- a/src/linux/Packaging.Linux/layout.sh +++ b/src/linux/Packaging.Linux/layout.sh @@ -23,10 +23,17 @@ case "$i" in CONFIGURATION="${i#*=}" shift # past argument=value ;; + --output=*) + PAYLOAD="${i#*=}" + shift # past argument=value + ;; --runtime=*) RUNTIME="${i#*=}" shift # past argument=value ;; + --symbol-output=*) + SYMBOLOUT="${i#*=}" + ;; *) # unknown option ;; @@ -46,10 +53,12 @@ FRAMEWORK=net8.0 # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" - -# Outputs -PAYLOAD="$PROJ_OUT/$CONFIGURATION/payload" -SYMBOLOUT="$PROJ_OUT/$CONFIGURATION/payload.sym" +if [ -z "$PAYLOAD" ]; then + die "--output was not set" +fi +if [ -z "$SYMBOLOUT" ]; then + SYMBOLOUT="$PAYLOAD.sym" +fi # Cleanup payload directory if [ -d "$PAYLOAD" ]; then diff --git a/src/linux/Packaging.Linux/pack.sh b/src/linux/Packaging.Linux/pack.sh index e69783bb6..52d0137e4 100755 --- a/src/linux/Packaging.Linux/pack.sh +++ b/src/linux/Packaging.Linux/pack.sh @@ -36,6 +36,10 @@ case "$i" in CONFIGURATION="${i#*=}" shift # past argument=value ;; + --output=*) + OUTPUT_ROOT="${i#*=}" + shift # past argument=value + ;; *) # unknown option ;; @@ -59,11 +63,15 @@ if [ -z "$RUNTIME" ]; then die "--runtime was not set" fi -TAROUT="$PROJ_OUT/$CONFIGURATION/tar/" +if [ -z "$OUTPUT_ROOT" ]; then + OUTPUT_ROOT="$PROJ_OUT/$CONFIGURATION" +fi + +TAROUT="$OUTPUT_ROOT/tar" TARBALL="$TAROUT/gcm-$RUNTIME.$VERSION.tar.gz" SYMTARBALL="$TAROUT/gcm-$RUNTIME.$VERSION-symbols.tar.gz" -DEBOUT="$PROJ_OUT/$CONFIGURATION/deb" +DEBOUT="$OUTPUT_ROOT/deb" DEBROOT="$DEBOUT/root" DEBPKG="$DEBOUT/gcm-$RUNTIME.$VERSION.deb" mkdir -p "$DEBROOT" From f893082095185e1ca735e2664ad8a05f64688556 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 11:41:24 +0100 Subject: [PATCH 61/84] dotnettool: translate layout+pack scripts to pwsh Translate the layout.sh and pack.sh Bash scripts to PowerShell scripts. We are now building the .NET tool NuGet packages on Windows. Signed-off-by: Matthew John Cheetham --- .github/workflows/release.yml | 2 +- src/shared/DotnetTool/dotnet-tool.nuspec | 5 +- src/shared/DotnetTool/layout.ps1 | 90 ++++++++++++++++++++++ src/shared/DotnetTool/layout.sh | 83 --------------------- src/shared/DotnetTool/pack.ps1 | 95 ++++++++++++++++++++++++ src/shared/DotnetTool/pack.sh | 52 ------------- 6 files changed, 188 insertions(+), 139 deletions(-) create mode 100644 src/shared/DotnetTool/layout.ps1 delete mode 100755 src/shared/DotnetTool/layout.sh create mode 100644 src/shared/DotnetTool/pack.ps1 delete mode 100755 src/shared/DotnetTool/pack.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2640fe21f..20d3309f1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -401,7 +401,7 @@ jobs: run: | src/shared/DotnetTool/pack.sh --configuration=Release \ --version="${{ needs.prereqs.outputs.version }}" \ - --publish-dir=$(pwd)/signed + --package-root=$(pwd)/signed - name: Upload unsigned package uses: actions/upload-artifact@v5 diff --git a/src/shared/DotnetTool/dotnet-tool.nuspec b/src/shared/DotnetTool/dotnet-tool.nuspec index cf9ba7444..35f81ebc9 100644 --- a/src/shared/DotnetTool/dotnet-tool.nuspec +++ b/src/shared/DotnetTool/dotnet-tool.nuspec @@ -6,13 +6,12 @@ Secure, cross-platform Git credential storage with authentication to Azure Repos, GitHub, and other popular Git hosting services. git-credential-manager images\icon.png - https://raw.githubusercontent.com/git-ecosystem/git-credential-manager/main/assets/gcm-transparent.png - - + + diff --git a/src/shared/DotnetTool/layout.ps1 b/src/shared/DotnetTool/layout.ps1 new file mode 100644 index 000000000..ca9b13011 --- /dev/null +++ b/src/shared/DotnetTool/layout.ps1 @@ -0,0 +1,90 @@ +<# +.SYNOPSIS + Lays out the .NET tool package directory. + +.PARAMETER Configuration + Build configuration (Debug/Release). Defaults to Debug. + +.PARAMETER Output + Root output directory for the nupkg layout. If omitted: + out/shared/DotnetTool/nupkg/ + +.EXAMPLE + pwsh ./layout.ps1 -Configuration Release + +.EXAMPLE + pwsh ./layout.ps1 -Output C:\temp\tool-layout + +#> + +[CmdletBinding()] +param( + [string]$Configuration = "Debug", + [string]$Output +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Make-Absolute { + param([string]$Path) + if ([string]::IsNullOrWhiteSpace($Path)) { return $null } + if ([System.IO.Path]::IsPathRooted($Path)) { return $Path } + return (Join-Path -Path (Get-Location) -ChildPath $Path) +} + +Write-Host "Starting layout..." -ForegroundColor Cyan + +# Directories +$ScriptDir = $PSScriptRoot +$Root = (Resolve-Path (Join-Path $ScriptDir "..\..\..")).Path +$Src = Join-Path $Root "src" +$Out = Join-Path $Root "out" +$DotnetToolRel = "shared/DotnetTool" +$GcmSrc = Join-Path $Src "shared\Git-Credential-Manager" +$ProjOut = Join-Path $Out $DotnetToolRel + +$Framework = "net8.0" + +if (-not $Output -or $Output.Trim() -eq "") { + $Output = Join-Path $ProjOut "nupkg\$Configuration" +} + +$ImgOut = Join-Path $Output "images" +$BinOut = Join-Path $Output "tools\$Framework\any" + +# Cleanup previous layout +if (Test-Path $Output) { + Write-Host "Cleaning existing output directory '$Output'..." + Remove-Item -Force -Recurse $Output +} + +# Recreate directories +$null = New-Item -ItemType Directory -Path $BinOut -Force +$null = New-Item -ItemType Directory -Path $ImgOut -Force + +# Determine DOTNET_ROOT if not set +if (-not $env:DOTNET_ROOT -or $env:DOTNET_ROOT.Trim() -eq "") { + $dotnetCmd = Get-Command dotnet -ErrorAction Stop + $env:DOTNET_ROOT = Split-Path -Parent $dotnetCmd.Source +} + +Write-Host "Publishing core application..." +& "$env:DOTNET_ROOT/dotnet" publish $GcmSrc ` + --configuration $Configuration ` + --framework $Framework ` + --output (Make-Absolute $BinOut) ` + -p:UseAppHost=false + +if ($LASTEXITCODE -ne 0) { + Write-Error "dotnet publish failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE +} + +Write-Host "Copying package configuration file..." +Copy-Item -Path (Join-Path $Src "$DotnetToolRel\DotnetToolSettings.xml") -Destination $BinOut -Force + +Write-Host "Copying images..." +Copy-Item -Path (Join-Path $Src "$DotnetToolRel\icon.png") -Destination $ImgOut -Force + +Write-Host "Layout complete." -ForegroundColor Green diff --git a/src/shared/DotnetTool/layout.sh b/src/shared/DotnetTool/layout.sh deleted file mode 100755 index f5244dbbd..000000000 --- a/src/shared/DotnetTool/layout.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash -make_absolute () { - case "$1" in - /*) - echo "$1" - ;; - *) - echo "$PWD/$1" - ;; - esac -} - -##################################################################### -# Lay out -##################################################################### -# Parse script arguments -for i in "$@" -do -case "$i" in - --configuration=*) - CONFIGURATION="${i#*=}" - shift # past argument=value - ;; - *) - # unknown option - ;; -esac -done - -# Directories -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" -SRC="$ROOT/src" -OUT="$ROOT/out" -GCM_SRC="$SRC/shared/Git-Credential-Manager" -DOTNET_TOOL="shared/DotnetTool" -PROJ_OUT="$OUT/$DOTNET_TOOL" - -CONFIGURATION="${CONFIGURATION:=Debug}" - -# Build parameters -FRAMEWORK=net8.0 - -# Outputs -OUTDIR="$PROJ_OUT/nupkg/$CONFIGURATION" -IMGOUT="$OUTDIR/images" -PAYLOAD="$OUTDIR/payload" -SYMBOLOUT="$OUTDIR/payload.sym" - -# Cleanup output directory -if [ -d "$OUTDIR" ]; then - echo "Cleaning existing output directory '$OUTDIR'..." - rm -rf "$OUTDIR" -fi - -# Ensure output directories exist -mkdir -p "$PAYLOAD" "$SYMBOLOUT" "$IMGOUT" - -if [ -z "$DOTNET_ROOT" ]; then - DOTNET_ROOT="$(dirname $(which dotnet))" -fi - -# Publish core application executables -echo "Publishing core application..." -$DOTNET_ROOT/dotnet publish "$GCM_SRC" \ - --configuration="$CONFIGURATION" \ - --framework="$FRAMEWORK" \ - --output="$(make_absolute "$PAYLOAD")" \ - -p:UseAppHost=false || exit 1 - -# Collect symbols -echo "Collecting managed symbols..." -mv "$PAYLOAD"/*.pdb "$SYMBOLOUT" || exit 1 - -# Copy DotnetToolSettings.xml file -echo "Copying out package configuration files..." -cp "$SRC/$DOTNET_TOOL/DotnetToolSettings.xml" "$PAYLOAD/" - -# Copy package icon image -echo "Copying images..." -cp "$SRC/$DOTNET_TOOL/icon.png" "$IMGOUT" || exit 1 - -echo "Build complete." diff --git a/src/shared/DotnetTool/pack.ps1 b/src/shared/DotnetTool/pack.ps1 new file mode 100644 index 000000000..6842d030a --- /dev/null +++ b/src/shared/DotnetTool/pack.ps1 @@ -0,0 +1,95 @@ +<# +.SYNOPSIS + Creates the NuGet package for the .NET tool. + +.PARAMETER Configuration + Build configuration (Debug/Release). Defaults to Debug. + +.PARAMETER Version + Package version (required). + +.PARAMETER PackageRoot + Root of the pre-laid-out package structure (from layout). Defaults to: + out/shared/DotnetTool/nupkg/ + +.PARAMETER Output + Optional directory for the produced .nupkg/.snupkg. If omitted NuGet chooses. + +.EXAMPLE + pwsh ./pack.ps1 -Version 2.0.123-beta + +.EXAMPLE + pwsh ./pack.ps1 -Configuration Release -Version 2.1.0 -Output C:\pkgs + +#> + +[CmdletBinding()] +param( + [string]$Configuration = "Debug", + [Parameter(Mandatory = $true)] + [string]$Version, + [string]$PackageRoot, + [string]$Output +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +Write-Host "Starting pack..." -ForegroundColor Cyan + +# Directories +$ScriptDir = $PSScriptRoot +$Root = (Resolve-Path (Join-Path $ScriptDir "..\..\..")).Path +$Src = Join-Path $Root "src" +$Out = Join-Path $Root "out" +$DotnetToolRel = "shared\DotnetTool" +$NuspecFile = Join-Path $Src "$DotnetToolRel\dotnet-tool.nuspec" + +if (-not (Test-Path $NuspecFile)) { + Write-Error "Could not locate nuspec file at '$NuspecFile'" + exit 1 +} + +if (-not $PackageRoot -or $PackageRoot.Trim() -eq "") { + $PackageRoot = Join-Path $Out "$DotnetToolRel\nupkg\$Configuration" +} + +if (-not (Test-Path $PackageRoot)) { + Write-Error "Package root '$PackageRoot' does not exist. Run layout.ps1 first." + exit 1 +} + +# Locate nuget +$nugetCmd = Get-Command nuget -ErrorAction SilentlyContinue +if (-not $nugetCmd) { + Write-Error "nuget CLI not found in PATH (install: https://www.nuget.org/downloads)" + exit 1 +} +$nugetExe = $nugetCmd.Source + +Write-Host "Creating .NET tool package..." + +$packArgs = @( + "pack", "$NuspecFile", + "-Properties", "Configuration=$Configuration", + "-Version", $Version, + "-Symbols", "-SymbolPackageFormat", "snupkg", + "-BasePath", "$PackageRoot" +) + +if ($Output -and $Output.Trim() -ne "") { + if (-not (Test-Path $Output)) { + Write-Host "Creating output directory '$Output'..." + New-Item -ItemType Directory -Force -Path $Output | Out-Null + } + $packArgs += @("-OutputDirectory", "$Output") +} + +& $nugetExe @packArgs + +if ($LASTEXITCODE -ne 0) { + Write-Error "nuget pack failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE +} + +Write-Host ".NET tool pack complete." -ForegroundColor Green diff --git a/src/shared/DotnetTool/pack.sh b/src/shared/DotnetTool/pack.sh deleted file mode 100755 index 5b2eaf8dc..000000000 --- a/src/shared/DotnetTool/pack.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -die () { - echo "$*" >&2 - exit 1 -} - -# Parse script arguments -for i in "$@" -do -case "$i" in - --configuration=*) - CONFIGURATION="${i#*=}" - shift # past argument=value - ;; - --version=*) - VERSION="${i#*=}" - shift # past argument=value - ;; - --publish-dir=*) - PUBLISH_DIR="${i#*=}" - shift # past argument=value - ;; - *) - # unknown option - ;; -esac -done - -CONFIGURATION="${CONFIGURATION:=Debug}" -if [ -z "$VERSION" ]; then - die "--version was not set" -fi - -# Directories -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" -SRC="$ROOT/src" -OUT="$ROOT/out" -DOTNET_TOOL="shared/DotnetTool" - -if [ -z "$PUBLISH_DIR" ]; then - PUBLISH_DIR="$OUT/$DOTNET_TOOL/nupkg/$CONFIGURATION" -fi - -echo "Creating dotnet tool package..." - -dotnet pack "$SRC/$DOTNET_TOOL/DotnetTool.csproj" \ - /p:Configuration="$CONFIGURATION" \ - /p:PackageVersion="$VERSION" \ - /p:PublishDir="$PUBLISH_DIR/" - -echo "Dotnet tool pack complete." From 6e374baf267e24f011f45017b7c87946620503cf Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Fri, 31 Oct 2025 11:26:41 +0000 Subject: [PATCH 62/84] osx/codesign.sh: print entitlements file before signing Signed-off-by: Matthew John Cheetham --- src/osx/Installer.Mac/codesign.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/osx/Installer.Mac/codesign.sh b/src/osx/Installer.Mac/codesign.sh index d66c8acd9..edd3d09fb 100755 --- a/src/osx/Installer.Mac/codesign.sh +++ b/src/osx/Installer.Mac/codesign.sh @@ -20,6 +20,11 @@ echo "Directory: $SIGN_DIR" echo "Developer ID: $DEVELOPER_ID" echo "Entitlements: $ENTITLEMENTS_FILE" echo "======== END INPUTS ========" +echo +echo "======== ENTITLEMENTS ========" +cat $ENTITLEMENTS_FILE +echo "======== END ENTITLEMENTS ========" +echo cd $SIGN_DIR for f in * From c09872027012218730c379a036435c16a7250bda Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 4 Nov 2025 13:44:28 +0000 Subject: [PATCH 63/84] osx/codesign.sh: apply linter recommendations Apply linter recommendations to the codesign.sh script used on macOS. Also always pass the absolute path of the entitlements file to the codesign command as using relative paths can sometimes fail. Signed-off-by: Matthew John Cheetham --- src/osx/Installer.Mac/codesign.sh | 38 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/osx/Installer.Mac/codesign.sh b/src/osx/Installer.Mac/codesign.sh index edd3d09fb..44feedb6f 100755 --- a/src/osx/Installer.Mac/codesign.sh +++ b/src/osx/Installer.Mac/codesign.sh @@ -15,6 +15,13 @@ elif [ -z "$ENTITLEMENTS_FILE" ]; then exit 1 fi +# The codesign command needs the entitlements file to be given as an absolute +# file path; relative paths can cause issues. +if [[ "${ENTITLEMENTS_FILE}" != /* ]]; then + echo "error: entitlements file argument must be an absolute path" + exit 1 +fi + echo "======== INPUTS ========" echo "Directory: $SIGN_DIR" echo "Developer ID: $DEVELOPER_ID" @@ -22,30 +29,31 @@ echo "Entitlements: $ENTITLEMENTS_FILE" echo "======== END INPUTS ========" echo echo "======== ENTITLEMENTS ========" -cat $ENTITLEMENTS_FILE +cat "$ENTITLEMENTS_FILE" echo "======== END ENTITLEMENTS ========" echo -cd $SIGN_DIR +cd "$SIGN_DIR" || exit 1 for f in * do - macho=$(file --mime $f | grep mach) + macho=$(file --mime "$f" | grep mach) # Runtime sign dylibs and Mach-O binaries - if [[ $f == *.dylib ]] || [ ! -z "$macho" ]; + if [[ $f == *.dylib ]] || [ -n "$macho" ]; then - echo "Runtime Signing $f" - codesign -s "$DEVELOPER_ID" $f --timestamp --force --options=runtime --entitlements $ENTITLEMENTS_FILE + echo "Signing with entitlements and hardening: $f" + codesign -s "$DEVELOPER_ID" "$f" --timestamp --force --options=runtime --entitlements "$ENTITLEMENTS_FILE" elif [ -d "$f" ]; then - echo "Signing files in subdirectory $f" - cd $f - for i in * - do - codesign -s "$DEVELOPER_ID" $i --timestamp --force - done - cd .. + echo "Signing files in subdirectory: $f" + ( + cd "$f" || exit 1 + for i in * + do + codesign -s "$DEVELOPER_ID" "$i" --timestamp --force + done + ) else - echo "Signing $f" - codesign -s "$DEVELOPER_ID" $f --timestamp --force + echo "Signing: $f" + codesign -s "$DEVELOPER_ID" "$f" --timestamp --force fi done From 2d42fe4423667c5993c80bc12c1aa8a3f1084fc1 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 15:40:25 +0100 Subject: [PATCH 64/84] .azure-pipelines/release.yml: add SDL pool info Add specific Windows pool information for SDL source tasks. These tasks only run on Windows. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index c2c644db8..3cd41d4b6 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -18,6 +18,12 @@ parameters: extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelines parameters: + sdl: + # SDL source analysis tasks only run on Windows images + sourceAnalysisPool: + name: GitClientPME-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows stages: - stage: windows displayName: 'Windows' From 7d409d74bddf9035890c81eae5d210ff074bc2c2 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 15:41:04 +0100 Subject: [PATCH 65/84] .azure-pipelines/release.yml: use simple build number Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 3cd41d4b6..72d1972b2 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -1,4 +1,4 @@ -name: Release-$(Date:yyyyMMdd)$(Rev:.r) +name: $(Date:yyyyMMdd)$(Rev:.r) trigger: none pr: none From 19a60783f5601dec4378ca75875e204ddd31ed87 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 15:45:05 +0100 Subject: [PATCH 66/84] .azure-pipelines/release.yml: add Windows builds Add Windows release build definitions on Azure Pipelines. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 188 +++++++++++++++++++++++++++++++++-- 1 file changed, 178 insertions(+), 10 deletions(-) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 72d1972b2..b62f76537 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -15,6 +15,31 @@ parameters: default: false displayName: 'Enable ESRP code signing' +# +# 1ES Pipeline Templates do not allow using a matrix strategy so we create +# a YAML object parameter with and foreach to create jobs for each entry. +# Each OS has its own matrix object since their build steps differ. +# + - name: windows_matrix + type: object + default: + - id: windows_x64 + jobName: 'Windows (x86)' + runtime: win-x86 + pool: GitClientPME-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows + +variables: + - name: 'esrpAppConnectionName' + value: '1ESGitClient-ESRP-App' + # ESRP signing variables set in the pipeline settings: + # - esrpEndpointUrl + # - esrpClientId + # - esrpTenantId + # - esrpKeyVaultName + # - esrpSignReqCertName + extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelines parameters: @@ -25,14 +50,157 @@ extends: image: win-x86_64-ado1es os: windows stages: - - stage: windows - displayName: 'Windows' + - stage: build + displayName: 'Build and Sign' jobs: - - job: win_x86_build - displayName: 'Windows Build and Sign (x86)' - pool: - name: GitClient-1ESHostedPool-intel-pc - image: win-x86_64-ado1es - os: windows - steps: - - checkout: self + # + # Windows build jobs + # + - ${{ each dim in parameters.windows_matrix }}: + - job: ${{ dim.id }} + displayName: ${{ dim.jobName }} + pool: + name: ${{ dim.pool }} + image: ${{ dim.image }} + os: ${{ dim.os }} + templateContext: + outputs: + - output: pipelineArtifact + targetPath: '$(Build.ArtifactStagingDirectory)\_final' + artifactName: '${{ dim.runtime }}' + steps: + - checkout: self + - task: PowerShell@2 + displayName: 'Read version file' + inputs: + targetType: inline + script: | + $version = (Get-Content .\VERSION) -replace '\.\d+$', '' + Write-Host "##vso[task.setvariable variable=version;isReadOnly=true]$version" + - task: UseDotNet@2 + displayName: 'Use .NET 8 SDK' + inputs: + packageType: sdk + version: '8.x' + - task: PowerShell@2 + displayName: 'Build payload' + inputs: + targetType: filePath + filePath: '.\src\windows\Installer.Windows\layout.ps1' + arguments: | + -Configuration Release ` + -Output $(Build.ArtifactStagingDirectory)\payload ` + -SymbolOutput $(Build.ArtifactStagingDirectory)\symbols_raw + - task: ArchiveFiles@2 + displayName: 'Archive symbols' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\symbols_raw' + includeRootFolder: false + archiveType: zip + archiveFile: '$(Build.ArtifactStagingDirectory)\symbols\gcm-win-x86-$(version)-symbols.zip' + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign payload' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)\payload' + pattern: | + **/*.exe + **/*.dll + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "OpusName": "Microsoft", + "OpusInfo": "https://www.microsoft.com", + "FileDigest": "/fd SHA256", + "PageHash": "/NPH", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + }, + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: PowerShell@2 + displayName: 'Build installers' + inputs: + targetType: inline + script: | + dotnet build '.\src\windows\Installer.Windows\Installer.Windows.csproj' ` + --configuration Release ` + --no-dependencies ` + -p:NoLayout=true ` + -p:PayloadPath="$(Build.ArtifactStagingDirectory)\payload" ` + -p:OutputPath="$(Build.ArtifactStagingDirectory)\installers" + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign installers' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)\installers' + pattern: '**/*.exe' + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "OpusName": "Microsoft", + "OpusInfo": "https://www.microsoft.com", + "FileDigest": "/fd SHA256", + "PageHash": "/NPH", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + }, + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: ArchiveFiles@2 + displayName: 'Archive signed payload' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\payload' + includeRootFolder: false + archiveType: zip + archiveFile: '$(Build.ArtifactStagingDirectory)\installers\gcm-win-x86-$(version).zip' + - task: PowerShell@2 + displayName: 'Collect artifacts for publishing' + inputs: + targetType: inline + script: | + New-Item -Path "$(Build.ArtifactStagingDirectory)\_final" -ItemType Directory -Force + Copy-Item "$(Build.ArtifactStagingDirectory)\installers\*.exe" -Destination "$(Build.ArtifactStagingDirectory)\_final" + Copy-Item "$(Build.ArtifactStagingDirectory)\installers\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" + Copy-Item "$(Build.ArtifactStagingDirectory)\symbols\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" + Copy-Item "$(Build.ArtifactStagingDirectory)\payload" -Destination "$(Build.ArtifactStagingDirectory)\_final" -Recurse From 620c3bf4ba547d86b33d036cf1249557586eace5 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 10:47:35 +0100 Subject: [PATCH 67/84] .azure-pipelines/release.yml: add macOS builds Add macOS release build definitions on Azure Pipelines. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 286 +++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index b62f76537..0666d77a3 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -30,9 +30,27 @@ parameters: image: win-x86_64-ado1es os: windows + - name: macos_matrix + type: object + default: + - id: macos_x64 + jobName: 'macOS (x64)' + runtime: osx-x64 + pool: 'Azure Pipelines' + image: macOS-latest + os: macos + - id: macos_arm64 + jobName: 'macOS (ARM64)' + runtime: osx-arm64 + pool: 'Azure Pipelines' + image: macOS-latest + os: macos + variables: - name: 'esrpAppConnectionName' value: '1ESGitClient-ESRP-App' + - name: 'esrpMIConnectionName' + value: '1ESGitClient-ESRP-MI' # ESRP signing variables set in the pipeline settings: # - esrpEndpointUrl # - esrpClientId @@ -204,3 +222,271 @@ extends: Copy-Item "$(Build.ArtifactStagingDirectory)\installers\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" Copy-Item "$(Build.ArtifactStagingDirectory)\symbols\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" Copy-Item "$(Build.ArtifactStagingDirectory)\payload" -Destination "$(Build.ArtifactStagingDirectory)\_final" -Recurse + + # + # macOS build jobs + # + - ${{ each dim in parameters.macos_matrix }}: + - job: ${{ dim.id }} + displayName: ${{ dim.jobName }} + pool: + name: ${{ dim.pool }} + image: ${{ dim.image }} + os: ${{ dim.os }} + templateContext: + outputs: + - output: pipelineArtifact + targetPath: '$(Build.ArtifactStagingDirectory)/_final' + artifactName: '${{ dim.runtime }}' + steps: + - checkout: self + - task: Bash@3 + displayName: 'Read version file' + inputs: + targetType: inline + script: | + echo "##vso[task.setvariable variable=version;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" + - task: UseDotNet@2 + displayName: 'Use .NET 8 SDK' + inputs: + packageType: sdk + version: '8.x' + - task: Bash@3 + displayName: 'Build payload' + inputs: + targetType: filePath + filePath: './src/osx/Installer.Mac/layout.sh' + arguments: | + --runtime="${{ dim.runtime }}" \ + --configuration="Release" \ + --output="$(Build.ArtifactStagingDirectory)/payload" \ + --symbol-output="$(Build.ArtifactStagingDirectory)/symbols_raw" + - task: ArchiveFiles@2 + displayName: 'Archive symbols' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/symbols_raw' + includeRootFolder: false + archiveType: tar + tarCompression: gz + archiveFile: '$(Build.ArtifactStagingDirectory)/symbols/gcm-${{ dim.runtime }}-$(version)-symbols.tar.gz' + - task: AzureKeyVault@2 + displayName: 'Download developer certificate' + inputs: + azureSubscription: '$(esrpMIConnectionName)' + keyVaultName: '$(esrpKeyVaultName)' + secretsFilter: 'mac-developer-certificate,mac-developer-certificate-password,mac-developer-certificate-identity' + - task: Bash@3 + displayName: 'Import developer certificate' + inputs: + targetType: inline + script: | + # Create and unlock a keychain for the developer certificate + security create-keychain -p pwd $(Agent.TempDirectory)/buildagent.keychain + security default-keychain -s $(Agent.TempDirectory)/buildagent.keychain + security unlock-keychain -p pwd $(Agent.TempDirectory)/buildagent.keychain + + echo $(mac-developer-certificate) | base64 -D > $(Agent.TempDirectory)/cert.p12 + echo $(mac-developer-certificate-password) > $(Agent.TempDirectory)/cert.password + + # Import the developer certificate + security import $(Agent.TempDirectory)/cert.p12 \ + -k $(Agent.TempDirectory)/buildagent.keychain \ + -P "$(mac-developer-certificate-password)" \ + -T /usr/bin/codesign + + # Clean up the cert file immediately after import + rm $(Agent.TempDirectory)/cert.p12 + + # Set ACLs to allow codesign to access the private key + security set-key-partition-list \ + -S apple-tool:,apple:,codesign: \ + -s -k pwd \ + $(Agent.TempDirectory)/buildagent.keychain + - task: Bash@3 + displayName: 'Developer sign payload files' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/tosign/payload + + # Copy the files that need signing (Mach-o executables and dylibs) + pushd $(Build.ArtifactStagingDirectory)/payload + find . -type f -exec file --mime {} + \ + | sed -n '/mach/s/: .*//p' \ + | while IFS= read -r f; do + rel="${f#./}" + tgt="$(Build.ArtifactStagingDirectory)/tosign/payload/$rel" + mkdir -p "$(dirname "$tgt")" + cp -- "$f" "$tgt" + done + popd + + # Developer sign the files + ./src/osx/Installer.Mac/codesign.sh \ + "$(Build.ArtifactStagingDirectory)/tosign/payload" \ + "$(mac-developer-certificate-identity)" \ + "$PWD/src/osx/Installer.Mac/entitlements.xml" + # ESRP code signing for macOS requires the files be packaged in a zip file for submission + - task: ArchiveFiles@2 + displayName: 'Archive files for signing' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/tosign/payload' + includeRootFolder: false + archiveType: zip + archiveFile: '$(Build.ArtifactStagingDirectory)/tosign/payload.zip' + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign payload' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/tosign' + pattern: 'payload.zip' + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "Hardening": "Enable" + } + } + ] + # Extract signed files, overwriting the unsigned files, ready for packaging + - task: Bash@3 + displayName: 'Extract signed payload files' + inputs: + targetType: inline + script: | + unzip -uo $(Build.ArtifactStagingDirectory)/tosign/payload.zip -d $(Build.ArtifactStagingDirectory)/payload + - task: Bash@3 + displayName: 'Build component package' + inputs: + targetType: filePath + filePath: './src/osx/Installer.Mac/pack.sh' + arguments: | + --version="$(version)" \ + --payload="$(Build.ArtifactStagingDirectory)/payload" \ + --output="$(Build.ArtifactStagingDirectory)/pkg/com.microsoft.gitcredentialmanager.component.pkg" + - task: Bash@3 + displayName: 'Build installer package' + inputs: + targetType: filePath + filePath: './src/osx/Installer.Mac/dist.sh' + arguments: | + --version="$(version)" \ + --runtime="${{ dim.runtime }}" \ + --package-path="$(Build.ArtifactStagingDirectory)/pkg" \ + --output="$(Build.ArtifactStagingDirectory)/installers-presign/gcm-${{ dim.runtime }}-$(version).pkg" + # ESRP code signing for macOS requires the files be packaged in a zip file first + - task: Bash@3 + displayName: 'Prepare installer package for signing' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/tosign + cd $(Build.ArtifactStagingDirectory)/installers-presign + zip -rX $(Build.ArtifactStagingDirectory)/tosign/installers-presign.zip *.pkg + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign installer package' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/tosign' + pattern: 'installers-presign.zip' + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "Hardening": "Enable" + } + } + ] + # Extract signed installer, overwriting the unsigned installer + - task: Bash@3 + displayName: 'Extract signed installer package' + inputs: + targetType: inline + script: | + unzip -uo $(Build.ArtifactStagingDirectory)/tosign/installers-presign.zip -d $(Build.ArtifactStagingDirectory)/installers + - task: Bash@3 + displayName: 'Prepare installer package for notarization' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/tosign + cd $(Build.ArtifactStagingDirectory)/installers + zip -rX $(Build.ArtifactStagingDirectory)/tosign/installers.zip *.pkg + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Notarize installer package' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/tosign' + pattern: 'installers.zip' + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppNotarize", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "BundleId": "com.microsoft.gitcredentialmanager" + } + } + ] + # Extract signed and notarized installer pkg files, overwriting the unsigned files, ready for upload + - task: Bash@3 + displayName: 'Extract signed and notarized installer package' + inputs: + targetType: inline + script: | + unzip -uo $(Build.ArtifactStagingDirectory)/tosign/installers.zip -d $(Build.ArtifactStagingDirectory)/installers + - task: ArchiveFiles@2 + displayName: 'Archive signed payload' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/payload' + includeRootFolder: false + archiveType: tar + tarCompression: gz + archiveFile: '$(Build.ArtifactStagingDirectory)/installers/gcm-${{ dim.runtime }}-$(version).tar.gz' + - task: Bash@3 + displayName: 'Collect artifacts for publishing' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/installers/*.pkg $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final + cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final From 296838cfc55491f8fc338255fb4f79ab7d3dbd83 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 10:48:28 +0100 Subject: [PATCH 68/84] .azure-pipelines/release.yml: add Linux builds Add Linux release build definitions on Azure Pipelines. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 0666d77a3..f0496aa57 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -46,6 +46,16 @@ parameters: image: macOS-latest os: macos + - name: linux_matrix + type: object + default: + - id: linux_x64 + jobName: 'Linux (x64)' + runtime: linux-x64 + pool: GitClientPME-1ESHostedPool-intel-pc + image: ubuntu-x86_64-ado1es + os: linux + variables: - name: 'esrpAppConnectionName' value: '1ESGitClient-ESRP-App' @@ -490,3 +500,102 @@ extends: cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final + + # + # Linux build jobs + # + - ${{ each dim in parameters.linux_matrix }}: + - job: ${{ dim.id }} + displayName: ${{ dim.jobName }} + pool: + name: ${{ dim.pool }} + image: ${{ dim.image }} + os: ${{ dim.os }} + templateContext: + outputs: + - output: pipelineArtifact + targetPath: '$(Build.ArtifactStagingDirectory)/_final' + artifactName: '${{ dim.runtime }}' + steps: + - checkout: self + - task: Bash@3 + displayName: 'Read version file' + inputs: + targetType: inline + script: | + echo "##vso[task.setvariable variable=version;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" + - task: UseDotNet@2 + displayName: 'Use .NET 8 SDK' + inputs: + packageType: sdk + version: '8.x' + - task: Bash@3 + displayName: 'Build payload' + inputs: + targetType: filePath + filePath: './src/linux/Packaging.Linux/layout.sh' + arguments: | + --runtime="${{ dim.runtime }}" \ + --configuration="Release" \ + --output="$(Build.ArtifactStagingDirectory)/payload" \ + --symbol-output="$(Build.ArtifactStagingDirectory)/symbols_raw" + - task: Bash@3 + displayName: 'Build packages' + inputs: + targetType: filePath + filePath: './src/linux/Packaging.Linux/pack.sh' + arguments: | + --version="$(version)" \ + --runtime="${{ dim.runtime }}" \ + --payload="$(Build.ArtifactStagingDirectory)/payload" \ + --symbols="$(Build.ArtifactStagingDirectory)/symbols_raw" \ + --output="$(Build.ArtifactStagingDirectory)/pkg" + - task: Bash@3 + displayName: 'Move packages' + inputs: + targetType: inline + script: | + # Move symbols + mkdir -p $(Build.ArtifactStagingDirectory)/symbols + mv $(Build.ArtifactStagingDirectory)/pkg/tar/gcm-*-symbols.tar.gz $(Build.ArtifactStagingDirectory)/symbols + + # Move binary packages + mkdir -p $(Build.ArtifactStagingDirectory)/installers + mv $(Build.ArtifactStagingDirectory)/pkg/tar/*.tar.gz $(Build.ArtifactStagingDirectory)/installers + mv $(Build.ArtifactStagingDirectory)/pkg/deb/*.deb $(Build.ArtifactStagingDirectory)/installers + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign Debian package' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/installers' + pattern: | + **/*.deb + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-453387-Pgp", + "OperationCode": "LinuxSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: Bash@3 + displayName: 'Collect artifacts for publishing' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/installers/*.deb $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final + cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final From 68d6cd6c0ec9b3842236537b807d1635bcced9c3 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 11:42:09 +0100 Subject: [PATCH 69/84] .azure-pipelines/release.yml: add .NET Tool release pipeline Add a release pipeline for the .NET Tool using Azure Pipelines. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 118 +++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index f0496aa57..bcf20f2a2 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -599,3 +599,121 @@ extends: cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final + + # + # .NET Tool build job + # + - job: dotnet_tool + displayName: '.NET Tool NuGet Package' + pool: + name: GitClientPME-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows + templateContext: + outputs: + - output: pipelineArtifact + targetPath: '$(Build.ArtifactStagingDirectory)/packages' + artifactName: 'dotnet-tool' + steps: + - checkout: self + - task: PowerShell@2 + displayName: 'Read version file' + inputs: + targetType: inline + script: | + $version = (Get-Content .\VERSION) -replace '\.\d+$', '' + Write-Host "##vso[task.setvariable variable=version;isReadOnly=true]$version" + - task: UseDotNet@2 + displayName: 'Use .NET 8 SDK' + inputs: + packageType: sdk + version: '8.x' + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet CLI' + inputs: + versionSpec: '>= 6.0' + - task: PowerShell@2 + displayName: 'Build payload' + inputs: + targetType: filePath + filePath: './src/shared/DotnetTool/layout.ps1' + arguments: | + -Configuration Release ` + -Output "$(Build.ArtifactStagingDirectory)/nupkg" + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign payload' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/nupkg' + pattern: | + **/*.exe + **/*.dll + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "OpusName": "Microsoft", + "OpusInfo": "https://www.microsoft.com", + "FileDigest": "/fd SHA256", + "PageHash": "/NPH", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + }, + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: PowerShell@2 + displayName: 'Create NuGet packages' + inputs: + targetType: filePath + filePath: './src/shared/DotnetTool/pack.ps1' + arguments: | + -Configuration Release ` + -Version "$(version)" ` + -PackageRoot "$(Build.ArtifactStagingDirectory)/nupkg" ` + -Output "$(Build.ArtifactStagingDirectory)/packages" + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign NuGet packages' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/packages' + pattern: | + **/*.nupkg + **/*.snupkg + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401405", + "OperationCode": "NuGetSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] From 66af9504aa24a3b149544a096b34044b82ab5ceb Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 14:31:24 +0100 Subject: [PATCH 70/84] .azure-pipelines/release.yml: add GitHub and NuGet.org publishing Add a new stage (after build) to publish the assets to GitHub and NuGet.org. Each target (GitHub and NuGet.org) need to run in separate jobs due to restrictions of the 1ES pipeline templates: - Publishing a NuGet package requires us to use template `outputs` - `type: releaseJob` cannot specify outputs - `type: releaseJob` is required to use the `GitHubRelease` task Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 112 +++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index bcf20f2a2..0e76acf89 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -14,6 +14,14 @@ parameters: type: boolean default: false displayName: 'Enable ESRP code signing' + - name: 'github' + type: boolean + default: false + displayName: 'Enable GitHub release publishing' + - name: 'nuget' + type: boolean + default: false + displayName: 'Enable NuGet package publishing' # # 1ES Pipeline Templates do not allow using a matrix strategy so we create @@ -61,6 +69,10 @@ variables: value: '1ESGitClient-ESRP-App' - name: 'esrpMIConnectionName' value: '1ESGitClient-ESRP-MI' + - name: 'githubConnectionName' + value: 'GitHub-GitCredentialManager' + - name: 'nugetConnectionName' + value: '1ESGitClient-NuGet' # ESRP signing variables set in the pipeline settings: # - esrpEndpointUrl # - esrpClientId @@ -717,3 +729,103 @@ extends: "Parameters": {} } ] + + - stage: release + displayName: 'Release' + dependsOn: [build] + condition: and(succeeded(), or(eq('${{ parameters.github }}', true), eq('${{ parameters.nuget }}', true))) + jobs: + - job: release_validation + displayName: 'Release validation' + pool: + name: GitClientPME-1ESHostedPool-intel-pc + image: ubuntu-x86_64-ado1es + os: linux + steps: + - task: Bash@3 + displayName: 'Read version file' + name: version + inputs: + targetType: inline + script: | + echo "##vso[task.setvariable variable=value;isOutput=true;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" + + - job: github + displayName: 'Publish GitHub release' + dependsOn: release_validation + condition: and(succeeded(), eq('${{ parameters.github }}', true)) + pool: + name: GitClientPME-1ESHostedPool-intel-pc + image: ubuntu-x86_64-ado1es + os: linux + variables: + version: $[dependencies.release_validation.outputs['version.value']] + templateContext: + type: releaseJob + isProduction: true + inputs: + # Installers and packages + - input: pipelineArtifact + artifactName: 'win-x86' + targetPath: $(Pipeline.Workspace)/assets/win-x86 + - input: pipelineArtifact + artifactName: 'osx-x64' + targetPath: $(Pipeline.Workspace)/assets/osx-x64 + - input: pipelineArtifact + artifactName: 'osx-arm64' + targetPath: $(Pipeline.Workspace)/assets/osx-arm64 + - input: pipelineArtifact + artifactName: 'linux-x64' + targetPath: $(Pipeline.Workspace)/assets/linux-x64 + - input: pipelineArtifact + artifactName: 'dotnet-tool' + targetPath: $(Pipeline.Workspace)/assets/dotnet-tool + steps: + - task: GitHubRelease@1 + displayName: 'Create Draft GitHub Release' + condition: and(succeeded(), eq('${{ parameters.github }}', true)) + inputs: + gitHubConnection: $(githubConnectionName) + repositoryName: git-ecosystem/git-credential-manager + tag: 'v$(version)' + tagSource: userSpecifiedTag + target: release + title: 'GCM $(version)' + isDraft: true + addChangeLog: false + assets: | + $(Pipeline.Workspace)/assets/win-x86/*.exe + $(Pipeline.Workspace)/assets/win-x86/*.zip + $(Pipeline.Workspace)/assets/osx-x64/*.pkg + $(Pipeline.Workspace)/assets/osx-x64/*.tar.gz + $(Pipeline.Workspace)/assets/osx-arm64/*.pkg + $(Pipeline.Workspace)/assets/osx-arm64/*.tar.gz + $(Pipeline.Workspace)/assets/linux-x64/*.deb + $(Pipeline.Workspace)/assets/linux-x64/*.tar.gz + $(Pipeline.Workspace)/assets/dotnet-tool/*.nupkg + $(Pipeline.Workspace)/assets/dotnet-tool/*.snupkg + + - job: nuget + displayName: 'Publish NuGet package' + dependsOn: release_validation + condition: and(succeeded(), eq('${{ parameters.nuget }}', true)) + pool: + name: GitClientPME-1ESHostedPool-intel-pc + image: ubuntu-x86_64-ado1es + os: linux + variables: + version: $[dependencies.release_validation.outputs['version.value']] + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'dotnet-tool' + targetPath: $(Pipeline.Workspace)/assets/dotnet-tool + outputs: + - output: nuget + condition: and(succeeded(), eq('${{ parameters.nuget }}', true)) + displayName: 'Publish .NET Tool NuGet package' + packagesToPush: '$(Pipeline.Workspace)/assets/dotnet-tool/*.nupkg;$(Pipeline.Workspace)/assets/dotnet-tool/*.snupkg' + packageParentPath: $(Pipeline.Workspace)/assets/dotnet-tool + nuGetFeedType: external + publishPackageMetadata: true + publishFeedCredentials: $(nugetConnectionName) From 80ef7490b5da095007a395f8d286a5767a682335 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 10 Nov 2025 13:53:27 +0000 Subject: [PATCH 71/84] .github/workflows: remove old release workflows Remove the GitHub Actions-based release workflow files. We are using Azure Pipelines instead now. Signed-off-by: Matthew John Cheetham --- .github/workflows/release-dotnet-tool.yaml | 22 - .github/workflows/release.yml | 672 --------------------- 2 files changed, 694 deletions(-) delete mode 100644 .github/workflows/release-dotnet-tool.yaml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release-dotnet-tool.yaml b/.github/workflows/release-dotnet-tool.yaml deleted file mode 100644 index 594a2f4a3..000000000 --- a/.github/workflows/release-dotnet-tool.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: release-dotnet-tool -on: - release: - types: [released] - -jobs: - release: - runs-on: windows-latest - environment: release - steps: - - name: Download NuGet package from release and publish - run: | - # Get asset information - $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json - $asset = $github.release.assets | Where-Object -Property name -match '.nupkg$' - - # Download asset - Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $asset.name - - # Publish asset - dotnet nuget push $asset.name --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json - shell: powershell diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 20d3309f1..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,672 +0,0 @@ -name: release - -on: - workflow_dispatch: - -permissions: - id-token: write - contents: write - -jobs: - prereqs: - name: Prerequisites - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v5 - - - name: Set version - run: echo "version=$(cat VERSION | sed -E 's/.[0-9]+$//')" >> $GITHUB_OUTPUT - id: version - -# ================================ -# macOS -# ================================ - create-macos-artifacts: - name: Create macOS artifacts - runs-on: macos-latest - environment: release - needs: prereqs - strategy: - matrix: - runtime: [ osx-x64, osx-arm64 ] - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build - run: | - dotnet build src/osx/Installer.Mac/*.csproj \ - --configuration=MacRelease --no-self-contained \ - --runtime=${{ matrix.runtime }} - - - name: Run macOS unit tests - run: | - dotnet test --configuration=MacRelease - - - name: Lay out payload and symbols - run: | - src/osx/Installer.Mac/layout.sh \ - --configuration=MacRelease --output=payload \ - --symbol-output=symbols --runtime=${{ matrix.runtime }} - - - name: Set up signing/notarization infrastructure - env: - A1: ${{ secrets.GATEWATCHER_DEVELOPER_ID_CERT }} - A2: ${{ secrets.GATEWATCHER_DEVELOPER_ID_PASSWORD }} - I1: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }} - I2: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }} - N1: ${{ secrets.APPLE_TEAM_ID }} - N2: ${{ secrets.APPLE_DEVELOPER_ID }} - N3: ${{ secrets.APPLE_DEVELOPER_PASSWORD }} - N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} - run: | - echo "Setting up signing certificates" - security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain - security default-keychain -s $RUNNER_TEMP/buildagent.keychain - security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain - - echo $A1 | base64 -D > $RUNNER_TEMP/cert.p12 - security import $RUNNER_TEMP/cert.p12 \ - -k $RUNNER_TEMP/buildagent.keychain \ - -P $A2 \ - -T /usr/bin/codesign - security set-key-partition-list \ - -S apple-tool:,apple:,codesign: \ - -s -k pwd \ - $RUNNER_TEMP/buildagent.keychain - - echo $I1 | base64 -D > $RUNNER_TEMP/cert.p12 - security import $RUNNER_TEMP/cert.p12 \ - -k $RUNNER_TEMP/buildagent.keychain \ - -P $I2 \ - -T /usr/bin/productbuild - security set-key-partition-list \ - -S apple-tool:,apple:,productbuild: \ - -s -k pwd \ - $RUNNER_TEMP/buildagent.keychain - - echo "Setting up notarytool" - xcrun notarytool store-credentials \ - --team-id $N1 \ - --apple-id $N2 \ - --password $N3 \ - "$N4" - - - name: Run codesign against payload - env: - A3: ${{ secrets.APPLE_APPLICATION_SIGNING_IDENTITY }} - run: | - ./src/osx/Installer.Mac/codesign.sh "payload" "$A3" \ - "$GITHUB_WORKSPACE/src/osx/Installer.Mac/entitlements.xml" - - - name: Create component package - run: | - src/osx/Installer.Mac/pack.sh --payload="payload" \ - --version="${{ needs.prereqs.outputs.version }}" \ - --output="components/com.microsoft.gitcredentialmanager.component.pkg" - - - name: Create and sign product archive - env: - I3: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }} - run: | - src/osx/Installer.Mac/dist.sh --package-path=components \ - --version="${{ needs.prereqs.outputs.version }}" \ - --runtime="${{ matrix.runtime }}" \ - --output="pkg/gcm-${{ matrix.runtime }}-${{ needs.prereqs.outputs.version }}.pkg" \ - --identity="$I3" || exit 1 - - - name: Notarize product archive - env: - N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} - run: | - src/osx/Installer.Mac/notarize.sh \ - --package="pkg/gcm-${{ matrix.runtime }}-${{ needs.prereqs.outputs.version }}.pkg" \ - --keychain-profile="$N4" - - - name: Upload artifacts - uses: actions/upload-artifact@v5 - with: - name: macos-${{ matrix.runtime }}-artifacts - path: | - ./pkg/* - ./symbols/* - ./payload/* - -# ================================ -# Windows -# ================================ - create-windows-artifacts: - name: Create Windows Artifacts - runs-on: windows-latest - environment: release - needs: prereqs - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build - run: | - dotnet build --configuration=WindowsRelease - - - name: Run Windows unit tests - run: | - dotnet test --configuration=WindowsRelease - - - name: Lay out Windows payload and symbols - run: | - cd $env:GITHUB_WORKSPACE\src\windows\Installer.Windows\ - ./layout.ps1 -Configuration WindowsRelease ` - -Output $env:GITHUB_WORKSPACE\payload ` - -SymbolOutput $env:GITHUB_WORKSPACE\symbols - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Sign payload files with Azure Code Signing - uses: azure/trusted-signing-action@v0.5.10 - with: - endpoint: https://wus2.codesigning.azure.net/ - trusted-signing-account-name: git-fundamentals-signing - certificate-profile-name: git-fundamentals-windows-signing - files-folder: ${{ github.workspace }}\payload - files-folder-filter: exe,dll - file-digest: SHA256 - timestamp-rfc3161: http://timestamp.acs.microsoft.com - timestamp-digest: SHA256 - - # The Azure Code Signing action overrides the .NET version, so we reset it. - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build with signed payload - run: | - dotnet build $env:GITHUB_WORKSPACE\src\windows\Installer.Windows ` - /p:PayloadPath=$env:GITHUB_WORKSPACE\payload /p:NoLayout=true ` - --configuration=WindowsRelease - mkdir installers - Move-Item -Path .\out\windows\Installer.Windows\bin\Release\net472\*.exe ` - -Destination $env:GITHUB_WORKSPACE\installers - - - name: Sign installers with Azure Code Signing - uses: azure/trusted-signing-action@v0.5.10 - with: - endpoint: https://wus2.codesigning.azure.net/ - trusted-signing-account-name: git-fundamentals-signing - certificate-profile-name: git-fundamentals-windows-signing - files-folder: ${{ github.workspace }}\installers - files-folder-filter: exe - file-digest: SHA256 - timestamp-rfc3161: http://timestamp.acs.microsoft.com - timestamp-digest: SHA256 - - - name: Upload artifacts - uses: actions/upload-artifact@v5 - with: - name: windows-artifacts - path: | - payload - installers - symbols - -# ================================ -# Linux -# ================================ - create-linux-artifacts: - name: Create Linux Artifacts - runs-on: ubuntu-latest - environment: release - needs: prereqs - strategy: - matrix: - runtime: [ linux-x64, linux-arm64, linux-arm ] - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build - run: | - dotnet build src/linux/Packaging.Linux/*.csproj \ - --configuration=LinuxRelease --no-self-contained \ - --runtime=${{ matrix.runtime }} - - - name: Run Linux unit tests - run: | - dotnet test --configuration=LinuxRelease - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Prepare for GPG signing - env: - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - GPG_KEY_SECRET_NAME: ${{ secrets.GPG_KEY_SECRET_NAME }} - GPG_PASSPHRASE_SECRET_NAME: ${{ secrets.GPG_PASSPHRASE_SECRET_NAME }} - GPG_KEYGRIP_SECRET_NAME: ${{ secrets.GPG_KEYGRIP_SECRET_NAME }} - run: | - # Install debsigs - sudo apt install debsigs - - # Download GPG key, passphrase, and keygrip from Azure Key Vault - key=$(az keyvault secret show --name $GPG_KEY_SECRET_NAME --vault-name $AZURE_VAULT --query "value") - passphrase=$(az keyvault secret show --name $GPG_PASSPHRASE_SECRET_NAME --vault-name $AZURE_VAULT --query "value") - keygrip=$(az keyvault secret show --name $GPG_KEYGRIP_SECRET_NAME --vault-name $AZURE_VAULT --query "value") - - # Remove quotes from downloaded values - key=$(sed -e 's/^"//' -e 's/"$//' <<<"$key") - passphrase=$(sed -e 's/^"//' -e 's/"$//' <<<"$passphrase") - keygrip=$(sed -e 's/^"//' -e 's/"$//' <<<"$keygrip") - - # Import GPG key - echo "$key" | base64 -d | gpg --import --no-tty --batch --yes - - # Configure GPG - echo "allow-preset-passphrase" > ~/.gnupg/gpg-agent.conf - gpg-connect-agent RELOADAGENT /bye - /usr/lib/gnupg2/gpg-preset-passphrase --preset "$keygrip" <<<"$passphrase" - - - name: Sign Debian package and tarball - run: | - # Sign Debian package - version=${{ needs.prereqs.outputs.version }} - mv out/linux/Packaging.Linux/Release/deb/gcm-${{ matrix.runtime }}.$version.deb . - debsigs --sign=origin --verify --check gcm-${{ matrix.runtime }}.$version.deb - - # Generate tarball signature file - mv -v out/linux/Packaging.Linux/Release/tar/* . - gpg --batch --yes --armor --output gcm-${{ matrix.runtime }}.$version.tar.gz.asc \ - --detach-sig gcm-${{ matrix.runtime }}.$version.tar.gz - - - name: Upload artifacts - uses: actions/upload-artifact@v5 - with: - name: ${{ matrix.runtime }}-artifacts - path: | - ./*.deb - ./*.asc - ./*.tar.gz - -# ================================ -# .NET Tool -# ================================ - dotnet-tool-build: - name: Build .NET tool - runs-on: ubuntu-latest - needs: prereqs - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build .NET tool - run: | - src/shared/DotnetTool/layout.sh --configuration=Release - - - name: Upload .NET tool artifacts - uses: actions/upload-artifact@v5 - with: - name: tmp.dotnet-tool-build - path: | - out/shared/DotnetTool/nupkg/Release - - dotnet-tool-payload-sign: - name: Sign .NET tool payload - runs-on: windows-latest - environment: release - needs: dotnet-tool-build - steps: - - uses: actions/checkout@v5 - - - name: Download payload - uses: actions/download-artifact@v6 - with: - name: tmp.dotnet-tool-build - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Install sign CLI tool - run: | - dotnet tool install -g sign --version 0.9.1-beta.24325.5 - - - name: Sign payload - run: | - sign.exe code trusted-signing payload/* ` - -tse https://wus2.codesigning.azure.net/ ` - -tsa git-fundamentals-signing ` - -tscp git-fundamentals-windows-signing - - - name: Lay out signed payload, images, and symbols - shell: bash - run: | - mkdir dotnet-tool-payload-sign - mv images payload.sym payload -t dotnet-tool-payload-sign - - - name: Upload signed payload - uses: actions/upload-artifact@v5 - with: - name: dotnet-tool-payload-sign - path: | - dotnet-tool-payload-sign - - dotnet-tool-pack: - name: Package .NET tool - runs-on: ubuntu-latest - needs: [ prereqs, dotnet-tool-payload-sign ] - steps: - - uses: actions/checkout@v5 - - - name: Download signed payload - uses: actions/download-artifact@v6 - with: - name: dotnet-tool-payload-sign - path: signed - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Package tool - run: | - src/shared/DotnetTool/pack.sh --configuration=Release \ - --version="${{ needs.prereqs.outputs.version }}" \ - --package-root=$(pwd)/signed - - - name: Upload unsigned package - uses: actions/upload-artifact@v5 - with: - name: tmp.dotnet-tool-package-unsigned - path: | - out/shared/DotnetTool/nupkg/Release/*.nupkg - - dotnet-tool-sign: - name: Sign .NET tool package - runs-on: windows-latest - environment: release - needs: dotnet-tool-pack - steps: - - uses: actions/checkout@v5 - - - name: Download unsigned package - uses: actions/download-artifact@v6 - with: - name: tmp.dotnet-tool-package-unsigned - path: nupkg - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Install sign CLI tool - run: | - dotnet tool install -g sign --version 0.9.1-beta.24325.5 - - - name: Sign package - run: | - sign.exe code trusted-signing nupkg/* ` - -tse https://wus2.codesigning.azure.net/ ` - -tsa git-fundamentals-signing ` - -tscp git-fundamentals-windows-signing - - mv nupkg/* . - - # Remove this once NuGet supports the subscriber identity validation EKU: - # https://github.com/NuGet/NuGetGallery/issues/10027 - - name: Extract signing certificate from package - shell: pwsh - run: | - dotnet tool install --global Knapcode.CertificateExtractor - $nupkg = gci *.nupkg - nuget-cert-extractor --file $nupkg --output certs --code-signing --author --leaf - $cert = gci certs\*.cer - mv $cert .\nuget-signing.cer - - - name: Publish signed package and certificate - uses: actions/upload-artifact@v5 - with: - name: dotnet-tool-sign - path: | - *.nupkg - *.cer - -# ================================ -# Validate -# ================================ - validate: - name: Validate installers - strategy: - matrix: - component: - - os: ubuntu-latest - artifact: linux-x64-artifacts - command: git-credential-manager - description: linux-x64 - - os: macos-latest - artifact: macos-osx-x64-artifacts - command: git-credential-manager - description: osx-x64 - - os: windows-latest - artifact: windows-artifacts - # Even when a standalone GCM version is installed, GitHub actions - # runners still only recognize the version bundled with Git for - # Windows due to its placement on the PATH. For this reason, we use - # the full path to our installation to validate the Windows version. - command: "$PROGRAMFILES (x86)/Git Credential Manager/git-credential-manager.exe" - description: windows - - os: ubuntu-latest - artifact: dotnet-tool-sign - command: git-credential-manager - description: dotnet-tool - runs-on: ${{ matrix.component.os }} - needs: [ create-macos-artifacts, create-windows-artifacts, create-linux-artifacts, dotnet-tool-sign ] - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Download artifacts - uses: actions/download-artifact@v6 - with: - name: ${{ matrix.component.artifact }} - - - name: Install Windows - if: contains(matrix.component.description, 'windows') - shell: pwsh - run: | - $exePaths = Get-ChildItem -Path ./installers/*.exe | %{$_.FullName} - foreach ($exePath in $exePaths) - { - Start-Process -Wait -FilePath "$exePath" -ArgumentList "/SILENT /VERYSILENT /NORESTART" - } - - - name: Install Linux x64 (Debian package) - if: contains(matrix.component.description, 'linux-x64') - run: | - debpath=$(find ./*.deb) - sudo apt install $debpath - "${{ matrix.component.command }}" configure - - - name: Install Linux x64 (tarball) - if: contains(matrix.component.description, 'linux-x64') - run: | - # Ensure we find only the source tarball, not the symbols - tarpath=$(find . -name '*[[:digit:]].tar.gz') - tar -xvf $tarpath -C /usr/local/bin - "${{ matrix.component.command }}" configure - - - name: Install macOS - if: contains(matrix.component.description, 'osx-x64') - run: | - # Only validate x64, given arm64 agents are not available - pkgpath=$(find ./pkg/*.pkg) - sudo installer -pkg $pkgpath -target / - - - name: Install .NET tool - if: contains(matrix.component.description, 'dotnet-tool') - run: | - nupkgpath=$(find ./*.nupkg) - dotnet tool install -g --add-source $(dirname "$nupkgpath") git-credential-manager - "${{ matrix.component.command }}" configure - - - name: Validate - shell: bash - run: | - "${{ matrix.component.command }}" --version | sed 's/+.*//' >actual - cat VERSION | sed -E 's/.[0-9]+$//' >expect - cmp expect actual || exit 1 - -# ================================ -# Publish -# ================================ - create-github-release: - name: Publish GitHub draft release - runs-on: ubuntu-latest - env: - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - GPG_PUBLIC_KEY_SECRET_NAME: ${{ secrets.GPG_PUBLIC_KEY_SECRET_NAME }} - environment: release - needs: [ prereqs, validate ] - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Download artifacts - uses: actions/download-artifact@v6 - - - name: Archive macOS payload and symbols - run: | - version="${{ needs.prereqs.outputs.version }}" - mkdir osx-payload-and-symbols - - tar -C macos-osx-x64-artifacts/payload -czf osx-payload-and-symbols/gcm-osx-x64-$version.tar.gz . - tar -C macos-osx-x64-artifacts/symbols -czf osx-payload-and-symbols/gcm-osx-x64-$version-symbols.tar.gz . - - tar -C macos-osx-arm64-artifacts/payload -czf osx-payload-and-symbols/gcm-osx-arm64-$version.tar.gz . - tar -C macos-osx-arm64-artifacts/symbols -czf osx-payload-and-symbols/gcm-osx-arm64-$version-symbols.tar.gz . - - - name: Archive Windows payload and symbols - run: | - version="${{ needs.prereqs.outputs.version }}" - mkdir win-x86-payload-and-symbols - zip -jr win-x86-payload-and-symbols/gcm-win-x86-$version.zip windows-artifacts/payload - zip -jr win-x86-payload-and-symbols/gcm-win-x86-$version-symbols.zip windows-artifacts/symbols - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Download GPG public key signature file - run: | - az keyvault secret show --name "$GPG_PUBLIC_KEY_SECRET_NAME" \ - --vault-name "$AZURE_VAULT" --query "value" \ - | sed -e 's/^"//' -e 's/"$//' | base64 -d >gcm-public.asc - cp gcm-public.asc linux-x64-artifacts/ - cp gcm-public.asc linux-arm64-artifacts/ - mv gcm-public.asc linux-arm-artifacts - - - uses: actions/github-script@v8 - with: - script: | - const fs = require('fs'); - const path = require('path'); - const version = "${{ needs.prereqs.outputs.version }}" - - var releaseMetadata = { - owner: context.repo.owner, - repo: context.repo.repo - }; - - // Create the release - var tagName = `v${version}`; - var createdRelease = await github.rest.repos.createRelease({ - ...releaseMetadata, - draft: true, - tag_name: tagName, - target_commitish: context.sha, - name: `GCM ${version}` - }); - releaseMetadata.release_id = createdRelease.data.id; - - // Uploads contents of directory to the release created above - async function uploadDirectoryToRelease(directory, includeExtensions=[]) { - return fs.promises.readdir(directory) - .then(async(files) => Promise.all( - files.filter(file => { - return includeExtensions.length==0 || includeExtensions.includes(path.extname(file).toLowerCase()); - }) - .map(async (file) => { - var filePath = path.join(directory, file); - github.rest.repos.uploadReleaseAsset({ - ...releaseMetadata, - name: file, - headers: { - "content-length": (await fs.promises.stat(filePath)).size - }, - data: fs.createReadStream(filePath) - }); - })) - ); - } - - await Promise.all([ - // Upload Windows artifacts - uploadDirectoryToRelease('windows-artifacts/installers'), - uploadDirectoryToRelease('win-x86-payload-and-symbols'), - - // Upload macOS artifacts - uploadDirectoryToRelease('macos-osx-x64-artifacts/pkg'), - uploadDirectoryToRelease('macos-osx-arm64-artifacts/pkg'), - uploadDirectoryToRelease('osx-payload-and-symbols'), - - // Upload Linux artifacts - uploadDirectoryToRelease('linux-x64-artifacts'), - uploadDirectoryToRelease('linux-arm64-artifacts'), - uploadDirectoryToRelease('linux-arm-artifacts'), - - // Upload .NET tool package - uploadDirectoryToRelease('dotnet-tool-sign'), - ]); From a254ae1d5ab36b481eb11f293b23b6ba2d835e7e Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Thu, 13 Nov 2025 14:02:54 +0000 Subject: [PATCH 72/84] release.yml: enable signing, GitHub, NuGet publishing Default enable ESRP code signing, as well as publishing to GitHub and NuGet.org. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 0e76acf89..da78d9334 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -12,15 +12,15 @@ resources: parameters: - name: 'esrp' type: boolean - default: false + default: true displayName: 'Enable ESRP code signing' - name: 'github' type: boolean - default: false + default: true displayName: 'Enable GitHub release publishing' - name: 'nuget' type: boolean - default: false + default: true displayName: 'Enable NuGet package publishing' # From 54da17bc37a0d1889a41d15e6bdd2bb2686d39d6 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Thu, 13 Nov 2025 16:33:54 +0000 Subject: [PATCH 73/84] VERSION: bump to 2.7 for next release Signed-off-by: Matthew John Cheetham --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index cfad4122e..21b5059f4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.1.0 +2.7.0.0 From 22239391673b91ef98787ab5e8f733538e9bcc4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:04:38 +0000 Subject: [PATCH 74/84] build(deps): bump DavidAnson/markdownlint-cli2-action Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 20.0.0 to 21.0.0. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/992badcdf24e3b8eb7e87ff9287fe931bcb00c6e...30a0e04f1870d58f8d717450cc6134995f993c63) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: 21.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 7ba06c156..088076826 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e + - uses: DavidAnson/markdownlint-cli2-action@30a0e04f1870d58f8d717450cc6134995f993c63 with: globs: | "**/*.md" From 9728211e8bd7aafc8346e50358dd86b2023c6770 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:03:45 +0000 Subject: [PATCH 75/84] build(deps): bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/continuous-integration.yml | 6 +++--- .github/workflows/lint-docs.yml | 4 ++-- .github/workflows/validate-install-from-source.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7ec8fbe4f..e68b89285 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,7 +22,7 @@ jobs: language: [ 'csharp' ] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5.0.0 diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index d3f304490..1437bcd2d 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,7 +16,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5.0.0 @@ -59,7 +59,7 @@ jobs: runtime: [ linux-x64, linux-arm64, linux-arm ] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5.0.0 @@ -103,7 +103,7 @@ jobs: runtime: [ osx-x64, osx-arm64 ] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5.0.0 diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 088076826..6d1e06b89 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -18,7 +18,7 @@ jobs: name: Lint markdown files runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: DavidAnson/markdownlint-cli2-action@30a0e04f1870d58f8d717450cc6134995f993c63 with: @@ -30,7 +30,7 @@ jobs: name: Check for broken links runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Run link checker # For any troubleshooting, see: diff --git a/.github/workflows/validate-install-from-source.yml b/.github/workflows/validate-install-from-source.yml index 542a07c7c..d1aea471a 100644 --- a/.github/workflows/validate-install-from-source.yml +++ b/.github/workflows/validate-install-from-source.yml @@ -41,7 +41,7 @@ jobs: GNUPGHOME=/root/.gnupg tdnf install tar -y # needed for `actions/checkout` fi - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: | sh "${GITHUB_WORKSPACE}/src/linux/Packaging.Linux/install-from-source.sh" -y From b41d366190e1eb462b5d0ebd65ce55470bca5286 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 20:03:28 +0000 Subject: [PATCH 76/84] build(deps): bump actions/setup-dotnet from 5.0.0 to 5.0.1 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v5.0.0...v5.0.1) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: 5.0.1 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 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e68b89285..b89613684 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v5.0.0 + uses: actions/setup-dotnet@v5.0.1 with: dotnet-version: 8.0.x diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1437bcd2d..ec253f546 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v5.0.0 + uses: actions/setup-dotnet@v5.0.1 with: dotnet-version: 8.0.x @@ -62,7 +62,7 @@ jobs: - uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v5.0.0 + uses: actions/setup-dotnet@v5.0.1 with: dotnet-version: 8.0.x @@ -106,7 +106,7 @@ jobs: - uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v5.0.0 + uses: actions/setup-dotnet@v5.0.1 with: dotnet-version: 8.0.x From 9f0691b7c0fbea3ff71723e21a611758dedf85c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:04:07 +0000 Subject: [PATCH 77/84] build(deps): bump DavidAnson/markdownlint-cli2-action Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 21.0.0 to 22.0.0. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/30a0e04f1870d58f8d717450cc6134995f993c63...07035fd053f7be764496c0f8d8f9f41f98305101) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: 22.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index 6d1e06b89..a6bfcb121 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: DavidAnson/markdownlint-cli2-action@30a0e04f1870d58f8d717450cc6134995f993c63 + - uses: DavidAnson/markdownlint-cli2-action@07035fd053f7be764496c0f8d8f9f41f98305101 with: globs: | "**/*.md" From 445fb200b732cee8507cab79e4d40226aa1b1638 Mon Sep 17 00:00:00 2001 From: Ridvan Gundogmus Date: Thu, 11 Dec 2025 15:11:15 +0100 Subject: [PATCH 78/84] Support Oracle Linux in install-from-source.sh Added 'ol' to the list of supported distributions for installation. Oracle Linux /etc/os-release output: $ docker run -it oraclelinux:9 cat /etc/os-release NAME="Oracle Linux Server" VERSION="9.7" ID="ol" ID_LIKE="fedora" VERSION_ID="9.7" PLATFORM_ID="platform:el9" This confirms: ID="ol" ID_LIKE="fedora" Since Oracle Linux is RHEL-compatible and part of the Fedora/RHEL family, handling it under the same case block (fedora | centos | rhel | ol) is appropriate and consistent with existing logic. --- src/linux/Packaging.Linux/install-from-source.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/Packaging.Linux/install-from-source.sh b/src/linux/Packaging.Linux/install-from-source.sh index 4e640aa07..1a8ede938 100755 --- a/src/linux/Packaging.Linux/install-from-source.sh +++ b/src/linux/Packaging.Linux/install-from-source.sh @@ -189,7 +189,7 @@ case "$distribution" in fi fi ;; - fedora | centos | rhel) + fedora | centos | rhel | ol) $sudo_cmd dnf upgrade -y # Install dotnet/GCM dependencies. From d85a5f7ad0818f9a91df2ee7d1b23d0158afd829 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:03:37 +0000 Subject: [PATCH 79/84] build(deps): bump actions/upload-artifact from 5 to 6 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/continuous-integration.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ec253f546..24617f881 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -42,7 +42,7 @@ jobs: mv out/windows/Installer.Windows/bin/Release/net472/gcm*.exe artifacts/ - name: Upload artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: win-x86 path: | @@ -86,7 +86,7 @@ jobs: mv out/linux/Packaging.Linux/Release/tar/*.tar.gz artifacts/ - name: Upload artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.runtime }} path: | @@ -131,7 +131,7 @@ jobs: mv out/osx/Installer.Mac/pkg/Release/gcm*.pkg artifacts/ - name: Upload artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.runtime }} path: | From 5e53ef63a8262d441d638683ebbea7e9b39ec496 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 17 Dec 2025 12:57:40 +0000 Subject: [PATCH 80/84] release: add linux-arm64 builds to offical releases The build process already supports ARM64 for Linux, so let's extend the official build process to also produce these binaries. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index da78d9334..a95e96451 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -63,6 +63,12 @@ parameters: pool: GitClientPME-1ESHostedPool-intel-pc image: ubuntu-x86_64-ado1es os: linux + - id: linux_arm64 + jobName: 'Linux (ARM64)' + runtime: linux-arm64 + pool: GitClientPME-1ESHostedPool-arm64-pc + image: ubuntu-arm64-ado1es + os: linux variables: - name: 'esrpAppConnectionName' @@ -777,6 +783,9 @@ extends: - input: pipelineArtifact artifactName: 'linux-x64' targetPath: $(Pipeline.Workspace)/assets/linux-x64 + - input: pipelineArtifact + artifactName: 'linux-arm64' + targetPath: $(Pipeline.Workspace)/assets/linux-arm64 - input: pipelineArtifact artifactName: 'dotnet-tool' targetPath: $(Pipeline.Workspace)/assets/dotnet-tool @@ -802,6 +811,8 @@ extends: $(Pipeline.Workspace)/assets/osx-arm64/*.tar.gz $(Pipeline.Workspace)/assets/linux-x64/*.deb $(Pipeline.Workspace)/assets/linux-x64/*.tar.gz + $(Pipeline.Workspace)/assets/linux-arm64/*.deb + $(Pipeline.Workspace)/assets/linux-arm64/*.tar.gz $(Pipeline.Workspace)/assets/dotnet-tool/*.nupkg $(Pipeline.Workspace)/assets/dotnet-tool/*.snupkg From a22cf9c19b1c088d42f09999c19afeecbc3978f1 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Thu, 18 Dec 2025 12:47:12 +0000 Subject: [PATCH 81/84] linux/pack.sh: detect runtime from arch if not specified Detect the runtime (linux-x64/linux-arm64/etc) from the current host architecture if not specified via the `--runtime` argument. We had already been doing this in the script, but after we already `die`-d when `--runtime` was missing! Oops. Let's move this auto-detection logic up-front, and also no longer rely on `dpkg-architecture` to determine the host arch; we can use `uname -m` instead. Signed-off-by: Matthew John Cheetham --- src/linux/Packaging.Linux/pack.sh | 44 ++++++++++++++----------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/linux/Packaging.Linux/pack.sh b/src/linux/Packaging.Linux/pack.sh index 52d0137e4..821d66ea1 100755 --- a/src/linux/Packaging.Linux/pack.sh +++ b/src/linux/Packaging.Linux/pack.sh @@ -59,14 +59,30 @@ fi if [ -z "$SYMBOLS" ]; then die "--symbols was not set" fi -if [ -z "$RUNTIME" ]; then - die "--runtime was not set" -fi - if [ -z "$OUTPUT_ROOT" ]; then OUTPUT_ROOT="$PROJ_OUT/$CONFIGURATION" fi +# Fall back to host architecture if no explicit runtime is given. +if test -z "$RUNTIME"; then + HOST_ARCH="`uname -m`" + + case $HOST_ARCH in + x86_64|amd64) + RUNTIME="linux-x64" + ;; + aarch64|arm64) + RUNTIME="linux-arm64" + ;; + armhf) + RUNTIME="linux-arm" + ;; + *) + die "Could not determine host architecture! ($HOST_ARCH)" + ;; + esac +fi + TAROUT="$OUTPUT_ROOT/tar" TARBALL="$TAROUT/gcm-$RUNTIME.$VERSION.tar.gz" SYMTARBALL="$TAROUT/gcm-$RUNTIME.$VERSION-symbols.tar.gz" @@ -108,26 +124,6 @@ INSTALL_TO="$DEBROOT/usr/local/share/gcm-core/" LINK_TO="$DEBROOT/usr/local/bin/" mkdir -p "$DEBROOT/DEBIAN" "$INSTALL_TO" "$LINK_TO" || exit 1 -# Fall back to host architecture if no explicit runtime is given. -if test -z "$RUNTIME"; then - HOST_ARCH="`dpkg-architecture -q DEB_HOST_ARCH`" - - case $HOST_ARCH in - amd64) - RUNTIME="linux-x64" - ;; - arm64) - RUNTIME="linux-arm64" - ;; - armhf) - RUNTIME="linux-arm" - ;; - *) - die "Could not determine host architecture!" - ;; - esac -fi - # Determine architecture for debian control file from the runtime architecture case $RUNTIME in linux-x64) From f6fe9ca629b29d7e7d06baab6166b3ac40fb6514 Mon Sep 17 00:00:00 2001 From: xfabo1 Date: Thu, 18 Dec 2025 14:22:28 +0100 Subject: [PATCH 82/84] Fix: Add flag to search query for SecretService to retrieve all accounts --- src/shared/Core/Interop/Linux/SecretServiceCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/Core/Interop/Linux/SecretServiceCollection.cs b/src/shared/Core/Interop/Linux/SecretServiceCollection.cs index 093baf5c3..0d6342af1 100644 --- a/src/shared/Core/Interop/Linux/SecretServiceCollection.cs +++ b/src/shared/Core/Interop/Linux/SecretServiceCollection.cs @@ -66,7 +66,7 @@ private unsafe IEnumerable Enumerate(string service, string account secService, ref schema, queryAttrs, - SecretSearchFlags.SECRET_SEARCH_UNLOCK, + SecretSearchFlags.SECRET_SEARCH_UNLOCK | SecretSearchFlags.SECRET_SEARCH_ALL, IntPtr.Zero, out error); From c6ad8f532f59c1aad61ccf042b39f8c7be0a0f9c Mon Sep 17 00:00:00 2001 From: Dennis Ameling Date: Thu, 27 Feb 2025 12:05:09 +0100 Subject: [PATCH 83/84] Add support for Windows x64 and arm64 builds Previously, GCM was only built for x86 on Windows. This commit adds support for building for and on Windows x64 and arm64. It builds the host architecture by default and supports explicitly specifying the target architecture. Signed-off-by: Dennis Ameling --- .azure-pipelines/release.yml | 32 +++++++- .github/workflows/continuous-integration.yml | 28 +++++-- .../Git-Credential-Manager.csproj | 5 +- .../Installer.Windows.csproj | 25 ++++-- src/windows/Installer.Windows/Setup.iss | 15 +++- src/windows/Installer.Windows/layout.ps1 | 79 +++++++++++++++---- 6 files changed, 148 insertions(+), 36 deletions(-) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index a95e96451..3e99b1454 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -31,12 +31,24 @@ parameters: - name: windows_matrix type: object default: - - id: windows_x64 + - id: windows_x86 jobName: 'Windows (x86)' runtime: win-x86 pool: GitClientPME-1ESHostedPool-intel-pc image: win-x86_64-ado1es os: windows + - id: windows_x64 + jobName: 'Windows (x64)' + runtime: win-x64 + pool: GitClientPME-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows + - id: windows_arm64 + jobName: 'Windows (ARM64)' + runtime: win-arm64 + pool: GitClientPME-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows - name: macos_matrix type: object @@ -136,14 +148,15 @@ extends: arguments: | -Configuration Release ` -Output $(Build.ArtifactStagingDirectory)\payload ` - -SymbolOutput $(Build.ArtifactStagingDirectory)\symbols_raw + -SymbolOutput $(Build.ArtifactStagingDirectory)\symbols_raw ` + -RuntimeIdentifier ${{ dim.runtime }} - task: ArchiveFiles@2 displayName: 'Archive symbols' inputs: rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\symbols_raw' includeRootFolder: false archiveType: zip - archiveFile: '$(Build.ArtifactStagingDirectory)\symbols\gcm-win-x86-$(version)-symbols.zip' + archiveFile: '$(Build.ArtifactStagingDirectory)\symbols\gcm-${{ dim.runtime }}-$(version)-symbols.zip' - task: EsrpCodeSigning@5 condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) displayName: 'Sign payload' @@ -195,6 +208,7 @@ extends: -p:NoLayout=true ` -p:PayloadPath="$(Build.ArtifactStagingDirectory)\payload" ` -p:OutputPath="$(Build.ArtifactStagingDirectory)\installers" + -p:RuntimeIdentifier="${{ dim.runtime }}" - task: EsrpCodeSigning@5 condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) displayName: 'Sign installers' @@ -239,7 +253,7 @@ extends: rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\payload' includeRootFolder: false archiveType: zip - archiveFile: '$(Build.ArtifactStagingDirectory)\installers\gcm-win-x86-$(version).zip' + archiveFile: '$(Build.ArtifactStagingDirectory)\installers\gcm-${{ dim.runtime }}-$(version).zip' - task: PowerShell@2 displayName: 'Collect artifacts for publishing' inputs: @@ -774,6 +788,12 @@ extends: - input: pipelineArtifact artifactName: 'win-x86' targetPath: $(Pipeline.Workspace)/assets/win-x86 + - input: pipelineArtifact + artifactName: 'win-x64' + targetPath: $(Pipeline.Workspace)/assets/win-x64 + - input: pipelineArtifact + artifactName: 'win-arm64' + targetPath: $(Pipeline.Workspace)/assets/win-arm64 - input: pipelineArtifact artifactName: 'osx-x64' targetPath: $(Pipeline.Workspace)/assets/osx-x64 @@ -805,6 +825,10 @@ extends: assets: | $(Pipeline.Workspace)/assets/win-x86/*.exe $(Pipeline.Workspace)/assets/win-x86/*.zip + $(Pipeline.Workspace)/assets/win-x64/*.exe + $(Pipeline.Workspace)/assets/win-x64/*.zip + $(Pipeline.Workspace)/assets/win-arm64/*.exe + $(Pipeline.Workspace)/assets/win-arm64/*.zip $(Pipeline.Workspace)/assets/osx-x64/*.pkg $(Pipeline.Workspace)/assets/osx-x64/*.tar.gz $(Pipeline.Workspace)/assets/osx-arm64/*.pkg diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 24617f881..7bb45f26a 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -13,7 +13,16 @@ jobs: # ================================ windows: name: Windows - runs-on: windows-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - runtime: win-x86 + os: windows-latest + - runtime: win-x64 + os: windows-latest + - runtime: win-arm64 + os: windows-11-arm steps: - uses: actions/checkout@v6 @@ -27,24 +36,29 @@ jobs: run: dotnet restore - name: Build - run: dotnet build --configuration WindowsRelease + run: | + dotnet build src/windows/Installer.Windows/Installer.Windows.csproj ` + --configuration=Release ` + --runtime=${{ matrix.runtime }} - name: Test run: | - dotnet test --verbosity normal --configuration=WindowsRelease + dotnet test --verbosity normal ` + --configuration=WindowsRelease ` + --runtime=${{ matrix.runtime }} - name: Prepare artifacts shell: bash run: | mkdir -p artifacts/bin - mv out/windows/Installer.Windows/bin/Release/net472/win-x86 artifacts/bin/ - cp out/windows/Installer.Windows/bin/Release/net472/win-x86.sym/* artifacts/bin/win-x86/ - mv out/windows/Installer.Windows/bin/Release/net472/gcm*.exe artifacts/ + mv out/windows/Installer.Windows/bin/Release/net472/${{ matrix.runtime }}/gcm*.exe artifacts/ + mv out/windows/Installer.Windows/bin/Release/net472/${{ matrix.runtime }} artifacts/bin/ + cp out/windows/Installer.Windows/bin/Release/net472/${{ matrix.runtime }}.sym/* artifacts/bin/${{ matrix.runtime }}/ - name: Upload artifacts uses: actions/upload-artifact@v6 with: - name: win-x86 + name: ${{ matrix.runtime }} path: | artifacts diff --git a/src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj b/src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj index 456adf547..8c469897e 100644 --- a/src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj +++ b/src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj @@ -1,11 +1,10 @@ - + Exe net8.0 net472;net8.0 - win-x86;osx-x64;linux-x64;osx-arm64;linux-arm64;linux-arm - x86 + win-x86;win-x64;win-arm64;osx-x64;linux-x64;osx-arm64;linux-arm64;linux-arm git-credential-manager GitCredentialManager $(RepoAssetsPath)gcmicon.ico diff --git a/src/windows/Installer.Windows/Installer.Windows.csproj b/src/windows/Installer.Windows/Installer.Windows.csproj index bbd49a291..eae3631f0 100644 --- a/src/windows/Installer.Windows/Installer.Windows.csproj +++ b/src/windows/Installer.Windows/Installer.Windows.csproj @@ -1,12 +1,19 @@ - + + + + win-x64 + win-x86 + win-arm64 + + net472 false false - $(PlatformOutPath)Installer.Windows\bin\$(Configuration)\net472\win-x86 + $(PlatformOutPath)Installer.Windows\bin\$(Configuration)\net472\$(RuntimeIdentifier) 6.3.1 @@ -27,12 +34,20 @@ - "$(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)" + "$(NuGetPackageRoot)Tools.InnoSetup\$(InnoSetupVersion)\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=system /DGcmRuntimeIdentifier="$(RuntimeIdentifier)" "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)" + "$(NuGetPackageRoot)Tools.InnoSetup\$(InnoSetupVersion)\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=user /DGcmRuntimeIdentifier="$(RuntimeIdentifier)" "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)" - + + + + + + diff --git a/src/windows/Installer.Windows/Setup.iss b/src/windows/Installer.Windows/Setup.iss index f03d16c9b..c15efe6d8 100644 --- a/src/windows/Installer.Windows/Setup.iss +++ b/src/windows/Installer.Windows/Setup.iss @@ -15,6 +15,10 @@ #error Installer target property 'InstallTarget' must be specifed #endif +#ifndef GcmRuntimeIdentifier + #error GCM Runtime Identifier 'GcmRuntimeIdentifier' must be specifed (e.g. win-x64) +#endif + #if InstallTarget == "user" #define GcmAppId "{{aa76d31d-432c-42ee-844c-bc0bc801cef3}}" #define GcmLongName "Git Credential Manager (User)" @@ -40,7 +44,6 @@ #define GcmRepoRoot "..\..\.." #define GcmAssets GcmRepoRoot + "\assets" #define GcmExe "git-credential-manager.exe" -#define GcmArch "x86" #ifnexist PayloadDir + "\" + GcmExe #error Payload files are missing @@ -67,9 +70,17 @@ AppUpdatesURL={#GcmUrl} AppContact={#GcmUrl} AppCopyright={#GcmCopyright} AppReadmeFile={#GcmReadme} +; Windows ARM64 supports installing and running x64 binaries, but not vice versa. +#if GcmRuntimeIdentifier=="win-x64" +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +#elif GcmRuntimeIdentifier=="win-arm64" +ArchitecturesAllowed=arm64 +ArchitecturesInstallIn64BitMode=arm64 +#endif VersionInfoVersion={#GcmVersion} LicenseFile={#GcmRepoRoot}\LICENSE -OutputBaseFilename={#GcmSetupExe}-win-{#GcmArch}-{#GcmVersionSimple} +OutputBaseFilename={#GcmSetupExe}-{#GcmRuntimeIdentifier}-{#GcmVersionSimple} DefaultDirName={autopf}\{#GcmShortName} Compression=lzma2 SolidCompression=yes diff --git a/src/windows/Installer.Windows/layout.ps1 b/src/windows/Installer.Windows/layout.ps1 index 818ee01c6..3fc43ab36 100644 --- a/src/windows/Installer.Windows/layout.ps1 +++ b/src/windows/Installer.Windows/layout.ps1 @@ -1,8 +1,29 @@ # Inputs -param ([Parameter(Mandatory)] $Configuration, [Parameter(Mandatory)] $Output, $SymbolOutput) +param ([Parameter(Mandatory)] $Configuration, [Parameter(Mandatory)] $Output, $RuntimeIdentifier, $SymbolOutput) Write-Output "Output: $Output" +# Determine a runtime if one was not provided +if (-not $RuntimeIdentifier) { + $arch = $env:PROCESSOR_ARCHITECTURE + switch ($arch) { + "AMD64" { $RuntimeIdentifier = "win-x64" } + "x86" { $RuntimeIdentifier = "win-x86" } + "ARM64" { $RuntimeIdentifier = "win-arm64" } + default { + Write-Host "Unknown architecture: $arch" + exit 1 + } + } +} + +Write-Output "Building for runtime '$RuntimeIdentifier'" + +if ($RuntimeIdentifier -ne 'win-x86' -and $RuntimeIdentifier -ne 'win-x64' -and $RuntimeIdentifier -ne 'win-arm64') { + Write-Host "Unsupported RuntimeIdentifier: $RuntimeIdentifier" + exit 1 +} + # Directories $THISDIR = $PSScriptRoot $ROOT = (Get-Item $THISDIR).Parent.Parent.Parent.FullName @@ -41,27 +62,55 @@ Write-Output "Publishing core application..." dotnet publish "$GCM_SRC" ` --framework net472 ` --configuration "$Configuration" ` - --runtime win-x86 ` + --runtime $RuntimeIdentifier ` --output "$PAYLOAD" # Delete libraries that are not needed for Windows but find their way # into the publish output. Remove-Item -Path "$PAYLOAD/*.dylib" -Force -ErrorAction Ignore -# Delete extraneous files that get included for other architectures -# We only care about x86 as the core GCM executable is only targeting x86 -Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore -Remove-Item -Path "$PAYLOAD/arm64/" -Recurse -Force -ErrorAction Ignore -Remove-Item -Path "$PAYLOAD/x64/" -Recurse -Force -ErrorAction Ignore +# Delete extraneous files that get included for other runtimes Remove-Item -Path "$PAYLOAD/musl-x64/" -Recurse -Force -ErrorAction Ignore -Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/" -Recurse -Force -ErrorAction Ignore -Remove-Item -Path "$PAYLOAD/runtimes/win-x64/" -Recurse -Force -ErrorAction Ignore - -# The Avalonia and MSAL binaries in these directories are already included in -# the $PAYLOAD directory directly, so we can delete these extra copies. -Remove-Item -Path "$PAYLOAD/x86/libSkiaSharp.dll" -Recurse -Force -ErrorAction Ignore -Remove-Item -Path "$PAYLOAD/x86/libHarfBuzzSharp.dll" -Recurse -Force -ErrorAction Ignore -Remove-Item -Path "$PAYLOAD/runtimes/win-x86/native/msalruntime_x86.dll" -Recurse -Force -ErrorAction Ignore + +switch ($RuntimeIdentifier) { + "win-x86" { + Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/arm64/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/x64/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-x64/" -Recurse -Force -ErrorAction Ignore + # The Avalonia and MSAL binaries are already included in the $PAYLOAD directory directly + Remove-Item -Path "$PAYLOAD/x86/libSkiaSharp.dll" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/x86/libHarfBuzzSharp.dll" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-x86/native/msalruntime_x86.dll" -Force -ErrorAction Ignore + } + "win-x64" { + Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/arm64/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/x86/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-x86/" -Recurse -Force -ErrorAction Ignore + # The Avalonia and MSAL binaries are already included in the $PAYLOAD directory directly + Remove-Item -Path "$PAYLOAD/x64/libSkiaSharp.dll" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/x64/libHarfBuzzSharp.dll" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/x64/libSkiaSharp.so" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/x64/libHarfBuzzSharp.so" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-x64/native/msalruntime.dll" -Force -ErrorAction Ignore + } + "win-arm64" { + Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/x86/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/x64/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-x86/" -Recurse -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-x64/" -Recurse -Force -ErrorAction Ignore + # The Avalonia and MSAL binaries are already included in the $PAYLOAD directory directly + Remove-Item -Path "$PAYLOAD/arm64/libSkiaSharp.dll" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/arm64/libHarfBuzzSharp.dll" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/arm64/libSkiaSharp.so" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/arm64/libHarfBuzzSharp.so" -Force -ErrorAction Ignore + Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/native/msalruntime_arm64.dll" -Force -ErrorAction Ignore + } +} # Delete localized resource assemblies - we don't localize the core GCM assembly anyway Get-ChildItem "$PAYLOAD" -Recurse -Include "*.resources.dll" | Remove-Item -Force -ErrorAction Ignore From 5c321e36fd96deff794c6971696ab48adc59cf15 Mon Sep 17 00:00:00 2001 From: Dennis Ameling Date: Tue, 13 Jan 2026 22:06:04 +0100 Subject: [PATCH 84/84] Apply suggestions from code review Co-authored-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 3e99b1454..aaf5e2d43 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -46,8 +46,8 @@ parameters: - id: windows_arm64 jobName: 'Windows (ARM64)' runtime: win-arm64 - pool: GitClientPME-1ESHostedPool-intel-pc - image: win-x86_64-ado1es + pool: GitClientPME-1ESHostedPool-arm64-pc + image: win-arm64-ado1es os: windows - name: macos_matrix @@ -207,7 +207,7 @@ extends: --no-dependencies ` -p:NoLayout=true ` -p:PayloadPath="$(Build.ArtifactStagingDirectory)\payload" ` - -p:OutputPath="$(Build.ArtifactStagingDirectory)\installers" + -p:OutputPath="$(Build.ArtifactStagingDirectory)\installers" ` -p:RuntimeIdentifier="${{ dim.runtime }}" - task: EsrpCodeSigning@5 condition: and(succeeded(), eq('${{ parameters.esrp }}', true))