Skip to content

Commit a26a591

Browse files
committed
Improve checkouts and handle partial history case
1 parent b88c8f6 commit a26a591

File tree

12 files changed

+336
-138
lines changed

12 files changed

+336
-138
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! Provides some slightly higher level tools to help with manipulating commits, in preperation for use in the editor.
2+
3+
use anyhow::Result;
4+
use gix::prelude::ObjectIdExt;
5+
6+
use crate::{
7+
commit::{DateMode, create},
8+
graph_rebase::Editor,
9+
};
10+
11+
impl Editor {
12+
/// Finds a commit from inside the editor's in memory repository.
13+
pub fn find_commit(&self, id: gix::ObjectId) -> Result<but_core::Commit<'_>> {
14+
but_core::Commit::from_id(id.attach(&self.repo))
15+
}
16+
17+
/// Writes a commit with correct signing to the in memory repostiory.
18+
pub fn write_commit(
19+
&self,
20+
commit: but_core::Commit<'_>,
21+
date_mode: DateMode,
22+
) -> Result<gix::ObjectId> {
23+
create(&self.repo, commit.inner, date_mode)
24+
}
25+
}

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

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
use std::collections::BTreeMap;
22

3-
use anyhow::Result;
3+
use anyhow::{Result, bail};
44
use but_graph::{Commit, CommitFlags, Graph, Segment};
55
use petgraph::Direction;
66

7-
use crate::graph_rebase::{Edge, Editor, Step, StepGraph, StepGraphIndex};
7+
use crate::graph_rebase::{Checkouts, Edge, Editor, Step, StepGraph, StepGraphIndex};
88

99
/// Provides an extension for creating an Editor out of the segment graph
1010
pub trait GraphExt {
1111
/// Creates an editor.
12-
fn to_editor(&self) -> Result<Editor>;
12+
fn to_editor(&self, repo: &gix::Repository) -> Result<Editor>;
1313
}
1414

