Skip to content

Commit d4a9f17

Browse files
authored
Merge pull request #10554 from Byron/next
V3 apply/unapply: MVP Early merge
2 parents d687a07 + 3ba762f commit d4a9f17

File tree

13 files changed

+395
-18
lines changed

13 files changed

+395
-18
lines changed

crates/but-core/src/ref_metadata.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,27 @@ pub struct Workspace {
3636
pub push_remote: Option<String>,
3737
}
3838

39+
impl Workspace {}
40+
3941
/// Mutations
4042
impl Workspace {
43+
/// Remove the named segment `branch`, which removes the whole stack if it's empty after removing a segment
44+
/// of that name.
45+
/// Returns `true` if it was removed or `false` if it wasn't found.
46+
pub fn remove_segment(&mut self, branch: &FullNameRef) -> bool {
47+
let Some((stack_idx, segment_idx)) = self.find_owner_indexes_by_name(branch) else {
48+
return false;
49+
};
50+
51+
let stack = &mut self.stacks[stack_idx];
52+
stack.branches.remove(segment_idx);
53+
54+
if stack.branches.is_empty() {
55+
self.stacks.remove(stack_idx);
56+
}
57+
true
58+
}
59+
4160
/// Insert `branch` as new stack if it's not yet contained in the workspace and if `order` is not `None` or push
4261
/// it to the end of the stack list.
4362
/// Note that `order` is only relevant at insertion time.
@@ -68,6 +87,28 @@ impl Workspace {
6887
}
6988
true
7089
}
90+
91+
/// Insert `branch` as new segment if it's not yet contained in the workspace,
92+
/// and insert it above the given `anchor` segment name, which maybe the tip of a stack or any segment within one
93+
/// Returns `true` if the ref was newly added, or `false` if it already existed, or `None` if `anchor` didn't exist.
94+
pub fn insert_new_segment_above_anchor_if_not_present(
95+
&mut self,
96+
branch: &FullNameRef,
97+
anchor: &FullNameRef,
98+
) -> Option<bool> {
99+
if self.contains_ref(branch) {
100+
return Some(false);
101+
};
102+
let (stack_idx, segment_idx) = self.find_owner_indexes_by_name(anchor)?;
103+
self.stacks[stack_idx].branches.insert(
104+
segment_idx,
105+
WorkspaceStackBranch {
106+
ref_name: branch.to_owned(),
107+
archived: false,
108+
},
109+
);
110+
Some(true)
111+
}
71112
}
72113

73114
/// Access

crates/but-core/tests/core/ref_metadata.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod workspace {
22
use but_core::ref_metadata::Workspace;
33

44
#[test]
5-
fn add_new_stack_if_not_present() {
5+
fn add_new_stack_if_not_present_journey() {
66
let mut ws = Workspace::default();
77
assert_eq!(ws.stacks.len(), 0);
88

@@ -18,6 +18,100 @@ mod workspace {
1818
let c_ref = r("refs/heads/C");
1919
assert!(ws.add_or_insert_new_stack_if_not_present(c_ref, None));
2020
assert_eq!(ws.stack_names().collect::<Vec<_>>(), [b_ref, a_ref, c_ref]);
21+
22+
assert!(ws.remove_segment(a_ref));
23+
assert!(ws.remove_segment(b_ref));
24+
assert!(!ws.remove_segment(b_ref));
25+
assert!(ws.remove_segment(c_ref));
26+
assert!(!ws.remove_segment(c_ref));
27+
28+
// Everything should be removed.
29+
insta::assert_debug_snapshot!(ws, @r"
30+
Workspace {
31+
ref_info: RefInfo { created_at: None, updated_at: None },
32+
stacks: [],
33+
target_ref: None,
34+
push_remote: None,
35+
}
36+
");
37+
}
38+
39+
#[test]
40+
fn insert_new_segment_above_anchor_if_not_present_journey() {
41+
let mut ws = Workspace::default();
42+
assert_eq!(ws.stacks.len(), 0);
43+
44+
let a_ref = r("refs/heads/A");
45+
let b_ref = r("refs/heads/B");
46+
assert_eq!(
47+
ws.insert_new_segment_above_anchor_if_not_present(b_ref, a_ref),
48+
None,
49+
"anchor doesn't exist"
50+
);
51+
assert!(ws.add_or_insert_new_stack_if_not_present(a_ref, None));
52+
assert_eq!(
53+
ws.insert_new_segment_above_anchor_if_not_present(b_ref, a_ref),
54+
Some(true),
55+
"anchor existed and it was added"
56+
);
57+
assert_eq!(
58+
ws.insert_new_segment_above_anchor_if_not_present(b_ref, a_ref),
59+
Some(false),
60+
"anchor existed and it was NOT added as it already existed"
61+
);
62+
63+
let c_ref = r("refs/heads/C");
64+
assert_eq!(
65+
ws.insert_new_segment_above_anchor_if_not_present(c_ref, a_ref),
66+
Some(true)
67+
);
68+
69+
insta::assert_snapshot!(but_testsupport::sanitize_uuids_and_timestamps(format!("{ws:#?}")), @r#"
70+
Workspace {
71+
ref_info: RefInfo { created_at: None, updated_at: None },
72+
stacks: [
73+
WorkspaceStack {
74+
id: 1,
75+
branches: [
76+
WorkspaceStackBranch {
77+
ref_name: FullName(
78+
"refs/heads/B",
79+
),
80+
archived: false,
81+
},
82+
WorkspaceStackBranch {
83+
ref_name: FullName(
84+
"refs/heads/C",
85+
),
86+
archived: false,
87+
},
88+
WorkspaceStackBranch {
89+
ref_name: FullName(
90+
"refs/heads/A",
91+
),
92+
archived: false,
93+
},
94+
],
95+
},
96+
],
97+
target_ref: None,
98+
push_remote: None,
99+
}
100+
"#);
101+
102+
assert!(ws.remove_segment(b_ref));
103+
assert!(ws.remove_segment(a_ref));
104+
assert!(ws.remove_segment(c_ref));
105+
106+
// Everything should be removed.
107+
insta::assert_debug_snapshot!(ws, @r"
108+
Workspace {
109+
ref_info: RefInfo { created_at: None, updated_at: None },
110+
stacks: [],
111+
target_ref: None,
112+
push_remote: None,
113+
}
114+
");
21115
}
22116

23117
fn r(name: &str) -> &gix::refs::FullNameRef {

crates/but-testing/src/args.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ pub struct Args {
3636

3737
#[derive(Debug, clap::Subcommand)]
3838
pub enum Subcommands {
39+
/// Make an existing branch appear in the workspace.
40+
Apply {
41+
/// The 0-based place in the worktree into which the branch should be inserted.
42+
#[clap(long, short = 'o')]
43+
order: Option<usize>,
44+
/// The name of the branch to apply to the workspace, like `feature` or `origin/other`.
45+
branch_name: String,
46+
},
3947
/// Add the given Git repository as project for use with GitButler.
4048
AddProject {
4149
/// The long name of the remote reference to track, like `refs/remotes/origin/main`,

crates/but-testing/src/command/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use but_core::UnifiedDiff;
33
use but_db::poll::ItemKind;
44
use but_graph::VirtualBranchesTomlMetadata;
55
use but_settings::AppSettings;
6+
use but_workspace::branch::OnWorkspaceMergeConflict;
7+
use but_workspace::branch::apply::{IntegrationMode, WorkspaceReferenceNaming};
8+
use but_workspace::branch::checkout::UncommitedWorktreeChanges;
69
use but_workspace::branch::create_reference::{Anchor, Position};
710
use but_workspace::{DiffSpec, HunkHeader};
811
use gitbutler_project::{Project, ProjectId};
@@ -634,6 +637,32 @@ pub fn remove_reference(
634637
Ok(())
635638
}
636639

640+
pub fn apply(args: &super::Args, short_name: &str, order: Option<usize>) -> anyhow::Result<()> {
641+
let (repo, project, graph, mut meta) =
642+
repo_and_maybe_project_and_graph(args, RepositoryOpenMode::Merge)?;
643+
let branch = repo.find_reference(short_name)?;
644+
let ws = graph.to_workspace()?;
645+
_ = but_workspace::branch::apply(
646+
branch.name(),
647+
&ws,
648+
&repo,
649+
&mut *meta,
650+
but_workspace::branch::apply::Options {
651+
integration_mode: IntegrationMode::AlwaysMerge,
652+
on_workspace_conflict: OnWorkspaceMergeConflict::MaterializeAndReportConflictingStacks,
653+
workspace_reference_naming: WorkspaceReferenceNaming::Default,
654+
uncommitted_changes: UncommitedWorktreeChanges::KeepAndAbortOnConflict,
655+
order,
656+
},
657+
)?;
658+
659+
if project.is_some() {
660+
// write metadata if there are projects - this is a special case while we use vb.toml.
661+
ManuallyDrop::into_inner(meta);
662+
}
663+
Ok(())
664+
}
665+
637666
pub fn create_reference(
638667
args: &super::Args,
639668
short_name: &str,

crates/but-testing/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use command::parse_diff_spec;
77
use gix::bstr::BString;
88

99
mod args;
10+
use crate::args::Subcommands;
1011
use crate::command::{RepositoryOpenMode, repo_and_maybe_project};
1112
use args::Args;
1213

@@ -31,6 +32,7 @@ async fn main() -> Result<()> {
3132
}
3233

3334
match &args.cmd {
35+
Subcommands::Apply { branch_name, order } => command::apply(&args, branch_name, *order),
3436
args::Subcommands::AddProject {
3537
switch_to_workspace,
3638
path,
@@ -39,7 +41,6 @@ async fn main() -> Result<()> {
3941
path.to_owned(),
4042
switch_to_workspace.to_owned(),
4143
),
42-
4344
args::Subcommands::RemoveReference {
4445
permit_empty_stacks,
4546
keep_metadata,

0 commit comments

Comments
 (0)