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