Skip to content

Commit 696a07e

Browse files
authored
Merge pull request #523 from gep13/support-gitlab
Add support for GitLab
2 parents 2325a95 + e208f76 commit 696a07e

26 files changed

+851
-131
lines changed

src/GitReleaseManager.Cli/Program.cs

+21-4
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
using GitReleaseManager.Core.Commands;
99
using GitReleaseManager.Core.Configuration;
1010
using GitReleaseManager.Core.Helpers;
11+
using GitReleaseManager.Core.Model;
1112
using GitReleaseManager.Core.Options;
1213
using GitReleaseManager.Core.Provider;
1314
using GitReleaseManager.Core.ReleaseNotes;
1415
using GitReleaseManager.Core.Templates;
1516
using Microsoft.Extensions.DependencyInjection;
17+
using NGitLab;
1618
using Octokit;
1719
using Serilog;
1820

@@ -96,7 +98,6 @@ private static void RegisterServices(BaseSubOptions options)
9698
.AddSingleton<IFileSystem>(fileSystem)
9799
.AddSingleton<IReleaseNotesExporter, ReleaseNotesExporter>()
98100
.AddSingleton<IReleaseNotesBuilder, ReleaseNotesBuilder>()
99-
.AddSingleton<IVcsProvider, GitHubProvider>()
100101
.AddSingleton<IVcsService, VcsService>();
101102

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

109-
var gitHubClient = new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(vcsOptions.Token) };
110-
serviceCollection = serviceCollection
111-
.AddSingleton<IGitHubClient>(gitHubClient);
110+
RegisterVcsProvider(vcsOptions, serviceCollection);
112111
}
113112

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

198197
private static void LogOptions(BaseSubOptions options)
199198
=> Log.Debug("{@Options}", options);
199+
200+
private static void RegisterVcsProvider(BaseVcsOptions vcsOptions, IServiceCollection serviceCollection)
201+
{
202+
Log.Information("Using {Provider} as VCS Provider", vcsOptions.Provider);
203+
if (vcsOptions.Provider == VcsProvider.GitLab)
204+
{
205+
serviceCollection
206+
.AddSingleton<IGitLabClient>((_) => new GitLabClient("https://gitlab.com", vcsOptions.Token))
207+
.AddSingleton<IVcsProvider, GitLabProvider>();
208+
}
209+
else
210+
{
211+
// default to Github
212+
serviceCollection
213+
.AddSingleton<IGitHubClient>((_) => new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(vcsOptions.Token) })
214+
.AddSingleton<IVcsProvider, GitHubProvider>();
215+
}
216+
}
200217
}
201218
}

src/GitReleaseManager.Core.Tests/Provider/GitHubProviderTests.cs

+39-39
Large diffs are not rendered by default.

src/GitReleaseManager.Core.Tests/VcsServiceTests.cs

+25-20
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public class VcsServiceTests
2626
{
2727
private const string OWNER = "owner";
2828
private const string REPOSITORY = "repository";
29-
private const int MILESTONE_NUMBER = 1;
29+
private const int MILESTONE_PUBLIC_NUMBER = 1;
30+
private const int MILESTONE_INTERNAL_NUMBER = 123;
3031
private const string MILESTONE_TITLE = "0.1.0";
3132
private const string TAG_NAME = "0.1.0";
3233
private const string RELEASE_NOTES = "Release Notes";
@@ -126,7 +127,7 @@ public async Task Should_Add_Assets()
126127
await _vcsService.AddAssetsAsync(OWNER, REPOSITORY, TAG_NAME, _assets).ConfigureAwait(false);
127128

128129
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
129-
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<int>()).ConfigureAwait(false);
130+
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<ReleaseAsset>()).ConfigureAwait(false);
130131
await _vcsProvider.Received(assetsCount).UploadAssetAsync(release, Arg.Any<ReleaseAssetUpload>()).ConfigureAwait(false);
131132

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

