@@ -4,6 +4,8 @@ use xshell::Shell;
44pub ( crate ) fn check_changelog ( shell : Shell , mut args : Arguments ) -> anyhow:: Result < ( ) > {
55 const CHANGELOG_PATH_RELATIVE : & str = "./CHANGELOG.md" ;
66
7+ let emit_github_messages = args. contains ( "--emit-github-messages" ) ;
8+
79 let from_branch = args
810 . free_from_str ( )
911 . ok ( )
@@ -60,7 +62,17 @@ pub(crate) fn check_changelog(shell: Shell, mut args: Arguments) -> anyhow::Resu
6062 }
6163
6264 for hunk in & hunks_in_a_released_section {
63- eprintln ! ( "{hunk}" ) ;
65+ eprintln ! ( "{}" , hunk. contents) ;
66+
67+ if emit_github_messages {
68+ let title = "Released changelog content changed" ;
69+ let message =
70+ "This PR changes changelog content that is already released." . to_owned ( ) ;
71+ println ! (
72+ "::error file=CHANGELOG.md,line={},endLine={},title={title}::{message}" ,
73+ hunk. change_start_line_num, hunk. change_end_line_num,
74+ )
75+ }
6476 }
6577 }
6678
@@ -76,6 +88,12 @@ pub(crate) fn check_changelog(shell: Shell, mut args: Arguments) -> anyhow::Resu
7688 }
7789}
7890
91+ struct Hunk < ' a > {
92+ change_start_line_num : u64 ,
93+ change_end_line_num : u64 ,
94+ contents : & ' a str ,
95+ }
96+
7997/// Given some `changelog_contents` (in Markdown) containing the full end state of the provided
8098/// `diff` (in [unified diff format]), return all hunks that are (1) below a `## Unreleased` section
8199/// _and_ (2) above all other second-level (i.e., `## …`) headings.
@@ -90,7 +108,7 @@ pub(crate) fn check_changelog(shell: Shell, mut args: Arguments) -> anyhow::Resu
90108/// `changelog_contents`. using hunk information to compare against `changelog_contents`.
91109///
92110/// Failing to uphold these assumptons is not unsafe, but will yield incorrect results.
93- fn hunks_in_a_released_section < ' a > ( changelog_contents : & str , diff : & ' a str ) -> Vec < & ' a str > {
111+ fn hunks_in_a_released_section < ' a > ( changelog_contents : & str , diff : & ' a str ) -> Vec < Hunk < ' a > > {
94112 let mut changelog_lines = changelog_contents. lines ( ) ;
95113
96114 let changelog_unreleased_line_num =
@@ -109,7 +127,7 @@ fn hunks_in_a_released_section<'a>(changelog_contents: &str, diff: &'a str) -> V
109127 SplitPrefixInclusive :: new ( "\n @@" , & diff[ first_hunk_idx..] ) . map ( |s| & s[ '\n' . len_utf8 ( ) ..] )
110128 } ;
111129 let hunks_in_a_released_section = hunks
112- . filter ( |hunk| {
130+ . filter_map ( |hunk| {
113131 let ( hunk_header, hunk_contents) = hunk. split_once ( '\n' ) . unwrap ( ) ;
114132
115133 // Reference: This is of the format `@@ -86,6 +88,10 @@ …`.
@@ -128,15 +146,25 @@ fn hunks_in_a_released_section<'a>(changelog_contents: &str, diff: &'a str) -> V
128146 . parse :: < u64 > ( )
129147 . unwrap ( ) ;
130148
131- let lines_until_first_change = hunk_contents
149+ let mut change_lines = hunk_contents
132150 . lines ( )
133- . take_while ( |l| l. starts_with ( ' ' ) )
134- . count ( ) as u64 ;
135-
136- let first_hunk_change_start_offset =
137- post_change_hunk_start_offset + lines_until_first_change;
138-
139- first_hunk_change_start_offset >= changelog_first_release_section_line_num
151+ . enumerate ( )
152+ . filter ( |( _idx, l) | !l. starts_with ( ' ' ) )
153+ . map ( |( zero_based_idx, _l) | {
154+ post_change_hunk_start_offset + ( zero_based_idx as u64 )
155+ } ) ;
156+
157+ let change_start_line_num = change_lines. next ( ) . unwrap ( ) ;
158+
159+ if change_start_line_num >= changelog_first_release_section_line_num {
160+ Some ( Hunk {
161+ contents : hunk,
162+ change_start_line_num,
163+ change_end_line_num : change_lines. last ( ) . unwrap_or ( change_start_line_num) ,
164+ } )
165+ } else {
166+ None
167+ }
140168 } )
141169 . collect :: < Vec < _ > > ( ) ;
142170
0 commit comments