Skip to content

Commit

Permalink
Merge pull request #523 from gep13/support-gitlab
Browse files Browse the repository at this point in the history
Add support for GitLab
  • Loading branch information
gep13 authored Nov 10, 2023
2 parents 2325a95 + e208f76 commit 696a07e
Show file tree
Hide file tree
Showing 26 changed files with 851 additions and 131 deletions.
25 changes: 21 additions & 4 deletions src/GitReleaseManager.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
using GitReleaseManager.Core.Commands;
using GitReleaseManager.Core.Configuration;
using GitReleaseManager.Core.Helpers;
using GitReleaseManager.Core.Model;
using GitReleaseManager.Core.Options;
using GitReleaseManager.Core.Provider;
using GitReleaseManager.Core.ReleaseNotes;
using GitReleaseManager.Core.Templates;
using Microsoft.Extensions.DependencyInjection;
using NGitLab;
using Octokit;
using Serilog;

Expand Down Expand Up @@ -96,7 +98,6 @@ private static void RegisterServices(BaseSubOptions options)
.AddSingleton<IFileSystem>(fileSystem)
.AddSingleton<IReleaseNotesExporter, ReleaseNotesExporter>()
.AddSingleton<IReleaseNotesBuilder, ReleaseNotesBuilder>()
.AddSingleton<IVcsProvider, GitHubProvider>()
.AddSingleton<IVcsService, VcsService>();

if (options is BaseVcsOptions vcsOptions)
Expand All @@ -106,9 +107,7 @@ private static void RegisterServices(BaseSubOptions options)
throw new Exception("The token option is not defined");
}

var gitHubClient = new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(vcsOptions.Token) };
serviceCollection = serviceCollection
.AddSingleton<IGitHubClient>(gitHubClient);
RegisterVcsProvider(vcsOptions, serviceCollection);
}

serviceCollection = serviceCollection
Expand Down Expand Up @@ -197,5 +196,23 @@ private static Task<int> ExecuteCommand<TOptions>(TOptions options)

private static void LogOptions(BaseSubOptions options)
=> Log.Debug("{@Options}", options);

private static void RegisterVcsProvider(BaseVcsOptions vcsOptions, IServiceCollection serviceCollection)
{
Log.Information("Using {Provider} as VCS Provider", vcsOptions.Provider);
if (vcsOptions.Provider == VcsProvider.GitLab)
{
serviceCollection
.AddSingleton<IGitLabClient>((_) => new GitLabClient("https://gitlab.com", vcsOptions.Token))
.AddSingleton<IVcsProvider, GitLabProvider>();
}
else
{
// default to Github
serviceCollection
.AddSingleton<IGitHubClient>((_) => new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(vcsOptions.Token) })
.AddSingleton<IVcsProvider, GitHubProvider>();
}
}
}
}
78 changes: 39 additions & 39 deletions src/GitReleaseManager.Core.Tests/Provider/GitHubProviderTests.cs

Large diffs are not rendered by default.

45 changes: 25 additions & 20 deletions src/GitReleaseManager.Core.Tests/VcsServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class VcsServiceTests
{
private const string OWNER = "owner";
private const string REPOSITORY = "repository";
private const int MILESTONE_NUMBER = 1;
private const int MILESTONE_PUBLIC_NUMBER = 1;
private const int MILESTONE_INTERNAL_NUMBER = 123;
private const string MILESTONE_TITLE = "0.1.0";
private const string TAG_NAME = "0.1.0";
private const string RELEASE_NOTES = "Release Notes";
Expand Down Expand Up @@ -126,7 +127,7 @@ public async Task Should_Add_Assets()
await _vcsService.AddAssetsAsync(OWNER, REPOSITORY, TAG_NAME, _assets).ConfigureAwait(false);

await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<int>()).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<ReleaseAsset>()).ConfigureAwait(false);
await _vcsProvider.Received(assetsCount).UploadAssetAsync(release, Arg.Any<ReleaseAssetUpload>()).ConfigureAwait(false);

_logger.DidNotReceive().Warning(Arg.Any<string>(), Arg.Any<string>());
Expand All @@ -149,7 +150,7 @@ public async Task Should_Add_Assets_With_Deleting_Existing_Assets()
await _vcsService.AddAssetsAsync(OWNER, REPOSITORY, TAG_NAME, _assets).ConfigureAwait(false);

