66
77use std:: ops:: Deref ;
88
9- use anyhow:: Context ;
9+ use anyhow:: { Context , Result } ;
1010use git2:: MergeOptions ;
1111use gitbutler_commit:: commit_ext:: CommitExt ;
12+ use gitbutler_oxidize:: git2_to_gix_object_id;
1213
1314#[ derive( Default ) ]
1415pub enum ConflictedTreeKey {
@@ -40,30 +41,55 @@ impl Deref for ConflictedTreeKey {
4041}
4142
4243pub trait RepositoryExt {
44+ /// Cherry-pick, but understands GitButler conflicted states.
45+ ///
46+ /// This method *should* always be used in favour of native functions.
4347 fn cherry_pick_gitbutler (
4448 & self ,
4549 head : & git2:: Commit ,
4650 to_rebase : & git2:: Commit ,
4751 merge_options : Option < & MergeOptions > ,
48- ) -> Result < git2:: Index , anyhow:: Error > ;
49- fn find_real_tree (
50- & self ,
51- commit : & git2:: Commit ,
52+ ) -> Result < git2:: Index > ;
53+
54+ /// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state
55+ /// or the tree according to `side` if it is conflicted.
56+ ///
57+ /// Unless you want to find a particular side, you likely want to pass Default::default()
58+ /// as the [`side`](ConflictedTreeKey) which will give the automatically resolved resolution
59+ fn find_real_tree ( & self , commit : & git2:: Commit , side : ConflictedTreeKey ) -> Result < git2:: Tree > ;
60+ }
61+
62+ pub trait GixRepositoryExt {
63+ /// Cherry-pick, but understands GitButler conflicted states.
64+ /// Note that it will automatically resolve conflicts in *our* favor, so any tree produced
65+ /// here can be used.
66+ ///
67+ /// This method *should* always be used in favour of native functions.
68+ fn cherry_pick_gitbutler < ' repo > (
69+ & ' repo self ,
70+ head : & git2:: Commit ,
71+ to_rebase : & git2:: Commit ,
72+ ) -> Result < gix:: merge:: tree:: Outcome < ' repo > > ;
73+
74+ /// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state
75+ /// or the tree according to `side` if it is conflicted.
76+ ///
77+ /// Unless you want to find a particular side, you likely want to pass Default::default()
78+ /// as the [`side`](ConflictedTreeKey) which will give the automatically resolved resolution
79+ fn find_real_tree < ' repo > (
80+ & ' repo self ,
81+ commit_id : & gix:: oid ,
5282 side : ConflictedTreeKey ,
53- ) -> Result < git2 :: Tree , anyhow :: Error > ;
83+ ) -> Result < gix :: Id < ' repo > > ;
5484}
5585
5686impl 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
6187 fn cherry_pick_gitbutler (
6288 & self ,
6389 head : & git2:: Commit ,
6490 to_rebase : & git2:: Commit ,
6591 merge_options : Option < & MergeOptions > ,
66- ) -> Result < git2:: Index , anyhow :: Error > {
92+ ) -> Result < git2:: Index > {
6793 // we need to do a manual 3-way patch merge
6894 // find the base, which is the parent of to_rebase
6995 let base = if to_rebase. is_conflicted ( ) {
@@ -77,22 +103,13 @@ impl RepositoryExt for git2::Repository {
77103 // Get the auto-resolution
78104 let ours = self . find_real_tree ( head, Default :: default ( ) ) ?;
79105 // Get the original theirs
80- let thiers = self . find_real_tree ( to_rebase, ConflictedTreeKey :: Theirs ) ?;
106+ let theirs = self . find_real_tree ( to_rebase, ConflictedTreeKey :: Theirs ) ?;
81107
82- self . merge_trees ( & base, & ours, & thiers , merge_options)
108+ self . merge_trees ( & base, & ours, & theirs , merge_options)
83109 . context ( "failed to merge trees for cherry pick" )
84110 }
85111
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 > {
112+ fn find_real_tree ( & self , commit : & git2:: Commit , side : ConflictedTreeKey ) -> Result < git2:: Tree > {
96113 let tree = commit. tree ( ) ?;
97114 if commit. is_conflicted ( ) {
98115 let conflicted_side = tree
@@ -105,3 +122,64 @@ impl RepositoryExt for git2::Repository {
105122 }
106123 }
107124}
125+
126+ impl GixRepositoryExt for gix:: Repository {
127+ fn cherry_pick_gitbutler < ' repo > (
128+ & ' repo self ,
129+ head : & git2:: Commit ,
130+ to_rebase : & git2:: Commit ,
131+ ) -> Result < gix:: merge:: tree:: Outcome < ' repo > > {
132+ // we need to do a manual 3-way patch merge
133+ // find the base, which is the parent of to_rebase
134+ let base = if to_rebase. is_conflicted ( ) {
135+ // Use to_rebase's recorded base
136+ self . find_real_tree (
137+ & git2_to_gix_object_id ( to_rebase. id ( ) ) ,
138+ ConflictedTreeKey :: Base ,
139+ ) ?
140+ } else {
141+ let base_commit = to_rebase. parent ( 0 ) ?;
142+ // Use the parent's auto-resolution
143+ self . find_real_tree ( & git2_to_gix_object_id ( base_commit. id ( ) ) , Default :: default ( ) ) ?
144+ } ;
145+ // Get the auto-resolution
146+ let ours = self . find_real_tree ( & git2_to_gix_object_id ( head. id ( ) ) , Default :: default ( ) ) ?;
147+ // Get the original theirs
148+ let theirs = self . find_real_tree (
149+ & git2_to_gix_object_id ( to_rebase. id ( ) ) ,
150+ ConflictedTreeKey :: Theirs ,
151+ ) ?;
152+
153+ self . merge_trees (
154+ base,
155+ ours,
156+ theirs,
157+ gix:: merge:: blob:: builtin_driver:: text:: Labels {
158+ ancestor : Some ( "base" . into ( ) ) ,
159+ current : Some ( "ours" . into ( ) ) ,
160+ other : Some ( "theirs" . into ( ) ) ,
161+ } ,
162+ self . tree_merge_options ( ) ?
163+ . with_tree_favor ( Some ( gix:: merge:: tree:: TreeFavor :: Ours ) )
164+ . with_file_favor ( Some ( gix:: merge:: tree:: FileFavor :: Ours ) ) ,
165+ )
166+ . context ( "failed to merge trees for cherry pick" )
167+ }
168+
169+ fn find_real_tree < ' repo > (
170+ & ' repo self ,
171+ commit_id : & gix:: oid ,
172+ side : ConflictedTreeKey ,
173+ ) -> Result < gix:: Id < ' repo > > {
174+ let commit = self . find_commit ( commit_id) ?;
175+ Ok ( if commit. is_conflicted ( ) {
176+ let tree = commit. tree ( ) ?;
177+ let conflicted_side = tree
178+ . find_entry ( & * side)
179+ . context ( "Failed to get conflicted side of commit" ) ?;
180+ conflicted_side. id ( )
181+ } else {
182+ commit. tree_id ( ) ?
183+ } )
184+ }
185+ }
0 commit comments