Skip to content

Commit 894fedb

Browse files
fix(git): error on missing history parent (#214)
1 parent a2954ea commit 894fedb

1 file changed

Lines changed: 126 additions & 42 deletions

File tree

src/git/metadata.rs

Lines changed: 126 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,96 @@ impl GitMetadataBackend {
193193
}
194194
}
195195

196+
#[cfg(test)]
197+
mod tests {
198+
use super::*;
199+
use std::io::Write;
200+
use std::process::{Command, Stdio};
201+
use tempfile::TempDir;
202+
203+
fn run_git(repo_path: &Path, args: &[&str]) -> String {
204+
let output = Command::new("git")
205+
.args(args)
206+
.current_dir(repo_path)
207+
.output()
208+
.expect("failed to run git");
209+
assert!(
210+
output.status.success(),
211+
"git {:?} failed: stdout={} stderr={}",
212+
args,
213+
String::from_utf8_lossy(&output.stdout),
214+
String::from_utf8_lossy(&output.stderr)
215+
);
216+
String::from_utf8(output.stdout).expect("git output should be UTF-8")
217+
}
218+
219+
fn write_commit_object(repo_path: &Path, commit_content: &str) -> String {
220+
let mut child = Command::new("git")
221+
.args(["hash-object", "-t", "commit", "-w", "--stdin"])
222+
.current_dir(repo_path)
223+
.stdin(Stdio::piped())
224+
.stdout(Stdio::piped())
225+
.stderr(Stdio::piped())
226+
.spawn()
227+
.expect("failed to run git hash-object");
228+
child
229+
.stdin
230+
.as_mut()
231+
.expect("hash-object stdin")
232+
.write_all(commit_content.as_bytes())
233+
.expect("failed to write commit content");
234+
let output = child.wait_with_output().expect("failed to hash commit");
235+
assert!(
236+
output.status.success(),
237+
"git hash-object failed: stdout={} stderr={}",
238+
String::from_utf8_lossy(&output.stdout),
239+
String::from_utf8_lossy(&output.stderr)
240+
);
241+
String::from_utf8(output.stdout)
242+
.expect("object id should be UTF-8")
243+
.trim()
244+
.to_string()
245+
}
246+
247+
#[test]
248+
fn walk_history_reports_missing_parent_commit() {
249+
let temp_dir = TempDir::new().unwrap();
250+
gix::init(temp_dir.path()).unwrap();
251+
run_git(temp_dir.path(), &["config", "user.name", "Test User"]);
252+
run_git(
253+
temp_dir.path(),
254+
&["config", "user.email", "test@example.com"],
255+
);
256+
257+
std::fs::write(temp_dir.path().join("data.txt"), "base\n").unwrap();
258+
run_git(temp_dir.path(), &["add", "data.txt"]);
259+
run_git(temp_dir.path(), &["commit", "-m", "base"]);
260+
261+
let tree_id = run_git(temp_dir.path(), &["rev-parse", "HEAD^{tree}"])
262+
.trim()
263+
.to_string();
264+
let missing_parent = "1111111111111111111111111111111111111111";
265+
let bad_commit = write_commit_object(
266+
temp_dir.path(),
267+
&format!(
268+
"tree {tree_id}\nparent {missing_parent}\nauthor Test User <test@example.com> 0 +0000\ncommitter Test User <test@example.com> 0 +0000\n\nbad parent\n"
269+
),
270+
);
271+
run_git(temp_dir.path(), &["update-ref", "HEAD", &bad_commit]);
272+
273+
let repo = gix::open(temp_dir.path()).unwrap();
274+
let backend = GitMetadataBackend::new(repo);
275+
let err = match backend.walk_history(100) {
276+
Ok(history) => panic!("history with a missing parent must fail, got {history:?}"),
277+
Err(err) => err,
278+
};
279+
assert!(
280+
err.to_string().contains("parent") || err.to_string().contains("commit"),
281+
"unexpected error: {err}"
282+
);
283+
}
284+
}
285+
196286
impl MetadataBackend for GitMetadataBackend {
197287
// ── Path access ──────────────────────────────────────────────────
198288

@@ -417,48 +507,42 @@ impl MetadataBackend for GitMetadataBackend {
417507

418508
let rev_walk = self.repo.rev_walk([head_commit.id()]);
419509

420-
match rev_walk.all() {
421-
Ok(walk) => {
422-
for info in walk.take(limit).flatten() {
423-
if let Ok(commit_obj) = info.object() {
424-
if let Ok(commit_ref) = commit_obj.decode() {
425-
let author = match commit_ref.author() {
426-
Ok(sig) => sig,
427-
Err(_) => continue,
428-
};
429-
let committer = match commit_ref.committer() {
430-
Ok(sig) => sig,
431-
Err(_) => continue,
432-
};
433-
let timestamp = author.time().map(|t| t.seconds).unwrap_or(0);
434-
history.push(CommitInfo {
435-
id: commit_obj.id().into(),
436-
author: format!(
437-
"{} <{}>",
438-
String::from_utf8_lossy(author.name),
439-
String::from_utf8_lossy(author.email)
440-
),
441-
committer: format!(
442-
"{} <{}>",
443-
String::from_utf8_lossy(committer.name),
444-
String::from_utf8_lossy(committer.email)
445-
),
446-
message: String::from_utf8_lossy(commit_ref.message).to_string(),
447-
timestamp,
448-
});
449-
}
450-
}
451-
}
452-
}
453-
Err(_) => {
454-
history.push(CommitInfo {
455-
id: head_commit.id().into(),
456-
author: "Unknown".to_string(),
457-
committer: "Unknown".to_string(),
458-
message: "Commit".to_string(),
459-
timestamp: 0,
460-
});
461-
}
510+
let walk = rev_walk
511+
.all()
512+
.map_err(|e| GitKvError::GitObjectError(format!("Failed to walk history: {e}")))?;
513+
514+
for info in walk.take(limit) {
515+
let info = info.map_err(|e| {
516+
GitKvError::GitObjectError(format!("Failed to read commit history: {e}"))
517+
})?;
518+
let commit_obj = info.object().map_err(|e| {
519+
GitKvError::GitObjectError(format!("Failed to load history commit: {e}"))
520+
})?;
521+
let commit_ref = commit_obj.decode().map_err(|e| {
522+
GitKvError::GitObjectError(format!("Failed to decode history commit: {e}"))
523+
})?;
524+
let author = commit_ref.author().map_err(|e| {
525+
GitKvError::GitObjectError(format!("Failed to decode history author: {e}"))
526+
})?;
527+
let committer = commit_ref.committer().map_err(|e| {
528+
GitKvError::GitObjectError(format!("Failed to decode history committer: {e}"))
529+
})?;
530+
let timestamp = author.time().map(|t| t.seconds).unwrap_or(0);
531+
history.push(CommitInfo {
532+
id: commit_obj.id().into(),
533+
author: format!(
534+
"{} <{}>",
535+
String::from_utf8_lossy(author.name),
536+
String::from_utf8_lossy(author.email)
537+
),
538+
committer: format!(
539+
"{} <{}>",
540+
String::from_utf8_lossy(committer.name),
541+
String::from_utf8_lossy(committer.email)
542+
),
543+
message: String::from_utf8_lossy(commit_ref.message).to_string(),
544+
timestamp,
545+
});
462546
}
463547

464548
Ok(history)

0 commit comments

Comments
 (0)