await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
await _vcsProvider.Received(releaseAssetsCount).DeleteAssetAsync(OWNER, REPOSITORY, releaseAsset.Id).ConfigureAwait(false);
await _vcsProvider.Received(releaseAssetsCount).DeleteAssetAsync(OWNER, REPOSITORY, releaseAsset).ConfigureAwait(false);
await _vcsProvider.Received(assetsCount).UploadAssetAsync(release, Arg.Any<ReleaseAssetUpload>()).ConfigureAwait(false);

_logger.Received(releaseAssetsCount).Warning(Arg.Any<string>(), Arg.Any<string>());
Expand All @@ -172,7 +173,7 @@ public async Task Should_Throw_Exception_On_Adding_Assets_When_Asset_File_Not_Ex
ex.Message.ShouldContain(assetFilePath);

await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<int>()).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<ReleaseAsset>()).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().UploadAssetAsync(release, Arg.Any<ReleaseAssetUpload>()).ConfigureAwait(false);
}

Expand All @@ -182,7 +183,7 @@ public async Task Should_Do_Nothing_On_Missing_Assets(IList<string> assets)
await _vcsService.AddAssetsAsync(OWNER, REPOSITORY, TAG_NAME, assets).ConfigureAwait(false);

await _vcsProvider.DidNotReceive().GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<int>()).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<ReleaseAsset>()).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().UploadAssetAsync(Arg.Any<Release>(), Arg.Any<ReleaseAssetUpload>()).ConfigureAwait(false);
}

Expand All @@ -205,7 +206,7 @@ public async Task Should_Create_Labels()
_vcsProvider.GetLabelsAsync(OWNER, REPOSITORY)
.Returns(Task.FromResult((IEnumerable<Label>)labels));

_vcsProvider.DeleteLabelAsync(OWNER, REPOSITORY, Arg.Any<string>())
_vcsProvider.DeleteLabelAsync(OWNER, REPOSITORY, Arg.Any<Label>())
.Returns(Task.CompletedTask);

_vcsProvider.CreateLabelAsync(OWNER, REPOSITORY, Arg.Any<Label>())
Expand All @@ -214,7 +215,7 @@ public async Task Should_Create_Labels()
await _vcsService.CreateLabelsAsync(OWNER, REPOSITORY).ConfigureAwait(false);

await _vcsProvider.Received(1).GetLabelsAsync(OWNER, REPOSITORY).ConfigureAwait(false);
await _vcsProvider.Received(labels.Count).DeleteLabelAsync(OWNER, REPOSITORY, Arg.Any<string>()).ConfigureAwait(false);
await _vcsProvider.Received(labels.Count).DeleteLabelAsync(OWNER, REPOSITORY, Arg.Any<Label>()).ConfigureAwait(false);
await _vcsProvider.Received(_configuration.Labels.Count).CreateLabelAsync(OWNER, REPOSITORY, Arg.Any<Label>()).ConfigureAwait(false);

_logger.Received(1).Verbose(Arg.Any<string>(), OWNER, REPOSITORY);
Expand All @@ -235,61 +236,65 @@ public async Task Should_Log_An_Warning_When_Labels_Not_Configured()
[Test]
public async Task Should_Close_Milestone()
{
var milestone = new Milestone { Number = MILESTONE_NUMBER };
var milestone = new Milestone { PublicNumber = MILESTONE_PUBLIC_NUMBER, InternalNumber = MILESTONE_INTERNAL_NUMBER };

_vcsProvider.GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Open)
.Returns(Task.FromResult(milestone));

_vcsProvider.SetMilestoneStateAsync(OWNER, REPOSITORY, milestone.Number, ItemState.Closed)
_vcsProvider.SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Closed)
.Returns(Task.CompletedTask);

await _vcsService.CloseMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE).ConfigureAwait(false);

await _vcsProvider.Received(1).GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Open).ConfigureAwait(false);
await _vcsProvider.Received(1).SetMilestoneStateAsync(OWNER, REPOSITORY, milestone.Number, ItemState.Closed).ConfigureAwait(false);
await _vcsProvider.Received(1).SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Closed).ConfigureAwait(false);
}

[Test]
public async Task Should_Log_An_Warning_On_Closing_When_Milestone_Cannot_Be_Found()
{
var milestone = new Milestone { PublicNumber = MILESTONE_PUBLIC_NUMBER, InternalNumber = MILESTONE_INTERNAL_NUMBER };

_vcsProvider.GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Open)
.Returns(Task.FromException<Milestone>(_notFoundException));

