Skip to content

Commit 311c8c8

Browse files
committed
Add graph overlay function on successful rebase
This function gives us access to what the but-graph would look like after materialization.
1 parent daded25 commit 311c8c8

4 files changed

Lines changed: 139 additions & 32 deletions

File tree

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

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use std::collections::{BTreeMap, HashMap};
1010

1111
use anyhow::{Context, Result, bail};
1212
use but_core::RefMetadata;
13+
use but_graph::init::Overlay;
1314
use gix::refs::transaction::RefEdit;
15+
16+
use crate::graph_rebase::util::collect_ordered_parents;
1417
pub mod cherry_pick;
1518
pub mod commit;
1619
pub mod materialize;
@@ -211,7 +214,7 @@ pub struct Editor<'ws, 'meta, M: RefMetadata> {
211214
/// Provides data about how the editor instance was transformed.
212215
history: RevisionHistory,
213216
/// A reference to the workspace that the editor was created for.
214-
pub workspace: &'ws mut but_graph::projection::Workspace,
217+
workspace: &'ws mut but_graph::projection::Workspace,
215218
/// A reference to the metadata that the editor was created for.
216219
meta: &'meta mut M,
217220
}
@@ -235,6 +238,68 @@ pub struct SuccessfulRebase<'ws, 'meta, M: RefMetadata> {
235238
meta: &'meta mut M,
236239
}
237240

