Skip to content

Commit 372a2c7

Browse files
committed
SQUASH ME
1 parent c048ffa commit 372a2c7

File tree

4 files changed

+150
-60
lines changed

4 files changed

+150
-60
lines changed

crates/but-core/src/ref_metadata.rs

Lines changed: 19 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.

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

Lines changed: 32 additions & 2 deletions
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,10 +18,26 @@ 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+
");
2137
}
2238

2339
#[test]
24-
fn insert_new_segment_above_anchor_if_not_present() {
40+
fn insert_new_segment_above_anchor_if_not_present_journey() {
2541
let mut ws = Workspace::default();
2642
assert_eq!(ws.stacks.len(), 0);
2743

@@ -82,6 +98,20 @@ mod workspace {
8298
push_remote: None,
8399
}
84100
"#);
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+
");
85115
}
86116

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

crates/but-workspace/src/branch/apply.rs

Lines changed: 98 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -263,39 +263,7 @@ pub(crate) mod function {
263263
{
264264
let ws_mut: &mut Workspace = &mut ws_md;
265265
for rn in &branches_to_apply {
266-
// Here we have to check if the new ref would be able to become its own stack,
267-
// or if it has to be a dependent branch. Stacks only work if the ref rests on a base
268-
// outside the workspace, so if we find it in the workspace (in an ambiguous spot) it must be
269-
// a dependent branch
270-
if let Some(segment_to_insert_above) = workspace
271-
.stacks
272-
.iter()
273-
.flat_map(|stack| stack.segments.iter())
274-
.find_map(|segment| {
275-
segment.commits.iter().flat_map(|c| c.refs.iter()).find_map(
276-
|ambiguous_rn| {
277-
(ambiguous_rn.as_ref() == *rn)
278-
.then_some(segment.ref_name.as_ref())
279-
.flatten()
280-
},
281-
)
282-
})
283-
{
284-
if ws_mut
285-
.insert_new_segment_above_anchor_if_not_present(
286-
rn,
287-
segment_to_insert_above.as_ref(),
288-
)
289-
.is_none()
290-
{
291-
// For now bail, until we know it's worth fixing this case automatically.
292-
bail!(
293-
"Missing reference {segment_to_insert_above} which should be known to workspace metadata to serve as insertion position for {rn}"
294-
);
295-
}
296-
} else {
297-
ws_mut.add_or_insert_new_stack_if_not_present(rn, order);
298-
}
266+
ws_mut.add_or_insert_new_stack_if_not_present(rn, order);
299267
}
300268
}
301269
let ws_md_override = Some((workspace_ref_name_to_update.clone(), (*ws_md).clone()));
@@ -368,8 +336,9 @@ pub(crate) mod function {
368336
);
369337
}
370338
// At this point, the workspace-metadata already knows the new branch(es), but the workspace itself
371-
// doesn't see one or more of to-be-applied branches. These are, however, part of the graph by now,
372-
// and we want to try to create a workspace merge.
339+
// doesn't see one or more of to-be-applied branches (to become stacks).
340+
// These are, however, part of the graph by now, and we want to try to create a workspace
341+
// merge.
373342
let mut in_memory_repo = repo.clone().for_tree_diffing()?.with_object_memory();
374343
let merge_result = WorkspaceCommit::from_new_merge_with_metadata(
375344
&ws_md.stacks,
@@ -390,33 +359,104 @@ pub(crate) mod function {
390359
.entrypoint_commit()
391360
.context("BUG: how is it possible that there is no head commit?")?
392361
.id;
393-
let new_head_id = merge_result.workspace_commit_id;
394-
let graph = graph.redo_traversal_with_overlay(
395-
&in_memory_repo,
396-
meta,
397-
overlay.with_entrypoint(new_head_id, Some(workspace_ref_name_to_update.clone())),
398-
)?;
362+
let mut new_head_id = merge_result.workspace_commit_id;
363+
let overlay =
364+
overlay.with_entrypoint(new_head_id, Some(workspace_ref_name_to_update.clone()));
365+
let mut graph =
366+
graph.redo_traversal_with_overlay(&in_memory_repo, meta, overlay.clone())?;
399367

400368
let workspace = graph.to_workspace()?;
401-
let unapplied_branches: Vec<_> = branches_to_apply
402-
.iter()
403-
.filter_map(|rn| {
404-
if workspace.refname_is_segment(rn) {
405-
None
406-
} else {
407-
Some(rn)
408-
}
409-
})
410-
.collect();
369+
let collect_unapplied_branches = |workspace: &but_graph::projection::Workspace| {
370+
branches_to_apply
371+
.iter()
372+
.filter(|rn| workspace.refname_is_segment(rn))
373+
.collect::<Vec<_>>()
374+
};
375+
let unapplied_branches = collect_unapplied_branches(&workspace);
411376
if !unapplied_branches.is_empty() {
412-
bail!(
413-
"Unexpectedly failed to apply {branches} which is/are still not in the workspace",
414-
branches = unapplied_branches
377+
// Now that the merge is done, try to redo the operation one last time with dependent branches instead.
378+
// Only do that for the still unapplied branches, which should always find some sort of anchor.
379+
let ws_mut: &mut Workspace = &mut ws_md;
380+
for branch_to_remove in &unapplied_branches {
381+
ws_mut.remove_segment(branch_to_remove);
382+
}
383+
for rn in &unapplied_branches {
384+
// Here we have to check if the new ref would be able to become its own stack,
385+
// or if it has to be a dependent branch. Stacks only work if the ref rests on a base
386+
// outside the workspace, so if we find it in the workspace (in an ambiguous spot) it must be
387+
// a dependent branch
388+
if let Some(segment_to_insert_above) = workspace
389+
.stacks
415390
.iter()
416-
.map(|rn| rn.shorten().to_string())
417-
.collect::<Vec<_>>()
418-
.join(", ")
419-
)
391+
.flat_map(|stack| stack.segments.iter())
392+
.find_map(|segment| {
393+
segment.commits.iter().flat_map(|c| c.refs.iter()).find_map(
394+
|ambiguous_rn| {
395+
(ambiguous_rn.as_ref() == **rn)
396+
.then_some(segment.ref_name.as_ref())
397+
.flatten()
398+
},
399+
)
400+
})
401+
{
402+
if ws_mut
403+
.insert_new_segment_above_anchor_if_not_present(
404+
rn,
405+
segment_to_insert_above.as_ref(),
406+
)
407+
.is_none()
408+
{
409+
// For now bail, until we know it's worth fixing this case automatically.
410+
bail!(
411+
"Missing reference {segment_to_insert_above} which should be known to workspace metadata to serve as insertion position for {rn}"
412+
);
413+
}
414+
} else {
415+
bail!(
416+
"Unexpectedly failed to find anchor for {rn} to make it a dependent branch"
417+
)
418+
}
419+
}
420+
421+
// Redo the merge, with the different stack configuration.
422+
// Note that this is the exception, typically using stacks will be fine.
423+
let merge_result = WorkspaceCommit::from_new_merge_with_metadata(
424+
&ws_md.stacks,
425+
workspace.graph,
426+
&in_memory_repo,
427+
Some(branch),
428+
)?;
429+
430+
if merge_result.has_conflicts() && on_workspace_conflict.should_abort() {
431+
return Ok(Outcome {
432+
graph: Cow::Owned(graph),
433+
workspace_ref_created: needs_ws_ref_creation,
434+
workspace_merge: Some(merge_result),
435+
});
436+
}
437+
new_head_id = merge_result.workspace_commit_id;
438+
let ws_md_override = Some((workspace_ref_name_to_update.clone(), (*ws_md).clone()));
439+
440+
graph = graph.redo_traversal_with_overlay(
441+
&in_memory_repo,
442+
meta,
443+
overlay
444+
.with_entrypoint(new_head_id, Some(workspace_ref_name_to_update.clone()))
445+
.with_workspace_metadata_override(ws_md_override),
446+
)?;
447+
let workspace = graph.to_workspace()?;
448+
let unapplied_branches = collect_unapplied_branches(&workspace);
449+
450+
if !unapplied_branches.is_empty() {
451+
bail!(
452+
"Unexpectedly failed to apply {branches} which is/are still not in the workspace",
453+
branches = unapplied_branches
454+
.iter()
455+
.map(|rn| rn.shorten().to_string())
456+
.collect::<Vec<_>>()
457+
.join(", ")
458+
)
459+
}
420460
}
421461

422462
// All work is done, persist and exit.

crates/gitbutler-branch-actions/src/base.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ fn go_back_to_integration(ctx: &CommandContext, default_target: &Target) -> Resu
7070
&gix_repo,
7171
but_workspace::branch::checkout::Options {
7272
uncommitted_changes: UncommitedWorktreeChanges::KeepAndAbortOnConflict,
73+
skip_head_update: false,
7374
},
7475
)?;
7576
} else {

0 commit comments

Comments
 (0)