Skip to content

Commit 9aa565b

Browse files
committed
Add tests for single-branch but branch apply
These are the things only modern code can do. While at it, also see what happens if there is no worktree, i.e. as there is a bare repository.
1 parent 5a84f11 commit 9aa565b

File tree

11 files changed

+250
-44
lines changed

11 files changed

+250
-44
lines changed

crates/but-graph/src/debug.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,13 @@ impl Graph {
262262
static SUFFIX: AtomicUsize = AtomicUsize::new(0);
263263
let suffix = SUFFIX.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
264264
let svg_name = format!("debug-graph-{suffix:02}.svg");
265+
let svg_path = std::env::var_os("CARGO_MANIFEST_DIR")
266+
.map(std::path::PathBuf::from)
267+
.unwrap_or_default()
268+
.join(svg_name);
265269
let mut dot = std::process::Command::new("dot")
266-
.args(["-Tsvg", "-o", &svg_name])
270+
.args(["-Tsvg", "-o"])
271+
.arg(&svg_path)
267272
.stdin(Stdio::piped())
268273
.stdout(Stdio::piped())
269274
.stderr(Stdio::piped())
@@ -284,11 +289,12 @@ impl Graph {
284289

285290
assert!(
286291
std::process::Command::new("open")
287-
.arg(&svg_name)
292+
.arg(&svg_path)
288293
.status()
289294
.unwrap()
290295
.success(),
291-
"Opening of {svg_name} failed"
296+
"Opening of {svg_path} failed",
297+
svg_path = svg_path.display()
292298
);
293299
}
294300

crates/but-graph/src/projection/stack.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ impl Stack {
9595

9696
impl Stack {
9797
/// A one-line string representing the stack itself, without its contents.
98-
pub fn debug_string(&self) -> String {
98+
///
99+
/// Use `id_override` to have it use this (usually controlled) id instead of what otherwise
100+
/// would be a generated one.
101+
pub fn debug_string(&self, id_override: Option<StackId>) -> String {
99102
let mut dbg = self
100103
.segments
101104
.first()
@@ -105,7 +108,7 @@ impl Stack {
105108
dbg.push_str(&base.to_hex_with_len(7).to_string());
106109
}
107110
dbg.insert(0, '≡');
108-
if let Some(id) = self.id {
111+
if let Some(id) = id_override.or(self.id) {
109112
let id_string = id.to_string().replace("0", "").replace("-", "");
110113
dbg.push_str(&format!(
111114
" {{{}}}",
@@ -122,7 +125,7 @@ impl Stack {
122125

123126
impl std::fmt::Debug for Stack {
124127
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125-
let mut s = f.debug_struct(&format!("Stack({})", self.debug_string()));
128+
let mut s = f.debug_struct(&format!("Stack({})", self.debug_string(None)));
126129
s.field("segments", &self.segments);
127130
if let Some(stack_id) = self.id {
128131
s.field("id", &stack_id);

crates/but-meta/src/legacy.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ impl Snapshot {
5555
if self.content == Default::default() {
5656
std::fs::remove_file(&self.path)?;
5757
} else {
58+
if let Some(dir) = self.path.parent() {
59+
std::fs::create_dir_all(dir)?;
60+
}
5861
fs::write(
5962
&self.path,
6063
toml::to_string(&self.to_consistent_data(reconcile))?,

crates/but-testsupport/src/graph.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::{BTreeMap, BTreeSet};
22

3+
use but_core::ref_metadata::StackId;
34
use but_graph::{
45
EntryPoint, Graph, SegmentIndex, SegmentMetadata, projection::StackCommitDebugFlags,
56
};
@@ -9,23 +10,43 @@ type StringTree = Tree<String>;
910

1011
/// Visualize `graph` as a tree.
1112
pub fn graph_workspace(workspace: &but_graph::projection::Workspace<'_>) -> StringTree {
13+
graph_workspace_inner(workspace, None)
14+
}
15+
16+
/// Visualize `graph` as a tree, and remap random stack ids to something deterministic.
17+
pub fn graph_workspace_determinisitcally(
18+
workspace: &but_graph::projection::Workspace<'_>,
19+
) -> StringTree {
20+
graph_workspace_inner(workspace, Some(Default::default()))
21+
}
22+
23+
fn graph_workspace_inner(
24+
workspace: &but_graph::projection::Workspace<'_>,
25+
mut stack_id_map: Option<BTreeMap<StackId, StackId>>,
26+
) -> StringTree {
1227
let commit_flags = if workspace.graph.hard_limit_hit() {
1328
StackCommitDebugFlags::HardLimitReached
1429
} else {
1530
Default::default()
1631
};
1732
let mut root = Tree::new(workspace.debug_string());
1833
for stack in &workspace.stacks {
19-
root.push(tree_for_stack(stack, commit_flags));
34+
root.push(tree_for_stack(stack, commit_flags, stack_id_map.as_mut()));
2035
}
2136
root
2237
}
2338

2439
fn tree_for_stack(
2540
stack: &but_graph::projection::Stack,
2641
commit_flags: StackCommitDebugFlags,
42+
stack_id_map: Option<&mut BTreeMap<StackId, StackId>>,
2743
) -> StringTree {
28-
let mut root = Tree::new(stack.debug_string());
44+
let mut root = Tree::new(
45+
stack.debug_string(stack.id.zip(stack_id_map).map(|(id, map)| {
46+
let next_id = StackId::from_number_for_testing((map.len() + 1) as u128);
47+
*map.entry(id).or_insert(next_id)
48+
})),
49+
);
2950
for segment in &stack.segments {
3051
root.push(tree_for_stack_segment(segment, commit_flags));
3152
}

crates/but-testsupport/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,8 +592,7 @@ pub fn debug_str(input: &dyn std::fmt::Debug) -> String {
592592
}
593593

594594
mod graph;
595-
596-
pub use graph::{graph_tree, graph_workspace};
595+
pub use graph::{graph_tree, graph_workspace, graph_workspace_determinisitcally};
597596

598597
mod prepare_cmd_env;
599598
pub use prepare_cmd_env::isolate_env_std_cmd;

crates/but/src/command/branch/apply.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,25 @@ pub fn apply(ctx: Context, branch_name: &str, out: &mut OutputChannel) -> anyhow
88
let repo = ctx.repo.get()?;
99

1010
let reference = repo.find_reference(branch_name)?;
11-
let _outcome = but_api::branch::apply(&ctx, reference.name())?;
11+
let mut outcome = but_api::branch::apply(&ctx, reference.name())?;
1212

1313
if let Some(out) = out.for_human() {
14-
let short_name = reference.name().shorten();
15-
let is_remote_reference = reference
16-
.name()
17-
.category()
18-
.is_some_and(|c| c == Category::RemoteBranch);
19-
if is_remote_reference {
20-
writeln!(out, "Applied remote branch '{short_name}' to workspace")
21-
} else {
22-
writeln!(out, "Applied branch '{short_name}' to workspace")
23-
}?;
14+
// Since `applied_branches` is the actual applied branches, turning remotes into local branches,
15+
// hack it into submission while the legacy version exists that it has to match.
16+
let special_case_remove_me_once_there_is_no_legacy_apply =
17+
outcome.applied_branches.len() == 1;
18+
if special_case_remove_me_once_there_is_no_legacy_apply {
19+
outcome.applied_branches = vec![reference.name().to_owned()];
20+
}
21+
for name in outcome.applied_branches {
22+
let short_name = name.shorten();
23+
let is_remote_reference = name.category().is_some_and(|c| c == Category::RemoteBranch);
24+
if is_remote_reference {
25+
writeln!(out, "Applied remote branch '{short_name}' to workspace")
26+
} else {
27+
writeln!(out, "Applied branch '{short_name}' to workspace")
28+
}?;
29+
}
2430
} else if let Some(out) = out.for_shell() {
2531
writeln!(out, "{reference_name}", reference_name = reference.name())?;
2632
}

crates/but/tests/but/command/branch/apply.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,81 @@
11
use crate::utils::{Sandbox, setup_metadata};
22
use snapbox::str;
33

4+
#[cfg(not(feature = "legacy"))]
5+
#[test]
6+
fn single_branch() -> anyhow::Result<()> {
7+
let env = Sandbox::open_with_default_settings("one-fork")?;
8+
insta::assert_snapshot!(env.git_log()?, @r"
9+
* bf53300 (A) add A
10+
| * b1540e5 (HEAD -> main) M
11+
|/
12+
| * 0e391b2 (origin/B) add B
13+
|/
14+
* e31e6ca (origin/main, origin/HEAD) add init
15+
");
16+
17+
env.but("branch apply A")
18+
.assert()
19+
.success()
20+
.stderr_eq(str![])
21+
.stdout_eq(str![[r#"
22+
Applied branch 'main' to workspace
23+
Applied branch 'A' to workspace
24+
25+
"#]]);
26+
27+
insta::assert_snapshot!(env.workspace_debug_at_head()?, @r"
28+
📕🏘️:0:gitbutler/workspace[🌳] <> ✓!
29+
└── ≡📙:1:main {1}
30+
├── 📙:1:main
31+
│ └── ·b1540e5 (🏘️)
32+
└── 📙:3:A →:2:
33+
├── ·bf53300*
34+
└── ·e31e6ca (🏘️)
35+
");
36+
37+
// TODO: fix this: A should be in workspace. Also: where is our metadata?
38+
insta::assert_snapshot!(env.git_log()?, @r"
39+
* bf53300 (A) add A
40+
| * e97d217 (HEAD -> gitbutler/workspace) GitButler Workspace Commit
41+
| * b1540e5 (main) M
42+
|/
43+
| * 0e391b2 (origin/B) add B
44+
|/
45+
* e31e6ca (origin/main, origin/HEAD) add init
46+
");
47+
48+
env.but("branch apply origin/B")
49+
.assert()
50+
.success()
51+
.stdout_eq(str![[r#"
52+
Applied remote branch 'origin/B' to workspace
53+
54+
"#]])
55+
.stderr_eq(str![""]);
56+
insta::assert_snapshot!(env.workspace_debug_at_head()?, @r"
57+
📕🏘️:0:gitbutler/workspace[🌳] <> ✓!
58+
└── ≡📙:1:main {1}
59+
├── 📙:1:main
60+
│ └── ·b1540e5 (🏘️)
61+
└── 📙:4:B <> origin/B →:2:⇡1
62+
├── ·0e391b2*
63+
└── ·e31e6ca (🏘️)
64+
");
65+
66+
// TODO: should be success and create a local tracking branch.
67+
insta::assert_snapshot!(env.git_log()?, @r"
68+
* bf53300 (A) add A
69+
| * 0e391b2 (origin/B, B) add B
70+
|/
71+
| * e97d217 (HEAD -> gitbutler/workspace) GitButler Workspace Commit
72+
| * b1540e5 (main) M
73+
|/
74+
* e31e6ca (origin/main, origin/HEAD) add init
75+
");
76+
Ok(())
77+
}
78+
479
use crate::command::branch::apply::utils::create_local_branch_with_commit_with_message;
580
use utils::create_local_branch_with_commit;
681

crates/but/tests/but/journey.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
//! Tests for various nice user-journeys, from different starting points, performing multiple common steps in sequence.
2-
use snapbox::{file, str};
2+
use crate::utils::Sandbox;
3+
use snapbox::str;
34

4-
use crate::utils::{Sandbox, setup_metadata};
5+
#[cfg(not(feature = "legacy"))]
6+
#[test]
7+
fn from_unborn() -> anyhow::Result<()> {
8+
let env = Sandbox::open_with_default_settings("unborn")?;
9+
insta::assert_snapshot!(env.git_log()?, @r"");
10+
11+
env.but("branch apply main")
12+
.assert()
13+
.failure()
14+
.stderr_eq(str![[r#"
15+
Error: The reference 'main' did not exist
16+
17+
"#]]);
18+
19+
// TODO: we should be able to use the CLI to create a commit
20+
Ok(())
21+
}
522

23+
// TODO: maybe this should be a non-legacy journey only as we start out without workspace?
24+
#[cfg(feature = "legacy")]
625
#[test]
7-
fn from_scratch_needs_work() -> anyhow::Result<()> {
26+
fn from_empty() -> anyhow::Result<()> {
827
let env = Sandbox::empty()?;
928

1029
env.but("status").assert().failure().stderr_eq(str![[r#"
@@ -94,8 +113,11 @@ Error: workspace at refs/heads/main is missing a base
94113
Ok(())
95114
}
96115

116+
#[cfg(feature = "legacy")]
97117
#[test]
98118
fn from_workspace() -> anyhow::Result<()> {
119+
use crate::utils::setup_metadata;
120+
use snapbox::file;
99121
let env = Sandbox::init_scenario_with_target_and_default_settings("two-stacks")?;
100122
insta::assert_snapshot!(env.git_log()?, @r"
101123
* c128bce (HEAD -> gitbutler/workspace) GitButler Workspace Commit

crates/but/tests/but/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@ mod command;
22
// TODO: Id tests can be on integration level, but shouldn't involve the CLI
33
#[cfg(feature = "legacy")]
44
mod id;
5-
#[cfg(feature = "legacy")]
65
mod journey;
76
pub mod utils;

0 commit comments

Comments
 (0)