151152
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
152-
await _vcsProvider.Received(releaseAssetsCount).DeleteAssetAsync(OWNER, REPOSITORY, releaseAsset.Id).ConfigureAwait(false);
153+
await _vcsProvider.Received(releaseAssetsCount).DeleteAssetAsync(OWNER, REPOSITORY, releaseAsset).ConfigureAwait(false);
153154
await _vcsProvider.Received(assetsCount).UploadAssetAsync(release, Arg.Any<ReleaseAssetUpload>()).ConfigureAwait(false);
154155

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

174175
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
175-
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<int>()).ConfigureAwait(false);
176+
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<ReleaseAsset>()).ConfigureAwait(false);
176177
await _vcsProvider.DidNotReceive().UploadAssetAsync(release, Arg.Any<ReleaseAssetUpload>()).ConfigureAwait(false);
177178
}
178179

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

184185
await _vcsProvider.DidNotReceive().GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
185-
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<int>()).ConfigureAwait(false);
186+
await _vcsProvider.DidNotReceive().DeleteAssetAsync(OWNER, REPOSITORY, Arg.Any<ReleaseAsset>()).ConfigureAwait(false);
186187
await _vcsProvider.DidNotReceive().UploadAssetAsync(Arg.Any<Release>(), Arg.Any<ReleaseAssetUpload>()).ConfigureAwait(false);
187188
}
188189

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

208-
_vcsProvider.DeleteLabelAsync(OWNER, REPOSITORY, Arg.Any<string>())
209+
_vcsProvider.DeleteLabelAsync(OWNER, REPOSITORY, Arg.Any<Label>())
209210
.Returns(Task.CompletedTask);
210211

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

216217
await _vcsProvider.Received(1).GetLabelsAsync(OWNER, REPOSITORY).ConfigureAwait(false);
217-
await _vcsProvider.Received(labels.Count).DeleteLabelAsync(OWNER, REPOSITORY, Arg.Any<string>()).ConfigureAwait(false);
218+
await _vcsProvider.Received(labels.Count).DeleteLabelAsync(OWNER, REPOSITORY, Arg.Any<Label>()).ConfigureAwait(false);
218219
await _vcsProvider.Received(_configuration.Labels.Count).CreateLabelAsync(OWNER, REPOSITORY, Arg.Any<Label>()).ConfigureAwait(false);
219220

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

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

243-
_vcsProvider.SetMilestoneStateAsync(OWNER, REPOSITORY, milestone.Number, ItemState.Closed)
244+
_vcsProvider.SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Closed)
244245
.Returns(Task.CompletedTask);
245246

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

248249
await _vcsProvider.Received(1).GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Open).ConfigureAwait(false);
249-
await _vcsProvider.Received(1).SetMilestoneStateAsync(OWNER, REPOSITORY, milestone.Number, ItemState.Closed).ConfigureAwait(false);
250+
await _vcsProvider.Received(1).SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Closed).ConfigureAwait(false);
250251
}
251252

252253
[Test]
253254
public async Task Should_Log_An_Warning_On_Closing_When_Milestone_Cannot_Be_Found()
254255
{
256+
var milestone = new Milestone { PublicNumber = MILESTONE_PUBLIC_NUMBER, InternalNumber = MILESTONE_INTERNAL_NUMBER };
257+
255258
_vcsProvider.GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Open)
256259
.Returns(Task.FromException<Milestone>(_notFoundException));
257260

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

260263
await _vcsProvider.Received(1).GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Open).ConfigureAwait(false);
261-
await _vcsProvider.DidNotReceive().SetMilestoneStateAsync(OWNER, REPOSITORY, MILESTONE_NUMBER, ItemState.Closed).ConfigureAwait(false);
264+
await _vcsProvider.DidNotReceive().SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Closed).ConfigureAwait(false);
262265
_logger.Received(1).Warning(UNABLE_TO_FOUND_MILESTONE_MESSAGE, "open", MILESTONE_TITLE, OWNER, REPOSITORY);
263266
}
264267

265268
[Test]
266269
public async Task Should_Open_Milestone()
267270
{
268-
var milestone = new Milestone { Number = MILESTONE_NUMBER };
271+
var milestone = new Milestone { PublicNumber = MILESTONE_PUBLIC_NUMBER, InternalNumber = MILESTONE_INTERNAL_NUMBER };
269272

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

273-
_vcsProvider.SetMilestoneStateAsync(OWNER, REPOSITORY, milestone.Number, ItemState.Open)
276+
_vcsProvider.SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Open)
274277
.Returns(Task.CompletedTask);
275278

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

