Skip to content

Commit 95cd28b

Browse files
committed
Initial implementation of rebase
tmp
1 parent 8af591a commit 95cd28b

File tree

3 files changed

+158
-52
lines changed

3 files changed

+158
-52
lines changed

crates/but-rebase/src/graph_rebase/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub enum Step {
3333

3434
/// Used to represent a connection between a given commit.
3535
#[derive(Debug, Clone)]
36-
struct Edge {
36+
pub(crate) struct Edge {
3737
/// Represents in which order the `parent` fields should be written out
3838
///
3939
/// A child commit should have edges that all have unique orders. In order

crates/but-rebase/src/graph_rebase/rebase.rs

Lines changed: 150 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,195 @@
22
33
use 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};
1314
use 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

2147
impl 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
}

crates/but-rebase/src/graph_rebase/testing.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use petgraph::dot::{Config, Dot};
55

6-
use crate::graph_rebase::{Editor, Step, StepGraph};
6+
use crate::graph_rebase::{Editor, Step, StepGraph, rebase::SuccessfulRebase};
77

88
/// An extension trait that adds debugging output for graphs
99
pub trait TestingDot {
@@ -17,6 +17,12 @@ impl TestingDot for Editor {
1717
}
1818
}
1919

20+
impl TestingDot for SuccessfulRebase {
21+
fn steps_dot(&self) -> String {
22+
self.graph.steps_dot()
23+
}
24+
}
25+
2026
impl TestingDot for StepGraph {
2127
fn steps_dot(&self) -> String {
2228
format!(

0 commit comments

Comments
 (0)