Split commit paragraphs on any blank line
The trailing comment block was found by searching for "\n\n", so a CRLF message kept its # Conflicts: list below the trailer. Walk the lines instead and treat any blank or whitespace-only line as a paragraph break. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: siGit Code <noreply@sigit.si>
Seto Elkahfi committed
Sep 29, 2026 at 08:18 UTC
182a30dc83e2d265073b2e8bf85588ca393ba828
1 file changed
+32
-7
src/tools.rs
+32
-7
@@ -2042,16 +2042,25 @@ fn git_head(cwd: &Path) -> Option<String> {
2042
/// `# Conflicts:` list stays in the commit. Any trailer after it is no longer
2043
/// in the last paragraph, and GitHub stops seeing it. Only trailing
2044
/// paragraphs made entirely of `#` lines go; a `#` line inside the message
2045
-/// (a Markdown heading in a `-m` body, say) is left alone.
2045
+/// (a Markdown heading in a `-m` body, say) is left alone. Paragraphs are
2046
+/// split on any blank line, so a run of them or CRLF endings work too.
2047
fn strip_trailing_comment_block(message: &str) -> &str {
2048
let mut rest = message.trim_end();
2049
loop {
2049
- let (head, last) = match rest.rfind("\n\n") {
2050
- Some(split) => (&rest[..split], &rest[split + 2..]),
2051
- None => return rest,
2052
- };
2053
- if last.lines().all(|line| line.starts_with('#')) {
2054
- rest = head.trim_end();
2050
+ // Byte offset just past the last blank line, i.e. where the final
2051
+ // paragraph starts. `rest` never ends in one, so that paragraph
2052
+ // is non-empty.
2053
+ let mut split = None;
2054
+ let mut offset = 0;
2055
+ for line in rest.split_inclusive('\n') {
2056
+ offset += line.len();
2057
+ if line.trim().is_empty() {
2058
+ split = Some(offset);
2059
+ }
2060
+ }
2061
+ let Some(split) = split else { return rest };
2062
+ if rest[split..].lines().all(|line| line.starts_with('#')) {
2063
+ rest = rest[..split].trim_end();
2064
} else {
2065
return rest;
2066
}
@@ -3315,6 +3324,22 @@ mod tests {
3324
let body = "Title\n\n## Why\n\nBecause\n\n# Note\nnot a comment";
3325
assert_eq!(strip_trailing_comment_block(body), body);
3326
assert_eq!(strip_trailing_comment_block("# only"), "# only");
3327
+ // Extra blank lines, whitespace-only lines, and CRLF still separate
3328
+ // paragraphs.
3329
+ assert_eq!(
3330
+ strip_trailing_comment_block("Merge\n\n\n\n# Conflicts:\n#\tf\n"),
3331
+ "Merge"
3332
+ );
3333
+ assert_eq!(
3334
+ strip_trailing_comment_block("Merge\n \t\n# Conflicts:\n#\tf"),
3335
+ "Merge"
3336
+ );
3337
+ assert_eq!(
3338
+ strip_trailing_comment_block("Merge\r\n\r\n# Conflicts:\r\n#\tf\r\n"),
3339
+ "Merge"
3340
+ );
3341
+ let crlf = "Title\r\n\r\n# Note\r\nnot a comment\r\n";
3342
+ assert_eq!(strip_trailing_comment_block(crlf), crlf.trim_end());
3343
}
3344
3345
/// Start a merge of `side` into `main` that conflicts on file.txt and