Skip to content

Commit 1e8dd78

Browse files
committed
Creating steps
1 parent e71ba4e commit 1e8dd78

File tree

12 files changed

+455
-11
lines changed

12 files changed

+455
-11
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ bitflags = "2.9.4"
112112
notify = "8.2.0"
113113
snapbox = "0.6.23"
114114
url = "2.5.7"
115+
petgraph = { version = "0.8.3", default-features = false, features = [
116+
"stable_graph",
117+
"std",
118+
] }
119+
115120
schemars = { version = "1.0.4", default-features = false, features = [
116121
"std",
117122
"derive",
@@ -120,8 +125,15 @@ async-openai = { version = "0.30.1", default-features = false, features = [
120125
"rustls",
121126
] }
122127
open = "5.3.2"
123-
regex = { version = "1.11.3", default-features = false, features = ["std", "unicode" ] }
124-
clap = { version = "4.5.51", default-features = false, features = ["derive", "std", "help"]}
128+
regex = { version = "1.11.3", default-features = false, features = [
129+
"std",
130+
"unicode",
131+
] }
132+
clap = { version = "4.5.51", default-features = false, features = [
133+
"derive",
134+
"std",
135+
"help",
136+
] }
125137
tracing-forest = { version = "0.3.0" }
126138
sysinfo = { version = "0.37.2", default-features = false }
127139

crates/but-graph/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ but-meta = { workspace = true, optional = true, features = ["legacy"] }
2020

2121
gix = { workspace = true, features = ["revision"] }
2222
bstr.workspace = true
23-
petgraph = { version = "0.8.3", default-features = false, features = ["stable_graph", "std"] }
23+
petgraph.workspace = true
2424
anyhow.workspace = true
2525
bitflags.workspace = true
2626
tracing.workspace = true
@@ -34,4 +34,3 @@ gitbutler-reference.workspace = true
3434

3535
gix-testtools.workspace = true
3636
insta = "1.43.2"
37-

crates/but-rebase/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ but-core.workspace = true
1414
but-gerrit.workspace = true
1515
but-error.workspace = true
1616
but-oxidize.workspace = true
17+
but-graph.workspace = true
1718

18-
gix = { workspace = true, features = ["revision", "merge"]}
19+
gix = { workspace = true, features = ["revision", "merge"] }
1920
anyhow.workspace = true
2021
tracing.workspace = true
2122
bstr.workspace = true
2223
tempfile.workspace = true
2324
serde.workspace = true
2425
toml.workspace = true
26+
petgraph.workspace = true
2527

2628
[dev-dependencies]
2729
but-testsupport.workspace = true
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use std::collections::BTreeMap;
2+
3+
use anyhow::Result;
4+
use but_graph::{Commit, Graph, Segment};
5+
use petgraph::{Direction, graph::NodeIndex};
6+
7+
use crate::graph_rebase::{Edge, Editor, Step, StepGraph, StepGraphIndex};
8+
9+
/// Provides an extension for creating an Editor out of the segment graph
10+
pub trait GraphExt {
11+
/// Creates an editor.
12+
fn create_editor(&self) -> Result<Editor>;
13+
}
14+
15+
impl GraphExt for Graph {
16+
/// Creates an editor out of the segment graph.
17+
fn create_editor(&self) -> Result<Editor> {
18+
let entrypoint = self.lookup_entrypoint()?;
19+
20+
// Commits in this list are ordred such that iterating in reverse will
21+
// have any relevant parent commits already inserted in the graph.
22+
let mut commits: Vec<Commit> = Vec::new();
23+
// References are ordered from child-most to parent-most
24+
let mut references: BTreeMap<gix::ObjectId, Vec<gix::refs::FullName>> = BTreeMap::new();
25+
26+
self.visit_all_segments_including_start_until(
27+
entrypoint.segment_index,
28+
Direction::Outgoing,
29+
|segment| {
30+
if let Some(refname) = segment.ref_name()
31+
&& let Some(commit) = find_nearest_commit(self, segment)
32+
{
33+
references
34+
.entry(commit.id)
35+
.and_modify(|rs| rs.push(refname.to_owned()))
36+
.or_insert_with(|| vec![refname.to_owned()]);
37+
}
38+
39+
for commit in &segment.commits {
40+
if !commit.refs.is_empty() {
41+
let refs = commit
42+
.refs
43+
.iter()
44+
.map(|r| r.ref_name.clone())
45+
.collect::<Vec<_>>();
46+
if let Some(entry) = references.get_mut(&commit.id) {
47+
entry.extend(refs);
48+
} else {
49+
references.insert(commit.id, refs);
50+
}
51+
}
52+
}
53+
54+
commits.extend(segment.commits.clone());
55+
56+
false
57+
},
58+
);
59+
60+
// When adding child-nodes, this lookup tells us where to find the
61+
// relevant "parent" to point to.
62+
let mut steps_for_commits: BTreeMap<gix::ObjectId, NodeIndex<StepGraphIndex>> =
63+
BTreeMap::new();
64+
let mut graph = StepGraph::new();
65+
66+
while let Some(c) = commits.pop() {
67+
let mut ni = graph.add_node(Step::Pick { id: c.id });
68+
69+
for (order, p) in c.parent_ids.iter().enumerate() {
70+
if let Some(parent_ni) = steps_for_commits.get(p) {
71+
graph.add_edge(ni, *parent_ni, Edge { order });
72+
}
73+
}
74+
75+
if let Some(refs) = references.get_mut(&c.id) {
76+
// We insert in reverse to preserve the child-most to
77+
// parent-most ordering that the frontend sees in the step graph
78+
for r in refs.iter().rev() {
79+
let ref_ni = graph.add_node(Step::Reference { refname: r.clone() });
80+
graph.add_edge(ref_ni, ni, Edge { order: 0 });
81+
ni = ref_ni;
82+
}
83+
}
84+
85+
steps_for_commits.insert(c.id, ni);
86+
}
87+
88+
Ok(Editor {
89+
graph,
90+
initial_references: references.values().flatten().cloned().collect(),
91+
})
92+
}
93+
}
94+
95+
/// Find the commit that is nearest to the top of the segment via a first parent
96+
/// traversal.
97+
fn find_nearest_commit<'graph>(
98+
graph: &'graph Graph,
99+
segment: &'graph Segment,
100+
) -> Option<&'graph Commit> {
101+
let mut target = Some(segment);
102+
while let Some(s) = target {
103+
if let Some(c) = s.commits.first() {
104+
return Some(c);
105+
}
106+
107+
target = graph
108+
.segments_below_in_order(s.id)
109+
.next()
110+
.map(|s| &graph[s.1]);
111+
}
112+
113+
None
114+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#![deny(missing_docs)]
2+
//! One graph based engine to rule them all,
3+
//! one vector based to find them,
4+
//! one mess of git2 code to bring them all,
5+
//! and in the darknes bind them.
6+
7+
use petgraph::graph::NodeIndex;
8+
9+
mod creation;
10+
pub use creation::GraphExt;
11+
12+
/// Utilities for testing
13+
pub mod testing;
14+
15+
/// Describes what action the engine should take
16+
#[derive(Debug, Clone)]
17+
pub enum Step {
18+
/// Cherry picks the given commit into the new location in the graph
19+
Pick {
20+
/// The ID of the commit getting picked
21+
id: gix::ObjectId,
22+
},
23+
/// Represents applying a reference to the commit found at it's first parent
24+
Reference {
25+
/// The refname
26+
refname: gix::refs::FullName,
27+
},
28+
/// Used as a placeholder after removing a pick or reference
29+
None,
30+
}
31+
32+
/// Used to represent a connection between a given commit.
33+
#[derive(Debug, Clone)]
34+
struct Edge {
35+
/// Represents in which order the `parent` fields should be written out
36+
///
37+
/// A child commit should have edges that all have unique orders. In order
38+
/// to achive that we can employ the following semantics.
39+
///
40+
/// When replacing a given parent with N other parents, the first in that list takes the old parent's order, and the rest take the
41+
order: usize,
42+
}
43+
44+
type StepGraphIndex = petgraph::stable_graph::DefaultIx;
45+
type StepGraph = petgraph::stable_graph::StableDiGraph<Step, Edge, StepGraphIndex>;
46+
47+
/// Points to a step in the rebase editor.
48+
#[derive(Debug, Clone)]
49+
pub struct Selector {
50+
id: NodeIndex<StepGraphIndex>,
51+
}
52+
53+
/// Used to manipulate a set of picks.
54+
#[derive(Debug, Clone)]
55+
pub struct Editor {
56+
/// The internal graph of steps
57+
graph: StepGraph,
58+
/// Initial references. This is used to track any references that might need
59+
/// deleted.
60+
initial_references: Vec<gix::refs::FullName>,
61+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![deny(missing_docs)]
2+
//! Testing utilities
3+
4+
use petgraph::dot::{Config, Dot};
5+
6+
use crate::graph_rebase::{Editor, Step};
7+
8+
impl Editor {
9+
/// Creates a dot graph with labels
10+
pub fn steps_dot(&self) -> String {
11+
format!(
12+
"{:?}",
13+
Dot::with_attr_getters(
14+
&self.graph,
15+
&[Config::EdgeNoLabel, Config::NodeNoLabel],
16+
&|_, v| format!("label=\"order: {}\"", v.weight().order),
17+
&|_, (_, step)| {
18+
match step {
19+
Step::Pick { id } => format!("label=\"pick: {}\"", id),
20+
Step::Reference { refname } => {
21+
format!("label=\"reference: {}\"", refname.as_bstr())
22+
}
23+
Step::None => "label=\"none\"".into(),
24+
}
25+
},
26+
)
27+
)
28+
}
29+
}

crates/but-rebase/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ use tracing::instrument;
1010

1111
use crate::commit::DateMode;
1212

13+
/// Tools for manipulating commits
14+
pub mod graph_rebase;
15+
1316
/// Types for use with cherry-picking
1417
pub mod cherry_pick;
1518
pub use cherry_pick::function::cherry_pick_one;

crates/but-rebase/tests/fixtures/rebase.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ git init four-commits
1010
echo "c" >c && git add . && git commit -m "c"
1111
)
1212

13+
git init many-references
14+
(cd many-references
15+
echo "base" >base && git add . && git commit -m "base"
16+
echo "a" >a && git add . && git commit -m "a"
17+
git branch X
18+
git branch Y
19+
git branch Z
20+
echo "b" >b && git add . && git commit -m "b"
21+
echo "c" >c && git add . && git commit -m "c"
22+
)
23+
1324
git init three-branches-merged
1425
(cd three-branches-merged
1526
seq 50 60 >file && git add . && git commit -m "base" && git tag base

0 commit comments

Comments
 (0)