Skip to content

Commit 9c920ce

Browse files
committed
SQUASH ME
1 parent f8752ab commit 9c920ce

File tree

9 files changed

+498
-265
lines changed

9 files changed

+498
-265
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/but-graph/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,4 @@ gix-testtools.workspace = true
3434
insta = "1.43.1"
3535
termtree = "0.5.1"
3636
but-testsupport.workspace = true
37-
regex = "1.11.1"
3837

crates/but-graph/src/ref_metadata_legacy.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,9 @@ impl RefMetadata for VirtualBranchesTomlMetadata {
239239
if stack_id.is_none() {
240240
stack_id = *branch.stack_id.borrow();
241241
} else if stack_id != *branch.stack_id.borrow() {
242-
bail!(
243-
"{}:Inconsistent stack detected, wanted {:?}, but got {:?}",
244-
branch.ref_name.as_bstr(),
245-
stack_id,
246-
branch.stack_id.borrow()
247-
)
242+
// The branch was fetched first, received a new stack-id to have some storage location for it.
243+
// Now we see the workspace and notice that it should be in this stack. So put it… for consistency.
244+
*branch.stack_id.borrow_mut() = stack_id;
248245
}
249246
}
250247

crates/but-graph/tests/graph/ref_metadata_legacy.rs

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use but_core::RefMetadata;
22
use but_core::ref_metadata::{StackId, ValueInfo, WorkspaceStack, WorkspaceStackBranch};
33
use but_graph::VirtualBranchesTomlMetadata;
44
use but_testsupport::gix_testtools::tempfile::{TempDir, tempdir};
5-
use std::collections::HashMap;
5+
use but_testsupport::{debug_str, sanitize_uuids_and_timestamps_with_mapping};
66
use std::ops::Deref;
77
use std::path::PathBuf;
88

