@@ -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