Skip to content

Commit dd117fe

Browse files
authored
Merge pull request #615 from Jericho/allow_milestone_without_issues
(#495) Allow milestones without issues
2 parents cfa3f16 + c4cdc00 commit dd117fe

File tree

12 files changed

+96
-29
lines changed

12 files changed

+96
-29
lines changed

Diff for: docs/input/docs/configuration/default-configuration.md

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ create:
2121
sha-section-line-format: "- `{1}\t{0}`"
2222
allow-update-to-published: false
2323
include-contributors: false
24+
allow-milestone-without-issues: false
2425
export:
2526
include-created-date-in-title: false
2627
created-date-string-format: ''
@@ -142,6 +143,11 @@ control the look and feel of the generated release notes.
142143
in the release notes. A contributor is defined as someone who opened an issue
143144
or submitted a PR. **NOTE:** This configuration option was added in version
144145
0.19.0 of GitReleaseManager.
146+
- **allow-milestone-without-issues**
147+
- A boolean value which indicates whether an empty release will be created, when
148+
no issues are found to be associated with a milestone. The contents of the
149+
empty release can be controlled via the associated Scriban template.
150+
**NOTE:** This configuration option was added in version 0.20.0 of GitReleaseManager.
145151

146152
See the [example create configuration section](create-configuration) to see an
147153
example of how a footer can be configured.

Diff for: src/GitReleaseManager.Core.Tests/VcsServiceTests.cs

+8-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using GitReleaseManager.Core.Model;
99
using GitReleaseManager.Core.Provider;
1010
using GitReleaseManager.Core.ReleaseNotes;
11-
using GitReleaseManager.Core.Templates;
1211
using NSubstitute;
1312
using NUnit.Framework;
1413
using Serilog;
@@ -303,7 +302,7 @@ public async Task Should_Create_Release_From_Milestone()
303302
{
304303
var release = new Release();
305304

306-
_releaseNotesBuilder.BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ReleaseTemplates.DEFAULT_NAME)
305+
_releaseNotesBuilder.BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, null)
307306
.Returns(Task.FromResult(RELEASE_NOTES));
308307

309308
_vcsProvider.GetReleaseAsync(OWNER, REPOSITORY, MILESTONE_TITLE)
@@ -315,7 +314,7 @@ public async Task Should_Create_Release_From_Milestone()
315314
var result = await _vcsService.CreateReleaseFromMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, MILESTONE_TITLE, null, null, false, null).ConfigureAwait(false);
316315
result.ShouldBeSameAs(release);
317316

318-
await _releaseNotesBuilder.Received(1).BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ReleaseTemplates.DEFAULT_NAME).ConfigureAwait(false);
317+
await _releaseNotesBuilder.Received(1).BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, null).ConfigureAwait(false);
319318
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, MILESTONE_TITLE).ConfigureAwait(false);
320319
await _vcsProvider.Received(1).CreateReleaseAsync(OWNER, REPOSITORY, Arg.Is<Release>(o =>
321320
o.Body == RELEASE_NOTES &&
@@ -333,7 +332,7 @@ public async Task Should_Create_Release_From_Milestone_With_Assets()
333332

334333
var assetsCount = _assets.Count;
335334

336-
_releaseNotesBuilder.BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ReleaseTemplates.DEFAULT_NAME)
335+
_releaseNotesBuilder.BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, null)
337336
.Returns(Task.FromResult(RELEASE_NOTES));
338337

339338
_vcsProvider.GetReleaseAsync(OWNER, REPOSITORY, MILESTONE_TITLE)
@@ -353,7 +352,7 @@ public async Task Should_Create_Release_From_Milestone_With_Assets()
353352
null).ConfigureAwait(false);
354353
result.ShouldBeSameAs(release);
355354

