22
33use std:: collections:: { HashMap , HashSet , VecDeque } ;
44
5- use crate :: {
6- ReferenceSpec ,
7- cherry_pick:: EmptyCommit ,
8- cherry_pick_one,
9- graph_rebase:: { Editor , Step , StepGraph , StepGraphIndex } ,
10- to_commit,
5+ use crate :: graph_rebase:: {
6+ Editor , Step , StepGraph , StepGraphIndex ,
7+ cherry_pick:: { CherryPickOutcome , cherry_pick} ,
8+ } ;
9+ use anyhow:: { Context , Result , bail} ;
10+ use gix:: refs:: {
11+ Target ,
12+ transaction:: { Change , LogChange , PreviousValue , RefEdit } ,
1113} ;
12- use anyhow:: { Context , Result } ;
1314use petgraph:: visit:: EdgeRef ;
1415
16+ /// Represents a successful rebase, and any valid, but potentially conflicting scenarios it had.
17+ #[ allow( unused) ]
18+ #[ derive( Debug , Clone ) ]
19+ pub struct SuccessfulRebase {
20+ /// A mapping of any commits that were rewritten as part of the rebase
21+ pub ( crate ) commit_mapping : HashMap < gix:: ObjectId , gix:: ObjectId > ,
22+ /// A mapping between the origional step graph and the new one
23+ pub ( crate ) graph_mapping : HashMap < StepGraphIndex , StepGraphIndex > ,
24+ /// Any reference edits that need to be commited as a result of the history
25+ /// rewrite
26+ pub ( crate ) ref_edits : Vec < RefEdit > ,
27+ /// The new step graph
28+ pub ( crate ) graph : StepGraph ,
29+ }
30+
1531/// Represents the rebase output and the varying degrees of success it had.
16- pub struct RebaseResult {
17- references : Vec < ReferenceSpec > ,
18- commit_map : HashMap < gix:: ObjectId , gix:: ObjectId > ,
32+ #[ derive( Debug , Clone ) ]
33+ pub enum RebaseOutcome {
34+ /// The rebase
35+ Success ( SuccessfulRebase ) ,
36+ /// The graph rebase failed because we encountered a situation where we
37+ /// couldn't merge bases.
38+ ///
39+ /// Holds the gix::ObjectId of the commit it failed to pick
40+ MergePickFailed ( gix:: ObjectId ) ,
41+ /// Symbolic reference was encountered. We should never be given these, and
42+ /// the sematics of how to work with them are currently unclear, so the
43+ /// rebase will be rejected if one is encountered.
44+ SymbolicRefEncountered ( gix:: refs:: FullName ) ,
1945}
2046
2147impl Editor {
2248 /// Perform the rebase
23- pub fn rebase ( & self , repo : & gix:: Repository ) -> Result < RebaseResult > {
49+ pub fn rebase ( & self , repo : & gix:: Repository ) -> Result < RebaseOutcome > {
2450 // First we want to get a list of nodes that can be reached by
2551 // traversing downwards from the heads that we care about.
2652 // Usually there would be just one "head" which is an index to access
2753 // the reference step for `gitbutler/workspace`, but there could be
2854 // multiple.
2955
30- let pick_mode = crate :: cherry_pick:: PickMode :: SkipIfNoop ;
31-
56+ let mut ref_edits = vec ! [ ] ;
3257 let steps_to_pick = order_steps_picking ( & self . graph , & self . heads ) ;
3358
3459 // A 1 to 1 mapping between the incoming graph and hte output graph
3560 let mut graph_mapping: HashMap < StepGraphIndex , StepGraphIndex > = HashMap :: new ( ) ;
3661 // The step graph with updated commit oids
3762 let mut output_graph = StepGraph :: new ( ) ;
63+ let mut commit_mapping = HashMap :: new ( ) ;
3864
3965 for step_idx in steps_to_pick {
4066 // Do the frikkin rebase man!
4167 let step = self . graph [ step_idx] . clone ( ) ;
4268 match step {
4369 Step :: Pick { id } => {
44- let commit = to_commit ( repo, id) ?;
4570 let graph_parents = collect_ordered_parents ( & self . graph , step_idx) ;
46-
47- match ( commit. parents . len ( ) , graph_parents. len ( ) ) {
48- ( 0 , 0 ) => {
49- let new_idx = output_graph. add_node ( step) ;
71+ let ontos = graph_parents
72+ . iter ( )
73+ . map ( |idx| {
74+ let Some ( new_idx) = graph_mapping. get ( idx) else {
75+ bail ! ( "A matching parent can't be found in the output graph" ) ;
76+ } ;
77+
78+ match output_graph[ * new_idx] {
79+ Step :: Pick { id } => Ok ( id) ,
80+ _ => bail ! ( "A parent in the output graph is not a pick" ) ,
81+ }
82+ } )
83+ . collect :: < Result < Vec < _ > > > ( ) ?;
84+
85+ let outcome = cherry_pick ( repo, id, & ontos) ?;
86+
87+ match outcome {
88+ CherryPickOutcome :: Commit ( new_id)
89+ | CherryPickOutcome :: ConflictedCommit ( new_id)
90+ | CherryPickOutcome :: Identity ( new_id) => {
91+ let new_idx = output_graph. add_node ( Step :: Pick { id : new_id } ) ;
5092 graph_mapping. insert ( step_idx, new_idx) ;
93+ if id != new_id {
94+ commit_mapping. insert ( id, new_id) ;
95+ }
5196 }
52- ( 1 , 0 ) => {
53- todo ! ( "1 to 0 cherry pick is not implemented yet" ) ;
97+ CherryPickOutcome :: FailedToMergeBases => {
98+ // Exit early - the rebase failed because it encountered a commit it couldn't pick
99+ return Ok ( RebaseOutcome :: MergePickFailed ( id) ) ;
54100 }
55- ( 0 , 1 ) => {
56- todo ! ( "0 to 1 cherry pick is not implemented yet" ) ;
57- }
58- ( 1 , 1 ) => {
59- let ( _, parent_id) = graph_parents. first ( ) . expect ( "Impossible" ) ;
60-
61- let new_commit = cherry_pick_one (
62- repo,
63- * parent_id,
64- id,
65- pick_mode,
66- EmptyCommit :: Keep ,
67- ) ?;
68-
69- let new_idx = output_graph. add_node ( Step :: Pick { id : new_commit } ) ;
70- graph_mapping. insert ( step_idx, new_idx) ;
71- }
72- ( _, _) => {
73- todo ! ( "N to >2 parents & >2 parents to N is not implemented yet" ) ;
101+ } ;
102+ }
103+ Step :: Reference { refname } => {
104+ let graph_parents = collect_ordered_parents ( & self . graph , step_idx) ;
105+ let first_parent_idx = graph_parents
106+ . first ( )
107+ . context ( "References should have at least one parent" ) ?;
108+ let Some ( new_idx) = graph_mapping. get ( first_parent_idx) else {
109+ bail ! ( "A matching parent can't be found in the output graph" ) ;
110+ } ;
111+
112+ let to_reference = match output_graph[ * new_idx] {
113+ Step :: Pick { id } => id,
114+ _ => bail ! ( "A parent in the output graph is not a pick" ) ,
115+ } ;
116+
117+ let reference = repo. try_find_reference ( & refname) ?;
118+
119+ if let Some ( reference) = reference {
120+ let target = reference. target ( ) ;
121+ match target {
122+ gix:: refs:: TargetRef :: Object ( id) => {
123+ if id != to_reference {
124+ ref_edits. push ( RefEdit {
125+ name : refname. clone ( ) ,
126+ change : Change :: Update {
127+ log : LogChange :: default ( ) ,
128+ expected : PreviousValue :: MustExistAndMatch (
129+ target. into ( ) ,
130+ ) ,
131+ new : Target :: Object ( to_reference) ,
132+ } ,
133+ deref : false ,
134+ } ) ;
135+ }
136+ }
137+ gix:: refs:: TargetRef :: Symbolic ( name) => {
138+ return Ok ( RebaseOutcome :: SymbolicRefEncountered ( name. to_owned ( ) ) ) ;
139+ }
74140 }
141+ } else {
142+ ref_edits. push ( RefEdit {
143+ name : refname. clone ( ) ,
144+ change : Change :: Update {
145+ log : LogChange :: default ( ) ,
146+ expected : PreviousValue :: MustNotExist ,
147+ new : Target :: Object ( to_reference) ,
148+ } ,
149+ deref : false ,
150+ } ) ;
75151 } ;
152+
153+ let new_idx = output_graph. add_node ( Step :: Reference { refname } ) ;
154+ graph_mapping. insert ( step_idx, new_idx) ;
155+ }
156+ Step :: None => {
157+ let new_idx = output_graph. add_node ( Step :: None ) ;
158+ graph_mapping. insert ( step_idx, new_idx) ;
76159 }
77- Step :: Reference { refname } => { }
78- Step :: None => { }
79160 } ;
161+
162+ // Find deleted references
163+ for reference in self . initial_references . iter ( ) {
164+ if !ref_edits
165+ . iter ( )
166+ . any ( |e| e. name . as_ref ( ) == reference. as_ref ( ) )
167+ {
168+ ref_edits. push ( RefEdit {
169+ name : reference. clone ( ) ,
170+ change : Change :: Delete {
171+ log : gix:: refs:: transaction:: RefLog :: AndReference ,
172+ expected : PreviousValue :: MustExist ,
173+ } ,
174+ deref : false ,
175+ } ) ;
176+ }
177+ }
80178 }
81179
82- todo ! ( )
180+ Ok ( RebaseOutcome :: Success ( SuccessfulRebase {
181+ ref_edits,
182+ commit_mapping,
183+ graph_mapping,
184+ graph : output_graph,
185+ } ) )
83186 }
84187}
85188
86189/// Find the parents of a given node that are commit - in correct parent
87190/// ordering.
88191///
89192/// We do this via a pruned depth first search.
90- fn collect_ordered_parents (
91- graph : & StepGraph ,
92- target : StepGraphIndex ,
93- ) -> Vec < ( StepGraphIndex , gix:: ObjectId ) > {
193+ fn collect_ordered_parents ( graph : & StepGraph , target : StepGraphIndex ) -> Vec < StepGraphIndex > {
94194 let mut potential_parent_edges = graph
95195 . edges_directed ( target, petgraph:: Direction :: Outgoing )
96196 . collect :: < Vec < _ > > ( ) ;
@@ -105,8 +205,8 @@ fn collect_ordered_parents(
105205 let mut parents = vec ! [ ] ;
106206
107207 while let Some ( candidate) = potential_parent_edges. pop ( ) {
108- if let Step :: Pick { id } = graph[ candidate. target ( ) ] {
109- parents. push ( ( candidate. target ( ) , id ) ) ;
208+ if let Step :: Pick { .. } = graph[ candidate. target ( ) ] {
209+ parents. push ( candidate. target ( ) ) ;
110210 // Don't persue the children
111211 continue ;
112212 } ;
@@ -223,7 +323,7 @@ mod test {
223323 graph. add_edge ( c, e, Edge { order : 1 } ) ;
224324
225325 let parents = collect_ordered_parents ( & graph, a) ;
226- assert_eq ! ( & parents, & [ ( b, b_id ) , ( d, d_id ) , ( e, e_id ) , ( f , f_id ) ] ) ;
326+ assert_eq ! ( & parents, & [ b, d, e, f ] ) ;
227327
228328 Ok ( ( ) )
229329 }
@@ -260,7 +360,7 @@ mod test {
260360 graph. add_edge ( c, e, Edge { order : 0 } ) ;
261361
262362 let parents = collect_ordered_parents ( & graph, a) ;
263- assert_eq ! ( & parents, & [ ( b, b_id ) , ( e, e_id ) , ( d, d_id ) , ( f , f_id ) ] ) ;
363+ assert_eq ! ( & parents, & [ b, e, d, f ] ) ;
264364
265365 Ok ( ( ) )
266366 }
0 commit comments