241+
impl<'ws, 'meta, M: RefMetadata> SuccessfulRebase<'ws, 'meta, M> {
242+
/// Returns a preview of what the but-graph will look like after
243+
/// materialization.
244+
///
245+
/// Any objects referenced in the resulting graph must be accessed via the
246+
/// in memory repo owned by the [`Editor`] since they might be in-memory
247+
/// only.
248+
pub fn overlayed_graph(&self) -> Result<but_graph::Graph> {
249+
let dropped_refs = self.ref_edits.iter().filter_map(|edit| match &edit.change {
250+
gix::refs::transaction::Change::Delete { .. } => Some(edit.name.clone()),
251+
_ => None,
252+
});
253+
let updated_refs = self.ref_edits.iter().filter_map(|edit| match &edit.change {
254+
gix::refs::transaction::Change::Update { new, .. } => Some(gix::refs::Reference {
255+
name: edit.name.clone(),
256+
target: new.clone(),
257+
// TODO(CTO): Peeled is only relevant for symbolic refs?
258+
peeled: None,
259+
}),
260+
_ => None,
261+
});
262+
263+
let Some((entrypoint_id, entrypoint_refname)) = self
264+
.checkouts
265+
.iter()
266+
.filter_map(|checkout| match checkout {
267+
Checkout::Head(selector) => {
268+
let selector = self.history.normalize_selector(*selector).ok()?;
269+
let step = &self.graph[selector.id];
270+
271+
match step {
272+
Step::None => None,
273+
Step::Pick(Pick { id, .. }) => Some((*id, None)),
274+
Step::Reference { refname } => {
275+
let parents = collect_ordered_parents(&self.graph, selector.id);
276+
277+
if let Some(to_reference) = parents.first()
278+
&& let Step::Pick(Pick { id, .. }) = self.graph[*to_reference]
279+
{
280+
Some((id, Some(refname.clone())))
281+
} else {
282+
None
283+
}
284+
}
285+
}
286+
}
287+
})
288+
.next()
289+
else {
290+
bail!("BUG: Tried to construct rebase engine graph overlay with no entrypoints");
291+
};
292+
293+
let overlay = Overlay::default()
294+
.with_references(updated_refs)
295+
.with_dropped_references(dropped_refs)
296+
.with_entrypoint(entrypoint_id, entrypoint_refname);
297+
self.workspace
298+
.graph
299+
.redo_traversal_with_overlay(&self.repo, self.meta, overlay)
300+
}
301+
}
302+
238303
/// The outcome of a materialize
239304
#[derive(Debug)]
240305
pub struct MaterializeOutcome<'ws, 'meta, M: RefMetadata> {

crates/but-rebase/tests/rebase/graph_rebase/rebase_identities.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use anyhow::Result;
44
use but_graph::Graph;
55
use but_rebase::graph_rebase::Editor;
6-
use but_testsupport::{graph_workspace, visualize_commit_graph_all};
6+
use but_testsupport::{graph_tree, graph_workspace, visualize_commit_graph_all};
77

88
use crate::utils::{fixture_writable, standard_options};
99

@@ -21,9 +21,15 @@ fn four_commits() -> Result<()> {
2121

2222
let graph = Graph::from_head(&repo, &*meta, standard_options())?.validated()?;
2323

24-
let mut ws = graph.into_workspace()?;
24+
let mut ws = graph.clone().into_workspace()?;
2525
let editor = Editor::create(&mut ws, &mut *meta, &repo)?;
2626
let outcome = editor.rebase()?;
27+
28+
assert_eq!(
29+
graph_tree(&graph).to_string(),
30+
graph_tree(&outcome.overlayed_graph()?).to_string()
31+
);
32+
2733
let outcome = outcome.materialize()?;
2834

2935
assert_eq!(visualize_commit_graph_all(&repo)?, before);
@@ -46,7 +52,7 @@ fn four_commits_with_short_traversal() -> Result<()> {
4652

4753
let options = standard_options().with_hard_limit(4);
4854
let graph = Graph::from_head(&repo, &*meta, options)?.validated()?;
49-
let mut ws = graph.into_workspace()?;
55+
let mut ws = graph.clone().into_workspace()?;
5056

5157
insta::assert_snapshot!(graph_workspace(&ws), @"
5258
⌂:0:main[🌳] <> ✓!
@@ -58,6 +64,12 @@ fn four_commits_with_short_traversal() -> Result<()> {
5864

5965
let editor = Editor::create(&mut ws, &mut *meta, &repo)?;
6066
let outcome = editor.rebase()?;
67+
68+
assert_eq!(
69+
graph_tree(&graph).to_string(),
70+
graph_tree(&outcome.overlayed_graph()?).to_string()
71+
);
72+
6173
let outcome = outcome.materialize()?;
6274

6375
assert_eq!(visualize_commit_graph_all(&repo)?, before);
@@ -83,9 +95,15 @@ fn merge_in_the_middle() -> Result<()> {
8395

8496
let graph = Graph::from_head(&repo, &*meta, standard_options())?.validated()?;
8597

86-
let mut ws = graph.into_workspace()?;
98+
let mut ws = graph.clone().into_workspace()?;
8799
let editor = Editor::create(&mut ws, &mut *meta, &repo)?;
88100
let outcome = editor.rebase()?;
101+
102+
assert_eq!(
103+
graph_tree(&graph).to_string(),
104+
graph_tree(&outcome.overlayed_graph()?).to_string()
105+
);
106+
89107
let outcome = outcome.materialize()?;
90108

91109
assert_eq!(visualize_commit_graph_all(&repo)?, before);
@@ -115,9 +133,15 @@ fn three_branches_merged() -> Result<()> {
115133

116134
let graph = Graph::from_head(&repo, &*meta, standard_options())?.validated()?;
117135

118-
let mut ws = graph.into_workspace()?;
136+
let mut ws = graph.clone().into_workspace()?;
119137
let editor = Editor::create(&mut ws, &mut *meta, &repo)?;
120138
let outcome = editor.rebase()?;
139+
140+
assert_eq!(
141+
graph_tree(&graph).to_string(),
142+
graph_tree(&outcome.overlayed_graph()?).to_string()
143+
);
144+
121145
let outcome = outcome.materialize()?;
122146

123147
assert_eq!(visualize_commit_graph_all(&repo)?, before);

crates/but-workspace/src/branch/move_branch.rs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ pub(super) mod function {
4848
///
4949
/// Returns the in memory update [outcome](Outcome) that can then used for materialisation.
5050
pub fn tear_off_branch<'ws, 'meta, M: RefMetadata>(
51-
mut editor: Editor<'ws, 'meta, M>,
51+
editor: Editor<'ws, 'meta, M>,
5252
subject_branch_name: &FullNameRef,
5353
stack_id_override: Option<StackId>,
5454
) -> anyhow::Result<Outcome<'ws, 'meta, M>> {
55-
let Some(source) = editor
56-
.workspace
57-
.find_segment_and_stack_by_refname(subject_branch_name)
58-
else {
55+
let sucessful_rebase = editor.rebase()?;
56+
let workspace = sucessful_rebase.overlayed_graph()?.into_workspace()?;
57+
let mut editor = sucessful_rebase.into_editor();
58+
let Some(source) = workspace.find_segment_and_stack_by_refname(subject_branch_name) else {
5959
bail!(
6060
"Couldn't find branch to move in workspace with reference name: {subject_branch_name}"
6161
);
@@ -64,7 +64,7 @@ pub(super) mod function {
6464
// We're currently stopping the move branch operations imperatively at this stage, in order to
6565
// reduce the scope of this first iteration of moving the branches.
6666
// TODO: Enable and test that we can move branches in any kind of workspace.
67-
match &editor.workspace.kind {
67+
match &workspace.kind {
6868
WorkspaceKind::Managed { .. } => {}
6969
WorkspaceKind::ManagedMissingWorkspaceCommit { .. } => {
7070
bail!("Moving branches currently need a workspace commit")
@@ -74,7 +74,7 @@ pub(super) mod function {
7474
}
7575
};
7676

77-
let mut ws_meta = editor.workspace.metadata.clone();
77+
let mut ws_meta = workspace.metadata.clone();
7878

7979
let (source_stack, subject_segment) = source;
8080

@@ -86,17 +86,16 @@ pub(super) mod function {
8686
});
8787
}
8888

89-
let Some(workspace_head) = editor.workspace.tip_commit().map(|commit| commit.id) else {
89+
let Some(workspace_head) = workspace.tip_commit().map(|commit| commit.id) else {
9090
bail!("Couldn't find workspace head.")
9191
};
9292
let head_selector = editor
9393
.select_commit(workspace_head)
9494
.context("Failed to find the workspace head in the graph.")?;
9595

96-
let Some(lower_bound_ref) = editor
97-
.workspace
96+
let Some(lower_bound_ref) = workspace
9897
.lower_bound_segment_id
99-
.map(|segment_id| &editor.workspace.graph[segment_id])
98+
.map(|segment_id| &workspace.graph[segment_id])
10099
.and_then(|segment| segment.ref_name())
101100
else {
102101
bail!("Tearing off a branch requires a workspace common base");
@@ -107,7 +106,13 @@ pub(super) mod function {
107106
.context("Failed to find target reference in graph.")?;
108107

109108
let (subject_delimiter, children_to_disconnect, parents_to_disconnect) =
110-
get_disconnect_parameters(&editor, source_stack, subject_segment, workspace_head)?;
109+
get_disconnect_parameters(
110+
&editor,
111+
&workspace,
112+
source_stack,
113+
subject_segment,
114+
workspace_head,
115+
)?;
111116

112117
editor.disconnect_segment_from(
113118
subject_delimiter.clone(),
@@ -158,24 +163,25 @@ pub(super) mod function {
158163
///
159164
/// Returns an [outcome](Outcome) for potential materialisation.
160165
pub fn move_branch<'ws, 'meta, M: RefMetadata>(
161-
mut editor: Editor<'ws, 'meta, M>,
166+
editor: Editor<'ws, 'meta, M>,
162167
subject_branch_name: &FullNameRef,
163168
target_branch_name: &FullNameRef,
164169
) -> anyhow::Result<Outcome<'ws, 'meta, M>> {
165-
let (source, destination) = retrieve_branches_and_containers(
166-
editor.workspace,
167-
subject_branch_name,
168-
target_branch_name,
169-
)?;
170+
let sucessful_rebase = editor.rebase()?;
171+
let workspace = sucessful_rebase.overlayed_graph()?.into_workspace()?;
172+
let mut editor = sucessful_rebase.into_editor();
173+
174+
let (source, destination) =
175+
retrieve_branches_and_containers(&workspace, subject_branch_name, target_branch_name)?;
170176

171-
let Some(workspace_head) = editor.workspace.tip_commit().map(|commit| commit.id) else {
177+
let Some(workspace_head) = workspace.tip_commit().map(|commit| commit.id) else {
172178
bail!("Couldn't find workspace head.")
173179
};
174180

175181
// We're currently stopping the move branch operations imperatively at this stage, in order to
176182
// reduce the scope of this first iteration of moving the branches.
177183
// TODO: Enable and test that we can move branches in any kind of workspace.
178-
match &editor.workspace.kind {
184+
match &workspace.kind {
179185
WorkspaceKind::Managed { .. } => {}
180186
WorkspaceKind::ManagedMissingWorkspaceCommit { .. } => {
181187
bail!("Moving branches currently need a workspace commit")
@@ -185,7 +191,7 @@ pub(super) mod function {
185191
}
186192
};
187193

188-
let mut ws_meta = editor.workspace.metadata.clone();
194+
let mut ws_meta = workspace.metadata.clone();
189195

190196
let (source_stack, subject_segment) = source;
191197
let (_, target_segment) = destination;
@@ -197,7 +203,13 @@ pub(super) mod function {
197203
.context("Failed to find target reference in graph.")?;
198204

199205
let (subject_delimiter, children_to_disconnect, parents_to_disconnect) =
200-
get_disconnect_parameters(&editor, &source_stack, &subject_segment, workspace_head)?;
206+
get_disconnect_parameters(
207+
&editor,
208+
&workspace,
209+
&source_stack,
210+
&subject_segment,
211+
workspace_head,
212+
)?;
201213

202214
let skip_reconnect_step = source_stack.segments.len() == 1;
203215
editor.disconnect_segment_from(
@@ -306,6 +318,7 @@ pub(super) mod function {
306318
/// as well as the right segment delimiter to move.
307319
fn get_disconnect_parameters<'ws, 'meta, M: RefMetadata>(
308320
editor: &Editor<'ws, 'meta, M>,
321+
workspace: &but_graph::projection::Workspace,
309322
source_stack: &Stack,
310323
subject_segment: &StackSegment,
311324
workspace_head: gix::ObjectId,
@@ -358,7 +371,7 @@ fn get_disconnect_parameters<'ws, 'meta, M: RefMetadata>(
358371
// graph data, and it's probably the target branch, which is not included in the workspace.
359372
let graph_base_segment = subject_segment
360373
.base_segment_id
361-
.map(|segment_idx| &editor.workspace.graph[segment_idx]);
374+
.map(|segment_idx| &workspace.graph[segment_idx]);
362375

363376
let parents_to_disconnect = if let Some(stack_base_segment) = stack_base_segment {
364377
// Base segment is part of the source stack.

crates/but-workspace/src/commit/move_commit.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,18 @@ pub(crate) mod function {
2525
/// The subject commit will be detached from the source segment, and inserted relative
2626
/// to a given anchor (branch or commit).
2727
pub fn move_commit<'ws, 'meta, M: RefMetadata>(
28-
mut editor: Editor<'ws, 'meta, M>,
28+
editor: Editor<'ws, 'meta, M>,
2929
subject_commit: impl ToCommitSelector,
3030
anchor: impl ToSelector,
3131
side: InsertSide,
3232
) -> anyhow::Result<SuccessfulRebase<'ws, 'meta, M>> {
33+
let sucessful_rebase = editor.rebase()?;
34+
let workspace = sucessful_rebase.overlayed_graph()?.into_workspace()?;
35+
let mut editor = sucessful_rebase.into_editor();
3336
let (subject_commit_selector, subject_commit) =
3437
editor.find_selectable_commit(subject_commit)?;
3538

36-
let subject = retrieve_commit_and_containers(editor.workspace, &subject_commit)?;
39+
let subject = retrieve_commit_and_containers(&workspace, &subject_commit)?;
3740

3841
let (source_stack, source_segment, _) = subject;
3942

@@ -54,6 +57,7 @@ pub(crate) mod function {
5457

5558
let parent_to_disconnect = determine_parent_selector(
5659
&editor,
60+
&workspace,
5761
source_stack,
5862
source_segment,
5963
index_of_subject_commit,
@@ -139,6 +143,7 @@ pub(crate) mod function {
139143
/// and the position of the source segment in the source stack.
140144
fn determine_parent_selector<'ws, 'meta, M: RefMetadata>(
141145
editor: &Editor<'ws, 'meta, M>,
146+
workspace: &but_graph::projection::Workspace,
142147
source_stack: &but_graph::projection::Stack,
143148
source_segment: &but_graph::projection::StackSegment,
144149
index_of_subject_commit: usize,
@@ -161,7 +166,7 @@ pub(crate) mod function {
161166
// Look for the base segment in the graph data, as a fallback if there's no stack segment found.
162167
let graph_base_segment_ref_name = source_segment
163168
.base_segment_id
164-
.map(|base_segment_id| &editor.workspace.graph[base_segment_id])
169+
.map(|base_segment_id| &workspace.graph[base_segment_id])
165170
.and_then(|segment| segment.ref_name());
166171

167172
match stack_base_segment_ref_name.or(graph_base_segment_ref_name) {

0 commit comments

Comments
 (0)