Skip to content

Commit e5c863c

Browse files
committed
SQUASH ME
1 parent 6c24af7 commit e5c863c

File tree

3 files changed

+200
-104
lines changed

3 files changed

+200
-104
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ pub type CommitOwnerIndexes = (usize, usize, CommitIndex);
6161

6262
/// Utilities
6363
impl Workspace<'_> {
64+
/// Return `true` if the workspace itself is where `HEAD` is pointing to.
65+
/// If `false`, one of the stack-segments is checked out instead.
66+
pub fn is_entrypoint(&self) -> bool {
67+
self.stacks
68+
.iter()
69+
.all(|s| s.segments.iter().all(|s| !s.is_entrypoint))
70+
}
6471
/// Lookup a triple obtained by [`Self::find_owner_indexes_by_commit_id()`] or panic.
6572
pub fn lookup_commit(&self, (stack_idx, seg_idx, cidx): CommitOwnerIndexes) -> &StackCommit {
6673
&self.stacks[stack_idx].segments[seg_idx].commits[cidx]
@@ -154,6 +161,32 @@ impl Workspace<'_> {
154161
self.find_segment_and_stack_by_refname(name).is_some()
155162
}
156163

164+
/// Return `true` if the entrypoint.
165+
pub fn is_reachable_from_entrypoint(&self, name: &gix::refs::FullNameRef) -> bool {
166+
if self.is_entrypoint() {
167+
self.refname_is_segment(name)
168+
} else {
169+
let Some((stack, segment_idx)) = self.stacks.iter().find_map(|stack| {
170+
stack
171+
.segments
172+
.iter()
173+
.enumerate()
174+
.find_map(|(idx, segment)| segment.is_entrypoint.then_some((stack, idx)))
175+
}) else {
176+
return false;
177+
};
178+
stack
179+
.segments
180+
.get(segment_idx..)
181+
.into_iter()
182+
.any(|segments| {
183+
segments
184+
.iter()
185+
.any(|s| s.ref_name.as_ref().is_some_and(|rn| rn.as_ref() == name))
186+
})
187+
}
188+
}
189+
157190
/// Try to find `name` in any named [`StackSegment`] and return it along with the stack containing it.
158191
pub fn find_segment_and_stack_by_refname(
159192
&self,

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

Lines changed: 95 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,8 @@ pub struct Options {
7373
pub(crate) mod function {
7474
use super::{Options, Outcome, WorkspaceReferenceNaming};
7575
use crate::ref_info::WorkspaceExt;
76-
use anyhow::{Context, bail};
77-
use but_core::ref_metadata::{StackId, ValueInfo, WorkspaceStack, WorkspaceStackBranch};
78-
use but_core::{RefMetadata, ref_metadata};
79-
use but_graph::init::Overlay;
76+
use anyhow::bail;
77+
use but_core::RefMetadata;
8078
use but_graph::projection::WorkspaceKind;
8179
use std::borrow::Cow;
8280

@@ -104,11 +102,21 @@ pub(crate) mod function {
104102
workspace_reference_naming,
105103
}: Options,
106104
) -> anyhow::Result<Outcome<'graph>> {
107-
if workspace.refname_is_segment(branch) {
108-
return Ok(Outcome {
109-
graph: Cow::Borrowed(workspace.graph),
110-
workspace_ref_created: false,
111-
});
105+
if workspace.is_reachable_from_entrypoint(branch) {
106+
if workspace.is_entrypoint()
107+
|| workspace
108+
.stacks
109+
.iter()
110+
.flat_map(|s| s.segments.iter().filter_map(|s| s.ref_name.as_ref()))
111+
.any(|rn| rn.as_ref() == branch)
112+
{
113+
return Ok(Outcome {
114+
graph: Cow::Borrowed(workspace.graph),
115+
workspace_ref_created: false,
116+
});
117+
}
118+
} else if workspace.refname_is_segment(branch) {
119+
todo!("checkout workspace so the to-be-applied branch becomes visible")
112120
}
113121

114122
if let Some(ws_ref_name) = workspace.ref_name() {
@@ -125,98 +133,86 @@ pub(crate) mod function {
125133
bail!("Refusing to work on workspace whose workspace commit isn't at the top");
126134
}
127135

128-
let (workspace_ref_name_to_update, ws_ref_metadata, branch_to_apply_metadata, graph) =
129-
match &workspace.kind {
130-
WorkspaceKind::Managed { ref_name }
131-
| WorkspaceKind::ManagedMissingWorkspaceCommit { ref_name } => (
132-
ref_name.clone(),
133-
meta.workspace(ref_name.as_ref())?,
134-
None,
135-
Cow::Borrowed(workspace.graph),
136-
),
137-
WorkspaceKind::AdHoc => {
138-
// We need to switch over to a possibly existing workspace.
139-
// We know that the current branch is *not* reachable from the workspace or isn't naturally included,
140-
// so it needs to be added as well.
141-
let next_ws_ref_name = match workspace_reference_naming {
142-
WorkspaceReferenceNaming::Default => {
143-
gix::refs::FullName::try_from("refs/heads/gitbutler/workspace")
144-
.expect("known statically")
145-
}
146-
WorkspaceReferenceNaming::Given(name) => name,
147-
};
148-
let ws_ref_id = match repo.try_find_reference(next_ws_ref_name.as_ref())? {
149-
None => {
150-
// Create a workspace reference later at the current AdHoc workspace id
151-
let ws_id = workspace
152-
.stacks
153-
.first()
154-
.and_then(|s| s.segments.first())
155-
.and_then(|s| s.commits.first().map(|c| c.id))
156-
.context("BUG: how can an empty ad-hoc workspace exist? Should have at least one stack-segment with commit")?;
157-
ws_id
158-
}
159-
Some(mut existing_workspace_reference) => {
160-
let id = existing_workspace_reference.peel_to_id_in_place()?;
161-
id.detach()
162-
}
163-
};
164-
165-
// Get as close as possible to what would happen if the branch was already part of it (without merging),
166-
// and branch-metadata can disambiguate. Having this data also isn't harmful.
167-
// We want to see if the branch is naturally included in workspace already.
168-
let branch_md = meta.branch(branch)?;
169-
let (branch_md_override, branch_md_to_set) = if branch_md.is_default() {
170-
(
171-
Some((branch.to_owned(), (*branch_md).clone())),
172-
Some(branch_md),
173-
)
174-
} else {
175-
(None, None)
176-
};
177-
let mut ws_md = meta.workspace(next_ws_ref_name.as_ref())?;
178-
{
179-
let ws_mut: &mut ref_metadata::Workspace = &mut *ws_md;
180-
ws_mut.stacks.push(WorkspaceStack {
181-
id: StackId::generate(),
182-
branches: vec![WorkspaceStackBranch {
183-
ref_name: branch.to_owned(),
184-
archived: false,
185-
}],
186-
})
187-
}
188-
let ws_md_override = Some((next_ws_ref_name.clone(), (*ws_md).clone()));
189-
190-
let graph = workspace.graph.redo_traversal_with_overlay(
191-
repo,
192-
meta,
193-
Overlay::default()
194-
.with_entrypoint(ws_ref_id, Some(next_ws_ref_name))
195-
.with_branch_metadata_override(branch_md_override)
196-
.with_workspace_metadata_override(ws_md_override),
197-
)?;
198-
199-
let ws = graph.to_workspace()?;
200-
if ws.refname_is_segment(branch) {
201-
todo!(
202-
"no need to add this branch, but we need to add the current ad-hoc branch instead"
203-
);
136+
// In general, we only have to deal with one branch to apply. But when we are on an adhoc workspace,
137+
// we need to assure both branches go into the existing or the new workspace.
138+
let (workspace_ref_name_to_update, branches_to_apply) = match &workspace.kind {
139+
WorkspaceKind::Managed { ref_name }
140+
| WorkspaceKind::ManagedMissingWorkspaceCommit { ref_name } => {
141+
(ref_name.clone(), vec![branch])
142+
}
143+
WorkspaceKind::AdHoc => {
144+
// We need to switch over to a possibly existing workspace.
145+
// We know that the current branch is *not* reachable from the workspace or isn't naturally included,
146+
// so it needs to be added as well.
147+
let next_ws_ref_name = match workspace_reference_naming {
148+
WorkspaceReferenceNaming::Default => {
149+
gix::refs::FullName::try_from("refs/heads/gitbutler/workspace")
150+
.expect("known statically")
204151
}
205-
206-
todo!("put current ad-hoc stack into ")
207-
}
208-
};
152+
WorkspaceReferenceNaming::Given(name) => name,
153+
};
154+
(
155+
next_ws_ref_name,
156+
workspace
157+
.ref_name()
158+
.into_iter()
159+
.chain(Some(branch))
160+
.collect(),
161+
)
162+
// let ws_ref_id = match repo.try_find_reference(next_ws_ref_name.as_ref())? {
163+
// None => {
164+
// // Create a workspace reference later at the current AdHoc workspace id
165+
// let ws_id = workspace
166+
// .stacks
167+
// .first()
168+
// .and_then(|s| s.segments.first())
169+
// .and_then(|s| s.commits.first().map(|c| c.id))
170+
// .context("BUG: how can an empty ad-hoc workspace exist? Should have at least one stack-segment with commit")?;
171+
// ws_id
172+
// }
173+
// Some(mut existing_workspace_reference) => {
174+
// let id = existing_workspace_reference.peel_to_id_in_place()?;
175+
// id.detach()
176+
// }
177+
// };
178+
179+
// let mut ws_md = meta.workspace(next_ws_ref_name.as_ref())?;
180+
// {
181+
// let ws_mut: &mut ref_metadata::Workspace = &mut *ws_md;
182+
// ws_mut.stacks.push(WorkspaceStack {
183+
// id: StackId::generate(),
184+
// branches: vec![WorkspaceStackBranch {
185+
// ref_name: branch.to_owned(),
186+
// archived: false,
187+
// }],
188+
// })
189+
// }
190+
// let ws_md_override = Some((next_ws_ref_name.clone(), (*ws_md).clone()));
191+
192+
// let graph = workspace.graph.redo_traversal_with_overlay(
193+
// repo,
194+
// meta,
195+
// Overlay::default()
196+
// .with_entrypoint(ws_ref_id, Some(next_ws_ref_name))
197+
// .with_branch_metadata_override(branch_md_override)
198+
// .with_workspace_metadata_override(ws_md_override),
199+
// )?;
200+
}
201+
};
209202

210203
// Everything worked? Assure the ref exists now that (nearly nothing) can go wrong anymore.
211-
let workspace_ref_created = false; // TODO: use rval of reference update to know if it existed.
212-
213-
if let Some(branch_md) = branch_to_apply_metadata {
214-
meta.set_branch(branch_md)?;
215-
}
216-
217-
Ok(Outcome {
218-
graph,
219-
workspace_ref_created,
220-
})
204+
let _workspace_ref_created = false; // TODO: use rval of reference update to know if it existed.
205+
206+
// if let Some(branch_md) = branch_to_apply_metadata {
207+
// meta.set_branch(branch_md)?;
208+
// }
209+
210+
todo!(
211+
"prepare outcome once all values were written out and the graph was regenerated - the simulation is now reality"
212+
);
213+
// Ok(Outcome {
214+
// graph: Cow::Borrowed(workspace.graph),
215+
// workspace_ref_created,
216+
// })
221217
}
222218
}

crates/but-workspace/tests/workspace/branch/apply_unapply_commit_uncommit.rs

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::ref_info::with_workspace_commit::utils::{
2-
named_read_only_in_memory_scenario, named_writable_scenario_with_description_and_graph,
2+
StackState, add_stack_with_segments, named_read_only_in_memory_scenario,
3+
named_writable_scenario_with_description_and_graph,
34
};
45
use crate::utils::r;
56
use but_graph::init::Options;
@@ -74,6 +75,7 @@ fn ws_ref_no_ws_commit_two_stacks_on_same_commit() -> anyhow::Result<()> {
7475
)?;
7576
insta::assert_debug_snapshot!(out, @r"
7677
Outcome {
78+
workspace_changed: false,
7779
workspace_ref_created: false,
7880
}
7981
");
@@ -90,6 +92,7 @@ fn ws_ref_no_ws_commit_two_stacks_on_same_commit() -> anyhow::Result<()> {
9092
)?;
9193
insta::assert_debug_snapshot!(out, @r"
9294
Outcome {
95+
workspace_changed: false,
9396
workspace_ref_created: false,
9497
}
9598
");
@@ -133,12 +136,13 @@ fn new_workspace_exists_elsewhere_and_to_be_applied_branch_exists_there() -> any
133136
)?;
134137
insta::assert_debug_snapshot!(out, @r"
135138
Outcome {
139+
workspace_changed: false,
136140
workspace_ref_created: false,
137141
}
138142
");
139143

140144
// HEAD must now point to the workspace (that already existed)
141-
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @"* e5d0542 (HEAD -> gitbutler/workspace, B, A) A");
145+
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @"* e5d0542 (HEAD -> gitbutler/workspace, main, B, A) A");
142146

143147
Ok(())
144148
}
@@ -177,6 +181,70 @@ fn detached_head_journey() -> anyhow::Result<()> {
177181
)?;
178182
insta::assert_debug_snapshot!(out, @r"
179183
Outcome {
184+
workspace_changed: false,
185+
workspace_ref_created: false,
186+
}
187+
");
188+
Ok(())
189+
}
190+
191+
#[test]
192+
fn auto_checkout_of_enclosing_workspace() -> anyhow::Result<()> {
193+
let (_tmp, graph, mut repo, mut meta, _description) =
194+
named_writable_scenario_with_description_and_graph(
195+
"ws-ref-no-ws-commit-one-stack-one-branch",
196+
|meta| {
197+
add_stack_with_segments(meta, 1, "A", StackState::InWorkspace, &[]);
198+
add_stack_with_segments(meta, 2, "B", StackState::InWorkspace, &[]);
199+
},
200+
)?;
201+
202+
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @"* e5d0542 (HEAD -> gitbutler/workspace, main, B, A) A");
203+
204+
let ws = graph.to_workspace()?;
205+
insta::assert_snapshot!(graph_workspace(&ws), @r"
206+
📕🏘️⚠️:0:gitbutler/workspace <> ✓! on e5d0542
207+
├── ≡📙:3:B on e5d0542
208+
│ └── 📙:3:B
209+
└── ≡📙:2:A on e5d0542
210+
└── 📙:2:A
211+
");
212+
213+
let (b_id, b_ref) = id_at(&repo, "B");
214+
let graph =
215+
but_graph::Graph::from_commit_traversal(b_id, b_ref.clone(), &meta, Default::default())?;
216+
let ws = graph.to_workspace()?;
217+
// TODO: fix this - the entrypoint shouldn't alter the stack setup.
218+
insta::assert_snapshot!(graph_workspace(&ws), @r"
219+
📕🏘️⚠️:1:gitbutler/workspace <> ✓!
220+
└── ≡👉📙:0:B
221+
├── 👉📙:0:B
222+
└── 📙:2:A
223+
└── ·e5d0542 (🏘️) ►main
224+
");
225+
226+
// Already applied (the HEAD points to it, it literally IS the workspace).
227+
let out =
228+
but_workspace::branch::apply(b_ref.as_ref(), &ws, &mut repo, &mut meta, default_options())?;
229+
insta::assert_debug_snapshot!(out, @r"
230+
Outcome {
231+
workspace_changed: false,
232+
workspace_ref_created: false,
233+
}
234+
");
235+
236+
// To apply, we just checkout the surrounding workspace.
237+
// TODO: doesn't work because A isn't a separate stack like it should.
238+
let out = but_workspace::branch::apply(
239+
r("refs/heads/A"),
240+
&ws,
241+
&mut repo,
242+
&mut meta,
243+
default_options(),
244+
)?;
245+
insta::assert_debug_snapshot!(out, @r"
246+
Outcome {
247+
workspace_changed: false,
180248
workspace_ref_created: false,
181249
}
182250
");
@@ -198,7 +266,6 @@ fn apply_nonexisting_branch_failure() -> anyhow::Result<()> {
198266
└── ·e5d0542 (🏘️) ►A, ►B, ►main
199267
");
200268

201-
// Idempotency
202269
let err = but_workspace::branch::apply(
203270
r("refs/heads/does-not-exist"),
204271
&ws,
@@ -209,7 +276,7 @@ fn apply_nonexisting_branch_failure() -> anyhow::Result<()> {
209276
.unwrap_err();
210277
assert_eq!(err.to_string(), "TBD");
211278

212-
// This fails without any effective change.
279+
// Nothing should be changed
213280
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @"* e5d0542 (HEAD -> gitbutler/workspace, main, B, A) A");
214281
Ok(())
215282
}
@@ -234,7 +301,7 @@ fn unborn_apply_needs_base() -> anyhow::Result<()> {
234301
└── :0:main
235302
");
236303

237-
// Idempotency
304+
// Idempotency in ad-hoc workspace
238305
let out = but_workspace::branch::apply(
239306
r("refs/heads/main"),
240307
&ws,

0 commit comments

Comments
 (0)