1- use std:: {
2- collections:: HashSet ,
3- path:: { Path , PathBuf } ,
4- } ;
1+ use std:: { collections:: HashSet , path:: PathBuf } ;
52
63use crate :: { LogUntil , RepositoryExt as _} ;
74use anyhow:: { Context , Result } ;
@@ -12,7 +9,7 @@ use gitbutler_commit::{
129 commit_ext:: CommitExt ,
1310 commit_headers:: { CommitHeadersV2 , HasCommitHeaders } ,
1411} ;
15- use gitbutler_oxidize:: gix_to_git2_oid;
12+ use gitbutler_oxidize:: { git2_to_gix_object_id , gix_to_git2_oid} ;
1613use serde:: { Deserialize , Serialize } ;
1714
1815/// cherry-pick based rebase, which handles empty commits
@@ -231,13 +228,13 @@ fn commit_conflicted_cherry_result<'repository>(
231228}
232229
233230fn extract_conflicted_files (
234- tree_id : gix:: Id < ' _ > ,
231+ merged_tree_id : gix:: Id < ' _ > ,
235232 merge_result : gix:: merge:: tree:: Outcome < ' _ > ,
236233 treat_as_unresolved : gix:: merge:: tree:: TreatAsUnresolved ,
237234) -> Result < ConflictEntries > {
238235 use gix:: index:: entry:: Stage ;
239- let repo = tree_id . repo ;
240- let mut index = repo. index_from_tree ( & tree_id ) ?;
236+ let repo = merged_tree_id . repo ;
237+ let mut index = repo. index_from_tree ( & merged_tree_id ) ?;
241238 merge_result. index_changed_after_applying_conflicts (
242239 & mut index,
243240 treat_as_unresolved,
@@ -307,7 +304,7 @@ fn extract_conflicted_files(
307304///
308305/// The `target_commit` and `incoming_commit` must have a common ancestor.
309306///
310- /// If there is a merge conflict, the
307+ /// If there is a merge conflict, we will **auto-resolve** to favor *our* side, the `incoming_commit`.
311308pub fn gitbutler_merge_commits < ' repository > (
312309 repository : & ' repository git2:: Repository ,
313310 target_commit : git2:: Commit < ' repository > ,
@@ -326,17 +323,28 @@ pub fn gitbutler_merge_commits<'repository>(
326323
327324 let target_merge_tree = repository. find_real_tree ( & target_commit, Default :: default ( ) ) ?;
328325 let incoming_merge_tree = repository. find_real_tree ( & incoming_commit, Default :: default ( ) ) ?;
329- let mut merged_index =
330- repository. merge_trees ( & base_tree, & incoming_merge_tree, & target_merge_tree, None ) ?;
326+ let gix_repo = gix_repository_for_merging ( repository. path ( ) ) ?;
327+ let mut merge_result = gix_repo. merge_trees (
328+ git2_to_gix_object_id ( base_tree. id ( ) ) ,
329+ git2_to_gix_object_id ( incoming_merge_tree. id ( ) ) ,
330+ git2_to_gix_object_id ( target_merge_tree. id ( ) ) ,
331+ gix:: merge:: blob:: builtin_driver:: text:: Labels {
332+ ancestor : Some ( "base" . into ( ) ) ,
333+ current : Some ( "ours" . into ( ) ) ,
334+ other : Some ( "theirs" . into ( ) ) ,
335+ } ,
336+ gix_repo
337+ . tree_merge_options ( ) ?
338+ . with_tree_favor ( Some ( gix:: merge:: tree:: TreeFavor :: Ours ) )
339+ . with_file_favor ( Some ( gix:: merge:: tree:: FileFavor :: Ours ) ) ,
340+ ) ?;
341+ let merged_tree_id = merge_result. tree . write ( ) ?;
331342
332343 let tree_oid;
333- let conflicted_files;
334-
335- if merged_index. has_conflicts ( ) {
336- conflicted_files = resolve_index ( repository, & mut merged_index) ?;
337-
338- // Index gets resolved from the `resolve_index` call above, so we can safly write it out
339- let resolved_tree_id = merged_index. write_tree_to ( repository) ?;
344+ let forced_resolution = gix:: merge:: tree:: TreatAsUnresolved :: forced_resolution ( ) ;
345+ let commit_headers = if merge_result. has_unresolved_conflicts ( forced_resolution) {
346+ let conflicted_files =
347+ extract_conflicted_files ( merged_tree_id, merge_result, forced_resolution) ?;
340348
341349 // convert files into a string and save as a blob
342350 let conflicted_files_string = toml:: to_string ( & conflicted_files) ?;
@@ -351,7 +359,7 @@ pub fn gitbutler_merge_commits<'repository>(
351359 tree_writer. insert ( & * ConflictedTreeKey :: Base , base_tree. id ( ) , 0o040000 ) ?;
352360 tree_writer. insert (
353361 & * ConflictedTreeKey :: AutoResolution ,
354- resolved_tree_id ,
362+ gix_to_git2_oid ( merged_tree_id ) ,
355363 0o040000 ,
356364 ) ?;
357365 tree_writer. insert (
@@ -367,27 +375,13 @@ pub fn gitbutler_merge_commits<'repository>(
367375 tree_writer. insert ( "README.txt" , readme_blob, 0o100644 ) ?;
368376
369377 tree_oid = tree_writer. write ( ) . context ( "failed to write tree" ) ?;
378+ conflicted_files. to_headers ( )
370379 } else {
371- conflicted_files = Default :: default ( ) ;
372- tree_oid = merged_index. write_tree_to ( repository) ?;
373- }
374-
375- let conflicted_file_count = conflicted_files. total_entries ( ) as u64 ;
376-
377- let commit_headers = if conflicted_file_count > 0 {
378- CommitHeadersV2 {
379- conflicted : Some ( conflicted_file_count) ,
380- ..Default :: default ( )
381- }
382- } else {
383- CommitHeadersV2 {
384- conflicted : None ,
385- ..Default :: default ( )
386- }
380+ tree_oid = gix_to_git2_oid ( merged_tree_id) ;
381+ CommitHeadersV2 :: default ( )
387382 } ;
388383
389384 let ( author, committer) = repository. signatures ( ) ?;
390-
391385 let commit_oid = crate :: RepositoryExt :: commit_with_signature (
392386 repository,
393387 None ,
@@ -453,209 +447,3 @@ impl ConflictEntries {
453447 }
454448 }
455449}
456-
457- /// Automatically resolves an index with a preferences for the "our" side
458- ///
459- /// Within our rebasing and merging logic, "their" is the commit that is getting
460- /// cherry picked, and "our" is the commit that it is getting cherry picked on
461- /// to.
462- ///
463- /// This means that if we experience a conflict, we drop the changes that are
464- /// in the commit that is getting cherry picked in favor of what came before it
465- fn resolve_index (
466- repository : & git2:: Repository ,
467- index : & mut git2:: Index ,
468- ) -> Result < ConflictEntries , anyhow:: Error > {
469- fn bytes_to_path ( path : & [ u8 ] ) -> Result < PathBuf > {
470- let path = std:: str:: from_utf8 ( path) ?;
471- Ok ( Path :: new ( path) . to_owned ( ) )
472- }
473-
474- let mut ancestor_entries = vec ! [ ] ;
475- let mut our_entries = vec ! [ ] ;
476- let mut their_entries = vec ! [ ] ;
477-
478- // Set the index on an in-memory repository
479- let in_memory_repository = repository. in_memory_repo ( ) ?;
480- in_memory_repository. set_index ( index) ?;
481-
482- let index_conflicts = index. conflicts ( ) ?. flatten ( ) . collect :: < Vec < _ > > ( ) ;
483-
484- for mut conflict in index_conflicts {
485- // There may be a case when there is an ancestor in the index without
486- // a "their" OR "our" side. This is probably caused by the same file
487- // getting renamed and modified in the two commits.
488- if let Some ( ancestor) = & conflict. ancestor {
489- let path = bytes_to_path ( & ancestor. path ) ?;
490- index. remove_path ( & path) ?;
491-
492- ancestor_entries. push ( path) ;
493- }
494-
495- if let ( Some ( their) , None ) = ( & conflict. their , & conflict. our ) {
496- // Their (the commit we're rebasing)'s change gets dropped
497- let their_path = bytes_to_path ( & their. path ) ?;
498- index. remove_path ( & their_path) ?;
499-
500- their_entries. push ( their_path) ;
501- } else if let ( None , Some ( our) ) = ( & conflict. their , & mut conflict. our ) {
502- // Our (the commit we're rebasing onto)'s gets kept
503- let blob = repository. find_blob ( our. id ) ?;
504- our. flags = 0 ; // For some unknown reason we need to set flags to 0
505- index. add_frombuffer ( our, blob. content ( ) ) ?;
506-
507- let our_path = bytes_to_path ( & our. path ) ?;
508-
509- our_entries. push ( our_path) ;
510- } else if let ( Some ( their) , Some ( our) ) = ( & conflict. their , & mut conflict. our ) {
511- // We keep our (the commit we're rebasing onto)'s side of the
512- // conflict
513- let their_path = bytes_to_path ( & their. path ) ?;
514- let blob = repository. find_blob ( our. id ) ?;
515-
516- index. remove_path ( & their_path) ?;
517- our. flags = 0 ; // For some unknown reason we need to set flags to 0
518- index. add_frombuffer ( our, blob. content ( ) ) ?;
519-
520- let our_path = bytes_to_path ( & our. path ) ?;
521-
522- their_entries. push ( their_path) ;
523- our_entries. push ( our_path) ;
524- }
525- }
526-
527- Ok ( ConflictEntries {
528- ancestor_entries,
529- our_entries,
530- their_entries,
531- } )
532- }
533-
534- #[ cfg( test) ]
535- mod test {
536- #[ cfg( test) ]
537- mod resolve_index {
538- use crate :: rebase:: resolve_index;
539- use gitbutler_testsupport:: testing_repository:: TestingRepository ;
540-
541- #[ test]
542- fn test_same_file_twice ( ) {
543- let test_repository = TestingRepository :: open ( ) ;
544-
545- // Make some commits
546- let a = test_repository. commit_tree ( None , & [ ( "foo.txt" , "a" ) ] ) ;
547- let b = test_repository. commit_tree ( None , & [ ( "foo.txt" , "b" ) ] ) ;
548- let c = test_repository. commit_tree ( None , & [ ( "foo.txt" , "c" ) ] ) ;
549- test_repository. commit_tree ( None , & [ ( "foo.txt" , "asdfasdf" ) ] ) ;
550-
551- // Merge the index
552- let mut index: git2:: Index = test_repository
553- . repository
554- . merge_trees (
555- & a. tree ( ) . unwrap ( ) , // Base
556- & b. tree ( ) . unwrap ( ) , // Ours
557- & c. tree ( ) . unwrap ( ) , // Theirs
558- None ,
559- )
560- . unwrap ( ) ;
561-
562- assert ! ( index. has_conflicts( ) ) ;
563-
564- // Call our index resolution function
565- resolve_index ( & test_repository. repository , & mut index) . unwrap ( ) ;
566-
567- // Ensure there are no conflicts
568- assert ! ( !index. has_conflicts( ) ) ;
569-
570- let tree = index. write_tree_to ( & test_repository. repository ) . unwrap ( ) ;
571- let tree: git2:: Tree = test_repository. repository . find_tree ( tree) . unwrap ( ) ;
572-
573- let blob = tree. get_name ( "foo.txt" ) . unwrap ( ) . id ( ) ; // We fail here to get the entry because the tree is empty
574- let blob: git2:: Blob = test_repository. repository . find_blob ( blob) . unwrap ( ) ;
575-
576- assert_eq ! ( blob. content( ) , b"b" )
577- }
578-
579- #[ test]
580- fn test_diverging_renames ( ) {
581- let test_repository = TestingRepository :: open ( ) ;
582-
583- // Make some commits
584- let a = test_repository. commit_tree ( None , & [ ( "foo.txt" , "a" ) ] ) ;
585- let b = test_repository. commit_tree ( None , & [ ( "bar.txt" , "a" ) ] ) ;
586- let c = test_repository. commit_tree ( None , & [ ( "baz.txt" , "a" ) ] ) ;
587- test_repository. commit_tree ( None , & [ ( "foo.txt" , "asdfasdf" ) ] ) ;
588-
589- // Merge the index
590- let mut index: git2:: Index = test_repository
591- . repository
592- . merge_trees (
593- & a. tree ( ) . unwrap ( ) , // Base
594- & b. tree ( ) . unwrap ( ) , // Ours
595- & c. tree ( ) . unwrap ( ) , // Theirs
596- None ,
597- )
598- . unwrap ( ) ;
599-
600- assert ! ( index. has_conflicts( ) ) ;
601-
602- // Call our index resolution function
603- resolve_index ( & test_repository. repository , & mut index) . unwrap ( ) ;
604-
605- // Ensure there are no conflicts
606- assert ! ( !index. has_conflicts( ) ) ;
607-
608- let tree = index. write_tree_to ( & test_repository. repository ) . unwrap ( ) ;
609- let tree: git2:: Tree = test_repository. repository . find_tree ( tree) . unwrap ( ) ;
610-
611- assert ! ( tree. get_name( "foo.txt" ) . is_none( ) ) ;
612- assert ! ( tree. get_name( "baz.txt" ) . is_none( ) ) ;
613-
614- let blob = tree. get_name ( "bar.txt" ) . unwrap ( ) . id ( ) ; // We fail here to get the entry because the tree is empty
615- let blob: git2:: Blob = test_repository. repository . find_blob ( blob) . unwrap ( ) ;
616-
617- assert_eq ! ( blob. content( ) , b"a" )
618- }
619-
620- #[ test]
621- fn test_converging_renames ( ) {
622- let test_repository = TestingRepository :: open ( ) ;
623-
624- // Make some commits
625- let a = test_repository. commit_tree ( None , & [ ( "foo.txt" , "a" ) , ( "bar.txt" , "b" ) ] ) ;
626- let b = test_repository. commit_tree ( None , & [ ( "baz.txt" , "a" ) ] ) ;
627- let c = test_repository. commit_tree ( None , & [ ( "baz.txt" , "b" ) ] ) ;
628- test_repository. commit_tree ( None , & [ ( "foo.txt" , "asdfasdf" ) ] ) ;
629-
630- // Merge the index
631- let mut index: git2:: Index = test_repository
632- . repository
633- . merge_trees (
634- & a. tree ( ) . unwrap ( ) , // Base
635- & b. tree ( ) . unwrap ( ) , // Ours
636- & c. tree ( ) . unwrap ( ) , // Theirs
637- None ,
638- )
639- . unwrap ( ) ;
640-
641- assert ! ( index. has_conflicts( ) ) ;
642-
643- // Call our index resolution function
644- resolve_index ( & test_repository. repository , & mut index) . unwrap ( ) ;
645-
646- // Ensure there are no conflicts
647- assert ! ( !index. has_conflicts( ) ) ;
648-
649- let tree = index. write_tree_to ( & test_repository. repository ) . unwrap ( ) ;
650- let tree: git2:: Tree = test_repository. repository . find_tree ( tree) . unwrap ( ) ;
651-
652- assert ! ( tree. get_name( "foo.txt" ) . is_none( ) ) ;
653- assert ! ( tree. get_name( "bar.txt" ) . is_none( ) ) ;
654-
655- let blob = tree. get_name ( "baz.txt" ) . unwrap ( ) . id ( ) ; // We fail here to get the entry because the tree is empty
656- let blob: git2:: Blob = test_repository. repository . find_blob ( blob) . unwrap ( ) ;
657-
658- assert_eq ! ( blob. content( ) , b"a" )
659- }
660- }
661- }
0 commit comments