278281
await _vcsProvider.Received(1).GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Closed).ConfigureAwait(false);
279-
await _vcsProvider.Received(1).SetMilestoneStateAsync(OWNER, REPOSITORY, milestone.Number, ItemState.Open).ConfigureAwait(false);
282+
await _vcsProvider.Received(1).SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Open).ConfigureAwait(false);
280283
_logger.Received(2).Verbose(Arg.Any<string>(), MILESTONE_TITLE, OWNER, REPOSITORY);
281284
}
282285

283286
[Test]
284287
public async Task Should_Log_An_Warning_On_Opening_When_Milestone_Cannot_Be_Found()
285288
{
289+
var milestone = new Milestone { PublicNumber = MILESTONE_PUBLIC_NUMBER, InternalNumber = MILESTONE_INTERNAL_NUMBER };
290+
286291
_vcsProvider.GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Closed)
287292
.Returns(Task.FromException<Milestone>(_notFoundException));
288293

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

291296
await _vcsProvider.Received(1).GetMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ItemStateFilter.Closed).ConfigureAwait(false);
292-
await _vcsProvider.DidNotReceive().SetMilestoneStateAsync(OWNER, REPOSITORY, MILESTONE_NUMBER, ItemState.Open).ConfigureAwait(false);
297+
await _vcsProvider.DidNotReceive().SetMilestoneStateAsync(OWNER, REPOSITORY, milestone, ItemState.Open).ConfigureAwait(false);
293298
_logger.Received(1).Warning(UNABLE_TO_FOUND_MILESTONE_MESSAGE, "closed", MILESTONE_TITLE, OWNER, REPOSITORY);
294299
}
295300

@@ -556,13 +561,13 @@ public async Task Should_Delete_Draft_Release()
556561
_vcsProvider.GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME)
557562
.Returns(Task.FromResult(release));
558563

559-
_vcsProvider.DeleteReleaseAsync(OWNER, REPOSITORY, release.Id)
564+
_vcsProvider.DeleteReleaseAsync(OWNER, REPOSITORY, release)
560565
.Returns(Task.CompletedTask);
561566

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

564569
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
565-
await _vcsProvider.Received(1).DeleteReleaseAsync(OWNER, REPOSITORY, release.Id).ConfigureAwait(false);
570+
await _vcsProvider.Received(1).DeleteReleaseAsync(OWNER, REPOSITORY, release).ConfigureAwait(false);
566571
}
567572

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

578583
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
579-
await _vcsProvider.DidNotReceive().DeleteReleaseAsync(OWNER, REPOSITORY, release.Id).ConfigureAwait(false);
584+
await _vcsProvider.DidNotReceive().DeleteReleaseAsync(OWNER, REPOSITORY, release).ConfigureAwait(false);
580585
_logger.Received(1).Warning(Arg.Any<string>(), TAG_NAME);
581586
}
582587

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

604-
_vcsProvider.PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME, release.Id)
609+
_vcsProvider.PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME, release)
605610
.Returns(Task.CompletedTask);
606611

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

609614
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, TAG_NAME).ConfigureAwait(false);
610-
await _vcsProvider.Received(1).PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME, release.Id).ConfigureAwait(false);
615+
await _vcsProvider.Received(1).PublishReleaseAsync(OWNER, REPOSITORY, TAG_NAME, release).ConfigureAwait(false);
611616
_logger.Received(1).Verbose(Arg.Any<string>(), TAG_NAME, OWNER, REPOSITORY);
612617
_logger.Received(1).Debug(Arg.Any<string>(), Arg.Any<Release>());
613618
}

src/GitReleaseManager.Core/Commands/AddAssetsCommand.cs

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ public AddAssetsCommand(IVcsService vcsService, ILogger logger)
1717

