From 782d16a4bb572336b2e217ad510f7e47073ac16e Mon Sep 17 00:00:00 2001 From: linbingquan <695601626@qq.com> Date: Fri, 16 Oct 2026 18:43:10 +0800 Subject: [PATCH 1/2] fix(git): synthesize diff line sigils in patches `git2`'s `DiffLine::content()` omits the `+`/`-`/space origin prefix, so generated patches were missing line markers. Add a `write_diff_line` helper that prepends the appropriate sigil for content lines while leaving file headers, hunk headers, binary notices, and EOF markers untouched, and use it for staged, unstaged, and branch-to-branch diffs. Add unit tests covering content lines, headers, binary notices, and `\ No newline at end of file` markers. --- crates/code2prompt-core/src/git.rs | 75 ++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/crates/code2prompt-core/src/git.rs b/crates/code2prompt-core/src/git.rs index 2f6971b..4be2e50 100644 --- a/crates/code2prompt-core/src/git.rs +++ b/crates/code2prompt-core/src/git.rs @@ -11,6 +11,28 @@ use log::info; use std::collections::HashSet; use std::path::{Path, PathBuf}; +/// Synthesize a diff-line sigil (`+`/`-`/` `) for added/removed/context lines. +/// +/// `git2`'s `DiffFormat::Patch` splits the sigil from the line content: +/// - `origin()` returns `'+'` / `'-'` / `' '` for added/removed/context lines +/// - `content()` returns ONLY the raw line text WITHOUT the sigil +/// - file headers (`'F'`), hunk headers (`'H'`), binary notices (`'B'`) and the +/// end-of-file markers (`'='/'>'/'<'`) already contain their full text in +/// `content()`, so they get no synthesized sigil. +/// +/// `content()` is appended unconditionally, so binary notices and the +/// `\ No newline at end of file` markers are preserved rather than dropped. +fn write_diff_line(buf: &mut Vec, origin: char, content: &[u8]) { + match origin { + '+' => buf.push(b'+'), + '-' => buf.push(b'-'), + ' ' => buf.push(b' '), + 'F' | 'H' | 'B' | '=' | '>' | '<' => {} // full text already in content() + _ => {} + } + buf.extend_from_slice(content); +} + /// Extract git diff for staged changes in repository. /// /// Generates diff between HEAD and index to show staged changes, with additional @@ -50,7 +72,7 @@ pub fn get_git_diff(repo_path: &Path) -> Result { let mut staged_diff_text = Vec::new(); staged_diff .print(git2::DiffFormat::Patch, |_delta, _hunk, line| { - staged_diff_text.extend_from_slice(line.content()); + write_diff_line(&mut staged_diff_text, line.origin(), line.content()); true }) .context("Failed to print staged diff")?; @@ -70,7 +92,7 @@ pub fn get_git_diff(repo_path: &Path) -> Result { let mut unstaged_diff_text = Vec::new(); unstaged_diff .print(git2::DiffFormat::Patch, |_delta, _hunk, line| { - unstaged_diff_text.extend_from_slice(line.content()); + write_diff_line(&mut unstaged_diff_text, line.origin(), line.content()); true }) .context("Failed to print unstaged diff")?; @@ -127,7 +149,7 @@ pub fn get_git_diff_between_branches( let mut diff_text = Vec::new(); diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| { - diff_text.extend_from_slice(line.content()); + write_diff_line(&mut diff_text, line.origin(), line.content()); true }) .context("Failed to print diff")?; @@ -266,3 +288,50 @@ pub fn get_git_log(repo_path: &Path, branch1: &str, branch2: &str) -> Result bool { repo.revparse_single(branch_name).is_ok() } + +#[cfg(test)] +mod tests { + use super::write_diff_line; + + #[test] + fn write_diff_line_synthesizes_sigils_for_content_lines() { + let mut buf = Vec::new(); + write_diff_line(&mut buf, '+', b"hello\n"); + assert_eq!(buf, b"+hello\n"); + + buf.clear(); + write_diff_line(&mut buf, '-', b"world\n"); + assert_eq!(buf, b"-world\n"); + + buf.clear(); + write_diff_line(&mut buf, ' ', b"ctx\n"); + assert_eq!(buf, b" ctx\n"); + } + + #[test] + fn write_diff_line_does_not_synthesize_sigil_for_headers_and_eof_markers() { + let mut buf = Vec::new(); + write_diff_line(&mut buf, 'F', b"diff --git a/x b/x\n"); + assert_eq!(buf, b"diff --git a/x b/x\n"); + + buf.clear(); + write_diff_line(&mut buf, '=', b"\\ No newline at end of file\n"); + assert_eq!(buf, b"\\ No newline at end of file\n"); + + buf.clear(); + write_diff_line(&mut buf, 'H', b"@@ -1,3 +1,4 @@\n"); + assert_eq!(buf, b"@@ -1,3 +1,4 @@\n"); + + buf.clear(); + write_diff_line(&mut buf, '>', b"old filename\n"); + assert_eq!(buf, b"old filename\n"); + + buf.clear(); + write_diff_line(&mut buf, '<', b"new filename\n"); + assert_eq!(buf, b"new filename\n"); + + buf.clear(); + write_diff_line(&mut buf, 'B', b"Binary files a/x and b/x differ\n"); + assert_eq!(buf, b"Binary files a/x and b/x differ\n"); + } +} From 7f7539ef2e92c4c23aa77060710d3a3396fbedcb Mon Sep 17 00:00:00 2001 From: linbingquan <695601626@qq.com> Date: Mon, 19 Oct 2026 16:53:43 +0800 Subject: [PATCH 2/2] test(core): move write_diff_line tests into git_test.rs and expose helper --- crates/code2prompt-core/src/git.rs | 49 +---------------------- crates/code2prompt-core/tests/git_test.rs | 46 ++++++++++++++++++++- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/crates/code2prompt-core/src/git.rs b/crates/code2prompt-core/src/git.rs index 4be2e50..1b1671c 100644 --- a/crates/code2prompt-core/src/git.rs +++ b/crates/code2prompt-core/src/git.rs @@ -22,7 +22,7 @@ use std::path::{Path, PathBuf}; /// /// `content()` is appended unconditionally, so binary notices and the /// `\ No newline at end of file` markers are preserved rather than dropped. -fn write_diff_line(buf: &mut Vec, origin: char, content: &[u8]) { +pub fn write_diff_line(buf: &mut Vec, origin: char, content: &[u8]) { match origin { '+' => buf.push(b'+'), '-' => buf.push(b'-'), @@ -288,50 +288,3 @@ pub fn get_git_log(repo_path: &Path, branch1: &str, branch2: &str) -> Result bool { repo.revparse_single(branch_name).is_ok() } - -#[cfg(test)] -mod tests { - use super::write_diff_line; - - #[test] - fn write_diff_line_synthesizes_sigils_for_content_lines() { - let mut buf = Vec::new(); - write_diff_line(&mut buf, '+', b"hello\n"); - assert_eq!(buf, b"+hello\n"); - - buf.clear(); - write_diff_line(&mut buf, '-', b"world\n"); - assert_eq!(buf, b"-world\n"); - - buf.clear(); - write_diff_line(&mut buf, ' ', b"ctx\n"); - assert_eq!(buf, b" ctx\n"); - } - - #[test] - fn write_diff_line_does_not_synthesize_sigil_for_headers_and_eof_markers() { - let mut buf = Vec::new(); - write_diff_line(&mut buf, 'F', b"diff --git a/x b/x\n"); - assert_eq!(buf, b"diff --git a/x b/x\n"); - - buf.clear(); - write_diff_line(&mut buf, '=', b"\\ No newline at end of file\n"); - assert_eq!(buf, b"\\ No newline at end of file\n"); - - buf.clear(); - write_diff_line(&mut buf, 'H', b"@@ -1,3 +1,4 @@\n"); - assert_eq!(buf, b"@@ -1,3 +1,4 @@\n"); - - buf.clear(); - write_diff_line(&mut buf, '>', b"old filename\n"); - assert_eq!(buf, b"old filename\n"); - - buf.clear(); - write_diff_line(&mut buf, '<', b"new filename\n"); - assert_eq!(buf, b"new filename\n"); - - buf.clear(); - write_diff_line(&mut buf, 'B', b"Binary files a/x and b/x differ\n"); - assert_eq!(buf, b"Binary files a/x and b/x differ\n"); - } -} diff --git a/crates/code2prompt-core/tests/git_test.rs b/crates/code2prompt-core/tests/git_test.rs index fa3b731..2bd4b9d 100644 --- a/crates/code2prompt-core/tests/git_test.rs +++ b/crates/code2prompt-core/tests/git_test.rs @@ -1,4 +1,6 @@ -use code2prompt_core::git::{get_git_diff, get_git_diff_between_branches, get_git_log}; +use code2prompt_core::git::{ + get_git_diff, get_git_diff_between_branches, get_git_log, write_diff_line, +}; #[cfg(test)] mod tests { @@ -385,4 +387,46 @@ mod tests { .to_string() .contains("Branch nonexistent_reference doesn't exist!")); } + + #[test] + fn write_diff_line_synthesizes_sigils_for_content_lines() { + let mut buf = Vec::new(); + write_diff_line(&mut buf, '+', b"hello\n"); + assert_eq!(buf, b"+hello\n"); + + buf.clear(); + write_diff_line(&mut buf, '-', b"world\n"); + assert_eq!(buf, b"-world\n"); + + buf.clear(); + write_diff_line(&mut buf, ' ', b"ctx\n"); + assert_eq!(buf, b" ctx\n"); + } + + #[test] + fn write_diff_line_does_not_synthesize_sigil_for_headers_and_eof_markers() { + let mut buf = Vec::new(); + write_diff_line(&mut buf, 'F', b"diff --git a/x b/x\n"); + assert_eq!(buf, b"diff --git a/x b/x\n"); + + buf.clear(); + write_diff_line(&mut buf, '=', b"\\ No newline at end of file\n"); + assert_eq!(buf, b"\\ No newline at end of file\n"); + + buf.clear(); + write_diff_line(&mut buf, 'H', b"@@ -1,3 +1,4 @@\n"); + assert_eq!(buf, b"@@ -1,3 +1,4 @@\n"); + + buf.clear(); + write_diff_line(&mut buf, '>', b"old filename\n"); + assert_eq!(buf, b"old filename\n"); + + buf.clear(); + write_diff_line(&mut buf, '<', b"new filename\n"); + assert_eq!(buf, b"new filename\n"); + + buf.clear(); + write_diff_line(&mut buf, 'B', b"Binary files a/x and b/x differ\n"); + assert_eq!(buf, b"Binary files a/x and b/x differ\n"); + } }