@@ -39,7 +39,7 @@ fn read_only() -> anyhow::Result<()> {
3939
let (mut store, _tmp) = vb_store_rw("virtual-branches-01")?;
4040
let ws = store.workspace("refs/heads/gitbutler/workspace".try_into()?)?;
4141
assert!(!ws.is_default(), "value read from file");
42-
let (actual, uuids) = sanitize_uuids_and_timestamps(debug_str(&ws.stacks));
42+
let (actual, uuids) = sanitize_uuids_and_timestamps_with_mapping(debug_str(&ws.stacks));
4343
insta::assert_snapshot!(actual, @r#"
4444
[
4545
WorkspaceStack {
@@ -421,7 +421,7 @@ fn create_workspace_and_stacks_with_branches_from_scratch() -> anyhow::Result<()
421421

422422
// Assure `ws` is what we think it should be - a single stack with one branch.
423423
let mut ws = store.workspace(workspace_name.as_ref())?;
424-
let (actual, uuids) = sanitize_uuids_and_timestamps(debug_str(&ws.stacks));
424+
let (actual, uuids) = sanitize_uuids_and_timestamps_with_mapping(debug_str(&ws.stacks));
425425
insta::assert_snapshot!(actual, @r#"
426426
[
427427
WorkspaceStack {
@@ -461,7 +461,7 @@ fn create_workspace_and_stacks_with_branches_from_scratch() -> anyhow::Result<()
461461
.expect("This is the way to add branches");
462462

463463
let mut ws = store.workspace(workspace_name.as_ref())?;
464-
let (actual, uuids) = sanitize_uuids_and_timestamps(debug_str(&ws.stacks));
464+
let (actual, uuids) = sanitize_uuids_and_timestamps_with_mapping(debug_str(&ws.stacks));
465465
insta::assert_snapshot!(actual, @r#"
466466
[
467467
WorkspaceStack {
@@ -491,7 +491,8 @@ fn create_workspace_and_stacks_with_branches_from_scratch() -> anyhow::Result<()
491491
drop(store);
492492

493493
assert!(toml_path.exists(), "file was written due to change");
494-
let (actual, uuids) = sanitize_uuids_and_timestamps(std::fs::read_to_string(&toml_path)?);
494+
let (actual, uuids) =
495+
sanitize_uuids_and_timestamps_with_mapping(std::fs::read_to_string(&toml_path)?);
495496
insta::assert_snapshot!(actual, @r#"
496497
[branch_targets]
497498
@@ -538,7 +539,7 @@ fn create_workspace_and_stacks_with_branches_from_scratch() -> anyhow::Result<()
538539
ws.deref(),
539540
"It's still what it was before - it was persisted"
540541
);
541-
let (actual, uuids) = sanitize_uuids_and_timestamps(debug_str(&new_ws.stacks));
542+
let (actual, uuids) = sanitize_uuids_and_timestamps_with_mapping(debug_str(&new_ws.stacks));
542543
insta::assert_snapshot!(actual, @r#"
543544
[
544545
WorkspaceStack {
@@ -576,7 +577,7 @@ fn create_workspace_and_stacks_with_branches_from_scratch() -> anyhow::Result<()
576577
);
577578
store.set_workspace(&ws)?;
578579
let mut ws = store.workspace(workspace_name.as_ref())?;
579-
let (actual, uuids) = sanitize_uuids_and_timestamps(debug_str(&ws.stacks));
580+
let (actual, uuids) = sanitize_uuids_and_timestamps_with_mapping(debug_str(&ws.stacks));
580581
insta::assert_snapshot!(actual, @r#"
581582
[
582583
WorkspaceStack {
@@ -639,7 +640,7 @@ fn create_workspace_and_stacks_with_branches_from_scratch() -> anyhow::Result<()
639640
store.set_workspace(&ws)?;
640641
let mut ws = store.workspace(ws.as_ref())?;
641642
// Two stacks are present now.
642-
let (actual, uuids) = sanitize_uuids_and_timestamps(debug_str(&ws.stacks));
643+
let (actual, uuids) = sanitize_uuids_and_timestamps_with_mapping(debug_str(&ws.stacks));
643644
insta::assert_snapshot!(actual, @r#"
644645
[
645646
WorkspaceStack {
@@ -960,42 +961,3 @@ fn roundtrip_journey(metadata: &mut impl RefMetadata) -> anyhow::Result<()> {
960961
assert_eq!(metadata.iter().count(), 0, "Nothing is left after deletion");
961962
Ok(())
962963
}
963-
964-
fn sanitize_uuids_and_timestamps(input: String) -> (String, HashMap<String, usize>) {
965-
let uuid_regex = regex::Regex::new(
966-
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}",
967-
)
968-
.unwrap();
969-
let timestamp_regex = regex::Regex::new(r#""\d{13}""#).unwrap();
970-
971-
let mut uuid_map: HashMap<String, usize> = HashMap::new();
972-
let mut uuid_counter = 1;
973-
974-
let mut timestamp_map: HashMap<String, usize> = HashMap::new();
975-
let mut timestamp_counter = 12_345;
976-
977-
let result = uuid_regex.replace_all(&input, |caps: &regex::Captures| {
978-
let uuid = caps.get(0).unwrap().as_str().to_string();
979-
let entry = uuid_map.entry(uuid).or_insert_with(|| {
980-
let num = uuid_counter;
981-
uuid_counter += 1;
982-
num
983-
});
984-
entry.to_string()
985-
});
986-
let result = timestamp_regex.replace_all(&result, |caps: &regex::Captures| {
987-
let timestamp = caps.get(0).unwrap().as_str().to_string();
988-
let entry = timestamp_map.entry(timestamp).or_insert_with(|| {
989-
let num = timestamp_counter;
990-
timestamp_counter += 1;
991-
num
992-
});
993-
entry.to_string()
994-
});
995-
996-
(result.to_string(), uuid_map)
997-
}
998-
999-
fn debug_str(input: &dyn std::fmt::Debug) -> String {
1000-
format!("{:#?}", input)
1001-
}

crates/but-testsupport/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ anyhow.workspace = true
1414
termtree = "0.5.1"
1515
gix-testtools.workspace = true
1616
but-graph.workspace = true
17+
regex = "1.11.1"
1718

1819
[dev-dependencies]
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use but_graph::SegmentMetadata;
2+
use but_graph::{EntryPoint, Graph, SegmentIndex};
3+
4+
use termtree::Tree;
5+
6+
use but_graph::projection::StackCommitDebugFlags;
7+
use std::collections::{BTreeMap, BTreeSet};
8+
9+
type StringTree = Tree<String>;
10+
11+
/// Visualize `graph` as a tree.
12+
pub fn graph_workspace(workspace: &but_graph::projection::Workspace<'_>) -> StringTree {
13+
let commit_flags = workspace
14+
.graph
15+
.hard_limit_hit()
16+
.then_some(StackCommitDebugFlags::HardLimitReached)
17+
.unwrap_or_default();
18+
let mut root = Tree::new(workspace.debug_string());
19+
for stack in &workspace.stacks {
20+
root.push(tree_for_stack(stack, commit_flags));
21+
}
22+
root
23+
}
24+
25+
fn tree_for_stack(
26+
stack: &but_graph::projection::Stack,
27+
commit_flags: StackCommitDebugFlags,
28+
) -> StringTree {
29+
let mut root = Tree::new(stack.debug_string());
30+
for segment in &stack.segments {
31+
root.push(tree_for_stack_segment(segment, commit_flags));
32+
}
33+
root
34+
}
35+
36+
fn tree_for_stack_segment(
37+
segment: &but_graph::projection::StackSegment,
38+
commit_flags: StackCommitDebugFlags,
39+
) -> StringTree {
40+
let mut root = Tree::new(segment.debug_string());
41+
if let Some(outside) = &segment.commits_outside {
42+
for commit in outside {
43+
root.push(format!("{}*", commit.debug_string(commit_flags)));
44+
}
45+
}
46+
for commit in &segment.commits_on_remote {
47+
root.push(commit.debug_string(commit_flags | StackCommitDebugFlags::RemoteOnly));
48+
}
49+
for commit in &segment.commits {
50+
root.push(commit.debug_string(commit_flags));
51+
}
52+
root
53+
}
54+
55+
/// Visualize `graph` as a tree.
56+
pub fn graph_tree(graph: &Graph) -> StringTree {
57+
let mut root = Tree::new("".to_string());
58+
let mut seen = Default::default();
59+
for sidx in graph.tip_segments() {
60+
root.push(recurse_segment(graph, sidx, &mut seen));
61+
}
62+
let missing = graph.num_segments() - seen.len();
63+
if missing > 0 {
64+
let mut missing = Tree::new(format!(
65+
"ERROR: disconnected {missing} nodes unreachable through base"
66+
));
67+
let mut newly_seen = Default::default();
68+
for sidx in graph.segments().filter(|sidx| !seen.contains(sidx)) {
69+
missing.push(recurse_segment(graph, sidx, &mut newly_seen));
70+
}
71+
root.push(missing);
72+
seen.extend(newly_seen);
73+
}
74+
75+
if seen.is_empty() {
76+
"<UNBORN>".to_string().into()
77+
} else {
78+
root
79+
}
80+
}
81+
82+
fn no_first_commit_on_named_segments(mut ep: EntryPoint<'_>) -> EntryPoint<'_> {
83+
if ep.segment.ref_name.is_some() && ep.commit_index == Some(0) {
84+
ep.commit_index = None;
85+
}
86+
ep
87+
}
88+
89+
fn tree_for_commit(
90+
commit: &but_graph::Commit,
91+
is_entrypoint: bool,
92+
is_early_end: bool,
93+
hard_limit_hit: bool,
94+
) -> StringTree {
95+
Graph::commit_debug_string(commit, is_entrypoint, is_early_end, hard_limit_hit).into()
96+
}
97+
fn recurse_segment(
98+
graph: &but_graph::Graph,
99+
sidx: SegmentIndex,
100+
seen: &mut BTreeSet<SegmentIndex>,
101+
) -> StringTree {
102+
let segment = &graph[sidx];
103+
if seen.contains(&sidx) {
104+
return format!(
105+
"→:{sidx}:{name}",
106+
sidx = sidx.index(),
107+
name = graph[sidx]
108+
.ref_name
109+
.as_ref()
110+
.map(|n| format!(
111+
" ({}{maybe_sibling})",
112+
Graph::ref_debug_string(n),
113+
maybe_sibling = segment
114+
.sibling_segment_id
115+
.map_or_else(String::new, |sid| format!(" →:{}:", sid.index()))
116+
))
117+
.unwrap_or_default()
118+
)
119+
.into();
120+
}
121+
seen.insert(sidx);
122+
let ep = no_first_commit_on_named_segments(graph.lookup_entrypoint().unwrap());
123+
let segment_is_entrypoint = ep.segment_index == sidx;
124+
let mut show_segment_entrypoint = segment_is_entrypoint;
125+
if segment_is_entrypoint {
126+
// Reduce noise by preferring ref-based entry-points.
127+
if segment.ref_name.is_none() && ep.commit_index.is_some() {
128+
show_segment_entrypoint = false;
129+
}
130+
}
131+
let connected_segments = {
132+
let mut m = BTreeMap::<_, Vec<_>>::new();
133+
let below = graph.segments_below_in_order(sidx).collect::<Vec<_>>();
134+
for (cidx, sidx) in below {
135+
m.entry(cidx).or_default().push(sidx);
136+
}
137+
m
138+
};
139+
140+
let mut root = Tree::new(format!(
141+
"{entrypoint}{meta}{arrow}:{id}[{generation}]:{ref_name_and_remote}",
142+
meta = match segment.metadata {
143+
None => {
144+
""
145+
}
146+
Some(SegmentMetadata::Workspace(_)) => {
147+
"📕"
148+
}
149+
Some(SegmentMetadata::Branch(_)) => {
150+
"📙"
151+
}
152+
},
153+
id = segment.id.index(),
154+
generation = segment.generation,
155+
arrow = if segment.workspace_metadata().is_some() {
156+
"►►►"
157+
} else {
158+
"►"
159+
},
160+
entrypoint = if show_segment_entrypoint {
161+
if ep.commit.is_none() && ep.commit_index.is_some() {
162+
"🫱"
163+
} else {
164+
"👉"
165+
}
166+
} else {
167+
""
168+
},
169+
ref_name_and_remote = Graph::ref_and_remote_debug_string(
170+
segment.ref_name.as_ref(),
171+
segment.remote_tracking_ref_name.as_ref(),
172+
segment.sibling_segment_id
173+
),
174+
));
175+
for (cidx, commit) in segment.commits.iter().enumerate() {
176+
let mut commit_tree = tree_for_commit(
177+
commit,
178+
segment_is_entrypoint && Some(cidx) == ep.commit_index,
179+
if cidx + 1 != segment.commits.len() {
180+
false
181+
} else {
182+
graph.is_early_end_of_traversal(sidx)
183+
},
184+
graph.hard_limit_hit(),
185+
);
186+
if let Some(segment_indices) = connected_segments.get(&Some(cidx)) {
187+
for sidx in segment_indices {
188+
commit_tree.push(recurse_segment(graph, *sidx, seen));
189+
}
190+
}
191+
root.push(commit_tree);
192+
}
193+
// Get the segments that are directly connected.
194+
if let Some(segment_indices) = connected_segments.get(&None) {
195+
for sidx in segment_indices {
196+
root.push(recurse_segment(graph, *sidx, seen));
197+
}
198+
}
199+
200+
root
201+
}

0 commit comments

Comments
 (0)