356-
await _releaseNotesBuilder.Received(1).BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ReleaseTemplates.DEFAULT_NAME).ConfigureAwait(false);
355+
await _releaseNotesBuilder.Received(1).BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, null).ConfigureAwait(false);
357356
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, MILESTONE_TITLE).ConfigureAwait(false);
358357
await _vcsProvider.Received(1).CreateReleaseAsync(OWNER, REPOSITORY, Arg.Is<Release>(o =>
359358
o.Body == RELEASE_NOTES &&
@@ -430,7 +429,7 @@ public async Task Should_Update_Published_Release_On_Creating_Release_From_Miles
430429

431430
_configuration.Create.AllowUpdateToPublishedRelease = updatePublishedRelease;
432431

433-
_releaseNotesBuilder.BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ReleaseTemplates.DEFAULT_NAME)
432+
_releaseNotesBuilder.BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, null)
434433
.Returns(Task.FromResult(RELEASE_NOTES));
435434

436435
_vcsProvider.GetReleaseAsync(OWNER, REPOSITORY, MILESTONE_TITLE)
@@ -442,7 +441,7 @@ public async Task Should_Update_Published_Release_On_Creating_Release_From_Miles
442441
var result = await _vcsService.CreateReleaseFromMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, MILESTONE_TITLE, null, null, false, null).ConfigureAwait(false);
443442
result.ShouldBeSameAs(release);
444443

445-
await _releaseNotesBuilder.Received(1).BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ReleaseTemplates.DEFAULT_NAME).ConfigureAwait(false);
444+
await _releaseNotesBuilder.Received(1).BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, null).ConfigureAwait(false);
446445
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, MILESTONE_TITLE).ConfigureAwait(false);
447446
await _vcsProvider.Received(1).UpdateReleaseAsync(OWNER, REPOSITORY, release).ConfigureAwait(false);
448447

@@ -458,7 +457,7 @@ public async Task Should_Throw_Exception_While_Updating_Published_Release_On_Cre
458457

459458
_configuration.Create.AllowUpdateToPublishedRelease = false;
460459

461-
_releaseNotesBuilder.BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ReleaseTemplates.DEFAULT_NAME)
460+
_releaseNotesBuilder.BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, null)
462461
.Returns(Task.FromResult(RELEASE_NOTES));
463462

464463
_vcsProvider.GetReleaseAsync(OWNER, REPOSITORY, MILESTONE_TITLE)
@@ -467,7 +466,7 @@ public async Task Should_Throw_Exception_While_Updating_Published_Release_On_Cre
467466
var ex = await Should.ThrowAsync<InvalidOperationException>(() => _vcsService.CreateReleaseFromMilestoneAsync(OWNER, REPOSITORY, MILESTONE_TITLE, MILESTONE_TITLE, null, null, false, null)).ConfigureAwait(false);
468467
ex.Message.ShouldBe($"Release with tag '{MILESTONE_TITLE}' not in draft state, so not updating");
469468

470-
await _releaseNotesBuilder.Received(1).BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, ReleaseTemplates.DEFAULT_NAME).ConfigureAwait(false);
469+
await _releaseNotesBuilder.Received(1).BuildReleaseNotesAsync(OWNER, REPOSITORY, MILESTONE_TITLE, null).ConfigureAwait(false);
471470
await _vcsProvider.Received(1).GetReleaseAsync(OWNER, REPOSITORY, MILESTONE_TITLE).ConfigureAwait(false);
472471
}
473472

Diff for: src/GitReleaseManager.Core/Configuration/Config.cs

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public Config()
2727
ShaSectionHeading = "SHA256 Hashes of the release artifacts",
2828
ShaSectionLineFormat = "- `{1}\t{0}`",
2929
AllowUpdateToPublishedRelease = false,
30+
AllowMilestonesWithoutIssues = false,
3031
IncludeContributors = false,
3132
};
3233

Diff for: src/GitReleaseManager.Core/Configuration/CreateConfig.cs

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public class CreateConfig
3535
[YamlMember(Alias = "allow-update-to-published")]
3636
public bool AllowUpdateToPublishedRelease { get; set; }
3737

38+
[YamlMember(Alias = "allow-milestone-without-issues")]
39+
public bool AllowMilestonesWithoutIssues { get; set; }
40+
3841
[YamlMember(Alias = "include-contributors")]
3942
public bool IncludeContributors { get; set; }
4043
}

Diff for: src/GitReleaseManager.Core/ReleaseNotes/ReleaseNotesBuilder.cs

