Skip to content

Commit fedaf30

Browse files
lcawlcotti
andcommitted
Change changelog --extract-release-notes impact on title (#3015)
Co-authored-by: Felipe Cotti <felipe.cotti@elastic.co>
1 parent dc9f290 commit fedaf30

12 files changed

Lines changed: 47 additions & 179 deletions

File tree

docs/cli/changelog/add.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,15 @@ docs-builder changelog add [options...] [-h|--help]
2929
: If the content contains any special characters such as backquotes, you must precede it with a backslash escape character (`\`).
3030

3131
`--no-extract-release-notes`
32-
: Optional: Turn off extraction of release notes from PR descriptions.
33-
: The extractor looks for content in various formats in the PR description:
32+
: Optional: Turn off extraction of release notes from PR or issue descriptions.
33+
: The extractor looks for content in various formats in the PR or issue description:
3434
: - `Release Notes: ...`
3535
: - `Release-Notes: ...`
3636
: - `release notes: ...`
3737
: - `Release Note: ...`
3838
: - `Release Notes - ...`
3939
: - `## Release Note` (as a markdown header)
40-
: Short release notes (≤120 characters, single line) are used as the changelog title (only if `--title` is not explicitly provided).
41-
: Long release notes (>120 characters or multi-line) are used as the changelog description (only if `--description` is not explicitly provided).
40+
: Matched release note text is used as the changelog description (only if `--description` is not explicitly provided). The changelog title is always taken from `--title` or from the PR or issue title, not from the release note section.
4241
: By default, the behavior is determined by the `extract.release_notes` changelog configuration setting.
4342

4443
`--feature-id <string?>`

docs/contribute/changelog.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,11 +541,10 @@ In particular, it looks for content in these formats in the PR description:
541541
- `Release Notes - This is the extracted sentence.`
542542
- `## Release Note` (as a markdown header)
543543

544-
The extracted content is handled differently based on its length:
544+
How the extracted text is used:
545545

546-
- **Short release notes (≤120 characters, single line)**: Used as the changelog title (only if `--title` is not explicitly provided)
547-
- **Long release notes (>120 characters or multi-line)**: Used as the changelog description (only if `--description` is not explicitly provided)
548-
- **No release note found**: No changes are made to the title or description
546+
- **Release note found**: The extracted text is used as the changelog description (only if `--description` is not explicitly provided). The changelog title comes from `--title` or the PR title, not from the release note section.
547+
- **No release note found**: No description is filled from the PR body; the title still comes from `--title` or the PR title as usual.
549548

550549
:::{note}
551550
If you explicitly provide `--title` or `--description`, those values take precedence over extracted release notes.

src/services/Elastic.Changelog/Creation/ChangelogCreationService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public record CreateChangelogArguments
4040
public bool UseIssueNumber { get; init; }
4141
public bool? StripTitlePrefix { get; init; }
4242
/// <summary>
43-
/// Whether to extract release notes from PR/issue descriptions. null = use config default.
43+
/// Whether to extract release note text from PR/issue descriptions for the entry description. null = use config default.
4444
/// </summary>
4545
public bool? ExtractReleaseNotes { get; init; }
4646

src/services/Elastic.Changelog/Creation/IssueInfoProcessor.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,22 +102,15 @@ public async Task<IssueProcessingResult> ProcessIssueAsync(
102102

103103
if (input.ExtractReleaseNotes ?? false)
104104
{
105-
var (releaseNoteTitle, releaseNoteDescription) = ReleaseNotesExtractor.ExtractReleaseNotes(issueInfo.Body);
106-
107-
if (releaseNoteTitle != null && string.IsNullOrWhiteSpace(input.Title))
108-
{
109-
derived.Title = releaseNoteTitle;
110-
logger.LogInformation("Using extracted release note as title: {Title}", derived.Title);
111-
}
112-
113-
if (releaseNoteDescription != null && string.IsNullOrWhiteSpace(input.Description))
105+
var releaseNote = ReleaseNotesExtractor.FindReleaseNote(issueInfo.Body);
106+
if (releaseNote != null && string.IsNullOrWhiteSpace(input.Description))
114107
{
115-
derived.Description = releaseNoteDescription;
116-
logger.LogInformation("Using extracted release note as description (length: {Length} characters)", releaseNoteDescription.Length);
108+
derived.Description = releaseNote;
109+
logger.LogInformation("Using extracted release note as description (length: {Length} characters)", releaseNote.Length);
117110
}
118111
}
119112

120-
if (string.IsNullOrWhiteSpace(input.Title) && derived.Title == null)
113+
if (string.IsNullOrWhiteSpace(input.Title))
121114
{
122115
if (string.IsNullOrWhiteSpace(issueInfo.Title))
123116
{

src/services/Elastic.Changelog/Creation/PrInfoProcessor.cs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -102,28 +102,19 @@ public async Task<PrProcessingResult> ProcessPrAsync(
102102
{
103103
var derived = new DerivedPrFields();
104104

105-
// Extract release notes from PR body if requested
105+
// Extract release note text from PR body for description if requested
106106
if (input.ExtractReleaseNotes ?? false)
107107
{
108-
var (releaseNoteTitle, releaseNoteDescription) = ReleaseNotesExtractor.ExtractReleaseNotes(prInfo.Body);
109-
110-
// Use short release note as title if title was not explicitly provided
111-
if (releaseNoteTitle != null && string.IsNullOrWhiteSpace(input.Title))
112-
{
113-
derived.Title = releaseNoteTitle;
114-
logger.LogInformation("Using extracted release note as title: {Title}", derived.Title);
115-
}
116-
117-
// Use long release note as description if description was not explicitly provided
118-
if (releaseNoteDescription != null && string.IsNullOrWhiteSpace(input.Description))
108+
var releaseNote = ReleaseNotesExtractor.FindReleaseNote(prInfo.Body);
109+
if (releaseNote != null && string.IsNullOrWhiteSpace(input.Description))
119110
{
120-
derived.Description = releaseNoteDescription;
121-
logger.LogInformation("Using extracted release note as description (length: {Length} characters)", releaseNoteDescription.Length);
111+
derived.Description = releaseNote;
112+
logger.LogInformation("Using extracted release note as description (length: {Length} characters)", releaseNote.Length);
122113
}
123114
}
124115

125-
// Use PR title if title was not explicitly provided and not already derived
126-
if (string.IsNullOrWhiteSpace(input.Title) && derived.Title == null)
116+
// Use PR title if title was not explicitly provided
117+
if (string.IsNullOrWhiteSpace(input.Title))
127118
{
128119
if (string.IsNullOrWhiteSpace(prInfo.Title))
129120
{

src/services/Elastic.Changelog/Evaluation/ChangelogPrEvaluationService.cs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -82,33 +82,23 @@ public async Task<bool> EvaluatePr(IDiagnosticsCollector collector, EvaluatePrAr
8282
return await SetOutputs(PrEvaluationResult.Skipped, skipLabels: skipLabels);
8383
}
8484

85-
// Resolve title: prefer release notes from PR body, fall back to PR title
85+
// Resolve title from PR title only (release note text is never used as title)
8686
var prTitle = input.PrTitle;
8787
if (input.StripTitlePrefix)
8888
prTitle = ChangelogTextUtilities.StripSquareBracketPrefix(prTitle);
8989

90-
string? title = null;
9190
string? description = null;
92-
9391
if (config.Extract.ReleaseNotes && !string.IsNullOrWhiteSpace(input.PrBody))
9492
{
95-
var (releaseNoteTitle, releaseNoteDescription) = ReleaseNotesExtractor.ExtractReleaseNotes(input.PrBody);
96-
97-
if (releaseNoteTitle != null)
98-
{
99-
title = releaseNoteTitle;
100-
_logger.LogInformation("Using extracted release note as title: {Title}", title);
101-
}
102-
103-
if (releaseNoteDescription != null)
93+
var releaseNote = ReleaseNotesExtractor.FindReleaseNote(input.PrBody);
94+
if (releaseNote != null)
10495
{
105-
description = releaseNoteDescription;
96+
description = releaseNote;
10697
_logger.LogInformation("Using extracted release note as description (length: {Length} characters)", description.Length);
10798
}
10899
}
109100

110-
// Fall back to PR title when no short release note was found
111-
title ??= prTitle;
101+
var title = prTitle;
112102

113103
if (string.IsNullOrWhiteSpace(title))
114104
{

src/services/Elastic.Changelog/ReleaseNotesExtractor.cs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ public static partial class ReleaseNotesExtractor
2020
[GeneratedRegex(@"(?:\n|^)\s*#*\s*release[\s-]?notes?[:\s-]*(.*?)(?:(\r?\n|\r){2}|$|((\r?\n|\r)\s*#+))", RegexOptions.IgnoreCase | RegexOptions.Singleline)]
2121
private static partial Regex ReleaseNoteRegex();
2222

23-
private const int MaxReleaseNoteTitleLength = 120;
24-
2523
/// <summary>
2624
/// Strips HTML comments from markdown text.
2725
/// This handles both single-line and multi-line comments.
@@ -76,29 +74,4 @@ private static string StripHtmlComments(string markdown)
7674

7775
return null;
7876
}
79-
80-
/// <summary>
81-
/// Extracts release notes from PR body and determines how to use them.
82-
/// </summary>
83-
/// <param name="prBody">The PR description body</param>
84-
/// <returns>
85-
/// A tuple where:
86-
/// - Item1: The title to use (either original title or extracted release note if short)
87-
/// - Item2: The description to use (extracted release note if long, otherwise null)
88-
/// </returns>
89-
public static (string? title, string? description) ExtractReleaseNotes(string? prBody)
90-
{
91-
var releaseNote = FindReleaseNote(prBody);
92-
93-
// No release note found: return nulls (use defaults)
94-
if (string.IsNullOrWhiteSpace(releaseNote))
95-
return (null, null);
96-
97-
// Long release note (>120 characters or multi-line): use in description
98-
if (releaseNote.Length > MaxReleaseNoteTitleLength || releaseNote.Contains('\n'))
99-
return (null, releaseNote);
100-
101-
// Short release note (≤120 characters, single line): use in title
102-
return (releaseNote, null);
103-
}
10477
}

src/services/Elastic.Changelog/Serialization/ChangelogConfigurationYaml.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ internal record BundleProfileYaml
354354
internal record ExtractConfigurationYaml
355355
{
356356
/// <summary>
357-
/// Whether to extract release notes from PR descriptions by default.
357+
/// Whether to extract release note text from PR or issue descriptions for the changelog entry description by default.
358+
/// Does not affect the title (title comes from <c>--title</c> or the PR/issue title).
358359
/// Defaults to true.
359360
/// </summary>
360361
public bool? ReleaseNotes { get; set; }

src/tooling/docs-builder/Commands/ChangelogCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ public Task<int> Init(
215215
/// <param name="areas">Optional: Area(s) affected (comma-separated or specify multiple times)</param>
216216
/// <param name="config">Optional: Path to the changelog.yml configuration file. Defaults to 'docs/changelog.yml'</param>
217217
/// <param name="description">Optional: Additional information about the change (max 600 characters)</param>
218-
/// <param name="noExtractReleaseNotes">Optional: Turn off extraction of release notes from PR descriptions. By default, release notes are extracted when using --prs. Short release notes (≤120 characters, single line) are used as the title, long release notes (>120 characters or multi-line) are used as the description.</param>
218+
/// <param name="noExtractReleaseNotes">Optional: Turn off extraction of release notes from PR descriptions. By default, release notes are extracted when using --prs. Matched release note text is used as the changelog description (only if --description is not explicitly provided). The changelog title comes from --title or the PR title, not from the release note section.</param>
219219
/// <param name="noExtractIssues">Optional: Turn off extraction of linked references. When using --prs: turns off extraction of linked issues from the PR body (e.g., "Fixes #123"). When using --issues: turns off extraction of linked PRs from the issue body (e.g., "Fixed by #123"). By default, linked references are extracted in both cases.</param>
220220
/// <param name="featureId">Optional: Feature flag ID</param>
221221
/// <param name="highlight">Optional: Include in release highlights</param>

tests/Elastic.Changelog.Tests/Changelogs/Create/ReleaseNoteExtractionTests.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Elastic.Changelog.Tests.Changelogs.Create;
1313
public class ReleaseNoteExtractionTests(ITestOutputHelper output) : CreateChangelogTestBase(output)
1414
{
1515
[Fact]
16-
public async Task CreateChangelog_WithExtractReleaseNotes_ShortReleaseNote_UsesAsTitle()
16+
public async Task CreateChangelog_WithExtractReleaseNotes_ShortReleaseNote_UsesPrTitleAndDescription()
1717
{
1818
// Arrange
1919
var prInfo = new GitHubPrInfo
@@ -70,14 +70,8 @@ public async Task CreateChangelog_WithExtractReleaseNotes_ShortReleaseNote_UsesA
7070
files.Should().HaveCount(1);
7171

7272
var yamlContent = await FileSystem.File.ReadAllTextAsync(files[0], TestContext.Current.CancellationToken);
73-
yamlContent.Should().Contain("title: Adds support for new aggregation types");
74-
// Description should not be set when release note is used as title
75-
if (yamlContent.Contains("description:"))
76-
{
77-
// If description field exists, it should be empty or commented out
78-
var descriptionLine = yamlContent.Split('\n').FirstOrDefault(l => l.Contains("description:"));
79-
descriptionLine.Should().MatchRegex(@"description:\s*(#|$)");
80-
}
73+
yamlContent.Should().Contain("title: Implement new aggregation API");
74+
yamlContent.Should().Contain("description: Adds support for new aggregation types");
8175
}
8276

8377
[Fact]
@@ -338,7 +332,7 @@ public async Task CreateChangelog_WithExtractReleaseNotes_ExplicitTitle_TakesPre
338332

339333
var yamlContent = await FileSystem.File.ReadAllTextAsync(files[0], TestContext.Current.CancellationToken);
340334
yamlContent.Should().Contain("title: Custom title");
341-
yamlContent.Should().NotContain("Adds support for new aggregation types");
335+
yamlContent.Should().Contain("description: Adds support for new aggregation types");
342336
}
343337

344338
[Fact]

0 commit comments

Comments
 (0)