await _vcsService.CloseMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE).ConfigureAwait(false);

await _vcsProvider.Received(1).GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Open).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().SetMilestoneStateAsync(OWNER, REPOSITORY, MILESTONE_NUMBER, ItemState.Closed).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Closed).ConfigureAwait(false);
_logger.Received(1).Warning(UNABLE_TO_FOUND_MILESTONE_MESSAGE, "open", MILESTONE_TITLE, OWNER, REPOSITORY);
}

[Test]
public async Task Should_Open_Milestone()
{
var milestone = new Milestone { Number = MILESTONE_NUMBER };
var milestone = new Milestone { PublicNumber = MILESTONE_PUBLIC_NUMBER, InternalNumber = MILESTONE_INTERNAL_NUMBER };

_vcsProvider.GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Closed)
.Returns(Task.FromResult(milestone));

_vcsProvider.SetMilestoneStateAsync(OWNER, REPOSITORY, milestone.Number, ItemState.Open)
_vcsProvider.SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Open)
.Returns(Task.CompletedTask);

await _vcsService.OpenMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE).ConfigureAwait(false);

await _vcsProvider.Received(1).GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Closed).ConfigureAwait(false);
await _vcsProvider.Received(1).SetMilestoneStateAsync(OWNER, REPOSITORY, milestone.Number, ItemState.Open).ConfigureAwait(false);
await _vcsProvider.Received(1).SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Open).ConfigureAwait(false);
_logger.Received(2).Verbose(Arg.Any<string>(), MILESTONE_TITLE, OWNER, REPOSITORY);
}

[Test]
public async Task Should_Log_An_Warning_On_Opening_When_Milestone_Cannot_Be_Found()
{
var milestone = new Milestone { PublicNumber = MILESTONE_PUBLIC_NUMBER, InternalNumber = MILESTONE_INTERNAL_NUMBER };

_vcsProvider.GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Closed)
.Returns(Task.FromException<Milestone>(_notFoundException));

await _vcsService.OpenMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE).ConfigureAwait(false);

await _vcsProvider.Received(1).GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Closed).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().SetMilestoneStateAsync(OWNER, REPOSITORY, MILESTONE_NUMBER, ItemState.Open).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Open).ConfigureAwait(false);
_logger.Received(1).Warning(UNABLE_TO_FOUND_MILESTONE_MESSAGE, "closed", MILESTONE_TITLE, OWNER, REPOSITORY);
}

Expand Down Expand Up @@ -556,13 +561,13 @@ public async Task Should_Delete_Draft_Release()
_vcsProvider.GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME)
.Returns(Task.FromResult(release));

_vcsProvider.DeleteReleaseAsync(OWNER, REPOSITORY, release.Id)
_vcsProvider.DeleteReleaseAsync(OWNER, REPOSITORY, release)
.Returns(Task.CompletedTask);

await _vcsService.DiscardReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);

await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
await _vcsProvider.Received(1).DeleteReleaseAsync(OWNER, REPOSITORY, release.Id).ConfigureAwait(false);
await _vcsProvider.Received(1).DeleteReleaseAsync(OWNER, REPOSITORY, release).ConfigureAwait(false);
}

[Test]
Expand All @@ -576,7 +581,7 @@ public async Task Should_Not_Delete_Published_Release()
await _vcsService.DiscardReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);

await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().DeleteReleaseAsync(OWNER, REPOSITORY, release.Id).ConfigureAwait(false);
await _vcsProvider.DidNotReceive().DeleteReleaseAsync(OWNER, REPOSITORY, release).ConfigureAwait(false);
_logger.Received(1).Warning(Arg.Any<string>(), TAG_NAME);
}

Expand All @@ -601,13 +606,13 @@ public async Task Should_Publish_Release()
_vcsProvider.GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME)
.Returns(Task.FromResult(release));

_vcsProvider.PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME, release.Id)
_vcsProvider.PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME, release)
.Returns(Task.CompletedTask);

await _vcsService.PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);