1515
impl GraphExt for Graph {
1616
/// Creates an editor out of the segment graph.
17-
fn to_editor(&self) -> Result<Editor> {
17+
fn to_editor(&self, repo: &gix::Repository) -> Result<Editor> {
1818
// TODO(CTO): Look into traversing "in workspace" segments that are not reachable from the entrypoint
1919
// TODO(CTO): Look into stopping at the common base
2020
let entrypoint = self.lookup_entrypoint()?;
@@ -67,14 +67,43 @@ impl GraphExt for Graph {
6767
let mut steps_for_commits: BTreeMap<gix::ObjectId, StepGraphIndex> = BTreeMap::new();
6868
let mut graph = StepGraph::new();
6969

70-
let mut last_inserted = None;
7170
while let Some(c) = commits.pop() {
72-
let mut ni = graph.add_node(Step::Pick { id: c.id });
73-
74-
for (order, p) in c.parent_ids.iter().enumerate() {
75-
if let Some(parent_ni) = steps_for_commits.get(p) {
76-
graph.add_edge(ni, *parent_ni, Edge { order });
77-
}
71+
let parent_steps = c
72+
.parent_ids
73+
.iter()
74+
.enumerate()
75+
.filter_map(|(o, step)| Some((o, steps_for_commits.get(step)?)))
76+
.collect::<Vec<_>>();
77+
78+
let has_no_parents = c.parent_ids.is_empty();
79+
let has_missing_parent_steps = parent_steps.len() != c.parent_ids.len();
80+
let has_no_parent_steps = parent_steps.is_empty();
81+
82+
// If we are missing _some_ but not all parents, something has gone wrong.
83+
if has_missing_parent_steps && !has_no_parent_steps {
84+
bail!(
85+
"Rebase creation has failed. Expected {} parent steps, found {}",
86+
c.parent_ids.len(),
87+
parent_steps.len()
88+
);
89+
};
90+
91+
// If the commit has parents in the commit graph, but none of
92+
// them are in the graph, this means but-graph did a partial
93+
// traversal and we want to preserve the commit as it is.
94+
let preserved_parents = if !has_no_parents && has_no_parent_steps {
95+
Some(c.parent_ids)
96+
} else {
97+
None
98+
};
99+
100+
let mut ni = graph.add_node(Step::Pick {
101+
id: c.id,
102+
preserved_parents,
103+
});
104+
105+
for (order, parent_ni) in parent_steps {
106+
graph.add_edge(ni, *parent_ni, Edge { order });
78107
}
79108

80109
if let Some(refs) = references.get_mut(&c.id) {
@@ -87,14 +116,16 @@ impl GraphExt for Graph {
87116
}
88117
}
89118

90-
last_inserted = Some(ni);
91119
steps_for_commits.insert(c.id, ni);
92120
}
93121

94122
Ok(Editor {
95123
graph,
96124
initial_references: references.values().flatten().cloned().collect(),
97-
heads: last_inserted.into_iter().collect(),
125+
// TODO(CTO): We need to eventually list all worktrees that we own
126+
// here so we can `safe_checkout` them too.
127+
checkouts: vec![Checkouts::Head],
128+
repo: repo.clone().with_object_memory(),
98129
})
99130
}
100131
}

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

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,41 @@
11
//! Functions for materializing a rebase
2-
use crate::graph_rebase::rebase::SuccessfulRebase;
2+
use crate::graph_rebase::{Checkouts, rebase::SuccessfulRebase};
33
use anyhow::Result;
4-
use but_core::worktree::{
5-
checkout::{Options, UncommitedWorktreeChanges},
6-
safe_checkout,
4+
use but_core::{
5+
ObjectStorageExt as _,
6+
worktree::{
7+
checkout::{Options, UncommitedWorktreeChanges},
8+
safe_checkout_from_head,
9+
},
710
};
811

912
impl SuccessfulRebase {
1013
/// Materializes a history rewrite
11-
pub fn materialize(self, repo: &gix::Repository) -> Result<()> {
14+
pub fn materialize(mut self) -> Result<()> {
15+
let repo = self.repo.clone();
16+
if let Some(memory) = self.repo.objects.take_object_memory() {
17+
memory.persist(self.repo)?;
18+
}
19+
1220
for checkout in self.checkouts {
13-
// TODO(CTO): improve safe_checkout to allow for speculation
14-
safe_checkout(
15-
checkout.old_head_id,
16-
checkout.head_id,
17-
repo,
18-
Options {
19-
uncommitted_changes: UncommitedWorktreeChanges::KeepAndAbortOnConflict,
20-
skip_head_update: true,
21-
},
22-
)?;
21+
match checkout {
22+
Checkouts::Head => {
23+
let head_oid = repo.head_commit()?.id;
24+
if let Some(new_head) = self.commit_mapping.get(&head_oid) {
25+
// If the head has changed (which means it's in the
26+
// commit mapping), perform a safe checkout.
27+
safe_checkout_from_head(
28+
*new_head,
29+
&repo,
30+
Options {
31+
uncommitted_changes:
32+
UncommitedWorktreeChanges::KeepAndAbortOnConflict,
33+
skip_head_update: true,
34+
},
35+
)?;
36+
}
37+
}
38+
}
2339
}
2440

2541
repo.edit_references(self.ref_edits.clone())?;

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod creation;
88
pub mod rebase;
99
pub use creation::GraphExt;
1010
pub mod cherry_pick;
11+
pub mod commit;
1112
pub mod materialize;
1213
pub mod mutate;
1314

@@ -21,6 +22,15 @@ pub enum Step {
2122
Pick {
2223
/// The ID of the commit getting picked
2324
id: gix::ObjectId,
25+
/// If we are dealing with a sub-graph with an incomplete history, we
26+
/// need to represent the bottom most commits in a way that we preserve
27+
/// their parents.
28+
///
29+
/// If this is Some, the commit WILL NOT be picked onto the parents the
30+
/// graph implies but instead on to the parents listed here.
31+
///
32+
/// This is intened to be a private API
33+
preserved_parents: Option<Vec<gix::ObjectId>>,
2434
},
2535
/// Represents applying a reference to the commit found at it's first parent
2636
Reference {
@@ -31,6 +41,16 @@ pub enum Step {
3141
None,
3242
}
3343

44+
impl Step {
45+
/// Creates a pick with the expected defaults
46+
pub fn new_pick(id: gix::ObjectId) -> Self {
47+
Self::Pick {
48+
id,
49+
preserved_parents: None,
50+
}
51+
}
52+
}
53+
3454
/// Used to represent a connection between a given commit.
3555
#[derive(Debug, Clone)]
3656
pub(crate) struct Edge {
@@ -52,6 +72,13 @@ pub struct Selector {
5272
id: StepGraphIndex,
5373
}
5474

75+
/// Represents places where `safe_checkout` should be called from
76+
#[derive(Debug, Clone)]
77+
pub(crate) enum Checkouts {
78+
/// The HEAD of the `repo` the editor was created for.
79+
Head,
80+
}
81+
5582
/// Used to manipulate a set of picks.
5683
#[derive(Debug, Clone)]
5784
pub struct Editor {
@@ -60,5 +87,8 @@ pub struct Editor {
6087
/// Initial references. This is used to track any references that might need
6188
/// deleted.
6289
initial_references: Vec<gix::refs::FullName>,
63-
heads: Vec<StepGraphIndex>,
90+
/// Worktrees that we might need to perform `safe_checkout` on.
91+
checkouts: Vec<Checkouts>,
92+
/// The in-memory repository that the rebase engine works with.
93+
repo: gix::Repository,
6494
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl Editor {
2020
/// Get a selector to a particular commit in the graph
2121
pub fn select_commit(&self, target: gix::ObjectId) -> Option<Selector> {
2222
for node_idx in self.graph.node_indices() {
23-
if let Step::Pick { id } = self.graph[node_idx]
23+
if let Step::Pick { id, .. } = self.graph[node_idx]
2424
&& id == target
2525
{
2626
return Some(Selector { id: node_idx });

0 commit comments

Comments
 (0)