Skip to content

Commit 74f3365

Browse files
committed
Test if topology order is violated
In case of a diamond graph where the common ancestor has a newer date than some of its children, the walk order of the current code will violate topology order.
1 parent b2a655e commit 74f3365

1 file changed

Lines changed: 108 additions & 1 deletion

File tree

asyncgit/src/sync/branch/merge_commit.rs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ mod test {
101101
branch_compare_upstream,
102102
remotes::{fetch, push::push_branch},
103103
tests::{
104-
debug_cmd_print, get_commit_ids, repo_clone,
104+
debug_cmd_print, get_commit_ids, repo_clone, repo_init,
105105
repo_init_bare, write_commit_file, write_commit_file_at,
106106
},
107107
RepoState,
@@ -278,4 +278,111 @@ mod test {
278278
let commits = get_commit_ids(&clone1, 10);
279279
assert_eq!(commits.len(), 1);
280280
}
281+
282+
/// Verify that the walker preserves topology order. This means that
283+
/// no parent is visited before any of its children.
284+
#[test]
285+
fn test_topology_order() {
286+
/* Test case
287+
a diamon shaped graph, where the common ancestor is younger than
288+
one of its children.
289+
290+
GP--P1--M
291+
\ /
292+
--P2
293+
294+
*/
295+
296+
let (_repo_dir, repo) = repo_init().unwrap();
297+
298+
// 1. Grandparent (GP) - Newest
299+
let gp = write_commit_file_at(
300+
&repo,
301+
"gp.txt",
302+
"gp",
303+
"gp",
304+
Time::new(1000, 0),
305+
);
306+
307+
// 2. Parent 1 (P1) - Older
308+
let p1 = write_commit_file_at(
309+
&repo,
310+
"p1.txt",
311+
"p1",
312+
"p1",
313+
Time::new(500, 0),
314+
);
315+
316+
// 3. Parent 2 (P2) - Older (diverging from GP)
317+
// Reset HEAD to GP so P2 becomes a child of GP
318+
repo.reset(
319+
repo.find_object(gp.into(), None)
320+
.unwrap()
321+
.as_commit()
322+
.unwrap()
323+
.as_object(),
324+
git2::ResetType::Hard,
325+
None,
326+
)
327+
.unwrap();
328+
let p2 = write_commit_file_at(
329+
&repo,
330+
"p2.txt",
331+
"p2",
332+
"p2",
333+
Time::new(400, 0),
334+
);
335+
336+
// 4. Merge commit (M) - The starting point of our walk
337+
// The heap now contains [p1, p2].
338+
// If we pop p1, we add gp. The heap is [gp, p2].
339+
// Because gp(1000) > p2(400), the walker returns gp before p2.
340+
// This is a violation: p2 is a child of gp and must be visited first.
341+
let p1_commit = repo.find_commit(p1.into()).unwrap();
342+
let p2_commit = repo.find_commit(p2.into()).unwrap();
343+
let tree = repo
344+
.find_tree(repo.index().unwrap().write_tree().unwrap())
345+
.unwrap();
346+
let sig = repo.signature().unwrap();
347+
let m = repo
348+
.commit(
349+
Some("HEAD"),
350+
&sig,
351+
&sig,
352+
"Merge p1 into p2",
353+
&tree,
354+
&[&p2_commit, &p1_commit],
355+
)
356+
.unwrap();
357+
let m = CommitId::new(m);
358+
359+
// Expected Topological Order: [M, P1, P2, GP] or [M, P2, P1, GP]
360+
// Actual Defective Order: [M, P1, GP, P2]
361+
// (GP jumps ahead of P2 because 1000 > 400)
362+
363+
let commits = get_commit_ids(&repo, 14);
364+
for (i, id) in commits.iter().enumerate() {
365+
println!("DEBUG: commits[{}] = {:?}", i, id);
366+
// Print the message of the commit to identify it
367+
let repo_path = &repo.path().to_path_buf().into();
368+
let details =
369+
crate::sync::get_commit_details(repo_path, *id)
370+
.unwrap();
371+
println!(
372+
"DEBUG: Message: {:?}",
373+
details.message.map(|m| m.combine())
374+
);
375+
}
376+
println!("DEBUG: Expected M is {:?}", m);
377+
println!("DEBUG: P1 is {:?}", &p1);
378+
println!("DEBUG: P2 is {:?}", &p2);
379+
println!("DEBUG: GP is {:?}", &gp);
380+
assert_eq!(commits[0], m);
381+
assert!(commits.contains(&p1));
382+
assert!(commits.contains(&p2));
383+
assert_eq!(
384+
commits[3], gp,
385+
"Violation: Grandparent must be the last commit"
386+
);
387+
}
281388
}

0 commit comments

Comments
 (0)