await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
await _vcsProvider.Received(1).PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME, release.Id).ConfigureAwait(false);
await _vcsProvider.Received(1).PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME, release).ConfigureAwait(false);
_logger.Received(1).Verbose(Arg.Any<string>(), TAG_NAME, OWNER, REPOSITORY);
_logger.Received(1).Debug(Arg.Any<string>(), Arg.Any<Release>());
}
Expand Down
8 changes: 8 additions & 0 deletions src/GitReleaseManager.Core/Commands/AddAssetsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public AddAssetsCommand(IVcsService vcsService, ILogger logger)

public async Task<int> ExecuteAsync(AddAssetSubOptions options)
{
var vcsOptions = options as BaseVcsOptions;

if (vcsOptions?.Provider == Model.VcsProvider.GitLab)
{
_logger.Error("The 'addasset' command is currently not supported when targeting GitLab.");
return 1;
}

_logger.Information("Uploading assets");
await _vcsService.AddAssetsAsync(options.RepositoryOwner, options.RepositoryName, options.TagName, options.AssetPaths).ConfigureAwait(false);

Expand Down
10 changes: 9 additions & 1 deletion src/GitReleaseManager.Core/Commands/ExportCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ public ExportCommand(IVcsService vcsService, ILogger logger)

public async Task<int> ExecuteAsync(ExportSubOptions options)
{
_logger.Information("Exporting release {TagName}", options.TagName);
if (string.IsNullOrWhiteSpace(options.TagName))
{
_logger.Information("Exporting all releases.");
}
else
{
_logger.Information("Exporting release {TagName}.", options.TagName);
}

var releasesContent = await _vcsService.ExportReleasesAsync(options.RepositoryOwner, options.RepositoryName, options.TagName, options.SkipPrereleases).ConfigureAwait(false);

using (var sw = new StreamWriter(File.Open(options.FileOutputPath, FileMode.OpenOrCreate)))
Expand Down
8 changes: 8 additions & 0 deletions src/GitReleaseManager.Core/Commands/LabelCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public LabelCommand(IVcsService vcsService, ILogger logger)

public async Task<int> ExecuteAsync(LabelSubOptions options)
{
var vcsOptions = options as BaseVcsOptions;

if (vcsOptions?.Provider == Model.VcsProvider.GitLab)
{
_logger.Error("The label command is currently not supported when targeting GitLab.");
return 1;
}

_logger.Information("Creating standard labels");
await _vcsService.CreateLabelsAsync(options.RepositoryOwner, options.RepositoryName).ConfigureAwait(false);

Expand Down
2 changes: 1 addition & 1 deletion src/GitReleaseManager.Core/Configuration/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class Config
- [GitHub release](https://github.com/{owner}/{repository}/releases/tag/{milestone})
Your **[GitReleaseManager](https://github.com/GitTools/GitReleaseManager)** bot :package::rocket:";
Your **[GitReleaseManager](https://github.com/GitTools/GitReleaseManager)** bot :package: :rocket:";

public Config()
{
Expand Down
26 changes: 24 additions & 2 deletions src/GitReleaseManager.Core/Extensions/MilestoneExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Octokit;
using Serilog;

namespace GitReleaseManager.Core.Extensions
Expand All @@ -8,7 +7,30 @@ public static class MilestoneExtensions
{
public static readonly ILogger _logger = Log.ForContext(typeof(MilestoneExtensions));

public static Version Version(this Milestone ver)
public static Version Version(this Octokit.Milestone ver)
{
if (ver is null)
{
throw new ArgumentNullException(nameof(ver));
}

var nameWithoutPrerelease = ver.Title.Split('-')[0];
if (nameWithoutPrerelease.StartsWith("v", StringComparison.OrdinalIgnoreCase))
{
_logger.Debug("Removing version prefix from {Name}.", ver.Title);
nameWithoutPrerelease = nameWithoutPrerelease.Remove(0, 1);
}

if (!System.Version.TryParse(nameWithoutPrerelease, out Version parsedVersion))
{
_logger.Warning("No valid version was found on {Title}.", ver.Title);
return new Version(0, 0);
}

return parsedVersion;
}

public static Version Version(this NGitLab.Models.Milestone ver)
{
if (ver is null)
{
Expand Down
1 change: 1 addition & 0 deletions src/GitReleaseManager.Core/GitReleaseManager.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="NGitLab" Version="6.39.0" />
<PackageReference Include="Octokit" Version="7.1.0" />
<PackageReference Include="Scriban" Version="5.7.0" />
<PackageReference Include="seriloganalyzer" Version="0.15.0" />
Expand Down
Loading

0 comments on commit 696a07e

Please sign in to comment.