From 6ebb139e66433202506db57618dc3e04e6a1a02c Mon Sep 17 00:00:00 2001 From: Bastien Olivier Dijkstra Date: Tue, 30 Sep 2025 16:53:12 +0200 Subject: [PATCH 1/3] ADD git tag support --- src/Dutchskull.Aspire.PolyRepo/GitConfig.cs | 2 + .../GitConfigBuilder.cs | 16 ++- .../ProcessCommandExecutor.cs | 104 ++++++++++++++---- .../RepositoryConfigBuilder.cs | 10 ++ 4 files changed, 109 insertions(+), 23 deletions(-) diff --git a/src/Dutchskull.Aspire.PolyRepo/GitConfig.cs b/src/Dutchskull.Aspire.PolyRepo/GitConfig.cs index ad86e02..55c645b 100644 --- a/src/Dutchskull.Aspire.PolyRepo/GitConfig.cs +++ b/src/Dutchskull.Aspire.PolyRepo/GitConfig.cs @@ -13,4 +13,6 @@ internal GitConfig() public required string Password { get; init; } public string[] CustomHeaders { get; init; } = []; + + public string? Tag { get; init; } } \ No newline at end of file diff --git a/src/Dutchskull.Aspire.PolyRepo/GitConfigBuilder.cs b/src/Dutchskull.Aspire.PolyRepo/GitConfigBuilder.cs index 4fa60e2..de7bf08 100644 --- a/src/Dutchskull.Aspire.PolyRepo/GitConfigBuilder.cs +++ b/src/Dutchskull.Aspire.PolyRepo/GitConfigBuilder.cs @@ -6,6 +6,7 @@ public class GitConfigBuilder private string? _url; private string? _username; private string[]? _customHeaders; + private string? _tag; internal GitConfigBuilder WithUrl(string url) { @@ -40,6 +41,12 @@ public GitConfig Build() _password = string.Empty; } + + if (string.IsNullOrEmpty(_url)) + { + throw new Exception("No git url was provided."); + } + _customHeaders ??= []; return new GitConfig @@ -47,7 +54,14 @@ public GitConfig Build() Url = _url, Username = _username, Password = _password, - CustomHeaders = _customHeaders + CustomHeaders = _customHeaders, + Tag = _tag }; } + + public GitConfigBuilder WithTag(string? tag) + { + _tag = tag; + return this; + } } \ No newline at end of file diff --git a/src/Dutchskull.Aspire.PolyRepo/ProcessCommandExecutor.cs b/src/Dutchskull.Aspire.PolyRepo/ProcessCommandExecutor.cs index 0a5e9f5..b5da370 100644 --- a/src/Dutchskull.Aspire.PolyRepo/ProcessCommandExecutor.cs +++ b/src/Dutchskull.Aspire.PolyRepo/ProcessCommandExecutor.cs @@ -2,6 +2,7 @@ using System.Text; using Dutchskull.Aspire.PolyRepo.Interfaces; using LibGit2Sharp; +using System.Linq; namespace Dutchskull.Aspire.PolyRepo; @@ -30,13 +31,33 @@ public void CloneGitRepository(GitConfig gitConfig, string resolvedRepositoryPat { Username = gitConfig.Username, Password = gitConfig.Password - }, CustomHeaders = gitConfig.CustomHeaders } }; Repository.Clone(gitConfig.Url, resolvedRepositoryPath, cloneOptions); + + if (string.IsNullOrWhiteSpace(gitConfig.Tag)) + { + return; + } + + using Repository repo = new(resolvedRepositoryPath); + + Remote? remote = repo.Network.Remotes.FirstOrDefault(); + ArgumentNullException.ThrowIfNull(remote); + + string[] tagRefSpec = [$"refs/tags/{gitConfig.Tag}:refs/tags/{gitConfig.Tag}"]; + Commands.Fetch(repo, remote.Name, tagRefSpec, cloneOptions.FetchOptions, null); + + Tag? tag = repo.Tags[gitConfig.Tag] ?? + throw new Exception($"Tag '{gitConfig.Tag}' not found in repository after fetch."); + + Commit? commit = (tag.PeeledTarget as Commit ?? repo.Lookup(tag.Target.Sha)) ?? + throw new Exception($"Tag '{gitConfig.Tag}' does not resolve to a commit."); + + repo.Reset(ResetMode.Hard, commit); } public int NpmInstall(string resolvedRepositoryPath) => @@ -46,11 +67,20 @@ public void PullAndResetRepository(GitConfig gitConfig, string repositoryConfigR { using Repository repository = new(repositoryConfigRepositoryPath); - string? branchName = repository.Head.TrackedBranch.FriendlyName; - Remote? remote = repository.Network.Remotes.FirstOrDefault(); + if (string.IsNullOrWhiteSpace(gitConfig.Tag)) + { + FetchCurrentBranch(gitConfig, repository); + return; + } + + FetchCurrentTag(gitConfig, repository); + } + + private static void FetchCurrentTag(GitConfig gitConfig, Repository repository) + { + Remote? remote = repository.Network.Remotes.FirstOrDefault(); ArgumentNullException.ThrowIfNull(remote); - ArgumentNullException.ThrowIfNull(branchName); FetchOptions fetchOptions = new() { @@ -62,15 +92,61 @@ public void PullAndResetRepository(GitConfig gitConfig, string repositoryConfigR CustomHeaders = gitConfig.CustomHeaders }; - IEnumerable references = remote.FetchRefSpecs.Select(x => x.Specification); + List references = [.. remote.FetchRefSpecs.Select(x => x.Specification)]; + references.Add($"refs/tags/{gitConfig.Tag}:refs/tags/{gitConfig.Tag}"); + Commands.Fetch(repository, remote.Name, references, fetchOptions, null); + Tag? tag = repository.Tags[gitConfig.Tag] ?? + throw new Exception($"Tag '{gitConfig.Tag}' not found after fetch."); + + Commit? commit = (tag.PeeledTarget as Commit ?? repository.Lookup(tag.Target.Sha)) ?? + throw new Exception($"Tag '{gitConfig.Tag}' does not resolve to a commit."); + + repository.Reset(ResetMode.Hard, commit); + } + + private static void FetchCurrentBranch(GitConfig gitConfig, Repository repository) + { + string? branchName = repository.Head.TrackedBranch?.FriendlyName; + Remote? branchRemote = repository.Network.Remotes.FirstOrDefault(); + + ArgumentNullException.ThrowIfNull(branchRemote); + ArgumentNullException.ThrowIfNull(branchName); + + FetchOptions branchFetchOptions = new() + { + CredentialsProvider = (_url, _user, _cred) => new UsernamePasswordCredentials + { + Username = gitConfig.Username, + Password = gitConfig.Password + }, + CustomHeaders = gitConfig.CustomHeaders + }; + + IEnumerable branchReferences = branchRemote.FetchRefSpecs.Select(x => x.Specification); + Commands.Fetch(repository, branchRemote.Name, branchReferences, branchFetchOptions, null); + Branch? remoteBranch = repository.Branches[branchName]; - Commit? latestCommit = remoteBranch.Tip; + Commit? latestCommit = remoteBranch?.Tip; repository.Reset(ResetMode.Hard, latestCommit); } + private static DataReceivedEventHandler LogData(StringBuilder output, string type) + { + return (sender, e) => + { + if (string.IsNullOrEmpty(e.Data)) + { + return; + } + + output.AppendLine(e.Data); + Console.WriteLine($"[{type}]: {e.Data}"); + }; + } + private static int RunProcess(string fileName, string arguments) { Process process = new() @@ -90,7 +166,6 @@ private static int RunProcess(string fileName, string arguments) StringBuilder error = new(); process.OutputDataReceived += LogData(output, "OUTPUT"); - process.ErrorDataReceived += LogData(error, "ERROR"); process.Start(); @@ -101,7 +176,6 @@ private static int RunProcess(string fileName, string arguments) if (process.ExitCode == 0) { Console.WriteLine($"Process {fileName} {arguments} finished successfully."); - return process.ExitCode; } @@ -110,18 +184,4 @@ private static int RunProcess(string fileName, string arguments) throw new Exception(errorMessage); } - - private static DataReceivedEventHandler LogData(StringBuilder output, string type) - { - return (sender, e) => - { - if (string.IsNullOrEmpty(e.Data)) - { - return; - } - - output.AppendLine(e.Data); - Console.WriteLine($"[{type}]: {e.Data}"); - }; - } } \ No newline at end of file diff --git a/src/Dutchskull.Aspire.PolyRepo/RepositoryConfigBuilder.cs b/src/Dutchskull.Aspire.PolyRepo/RepositoryConfigBuilder.cs index 76c31c3..dc64b2c 100644 --- a/src/Dutchskull.Aspire.PolyRepo/RepositoryConfigBuilder.cs +++ b/src/Dutchskull.Aspire.PolyRepo/RepositoryConfigBuilder.cs @@ -11,6 +11,7 @@ public class RepositoryConfigBuilder private bool _keepUpToDate; private IProcessCommandExecutor? _processCommandsExecutor; private string _targetPath = "."; + private string? _tag = null; public RepositoryConfig Build() { @@ -24,6 +25,7 @@ public RepositoryConfig Build() GitConfigBuilder gitConfigBuilder = new(); gitConfigBuilder.WithUrl(_gitUrl); + gitConfigBuilder.WithTag(_tag); _gitConfigBuilder?.Invoke(gitConfigBuilder); return new RepositoryConfig @@ -45,6 +47,14 @@ public RepositoryConfigBuilder WithTargetPath(string targetPath) return this; } + + public RepositoryConfigBuilder WithTag(string tag) + { + _tag = tag; + + return this; + } + public RepositoryConfigBuilder WithDefaultBranch(string branch) { _branch = branch; From 26573bfeefad8c4c0935e0c857bdd69b4bb28b9f Mon Sep 17 00:00:00 2001 From: Bastien Olivier Dijkstra Date: Tue, 30 Sep 2025 17:09:22 +0200 Subject: [PATCH 2/3] FIX pipeline --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 15afdfc..ed88a59 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - name: Set up .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: "8.0.x" + dotnet-version: "9.0.x" - name: Install GitVersion uses: gittools/actions/gitversion/setup@v2.0.1 From 40b768eab1e85bc7646a01d041cc363c629d8dfe Mon Sep 17 00:00:00 2001 From: Bastien Olivier Dijkstra Date: Sat, 4 Oct 2025 19:13:43 +0200 Subject: [PATCH 3/3] fix: truncate the version branch name so it is always valid --- .github/workflows/main.yml | 2 +- GitVersion.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dbf18c4..7685bb6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,7 @@ jobs: run: dotnet build src/Dutchskull.Aspire.PolyRepo/Dutchskull.Aspire.PolyRepo.csproj --configuration Release --no-restore - name: Pack - run: dotnet pack src/Dutchskull.Aspire.PolyRepo/Dutchskull.Aspire.PolyRepo.csproj --configuration Release --no-restore --output ./nupkg /p:Version=${{ env.semVer }} + run: dotnet pack src/Dutchskull.Aspire.PolyRepo/Dutchskull.Aspire.PolyRepo.csproj --configuration Release --no-restore --output ./nupkg /p:Version=${{ env.fullSemVer }} - name: Publish to NuGet run: dotnet nuget push ./nupkg/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} diff --git a/GitVersion.yml b/GitVersion.yml index 54e2ef0..01e95e8 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -13,7 +13,7 @@ branches: increment: Minor feature: - regex: ^features?[/-](?.+)$ + regex: ^features?[/-](?.{1,20}) label: '{BranchName}' increment: Patch source-branches: