Skip to content

Commit 08aa662

Browse files
authored
Merge pull request #14700 from gitbutlerapp/GB-1745/support-anonymous-segments-in-but-status
fix(but): assign short IDs to anonymous segments
2 parents 88a2762 + 7a4b338 commit 08aa662

5 files changed

Lines changed: 80 additions & 19 deletions

File tree

crates/but/src/command/legacy/status/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,16 +1281,22 @@ fn print_group(
12811281
branch_suffix.push(Span::styled(no_commits, t.hint));
12821282
}
12831283

1284+
let (decoration_start, decoration_end) = if branch.is_empty() {
1285+
("", "")
1286+
} else {
1287+
(" [", "]")
1288+
};
1289+
12841290
output.branch(
12851291
Vec::from([Span::raw(format!("┊{notch}┄"))]),
12861292
BranchLineContent {
12871293
id: Vec::from([Span::styled(segment.short_id.clone(), t.cli_id)]),
1288-
decoration_start: Vec::from([Span::raw(" [")]),
1294+
decoration_start: Vec::from([Span::raw(decoration_start)]),
12891295
branch_name: Vec::from([
12901296
Span::styled(branch, t.local_branch),
12911297
Span::raw(workspace),
12921298
]),
1293-
decoration_end: Vec::from([Span::raw("]")]),
1299+
decoration_end: Vec::from([Span::raw(decoration_end)]),
12941300
suffix: branch_suffix,
12951301
},
12961302
branch_cli_id,

crates/but/src/id/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,10 @@ impl IdMap {
779779
}
780780

781781
fn parse_element<'a>(&'a self, element: &str) -> anyhow::Result<Vec<Box<dyn Node<'a> + 'a>>> {
782+
if element.is_empty() {
783+
return Ok(vec![]);
784+
}
785+
782786
// Parse known suffixes.
783787
if let Some(prefix) = element.strip_suffix("@{stack}") {
784788
let mut matches = Vec::<Box<dyn Node<'a> + 'a>>::new();

crates/but/src/id/stacks_info.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -88,24 +88,25 @@ fn populate_branch_short_ids(
8888
.iter_mut()
8989
.flat_map(|stack| stack.segments.iter_mut())
9090
{
91-
let Some(branch_name) = segment.branch_name() else {
92-
// The branch CliId is its name, so if this segment doesn't have a
93-
// name, it doesn't need an ID.
94-
continue;
95-
};
96-
segment.short_id = 'short_id: {
97-
// Find first non-conflicting pair or triple (i.e. used in
98-
// exactly one branch) and use it.
99-
for candidate in branch_name.windows(2).chain(branch_name.windows(3)) {
100-
if let Ok(short_id) = str::from_utf8(candidate)
101-
&& let Some(true) = maybe_mark_used(candidate, id_usage)
102-
{
103-
break 'short_id short_id.to_owned();
91+
if let Some(branch_name) = segment.branch_name() {
92+
segment.short_id = 'short_id: {
93+
// Find first non-conflicting pair or triple (i.e. used in
94+
// exactly one branch) and use it.
95+
for candidate in branch_name.windows(2).chain(branch_name.windows(3)) {
96+
if let Ok(short_id) = str::from_utf8(candidate)
97+
&& let Some(true) = maybe_mark_used(candidate, id_usage)
98+
{
99+
break 'short_id short_id.to_owned();
100+
}
104101
}
105-
}
106-
// If none available, use next available ID.
107-
id_usage.next_available()?.to_short_id()
108-
};
102+
// If none available, use next available ID.
103+
id_usage.next_available()?.to_short_id()
104+
};
105+
} else {
106+
// This sgement is anonymous, so we have no name to base the ID on. We just assign it a
107+
// generic ID, which allows some rudimentary stuff to work (e.g. `but status`).
108+
segment.short_id = id_usage.next_available()?.to_short_id();
109+
}
109110
}
110111

111112
Ok(())

crates/but/tests/but/command/status.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,33 @@ fn worktrees() {
4646
]);
4747
}
4848

49+
#[test]
50+
fn anonymous_segment() {
51+
let env =
52+
Sandbox::init_scenario_with_target_and_default_settings("one-stack-anonymous-segment");
53+
env.setup_metadata(&["A"]);
54+
55+
env.but("status")
56+
.assert()
57+
.success()
58+
.stderr_eq(snapbox::str![])
59+
.stdout_eq(snapbox::str![[r#"
60+
╭┄zz [uncommitted] (no changes)
61+
62+
┊╭┄g0
63+
┊● b36b65c anonymous (no changes)
64+
┊│
65+
┊├┄h0 [A]
66+
┊● 9477ae7 add A
67+
├╯
68+
69+
┴ 0dc3733 (common base) 2000-01-02 add M
70+
71+
Hint: run `but help` for all commands
72+
73+
"#]]);
74+
}
75+
4976
#[test]
5077
fn unborn() {
5178
let env = Sandbox::open_scenario_with_target_and_default_settings("unborn");
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
3+
set -eu -o pipefail
4+
5+
source "${BASH_SOURCE[0]%/*}/shared.sh"
6+
7+
### General Description
8+
9+
# A workspace with one named branch and an unreferenced commit above it, projected as an
10+
# anonymous stack segment.
11+
git-init-frozen
12+
commit-file M
13+
setup_target_to_match_main
14+
15+
git checkout -b A
16+
commit-file A
17+
18+
git checkout --detach
19+
commit anonymous
20+
git branch -f A HEAD^
21+
22+
# HEAD remains on the anonymous commit, so the workspace commit is created above it.
23+
create_workspace_commit_once

0 commit comments

Comments
 (0)