+21-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public ReleaseNotesBuilder(IVcsProvider vcsProvider, ILogger logger, IFileSystem
3535
_templateFactory = templateFactory;
3636
}
3737

38-
public async Task<string> BuildReleaseNotesAsync(string user, string repository, string milestoneTitle, string template)
38+
public async Task<string> BuildReleaseNotesAsync(string user, string repository, string milestoneTitle, string customTemplate)
3939
{
4040
_user = user;
4141
_repository = repository;
@@ -58,12 +58,31 @@ public async Task<string> BuildReleaseNotesAsync(string user, string repository,
5858

5959
var numberOfCommits = await _vcsProvider.GetCommitsCountAsync(_user, _repository, @base, head).ConfigureAwait(false);
6060

61-
if (issues.Count == 0)
61+
if (issues.Count == 0 && !_configuration.Create.AllowMilestonesWithoutIssues)
6262
{
6363
var logMessage = string.Format(CultureInfo.CurrentCulture, "No closed issues have been found for milestone {0}, or all assigned issues are meant to be excluded from release notes, aborting release creation.", _milestoneTitle);
6464
throw new InvalidOperationException(logMessage);
6565
}
6666

67+
// By default we use the custom template, if it was provided.
68+
// Otherwise, we determine which template we should use.
69+
var template = customTemplate;
70+
if (string.IsNullOrWhiteSpace(template))
71+
{
72+
if (issues.Count == 0)
73+
{
74+
template = ReleaseTemplates.NO_ISSUES_NAME;
75+
}
76+
else if (_configuration.Create.IncludeContributors)
77+
{
78+
template = ReleaseTemplates.CONTRIBUTORS_NAME;
79+
}
80+
else
81+
{
82+
template = ReleaseTemplates.DEFAULT_NAME;
83+
}
84+
}
85+
6786
var commitsLink = _vcsProvider.GetCommitsUrl(_user, _repository, _targetMilestone?.Title, previousMilestone?.Title);
6887

6988
var issuesDict = GetIssuesDict(issues);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{{ if config.create.include_footer }}
2+
3+
### {{ config.create.footer_heading }}
4+
5+
{{ if config.create.milestone_replace_text
6+
replace_milestone_title config.create.footer_content config.create.milestone_replace_text milestone.target.title
7+
else
8+
config.create.footer_content
9+
end
10+
end }}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{{-
2+
include 'release-info'
3+
if milestone.target.description
4+
include 'milestone'
5+
end
6+
include 'issues' | string.rstrip
7+
if template_kind == "CREATE"
8+
include 'create/footer'
9+
end
10+
~}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
This release had no issues associated with it.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
{{ milestone.target.description }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{{
2+
if commits.count > 0
3+
}}As part of this release we had [{{ commits.count }} {{ commits.count | string.pluralize "commit" "commits" }}]({{ commits.html_url }}).
4+
{{ end -}}

Diff for: src/GitReleaseManager.Core/VcsService.cs

+2-12
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using GitReleaseManager.Core.Model;
1414
using GitReleaseManager.Core.Provider;
1515
using GitReleaseManager.Core.ReleaseNotes;
16-
using GitReleaseManager.Core.Templates;
1716
using Serilog;
1817

1918
namespace GitReleaseManager.Core
@@ -46,16 +45,7 @@ public async Task<Release> CreateEmptyReleaseAsync(string owner, string reposito
4645

4746
public async Task<Release> CreateReleaseFromMilestoneAsync(string owner, string repository, string milestone, string releaseName, string targetCommitish, IList<string> assets, bool prerelease, string templateFilePath)
4847
{
49-
var templatePath = _configuration.Create.IncludeContributors
50-
? ReleaseTemplates.CONTRIBUTORS_NAME
51-
: ReleaseTemplates.DEFAULT_NAME;
52-
53-
if (!string.IsNullOrWhiteSpace(templateFilePath))
54-
{
55-
templatePath = templateFilePath;
56-
}
57-
58-
var releaseNotes = await _releaseNotesBuilder.BuildReleaseNotesAsync(owner, repository, milestone, templatePath).ConfigureAwait(false);
48+
var releaseNotes = await _releaseNotesBuilder.BuildReleaseNotesAsync(owner, repository, milestone, templateFilePath).ConfigureAwait(false);
5949
var release = await CreateReleaseAsync(owner, repository, releaseName, milestone, releaseNotes, prerelease, targetCommitish, assets).ConfigureAwait(false);
6050

6151
return release;
@@ -67,7 +57,7 @@ public async Task<Release> CreateReleaseFromInputFileAsync(string owner, string
6757

6858
_logger.Verbose("Reading release notes from: '{FilePath}'", inputFilePath);
6959

70-
var releaseNotes = File.ReadAllText(inputFilePath);
60+
var releaseNotes = await File.ReadAllTextAsync(inputFilePath).ConfigureAwait(false);
7161
var release = await CreateReleaseAsync(owner, repository, name, name, releaseNotes, prerelease, targetCommitish, assets).ConfigureAwait(false);
7262

7363
return release;

Diff for: src/GitReleaseManager.IntegrationTests/ReleaseNotesBuilderIntegrationTests.cs

+27-6
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,38 @@ public async Task SingleMilestone()
7474
var configuration = ConfigurationProvider.Provide(currentDirectory, fileSystem);
7575
configuration.IssueLabelsExclude.Add("Internal Refactoring"); // This is necessary to generate the release notes for GitReleaseManager version 0.12.0
7676

77-
// Indicate whether you want to include the 'Contributors' section in the release notes
77+
// Indicate that we want to include the 'Contributors' section in the release notes
7878
configuration.Create.IncludeContributors = true;
7979

80-
// Pick the template based on whether you want to include the 'Contributors' section in the release notes
81-
var templatePath = configuration.Create.IncludeContributors
82-
? ReleaseTemplates.CONTRIBUTORS_NAME
83-
: ReleaseTemplates.DEFAULT_NAME;
80+
var vcsProvider = new GitHubProvider(_gitHubClient, _mapper, _graphQlClient);
81+
var releaseNotesBuilder = new ReleaseNotesBuilder(vcsProvider, _logger, fileSystem, configuration, new TemplateFactory(fileSystem, configuration, TemplateKind.Create));
82+
var result = await releaseNotesBuilder.BuildReleaseNotesAsync("GitTools", "GitReleaseManager", "0.12.0", string.Empty).ConfigureAwait(false); // 0.12.0 contains a mix of issues and PRs
83+
Debug.WriteLine(result);
84+
ClipBoardHelper.SetClipboard(result);
85+
}
86+
}
87+
88+
[Test]
89+
[Explicit]
90+
public async Task MilestoneWithoutIssues()
91+
{
92+
if (string.IsNullOrWhiteSpace(_token))
93+
{
94+
Assert.Inconclusive("Unable to locate credentials for accessing GitHub API");
95+
}
96+
else
97+
{
98+
var fileSystem = new FileSystem(new CreateSubOptions());
99+
var currentDirectory = Environment.CurrentDirectory;
100+
101+
var configuration = ConfigurationProvider.Provide(currentDirectory, fileSystem);
102+
103+
// Indicate that we allow milestones without issues
104+
configuration.Create.AllowMilestonesWithoutIssues = true;
84105

85106
var vcsProvider = new GitHubProvider(_gitHubClient, _mapper, _graphQlClient);
86107
var releaseNotesBuilder = new ReleaseNotesBuilder(vcsProvider, _logger, fileSystem, configuration, new TemplateFactory(fileSystem, configuration, TemplateKind.Create));
87-
var result = await releaseNotesBuilder.BuildReleaseNotesAsync("GitTools", "GitReleaseManager", "0.12.0", templatePath).ConfigureAwait(false); // 0.12.0 contains a mix of issues and PRs
108+
var result = await releaseNotesBuilder.BuildReleaseNotesAsync("jericho", "_testing", "0.1.0", string.Empty).ConfigureAwait(false); // There are no issues associated with milestone 0.1.0 in my testing repo.
88109
Debug.WriteLine(result);
89110
ClipBoardHelper.SetClipboard(result);
90111
}

0 commit comments

Comments
 (0)