1- // tree_writer.insert(".conflict-side-0", side0.id(), 0o040000)?;
2- // tree_writer.insert(".conflict-side-1", side1.id(), 0o040000)?;
3- // tree_writer.insert(".conflict-base-0", base_tree.id(), 0o040000)?;
4- // tree_writer.insert(".auto-resolution", resolved_tree_id, 0o040000)?;
5- // tree_writer.insert(".conflict-files", conflicted_files_blob, 0o100644)?;
6-
71use std:: ops:: Deref ;
82
9- use anyhow:: Context ;
3+ use anyhow:: { Context , Result } ;
104use git2:: MergeOptions ;
115use gitbutler_commit:: commit_ext:: CommitExt ;
6+ use gitbutler_oxidize:: git2_to_gix_object_id;
127
138#[ derive( Default ) ]
149pub enum ConflictedTreeKey {
@@ -40,30 +35,55 @@ impl Deref for ConflictedTreeKey {
4035}
4136
4237pub trait RepositoryExt {
38+ /// Cherry-pick, but understands GitButler conflicted states.
39+ ///
40+ /// This method *should* always be used in favour of native functions.
4341 fn cherry_pick_gitbutler (
4442 & self ,
4543 head : & git2:: Commit ,
4644 to_rebase : & git2:: Commit ,
4745 merge_options : Option < & MergeOptions > ,
48- ) -> Result < git2:: Index , anyhow:: Error > ;
49- fn find_real_tree (
50- & self ,
51- commit : & git2:: Commit ,
46+ ) -> Result < git2:: Index > ;
47+
48+ /// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state
49+ /// or the tree according to `side` if it is conflicted.
50+ ///
51+ /// Unless you want to find a particular side, you likely want to pass Default::default()
52+ /// as the [`side`](ConflictedTreeKey) which will give the automatically resolved resolution
53+ fn find_real_tree ( & self , commit : & git2:: Commit , side : ConflictedTreeKey ) -> Result < git2:: Tree > ;
54+ }
55+
56+ pub trait GixRepositoryExt {
57+ /// Cherry-pick, but understands GitButler conflicted states.
58+ /// Note that it will automatically resolve conflicts in *our* favor, so any tree produced
59+ /// here can be used.
60+ ///
61+ /// This method *should* always be used in favour of native functions.
62+ fn cherry_pick_gitbutler < ' repo > (
63+ & ' repo self ,
64+ head : & git2:: Commit ,
65+ to_rebase : & git2:: Commit ,
66+ ) -> Result < gix:: merge:: tree:: Outcome < ' repo > > ;
67+
68+ /// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state
69+ /// or the tree according to `side` if it is conflicted.
70+ ///
71+ /// Unless you want to find a particular side, you likely want to pass Default::default()
72+ /// as the [`side`](ConflictedTreeKey) which will give the automatically resolved resolution
73+ fn find_real_tree < ' repo > (
74+ & ' repo self ,
75+ commit_id : & gix:: oid ,
5276 side : ConflictedTreeKey ,
53- ) -> Result < git2 :: Tree , anyhow :: Error > ;
77+ ) -> Result < gix :: Id < ' repo > > ;
5478}
5579
5680impl RepositoryExt for git2:: Repository {
57- /// cherry-pick, but understands GitButler conflicted states
58- ///
59- /// cherry_pick_gitbutler should always be used in favour of libgit2 or gitoxide
60- /// cherry pick functions
6181 fn cherry_pick_gitbutler (
6282 & self ,
6383 head : & git2:: Commit ,
6484 to_rebase : & git2:: Commit ,
6585 merge_options : Option < & MergeOptions > ,
66- ) -> Result < git2:: Index , anyhow :: Error > {
86+ ) -> Result < git2:: Index > {
6787 // we need to do a manual 3-way patch merge
6888 // find the base, which is the parent of to_rebase
6989 let base = if to_rebase. is_conflicted ( ) {
@@ -77,22 +97,13 @@ impl RepositoryExt for git2::Repository {
7797 // Get the auto-resolution
7898 let ours = self . find_real_tree ( head, Default :: default ( ) ) ?;
7999 // Get the original theirs
80- let thiers = self . find_real_tree ( to_rebase, ConflictedTreeKey :: Theirs ) ?;
100+ let theirs = self . find_real_tree ( to_rebase, ConflictedTreeKey :: Theirs ) ?;
81101
82- self . merge_trees ( & base, & ours, & thiers , merge_options)
102+ self . merge_trees ( & base, & ours, & theirs , merge_options)
83103 . context ( "failed to merge trees for cherry pick" )
84104 }
85105
86- /// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state
87- /// or the parent parent tree if it is in a conflicted state
88- ///
89- /// Unless you want to find a particular side, you likly want to pass Default::default()
90- /// as the ConfclitedTreeKey which will give the automatically resolved resolution
91- fn find_real_tree (
92- & self ,
93- commit : & git2:: Commit ,
94- side : ConflictedTreeKey ,
95- ) -> Result < git2:: Tree , anyhow:: Error > {
106+ fn find_real_tree ( & self , commit : & git2:: Commit , side : ConflictedTreeKey ) -> Result < git2:: Tree > {
96107 let tree = commit. tree ( ) ?;
97108 if commit. is_conflicted ( ) {
98109 let conflicted_side = tree
@@ -105,3 +116,64 @@ impl RepositoryExt for git2::Repository {
105116 }
106117 }
107118}
119+
120+ impl GixRepositoryExt for gix:: Repository {
121+ fn cherry_pick_gitbutler < ' repo > (
122+ & ' repo self ,
123+ head : & git2:: Commit ,
124+ to_rebase : & git2:: Commit ,
125+ ) -> Result < gix:: merge:: tree:: Outcome < ' repo > > {
126+ // we need to do a manual 3-way patch merge
127+ // find the base, which is the parent of to_rebase
128+ let base = if to_rebase. is_conflicted ( ) {
129+ // Use to_rebase's recorded base
130+ self . find_real_tree (
131+ & git2_to_gix_object_id ( to_rebase. id ( ) ) ,
132+ ConflictedTreeKey :: Base ,
133+ ) ?
134+ } else {
135+ let base_commit = to_rebase. parent ( 0 ) ?;
136+ // Use the parent's auto-resolution
137+ self . find_real_tree ( & git2_to_gix_object_id ( base_commit. id ( ) ) , Default :: default ( ) ) ?
138+ } ;
139+ // Get the auto-resolution
140+ let ours = self . find_real_tree ( & git2_to_gix_object_id ( head. id ( ) ) , Default :: default ( ) ) ?;
141+ // Get the original theirs
142+ let theirs = self . find_real_tree (
143+ & git2_to_gix_object_id ( to_rebase. id ( ) ) ,
144+ ConflictedTreeKey :: Theirs ,
145+ ) ?;
146+
147+ self . merge_trees (
148+ base,
149+ ours,
150+ theirs,
151+ gix:: merge:: blob:: builtin_driver:: text:: Labels {
152+ ancestor : Some ( "base" . into ( ) ) ,
153+ current : Some ( "ours" . into ( ) ) ,
154+ other : Some ( "theirs" . into ( ) ) ,
155+ } ,
156+ self . tree_merge_options ( ) ?
157+ . with_tree_favor ( Some ( gix:: merge:: tree:: TreeFavor :: Ours ) )
158+ . with_file_favor ( Some ( gix:: merge:: tree:: FileFavor :: Ours ) ) ,
159+ )
160+ . context ( "failed to merge trees for cherry pick" )
161+ }
162+
163+ fn find_real_tree < ' repo > (
164+ & ' repo self ,
165+ commit_id : & gix:: oid ,
166+ side : ConflictedTreeKey ,
167+ ) -> Result < gix:: Id < ' repo > > {
168+ let commit = self . find_commit ( commit_id) ?;
169+ Ok ( if commit. is_conflicted ( ) {
170+ let tree = commit. tree ( ) ?;
171+ let conflicted_side = tree
172+ . find_entry ( & * side)
173+ . context ( "Failed to get conflicted side of commit" ) ?;
174+ conflicted_side. id ( )
175+ } else {
176+ commit. tree_id ( ) ?
177+ } )
178+ }
179+ }
0 commit comments