From ec81ec28229b977cde924e61e65208e3c5b8918a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 06:08:50 +0000 Subject: [PATCH 1/4] Initial plan From 4d76339b292bc22b127252867d5d12274daa9aa2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 06:12:48 +0000 Subject: [PATCH 2/4] fix: preserve Fixes:/PMS: issue references in --amend mode When using `git-commit-helper commit --amend`, issue references (Fixes: and PMS:) from the original commit were being lost because the AI-generated message didn't always include them. Now, when --amend is used without explicitly providing new --issues, the tool extracts Fixes: and PMS: marks from the original commit message and appends any missing ones to the new commit message. The Change-Id preservation logic already existed and is unchanged. Co-authored-by: zccrs <13449038+zccrs@users.noreply.github.com> --- src/commit.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/src/commit.rs b/src/commit.rs index 4105a8f..0f55ec8 100644 --- a/src/commit.rs +++ b/src/commit.rs @@ -877,6 +877,35 @@ pub async fn generate_commit_message( eprintln!("警告: 解析 issues 参数失败: {}", e); } } + } else if amend { + // 在 amend 模式下且未提供新的 issues 参数时,从原提交中保留 issue 引用(Fixes:/PMS:) + if let Some(ref orig_msg) = original_message { + let orig_commit = CommitMessage::parse(orig_msg); + let issue_marks: Vec = orig_commit.marks.iter() + .filter(|mark| { + let mark_lower = mark.to_lowercase(); + mark_lower.starts_with("fixes:") || mark_lower.starts_with("pms:") + }) + .cloned() + .collect(); + // 只添加新内容中尚未包含的标记 + let marks_to_add: Vec = issue_marks.into_iter() + .filter(|mark| { + let mark_key = mark.split(':').next().unwrap_or("").trim().to_lowercase(); + !content.lines().any(|line| { + line.trim().split(':').next() + .map_or(false, |k| k.trim().to_lowercase() == mark_key) + }) + }) + .collect(); + if !marks_to_add.is_empty() { + if !content.ends_with('\n') { + content.push('\n'); + } + content.push('\n'); + content.push_str(&marks_to_add.join("\n")); + } + } } // 在 amend 模式下,如果原提交有 Change-Id,保留它 @@ -1154,4 +1183,80 @@ mod tests { // Change-Id 应该在最后 assert!(result.ends_with("Change-Id: I1234567890abcdef1234567890abcdef12345678")); } + + // 测试 amend 模式下从原提交消息中提取 issue 引用标记 + #[test] + fn test_extract_issue_marks_from_original_commit() { + let original = "fix: some bug\n\nDescription here\n\nFixes: #123\nPMS: BUG-456\nChange-Id: Iabc123\n"; + let orig_commit = CommitMessage::parse(original); + let issue_marks: Vec = orig_commit.marks.iter() + .filter(|mark| { + let mark_lower = mark.to_lowercase(); + mark_lower.starts_with("fixes:") || mark_lower.starts_with("pms:") + }) + .cloned() + .collect(); + assert_eq!(issue_marks.len(), 2); + assert!(issue_marks.contains(&"Fixes: #123".to_string())); + assert!(issue_marks.contains(&"PMS: BUG-456".to_string())); + // Change-Id 不应被提取为 issue 标记 + assert!(!issue_marks.iter().any(|m| m.starts_with("Change-Id:"))); + } + + // 测试 amend 时新内容已包含 issue 引用时不重复添加 + #[test] + fn test_amend_no_duplicate_issue_marks() { + let original = "fix: some bug\n\nFixes: #123\n"; + let orig_commit = CommitMessage::parse(original); + let issue_marks: Vec = orig_commit.marks.iter() + .filter(|mark| { + let mark_lower = mark.to_lowercase(); + mark_lower.starts_with("fixes:") || mark_lower.starts_with("pms:") + }) + .cloned() + .collect(); + + // 新内容已包含 Fixes: + let new_content = "fix: improved bug fix\n\nBetter description\n\nFixes: #123"; + let marks_to_add: Vec = issue_marks.into_iter() + .filter(|mark| { + let mark_key = mark.split(':').next().unwrap_or("").trim().to_lowercase(); + !new_content.lines().any(|line| { + line.trim().split(':').next() + .map_or(false, |k| k.trim().to_lowercase() == mark_key) + }) + }) + .collect(); + // 不应重复添加 + assert!(marks_to_add.is_empty()); + } + + // 测试 amend 时新内容不含 issue 引用时正确添加 + #[test] + fn test_amend_preserves_issue_marks_when_missing() { + let original = "fix: some bug\n\nFixes: #123\nPMS: BUG-456\n"; + let orig_commit = CommitMessage::parse(original); + let issue_marks: Vec = orig_commit.marks.iter() + .filter(|mark| { + let mark_lower = mark.to_lowercase(); + mark_lower.starts_with("fixes:") || mark_lower.starts_with("pms:") + }) + .cloned() + .collect(); + + // 新内容不含 issue 引用 + let new_content = "fix: improved bug fix\n\nBetter description"; + let marks_to_add: Vec = issue_marks.into_iter() + .filter(|mark| { + let mark_key = mark.split(':').next().unwrap_or("").trim().to_lowercase(); + !new_content.lines().any(|line| { + line.trim().split(':').next() + .map_or(false, |k| k.trim().to_lowercase() == mark_key) + }) + }) + .collect(); + assert_eq!(marks_to_add.len(), 2); + assert!(marks_to_add.contains(&"Fixes: #123".to_string())); + assert!(marks_to_add.contains(&"PMS: BUG-456".to_string())); + } } From b1090be012d8078536f738c49d78c75e2534b638 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 07:16:41 +0000 Subject: [PATCH 3/4] fix: preserve all marks (Fixes/PMS/Log/etc) in --amend mode, not just issue refs Expand the amend mark preservation from only Fixes:/PMS: to all marks from the original commit (excluding Change-Id which is already handled by append_change_id). This ensures Log:, Fixes:, PMS:, and any other single-line marks are retained when amending a commit. Co-authored-by: zccrs <13449038+zccrs@users.noreply.github.com> --- src/commit.rs | 81 +++++++++++++++++++++------------------------------ 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/src/commit.rs b/src/commit.rs index 0f55ec8..a5577b5 100644 --- a/src/commit.rs +++ b/src/commit.rs @@ -878,25 +878,20 @@ pub async fn generate_commit_message( } } } else if amend { - // 在 amend 模式下且未提供新的 issues 参数时,从原提交中保留 issue 引用(Fixes:/PMS:) + // 在 amend 模式下且未提供新的 issues 参数时,从原提交中保留所有标记字段 + // (Change-Id 由 append_change_id 单独处理,此处跳过) if let Some(ref orig_msg) = original_message { let orig_commit = CommitMessage::parse(orig_msg); - let issue_marks: Vec = orig_commit.marks.iter() - .filter(|mark| { - let mark_lower = mark.to_lowercase(); - mark_lower.starts_with("fixes:") || mark_lower.starts_with("pms:") - }) - .cloned() - .collect(); - // 只添加新内容中尚未包含的标记 - let marks_to_add: Vec = issue_marks.into_iter() + // 只添加新内容中尚未包含的标记(排除 Change-Id,它由 append_change_id 处理) + let marks_to_add: Vec = orig_commit.marks.iter() .filter(|mark| { let mark_key = mark.split(':').next().unwrap_or("").trim().to_lowercase(); - !content.lines().any(|line| { + mark_key != "change-id" && !content.lines().any(|line| { line.trim().split(':').next() .map_or(false, |k| k.trim().to_lowercase() == mark_key) }) }) + .cloned() .collect(); if !marks_to_add.is_empty() { if !content.ends_with('\n') { @@ -1184,79 +1179,71 @@ mod tests { assert!(result.ends_with("Change-Id: I1234567890abcdef1234567890abcdef12345678")); } - // 测试 amend 模式下从原提交消息中提取 issue 引用标记 + // 测试从原提交消息中提取所有需保留的标记(排除 Change-Id) #[test] fn test_extract_issue_marks_from_original_commit() { - let original = "fix: some bug\n\nDescription here\n\nFixes: #123\nPMS: BUG-456\nChange-Id: Iabc123\n"; + let original = "fix: some bug\n\nDescription here\n\nFixes: #123\nPMS: BUG-456\nLog: Fix critical bug\nChange-Id: Iabc123\n"; let orig_commit = CommitMessage::parse(original); - let issue_marks: Vec = orig_commit.marks.iter() + let marks_to_preserve: Vec = orig_commit.marks.iter() .filter(|mark| { - let mark_lower = mark.to_lowercase(); - mark_lower.starts_with("fixes:") || mark_lower.starts_with("pms:") + let mark_key = mark.split(':').next().unwrap_or("").trim().to_lowercase(); + mark_key != "change-id" }) .cloned() .collect(); - assert_eq!(issue_marks.len(), 2); - assert!(issue_marks.contains(&"Fixes: #123".to_string())); - assert!(issue_marks.contains(&"PMS: BUG-456".to_string())); - // Change-Id 不应被提取为 issue 标记 - assert!(!issue_marks.iter().any(|m| m.starts_with("Change-Id:"))); + assert_eq!(marks_to_preserve.len(), 3); + assert!(marks_to_preserve.contains(&"Fixes: #123".to_string())); + assert!(marks_to_preserve.contains(&"PMS: BUG-456".to_string())); + assert!(marks_to_preserve.contains(&"Log: Fix critical bug".to_string())); + // Change-Id 不应被保留(由 append_change_id 单独处理) + assert!(!marks_to_preserve.iter().any(|m| m.to_lowercase().starts_with("change-id:"))); } - // 测试 amend 时新内容已包含 issue 引用时不重复添加 + // 测试 amend 时新内容已包含标记时不重复添加 #[test] fn test_amend_no_duplicate_issue_marks() { - let original = "fix: some bug\n\nFixes: #123\n"; + let original = "fix: some bug\n\nFixes: #123\nLog: Fix bug\n"; let orig_commit = CommitMessage::parse(original); - let issue_marks: Vec = orig_commit.marks.iter() - .filter(|mark| { - let mark_lower = mark.to_lowercase(); - mark_lower.starts_with("fixes:") || mark_lower.starts_with("pms:") - }) - .cloned() - .collect(); - // 新内容已包含 Fixes: - let new_content = "fix: improved bug fix\n\nBetter description\n\nFixes: #123"; - let marks_to_add: Vec = issue_marks.into_iter() + // 新内容已包含 Fixes: 和 Log: + let new_content = "fix: improved bug fix\n\nBetter description\n\nFixes: #123\nLog: Fix bug"; + let marks_to_add: Vec = orig_commit.marks.iter() .filter(|mark| { let mark_key = mark.split(':').next().unwrap_or("").trim().to_lowercase(); - !new_content.lines().any(|line| { + mark_key != "change-id" && !new_content.lines().any(|line| { line.trim().split(':').next() .map_or(false, |k| k.trim().to_lowercase() == mark_key) }) }) + .cloned() .collect(); // 不应重复添加 assert!(marks_to_add.is_empty()); } - // 测试 amend 时新内容不含 issue 引用时正确添加 + // 测试 amend 时新内容不含标记时正确添加所有标记 #[test] fn test_amend_preserves_issue_marks_when_missing() { - let original = "fix: some bug\n\nFixes: #123\nPMS: BUG-456\n"; + let original = "fix: some bug\n\nFixes: #123\nPMS: BUG-456\nLog: Fix critical bug\nChange-Id: Iabc123\n"; let orig_commit = CommitMessage::parse(original); - let issue_marks: Vec = orig_commit.marks.iter() - .filter(|mark| { - let mark_lower = mark.to_lowercase(); - mark_lower.starts_with("fixes:") || mark_lower.starts_with("pms:") - }) - .cloned() - .collect(); - // 新内容不含 issue 引用 + // 新内容不含任何标记 let new_content = "fix: improved bug fix\n\nBetter description"; - let marks_to_add: Vec = issue_marks.into_iter() + let marks_to_add: Vec = orig_commit.marks.iter() .filter(|mark| { let mark_key = mark.split(':').next().unwrap_or("").trim().to_lowercase(); - !new_content.lines().any(|line| { + mark_key != "change-id" && !new_content.lines().any(|line| { line.trim().split(':').next() .map_or(false, |k| k.trim().to_lowercase() == mark_key) }) }) + .cloned() .collect(); - assert_eq!(marks_to_add.len(), 2); + assert_eq!(marks_to_add.len(), 3); assert!(marks_to_add.contains(&"Fixes: #123".to_string())); assert!(marks_to_add.contains(&"PMS: BUG-456".to_string())); + assert!(marks_to_add.contains(&"Log: Fix critical bug".to_string())); + // Change-Id 不应被包含(由 append_change_id 处理) + assert!(!marks_to_add.iter().any(|m| m.to_lowercase().starts_with("change-id:"))); } } From 7a2e7c4f0cf30c83057598632c021c5c0ad88434 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 07:24:01 +0000 Subject: [PATCH 4/4] refactor: move amend mark preservation into a dedicated if amend block Separate the mark preservation logic from the --issues parameter check. Instead of nesting in `else if amend`, use a standalone `if amend { ... }` block that handles all amend-specific mark preservation (including Change-Id) in one place, after the --issues handling. Co-authored-by: zccrs <13449038+zccrs@users.noreply.github.com> --- src/commit.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/commit.rs b/src/commit.rs index a5577b5..9e7c194 100644 --- a/src/commit.rs +++ b/src/commit.rs @@ -877,12 +877,13 @@ pub async fn generate_commit_message( eprintln!("警告: 解析 issues 参数失败: {}", e); } } - } else if amend { - // 在 amend 模式下且未提供新的 issues 参数时,从原提交中保留所有标记字段 - // (Change-Id 由 append_change_id 单独处理,此处跳过) + } + + // 在 amend 模式下,保留原提交中所有未被新内容覆盖的标记字段 + // (Change-Id 须保持在最后,由 append_change_id 单独处理) + if amend { if let Some(ref orig_msg) = original_message { let orig_commit = CommitMessage::parse(orig_msg); - // 只添加新内容中尚未包含的标记(排除 Change-Id,它由 append_change_id 处理) let marks_to_add: Vec = orig_commit.marks.iter() .filter(|mark| { let mark_key = mark.split(':').next().unwrap_or("").trim().to_lowercase(); @@ -901,10 +902,6 @@ pub async fn generate_commit_message( content.push_str(&marks_to_add.join("\n")); } } - } - - // 在 amend 模式下,如果原提交有 Change-Id,保留它 - if amend { if let Some(change_id) = original_change_id { content = append_change_id(&content, &change_id); }