Skip to content
This repository was archived by the owner on Mar 27, 2026. It is now read-only.

Commit d38eb94

Browse files
committed
compare with previous commit
1 parent e3128d7 commit d38eb94

7 files changed

Lines changed: 112 additions & 139 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "repodiff"
3-
version = "0.4.0"
3+
version = "0.4.1"
44
edition = "2024"
55
authors = ["christso"]
66
description = "A tool for generating optimized git diffs for LLM analysis"

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,17 @@ repodiff -b main -o output.txt
5454
repodiff -c abc1234 -d 5678def -o output.txt
5555
```
5656

57+
### Compare a Commit with its Parent (Previous) Commit
58+
59+
```bash
60+
repodiff -c abc1234 -p -o output.txt
61+
```
62+
5763
Parameters:
5864
* `-b`, `--branch`: Branch to compare with (e.g., `main` or `master`)
5965
* `-c`, `--commit1`: First commit hash
6066
* `-d`, `--commit2`: Second commit hash
67+
* `-p`, `--previous`: Compare the specified commit (via `-c`) with its parent commit
6168
* `-o`, `--output_file`: (Optional) Path to the output file. If not provided, the diff will be written to a default file in the system's temporary directory.
6269
* `-v`, `--version`: Display the current version of RepoDiff
6370
* `-h`, `--help`: Print help information

ai-workspace/core-prd.md

Lines changed: 4 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ RepoDiff generates a simplified and context-aware unified diff of a Git reposito
1313
* **Context Lines** Defines the context for including the surrounding signatures.
1414
3. **User-Friendly Output:** Generate a valid unified diff with clear placeholders (e.g., `⋮----`) indicating omitted code sections and an instructional header explaining the conventions.
1515
4. **Configurable**: Allow users to set the config via a json file.
16+
5. **Flexible Commit Comparison**: Support various ways to compare commits, including comparing a specific commit with its parent commit.
1617

1718
## 3. Example Configuration
1819

@@ -70,8 +71,8 @@ This section details the handling of C# (`*.cs`) files.
7071
* **Behavior Summary:**
7172

7273
1. **Changed Methods:**
73-
* If `include_method_body` is `true`: Include the entire body of each changed method
74-
* If `include_method_body` is `false`: Replace the body with `⋮----`
74+
* If `include_method_body` is `true`, Include the entire body of each changed method
75+
* If `include_method_body` is `false`, Replace the body with `⋮----`
7576

7677
2. **Contextual Methods:**
7778
* If `include_signatures` is `true`:
@@ -128,137 +129,4 @@ This section details the handling of C# (`*.cs`) files.
128129

129130
Include a header in the final diff explaining placeholders:
130131

131-
```
132-
NOTE: Some method bodies have been replaced with "⋮----" to improve clarity for code reviews and LLM analysis.
133-
134-
- The "⋮----" placeholder indicates that a method body has been omitted or truncated based on the configuration settings.
135-
- include_method_body: true, will cause the entire method to be included.
136-
- include_signatures: true, will include the signatures of methods that surround the changed method.
137-
```
138-
## 8. Output Format
139-
Generate a valid unified diff with adjusted hunk headers to reflect the included lines. Insert placeholder lines like `// ⋮----` (prefixed with a space as a context line) *within contextual methods* to indicate omitted code sections when the body exceeds `2 * context_lines` lines. Ensure that hunk headers accurately represent the line ranges shown, even when parts of method bodies are omitted, maintaining compatibility with unified diff parsers while clearly marking omitted code.
140-
141-
## 9. Pseudocode (Rust - Illustrative)
142-
143-
```rust
144-
// Simplified and illustrative - not a complete implementation
145-
146-
fn generate_single_pass_diff(commit1: &str, commit2: &str) -> Result<String> {
147-
GitOperations::run_git_diff(commit1, commit2, 999999) // Large unified context
148-
}
149-
150-
fn parse_unified_diff(diff_output: &str) -> Result<HashMap<String, Vec<Hunk>>> {
151-
DiffParser::parse(diff_output) // Parses into a structured representation
152-
}
153-
154-
fn post_process_files(patch_dict: &mut HashMap<String, Vec<Hunk>>, config: &Config) {
155-
for (filename, hunks) in patch_dict.iter_mut() {
156-
if let Some(rule) = config.find_matching_rule(filename) {
157-
if rule.file_pattern.ends_with(".cs") {
158-
apply_csharp_rules(hunks, rule);
159-
} else {
160-
apply_context_filter(hunks, rule.context_lines); // For other file types
161-
}
162-
}
163-
}
164-
}
165-
166-
fn apply_csharp_rules(hunks: &mut Vec<Hunk>, rule: &Rule) {
167-
// 1. Find changed methods
168-
let changed_methods = find_changed_methods(hunks);
169-
170-
// 2. Use a parser (Tree-sitter) to find *all* methods in the file
171-
let all_methods = find_all_methods(hunks);
172-
173-
// Include namespace and class declarations
174-
include_namespace_and_class_declarations(hunks);
175-
176-
for method in &all_methods {
177-
if changed_methods.contains(method) {
178-
// 3. Handle changed method body based on include_method_body
179-
if rule.include_method_body {
180-
include_full_method_body(hunks, method);
181-
} else {
182-
replace_method_body_with_placeholder(hunks, method);
183-
}
184-
} else if rule.include_signatures {
185-
// Check if this is a contextual method
186-
let mut is_contextual = false;
187-
for changed_method in &changed_methods{
188-
let context_range = calculate_context_range(changed_method, rule.context_lines);
189-
if is_method_within_context_range(method, &context_range) {
190-
is_contextual = true;
191-
break;
192-
}
193-
}
194-
if is_contextual {
195-
include_method_signature(hunks, method, true); // Mark as context
196-
// Handle contextual method body (new logic)
197-
let body_lines = get_method_body_lines(hunks, method); // Helper function
198-
if body_lines.len() <= 2 * rule.context_lines {
199-
// Include the entire body, marking each line as context.
200-
for line in body_lines {
201-
include_context_line(hunks, line); //Helper to mark with " "
202-
}
203-
} else {
204-
// Include first and last context_lines, with a placeholder.
205-
for line in body_lines.iter().take(rule.context_lines) {
206-
include_context_line(hunks, line.to_string());
207-
}
208-
include_context_line(hunks, "// ⋮----".to_string());
209-
for line in body_lines.iter().rev().take(rule.context_lines).rev() {
210-
include_context_line(hunks, line.to_string());
211-
}
212-
}
213-
}
214-
}
215-
}
216-
217-
// 4. Handle "other code"
218-
handle_other_code(hunks, rule.context_lines);
219-
220-
// 5. Adjust Hunk Headers: VERY IMPORTANT. After all modifications, adjust hunk headers.
221-
adjust_hunk_headers(hunks);
222-
}
223-
224-
fn main() -> Result<()> {
225-
let config = Config::load("config.json")?;
226-
let raw_diff = generate_single_pass_diff("commit1", "commit2")?;
227-
let mut patch_dict = parse_unified_diff(&raw_diff)?;
228-
post_process_files(&mut patch_dict, &config);
229-
230-
// Reconstruct the final unified diff output
231-
let final_output = DiffParser::reconstruct_patch(&patch_dict);
232-
println!("{}", final_output);
233-
Ok(())
234-
}
235-
236-
// Helper functions (placeholders - would need full implementation)
237-
fn find_changed_methods(hunks: &Vec<Hunk>) -> Vec<MethodInfo> { /* ... */ }
238-
fn find_all_methods(hunks: &Vec<Hunk>) -> Vec<MethodInfo> { /* ... */ } // Uses parser
239-
fn calculate_context_range(method: &MethodInfo, context_lines: usize) -> (usize, usize) { /* ... */ }
240-
fn is_method_within_context_range(method: &MethodInfo, range: &(usize, usize)) -> bool { /* ... */ }
241-
fn include_full_method_body(hunks: &mut Vec<Hunk>, method: &MethodInfo) { /* ... */ }
242-
fn replace_method_body_with_placeholder(hunks: &mut Vec<Hunk>, method: &MethodInfo) { /* ... */ }
243-
fn include_method_signature(hunks: &mut Vec<Hunk>, method: &MethodInfo, is_context: bool) { /* ... */ }
244-
fn include_namespace_and_class_declarations(hunks: &mut Vec<Hunk>) { /* ... */ }
245-
fn get_method_body_lines(hunks: &Vec<Hunk>, method: &MethodInfo) -> Vec<String> { /* ... */ }
246-
fn include_context_line(hunks: &mut Vec<Hunk>, line: String) { /* ... */ }
247-
fn handle_other_code(hunks: &mut Vec<Hunk>, context_lines: usize) { /* ... */ }
248-
fn adjust_hunk_headers(hunks: &mut Vec<Hunk>) {/* ... */ }
249-
fn apply_context_filter(hunks: &mut Vec<Hunk>, context_lines: usize) {/* ... */ } //Helper for non .cs files
250-
251-
// Struct to represent method information (example)
252-
struct MethodInfo {
253-
start_line: usize,
254-
end_line: usize,
255-
signature_line: usize,
256-
name: String,
257-
// ... other relevant data ...
258-
}
259-
```
260-
261-
## 10. Performance
262-
263-
* **Single Git Command:** Efficient for a moderate number of changed files (e.g., 20-100).
264-
* **In-Memory Post-Processing:** The Rust implementation should be performant, with the most significant overhead likely coming from parsing (especially with Tree-sitter). Efficient data structures and algorithms should be used.
132+
```

src/cli.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ pub struct Args {
2424
/// Compare the latest commit on the current branch to the latest common commit with another branch
2525
#[arg(short, long)]
2626
pub branch: Option<String>,
27+
28+
/// Compare the specified commit with its parent (previous) commit
29+
#[arg(short = 'p', long = "previous", requires = "commit1", conflicts_with_all = ["commit2", "branch"])]
30+
pub use_previous: bool,
2731
}
2832

2933
/// Main entry point for the CLI
@@ -47,10 +51,22 @@ pub fn run() -> Result<()> {
4751
&commit2[..12.min(commit2.len())]
4852
);
4953

54+
(commit1, commit2)
55+
} else if args.use_previous && args.commit1.is_some() {
56+
let commit2 = args.commit1.clone().unwrap();
57+
let commit1 = git_ops.get_previous_commit(&commit2)?;
58+
59+
// Print the commits being used for the comparison
60+
println!(
61+
"Comparing commit {} with its parent commit {}.",
62+
&commit2[..12.min(commit2.len())],
63+
&commit1[..12.min(commit1.len())]
64+
);
65+
5066
(commit1, commit2)
5167
} else {
5268
if args.commit1.is_none() || args.commit2.is_none() {
53-
eprintln!("You must either provide two commit hashes using --commit1 and --commit2, or use the -b option to compare against another branch.");
69+
eprintln!("You must either provide two commit hashes using --commit1 and --commit2, or use the -b option to compare against another branch, or use -p with -c to compare with the previous commit.");
5470
process::exit(1);
5571
}
5672

src/utils/git_operations.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,30 @@ impl GitOperations {
8686

8787
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
8888
}
89+
90+
/// Get the previous commit of a given commit hash
91+
///
92+
/// # Arguments
93+
///
94+
/// * `commit` - The commit hash to get the previous commit for
95+
///
96+
/// # Returns
97+
///
98+
/// The hash of the previous commit
99+
pub fn get_previous_commit(&self, commit: &str) -> Result<String> {
100+
let output = Command::new("git")
101+
.args(["rev-parse", &format!("{}^1", commit)])
102+
.output()
103+
.map_err(|e| RepoDiffError::GitError(format!("Failed to get previous commit for '{}': {}", commit, e)))?;
104+
105+
if !output.status.success() {
106+
return Err(RepoDiffError::GitError(format!(
107+
"Failed to get previous commit for '{}': {}",
108+
commit,
109+
String::from_utf8_lossy(&output.stderr)
110+
)));
111+
}
112+
113+
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
114+
}
89115
}

tests/git_operations_test.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,60 @@ fn test_get_latest_common_commit_with_branch() {
210210

211211
// The common ancestor should be the initial commit
212212
assert_eq!(ancestor, initial_commit);
213+
}
214+
215+
#[test]
216+
#[ignore] // Ignore by default as it requires git to be installed
217+
fn test_get_previous_commit() {
218+
let temp_dir = setup_test_repo();
219+
let repo_path = temp_dir.path();
220+
221+
// Get the initial commit hash
222+
let output = Command::new("git")
223+
.args(["rev-parse", "HEAD"])
224+
.current_dir(repo_path)
225+
.output()
226+
.expect("Failed to get commit hash");
227+
228+
let initial_commit = String::from_utf8_lossy(&output.stdout).trim().to_string();
229+
230+
// Modify the file and create a new commit
231+
let file_path = repo_path.join("file1.txt");
232+
fs::write(&file_path, "Modified content").expect("Failed to modify file");
233+
234+
Command::new("git")
235+
.args(["add", "file1.txt"])
236+
.current_dir(repo_path)
237+
.output()
238+
.expect("Failed to add modified file");
239+
240+
Command::new("git")
241+
.args(["commit", "-m", "Second commit"])
242+
.current_dir(repo_path)
243+
.output()
244+
.expect("Failed to commit modified file");
245+
246+
// Get the second commit hash
247+
let output = Command::new("git")
248+
.args(["rev-parse", "HEAD"])
249+
.current_dir(repo_path)
250+
.output()
251+
.expect("Failed to get second commit hash");
252+
253+
let second_commit = String::from_utf8_lossy(&output.stdout).trim().to_string();
254+
255+
// Test the get_previous_commit function
256+
let git_operations = GitOperations::new();
257+
258+
// Change to the repo directory for the test
259+
let current_dir = std::env::current_dir().unwrap();
260+
std::env::set_current_dir(repo_path).unwrap();
261+
262+
let previous_commit = git_operations.get_previous_commit(&second_commit).unwrap();
263+
264+
// Change back to the original directory
265+
std::env::set_current_dir(current_dir).unwrap();
266+
267+
// The previous commit should be the initial commit
268+
assert_eq!(previous_commit, initial_commit);
213269
}

0 commit comments

Comments
 (0)