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
@@ -232,13 +229,13 @@ fn commit_conflicted_cherry_result<'repository>(
232229}
233230
234231fn extract_conflicted_files (
235- tree_id : gix:: Id < ' _ > ,
232+ merged_tree_id : gix:: Id < ' _ > ,
236233 merge_result : gix:: merge:: tree:: Outcome < ' _ > ,
237234 treat_as_unresolved : gix:: merge:: tree:: TreatAsUnresolved ,
238235) -> Result < ConflictEntries > {
239236 use gix:: index:: entry:: Stage ;
240- let repo = tree_id . repo ;
241- let mut index = repo. index_from_tree ( & tree_id ) ?;
237+ let repo = merged_tree_id . repo ;
238+ let mut index = repo. index_from_tree ( & merged_tree_id ) ?;
242239 merge_result. index_changed_after_applying_conflicts ( & mut index, treat_as_unresolved) ;
243240 let ( mut ancestor_entries, mut our_entries, mut their_entries) =
244241 ( Vec :: new ( ) , Vec :: new ( ) , Vec :: new ( ) ) ;
@@ -304,7 +301,7 @@ fn extract_conflicted_files(
304301///
305302/// The `target_commit` and `incoming_commit` must have a common ancestor.
306303///
307- /// If there is a merge conflict, the
304+ /// If there is a merge conflict, we will **auto-resolve** to favor *our* side, the `incoming_commit`.
308305pub fn gitbutler_merge_commits < ' repository > (
309306 repository : & ' repository git2:: Repository ,
310307 target_commit : git2:: Commit < ' repository > ,
@@ -323,17 +320,28 @@ pub fn gitbutler_merge_commits<'repository>(
323320
324321 let target_merge_tree = repository. find_real_tree ( & target_commit, Default :: default ( ) ) ?;
325322 let incoming_merge_tree = repository. find_real_tree ( & incoming_commit, Default :: default ( ) ) ?;
326- let mut merged_index =
327- repository. merge_trees ( & base_tree, & incoming_merge_tree, & target_merge_tree, None ) ?;
323+ let gix_repo = gix_repository_for_merging ( repository. path ( ) ) ?;
324+ let mut merge_result = gix_repo. merge_trees (
325+ git2_to_gix_object_id ( base_tree. id ( ) ) ,
326+ git2_to_gix_object_id ( incoming_merge_tree. id ( ) ) ,
327+ git2_to_gix_object_id ( target_merge_tree. id ( ) ) ,
328+ gix:: merge:: blob:: builtin_driver:: text:: Labels {
329+ ancestor : Some ( "base" . into ( ) ) ,
330+ current : Some ( "ours" . into ( ) ) ,
331+ other : Some ( "theirs" . into ( ) ) ,
332+ } ,
333+ gix_repo
334+ . tree_merge_options ( ) ?
335+ . with_tree_favor ( Some ( gix:: merge:: tree:: TreeFavor :: Ours ) )
336+ . with_file_favor ( Some ( gix:: merge:: tree:: FileFavor :: Ours ) ) ,
337+ ) ?;
338+ let merged_tree_id = merge_result. tree . write ( ) ?;
328339
329340 let tree_oid;
330- let conflicted_files;
331-
332- if merged_index. has_conflicts ( ) {
333- conflicted_files = resolve_index ( repository, & mut merged_index) ?;
334-
335- // Index gets resolved from the `resolve_index` call above, so we can safly write it out
336- let resolved_tree_id = merged_index. write_tree_to ( repository) ?;
341+ let forced_resolution = gix:: merge:: tree:: TreatAsUnresolved :: forced_resolution ( ) ;
342+ let commit_headers = if merge_result. has_unresolved_conflicts ( forced_resolution) {
343+ let conflicted_files =
344+ extract_conflicted_files ( merged_tree_id, merge_result, forced_resolution) ?;
337345
338346 // convert files into a string and save as a blob
339347 let conflicted_files_string = toml:: to_string ( & conflicted_files) ?;
@@ -348,7 +356,7 @@ pub fn gitbutler_merge_commits<'repository>(
348356 tree_writer. insert ( & * ConflictedTreeKey :: Base , base_tree. id ( ) , 0o040000 ) ?;
349357 tree_writer. insert (
350358 & * ConflictedTreeKey :: AutoResolution ,
351- resolved_tree_id ,
359+ gix_to_git2_oid ( merged_tree_id ) ,
352360 0o040000 ,
353361 ) ?;
354362 tree_writer. insert (
@@ -364,27 +372,13 @@ pub fn gitbutler_merge_commits<'repository>(
364372 tree_writer. insert ( "README.txt" , readme_blob, 0o100644 ) ?;
365373
366374 tree_oid = tree_writer. write ( ) . context ( "failed to write tree" ) ?;
375+ conflicted_files. to_headers ( )
367376 } else {
368- conflicted_files = Default :: default ( ) ;
369- tree_oid = merged_index. write_tree_to ( repository) ?;
370- }
371-
372- let conflicted_file_count = conflicted_files. total_entries ( ) as u64 ;
373-
374- let commit_headers = if conflicted_file_count > 0 {
375- CommitHeadersV2 {
376- conflicted : Some ( conflicted_file_count) ,
377- ..Default :: default ( )
378- }
379- } else {
380- CommitHeadersV2 {
381- conflicted : None ,
382- ..Default :: default ( )
383- }
377+ tree_oid = gix_to_git2_oid ( merged_tree_id) ;
378+ CommitHeadersV2 :: default ( )
384379 } ;
385380
386381 let ( author, committer) = repository. signatures ( ) ?;
387-
388382 let commit_oid = crate :: RepositoryExt :: commit_with_signature (
389383 repository,
390384 None ,
@@ -450,209 +444,3 @@ impl ConflictEntries {
450444 }
451445 }
452446}
453-
454- /// Automatically resolves an index with a preferences for the "our" side
455- ///
456- /// Within our rebasing and merging logic, "their" is the commit that is getting
457- /// cherry picked, and "our" is the commit that it is getting cherry picked on
458- /// to.
459- ///
460- /// This means that if we experience a conflict, we drop the changes that are
461- /// in the commit that is getting cherry picked in favor of what came before it
462- fn resolve_index (
463- repository : & git2:: Repository ,
464- index : & mut git2:: Index ,
465- ) -> Result < ConflictEntries , anyhow:: Error > {
466- fn bytes_to_path ( path : & [ u8 ] ) -> Result < PathBuf > {
467- let path = std:: str:: from_utf8 ( path) ?;
468- Ok ( Path :: new ( path) . to_owned ( ) )
469- }
470-
471- let mut ancestor_entries = vec ! [ ] ;
472- let mut our_entries = vec ! [ ] ;
473- let mut their_entries = vec ! [ ] ;
474-
475- // Set the index on an in-memory repository
476- let in_memory_repository = repository. in_memory_repo ( ) ?;
477- in_memory_repository. set_index ( index) ?;
478-
479- let index_conflicts = index. conflicts ( ) ?. flatten ( ) . collect :: < Vec < _ > > ( ) ;
480-
481- for mut conflict in index_conflicts {
482- // There may be a case when there is an ancestor in the index without
483- // a "their" OR "our" side. This is probably caused by the same file
484- // getting renamed and modified in the two commits.
485- if let Some ( ancestor) = & conflict. ancestor {
486- let path = bytes_to_path ( & ancestor. path ) ?;
487- index. remove_path ( & path) ?;
488-
489- ancestor_entries. push ( path) ;
490- }
491-
492- if let ( Some ( their) , None ) = ( & conflict. their , & conflict. our ) {
493- // Their (the commit we're rebasing)'s change gets dropped
494- let their_path = bytes_to_path ( & their. path ) ?;
495- index. remove_path ( & their_path) ?;
496-
497- their_entries. push ( their_path) ;
498- } else if let ( None , Some ( our) ) = ( & conflict. their , & mut conflict. our ) {
499- // Our (the commit we're rebasing onto)'s gets kept
500- let blob = repository. find_blob ( our. id ) ?;
501- our. flags = 0 ; // For some unknown reason we need to set flags to 0
502- index. add_frombuffer ( our, blob. content ( ) ) ?;
503-
504- let our_path = bytes_to_path ( & our. path ) ?;
505-
506- our_entries. push ( our_path) ;
507- } else if let ( Some ( their) , Some ( our) ) = ( & conflict. their , & mut conflict. our ) {
508- // We keep our (the commit we're rebasing onto)'s side of the
509- // conflict
510- let their_path = bytes_to_path ( & their. path ) ?;
511- let blob = repository. find_blob ( our. id ) ?;
512-
513- index. remove_path ( & their_path) ?;
514- our. flags = 0 ; // For some unknown reason we need to set flags to 0
515- index. add_frombuffer ( our, blob. content ( ) ) ?;
516-
517- let our_path = bytes_to_path ( & our. path ) ?;
518-
519- their_entries. push ( their_path) ;
520- our_entries. push ( our_path) ;
521- }
522- }
523-
524- Ok ( ConflictEntries {
525- ancestor_entries,
526- our_entries,
527- their_entries,
528- } )
529- }
530-
531- #[ cfg( test) ]
532- mod test {
533- #[ cfg( test) ]
534- mod resolve_index {
535- use crate :: rebase:: resolve_index;
536- use gitbutler_testsupport:: testing_repository:: TestingRepository ;
537-
538- #[ test]
539- fn test_same_file_twice ( ) {
540- let test_repository = TestingRepository :: open ( ) ;
541-
542- // Make some commits
543- let a = test_repository. commit_tree ( None , & [ ( "foo.txt" , "a" ) ] ) ;
544- let b = test_repository. commit_tree ( None , & [ ( "foo.txt" , "b" ) ] ) ;
545- let c = test_repository. commit_tree ( None , & [ ( "foo.txt" , "c" ) ] ) ;
546- test_repository. commit_tree ( None , & [ ( "foo.txt" , "asdfasdf" ) ] ) ;
547-
548- // Merge the index
549- let mut index: git2:: Index = test_repository
550- . repository
551- . merge_trees (
552- & a. tree ( ) . unwrap ( ) , // Base
553- & b. tree ( ) . unwrap ( ) , // Ours
554- & c. tree ( ) . unwrap ( ) , // Theirs
555- None ,
556- )
557- . unwrap ( ) ;
558-
559- assert ! ( index. has_conflicts( ) ) ;
560-
561- // Call our index resolution function
562- resolve_index ( & test_repository. repository , & mut index) . unwrap ( ) ;
563-
564- // Ensure there are no conflicts
565- assert ! ( !index. has_conflicts( ) ) ;
566-
567- let tree = index. write_tree_to ( & test_repository. repository ) . unwrap ( ) ;
568- let tree: git2:: Tree = test_repository. repository . find_tree ( tree) . unwrap ( ) ;
569-
570- let blob = tree. get_name ( "foo.txt" ) . unwrap ( ) . id ( ) ; // We fail here to get the entry because the tree is empty
571- let blob: git2:: Blob = test_repository. repository . find_blob ( blob) . unwrap ( ) ;
572-
573- assert_eq ! ( blob. content( ) , b"b" )
574- }
575-
576- #[ test]
577- fn test_diverging_renames ( ) {
578- let test_repository = TestingRepository :: open ( ) ;
579-
580- // Make some commits
581- let a = test_repository. commit_tree ( None , & [ ( "foo.txt" , "a" ) ] ) ;
582- let b = test_repository. commit_tree ( None , & [ ( "bar.txt" , "a" ) ] ) ;
583- let c = test_repository. commit_tree ( None , & [ ( "baz.txt" , "a" ) ] ) ;
584- test_repository. commit_tree ( None , & [ ( "foo.txt" , "asdfasdf" ) ] ) ;
585-
586- // Merge the index
587- let mut index: git2:: Index = test_repository
588- . repository
589- . merge_trees (
590- & a. tree ( ) . unwrap ( ) , // Base
591- & b. tree ( ) . unwrap ( ) , // Ours
592- & c. tree ( ) . unwrap ( ) , // Theirs
593- None ,
594- )
595- . unwrap ( ) ;
596-
597- assert ! ( index. has_conflicts( ) ) ;
598-
599- // Call our index resolution function
600- resolve_index ( & test_repository. repository , & mut index) . unwrap ( ) ;
601-
602- // Ensure there are no conflicts
603- assert ! ( !index. has_conflicts( ) ) ;
604-
605- let tree = index. write_tree_to ( & test_repository. repository ) . unwrap ( ) ;
606- let tree: git2:: Tree = test_repository. repository . find_tree ( tree) . unwrap ( ) ;
607-
608- assert ! ( tree. get_name( "foo.txt" ) . is_none( ) ) ;
609- assert ! ( tree. get_name( "baz.txt" ) . is_none( ) ) ;
610-
611- let blob = tree. get_name ( "bar.txt" ) . unwrap ( ) . id ( ) ; // We fail here to get the entry because the tree is empty
612- let blob: git2:: Blob = test_repository. repository . find_blob ( blob) . unwrap ( ) ;
613-
614- assert_eq ! ( blob. content( ) , b"a" )
615- }
616-
617- #[ test]
618- fn test_converging_renames ( ) {
619- let test_repository = TestingRepository :: open ( ) ;
620-
621- // Make some commits
622- let a = test_repository. commit_tree ( None , & [ ( "foo.txt" , "a" ) , ( "bar.txt" , "b" ) ] ) ;
623- let b = test_repository. commit_tree ( None , & [ ( "baz.txt" , "a" ) ] ) ;
624- let c = test_repository. commit_tree ( None , & [ ( "baz.txt" , "b" ) ] ) ;
625- test_repository. commit_tree ( None , & [ ( "foo.txt" , "asdfasdf" ) ] ) ;
626-
627- // Merge the index
628- let mut index: git2:: Index = test_repository
629- . repository
630- . merge_trees (
631- & a. tree ( ) . unwrap ( ) , // Base
632- & b. tree ( ) . unwrap ( ) , // Ours
633- & c. tree ( ) . unwrap ( ) , // Theirs
634- None ,
635- )
636- . unwrap ( ) ;
637-
638- assert ! ( index. has_conflicts( ) ) ;
639-
640- // Call our index resolution function
641- resolve_index ( & test_repository. repository , & mut index) . unwrap ( ) ;
642-
643- // Ensure there are no conflicts
644- assert ! ( !index. has_conflicts( ) ) ;
645-
646- let tree = index. write_tree_to ( & test_repository. repository ) . unwrap ( ) ;
647- let tree: git2:: Tree = test_repository. repository . find_tree ( tree) . unwrap ( ) ;
648-
649- assert ! ( tree. get_name( "foo.txt" ) . is_none( ) ) ;
650- assert ! ( tree. get_name( "bar.txt" ) . is_none( ) ) ;
651-
652- let blob = tree. get_name ( "baz.txt" ) . unwrap ( ) . id ( ) ; // We fail here to get the entry because the tree is empty
653- let blob: git2:: Blob = test_repository. repository . find_blob ( blob) . unwrap ( ) ;
654-
655- assert_eq ! ( blob. content( ) , b"a" )
656- }
657- }
658- }
0 commit comments