-
Couldn't load subscription status.
- Fork 1
[Refactor] Extract diff parsing into src/diff module #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e272d7f
51ab821
4206c0f
0029022
b525f80
937db80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| //! Diff processing and parsing utilities. | ||
| //! | ||
| //! This module handles parsing git diffs into structured data | ||
| //! and provides utilities for working with diff content. | ||
|
|
||
| pub mod parser; | ||
| pub mod traits; | ||
|
|
||
| pub use parser::{parse_diff, ParsedFile}; | ||
| pub use traits::{DiffDeltaPath, FilePath, Utf8String}; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,359 @@ | ||||||||||||||||||||
| //! Git diff parsing utilities. | ||||||||||||||||||||
| use anyhow::Result; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// Represents a parsed file from a git diff | ||||||||||||||||||||
| #[derive(Debug, Clone)] | ||||||||||||||||||||
| pub struct ParsedFile { | ||||||||||||||||||||
| pub path: String, | ||||||||||||||||||||
| pub operation: String, | ||||||||||||||||||||
| pub diff_content: String | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// Extracts file path from diff header parts | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// Handles various git prefixes (a/, b/, c/, i/) and /dev/null for deleted files. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// # Arguments | ||||||||||||||||||||
| /// * `parts` - The whitespace-split parts from a "diff --git" line | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// # Returns | ||||||||||||||||||||
| /// * `Option<String>` - The extracted path without prefixes, or None if parsing fails | ||||||||||||||||||||
| fn extract_file_path_from_diff_parts(parts: &[&str]) -> Option<String> { | ||||||||||||||||||||
| if parts.len() < 4 { | ||||||||||||||||||||
| return None; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Helper to strip git prefixes (a/, b/, c/, i/) | ||||||||||||||||||||
| let strip_prefix = |s: &str| { | ||||||||||||||||||||
| s.trim_start_matches("a/") | ||||||||||||||||||||
| .trim_start_matches("b/") | ||||||||||||||||||||
| .trim_start_matches("c/") | ||||||||||||||||||||
| .trim_start_matches("i/") | ||||||||||||||||||||
| .to_string() | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let new_path = strip_prefix(parts[3]); | ||||||||||||||||||||
| let old_path = strip_prefix(parts[2]); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Prefer new path unless it's /dev/null (deleted file) | ||||||||||||||||||||
| Some(if new_path == "/dev/null" || new_path == "dev/null" { | ||||||||||||||||||||
| old_path | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| new_path | ||||||||||||||||||||
| }) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// Parse git diff into individual file changes. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// Handles various diff formats including: | ||||||||||||||||||||
| /// - Standard git diff output | ||||||||||||||||||||
| /// - Diffs with commit hashes | ||||||||||||||||||||
| /// - Diffs with various path prefixes (a/, b/, c/, i/) | ||||||||||||||||||||
| /// - Deleted files (/dev/null paths) | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// # Arguments | ||||||||||||||||||||
| /// * `diff_content` - Raw git diff text | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// # Returns | ||||||||||||||||||||
| /// * `Result<Vec<ParsedFile>>` - Parsed files or error | ||||||||||||||||||||
| pub fn parse_diff(diff_content: &str) -> Result<Vec<ParsedFile>> { | ||||||||||||||||||||
oleander marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
| let mut files = Vec::new(); | ||||||||||||||||||||
| let mut current_file: Option<ParsedFile> = None; | ||||||||||||||||||||
| let mut current_diff = String::new(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Debug output | ||||||||||||||||||||
| log::debug!("Parsing diff with {} lines", diff_content.lines().count()); | ||||||||||||||||||||
oleander marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| // Add more detailed logging for debugging | ||||||||||||||||||||
| if log::log_enabled!(log::Level::Debug) && !diff_content.is_empty() { | ||||||||||||||||||||
| // Make sure we truncate at a valid UTF-8 character boundary | ||||||||||||||||||||
| let preview = if diff_content.len() > 500 { | ||||||||||||||||||||
| let truncated_index = diff_content | ||||||||||||||||||||
| .char_indices() | ||||||||||||||||||||
| .take_while(|(i, _)| *i < 500) | ||||||||||||||||||||
| .last() | ||||||||||||||||||||
| .map(|(i, c)| i + c.len_utf8()) | ||||||||||||||||||||
| .unwrap_or(0); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| format!("{}... (truncated)", &diff_content[..truncated_index]) | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| diff_content.to_string() | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| log::debug!("Diff content preview: \n{preview}"); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Handle different diff formats | ||||||||||||||||||||
| let mut in_diff_section = false; | ||||||||||||||||||||
| let mut _commit_hash_line: Option<&str> = None; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // First scan to detect if this is a commit message with hash | ||||||||||||||||||||
| for line in diff_content.lines().take(3) { | ||||||||||||||||||||
| if line.len() >= 40 && line.chars().take(40).all(|c| c.is_ascii_hexdigit()) { | ||||||||||||||||||||
| _commit_hash_line = Some(line); | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
Comment on lines
+88
to
+97
|
||||||||||||||||||||
| let mut _commit_hash_line: Option<&str> = None; | |
| // First scan to detect if this is a commit message with hash | |
| for line in diff_content.lines().take(3) { | |
| if line.len() >= 40 && line.chars().take(40).all(|c| c.is_ascii_hexdigit()) { | |
| _commit_hash_line = Some(line); | |
| break; | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Since diff::mod.rs re-exports parse_diff, prefer use ai::diff::parse_diff; to decouple the example from internal module structure and rely on the intended public API surface.