1818
public async Task<int> ExecuteAsync(AddAssetSubOptions options)
1919
{
20+
var vcsOptions = options as BaseVcsOptions;
21+
22+
if (vcsOptions?.Provider == Model.VcsProvider.GitLab)
23+
{
24+
_logger.Error("The 'addasset' command is currently not supported when targeting GitLab.");
25+
return 1;
26+
}
27+
2028
_logger.Information("Uploading assets");
2129
await _vcsService.AddAssetsAsync(options.RepositoryOwner, options.RepositoryName, options.TagName, options.AssetPaths).ConfigureAwait(false);
2230

src/GitReleaseManager.Core/Commands/ExportCommand.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@ public ExportCommand(IVcsService vcsService, ILogger logger)
1818

1919
public async Task<int> ExecuteAsync(ExportSubOptions options)
2020
{
21-
_logger.Information("Exporting release {TagName}", options.TagName);
21+
if (string.IsNullOrWhiteSpace(options.TagName))
22+
{
23+
_logger.Information("Exporting all releases.");
24+
}
25+
else
26+
{
27+
_logger.Information("Exporting release {TagName}.", options.TagName);
28+
}
29+
2230
var releasesContent = await _vcsService.ExportReleasesAsync(options.RepositoryOwner, options.RepositoryName, options.TagName, options.SkipPrereleases).ConfigureAwait(false);
2331

2432
using (var sw = new StreamWriter(File.Open(options.FileOutputPath, FileMode.OpenOrCreate)))

src/GitReleaseManager.Core/Commands/LabelCommand.cs

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ public LabelCommand(IVcsService vcsService, ILogger logger)
1717

1818
public async Task<int> ExecuteAsync(LabelSubOptions options)
1919
{
20+
var vcsOptions = options as BaseVcsOptions;
21+
22+
if (vcsOptions?.Provider == Model.VcsProvider.GitLab)
23+
{
24+
_logger.Error("The label command is currently not supported when targeting GitLab.");
25+
return 1;
26+
}
27+
2028
_logger.Information("Creating standard labels");
2129
await _vcsService.CreateLabelsAsync(options.RepositoryOwner, options.RepositoryName).ConfigureAwait(false);
2230

src/GitReleaseManager.Core/Configuration/Config.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class Config
1212
1313
- [GitHub release](https://github.com/{owner}/{repository}/releases/tag/{milestone})
1414
15-
Your **[GitReleaseManager](https://github.com/GitTools/GitReleaseManager)** bot :package::rocket:";
15+
Your **[GitReleaseManager](https://github.com/GitTools/GitReleaseManager)** bot :package: :rocket:";
1616

1717
public Config()
1818
{

src/GitReleaseManager.Core/Extensions/MilestoneExtensions.cs

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using Octokit;
32
using Serilog;
43

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

11-
public static Version Version(this Milestone ver)
10+
public static Version Version(this Octokit.Milestone ver)
11+
{
12+
if (ver is null)
13+
{
14+
throw new ArgumentNullException(nameof(ver));
15+
}
16+
17+
var nameWithoutPrerelease = ver.Title.Split('-')[0];
18+
if (nameWithoutPrerelease.StartsWith("v", StringComparison.OrdinalIgnoreCase))
19+
{
20+
_logger.Debug("Removing version prefix from {Name}.", ver.Title);
21+
nameWithoutPrerelease = nameWithoutPrerelease.Remove(0, 1);
22+
}
23+
24+
if (!System.Version.TryParse(nameWithoutPrerelease, out Version parsedVersion))
25+
{
26+
_logger.Warning("No valid version was found on {Title}.", ver.Title);
27+
return new Version(0, 0);
28+
}
29+
30+
return parsedVersion;
31+
}
32+
33+
public static Version Version(this NGitLab.Models.Milestone ver)
1234
{
1335
if (ver is null)
1436
{

src/GitReleaseManager.Core/GitReleaseManager.Core.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2424
<PrivateAssets>all</PrivateAssets>
2525
</PackageReference>
26+
<PackageReference Include="NGitLab" Version="6.39.0" />
2627
<PackageReference Include="Octokit" Version="7.1.0" />
2728
<PackageReference Include="Scriban" Version="5.7.0" />
2829
<PackageReference Include="seriloganalyzer" Version="0.15.0" />

0 commit comments

Comments
 (0)