Skip to content

Commit 74551bf

Browse files
committed
Add first test that triggers a checkout and implement it
1 parent 59ad216 commit 74551bf

File tree

15 files changed

+1023
-70
lines changed

15 files changed

+1023
-70
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::projection::workspace;
88
use crate::{
99
CommitFlags, CommitIndex, Graph, Segment, SegmentIndex,
1010
projection::{Stack, StackCommit, StackCommitFlags, StackSegment},
11+
segment,
1112
};
1213
use anyhow::Context;
1314
use bstr::{BStr, ByteSlice};
@@ -68,6 +69,17 @@ impl Workspace<'_> {
6869
.iter()
6970
.all(|s| s.segments.iter().all(|s| !s.is_entrypoint))
7071
}
72+
73+
/// Return the `commit` at the tip of the workspace itself, and do so by following empty segments along the
74+
/// first parent until the first commit is found.
75+
/// This importantly is different from the [`Graph::lookup_entrypoint()`] `commit`, as the entrypoint could be anywhere
76+
/// inside the workspace as well.
77+
///
78+
/// Note that this commit could also be the base of the workspace, particularly if there is no commits in the workspace.
79+
pub fn tip_commit(&self) -> Option<&segment::Commit> {
80+
self.graph.tip_skip_empty(self.id)
81+
}
82+
7183
/// Lookup a triple obtained by [`Self::find_owner_indexes_by_commit_id()`] or panic.
7284
pub fn lookup_commit(&self, (stack_idx, seg_idx, cidx): CommitOwnerIndexes) -> &StackCommit {
7385
&self.stacks[stack_idx].segments[seg_idx].commits[cidx]
@@ -161,23 +173,28 @@ impl Workspace<'_> {
161173
self.find_segment_and_stack_by_refname(name).is_some()
162174
}
163175

164-
/// Return `true` if the entrypoint.
176+
/// Return `true` if `name` is in the ancestry of the workspace entrypoint, and is IN the workspace as well.
165177
pub fn is_reachable_from_entrypoint(&self, name: &gix::refs::FullNameRef) -> bool {
178+
if self.ref_name().filter(|_| self.is_entrypoint()) == Some(name) {
179+
return true;
180+
}
166181
if self.is_entrypoint() {
167182
self.refname_is_segment(name)
168183
} 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 {
184+
let Some((entrypoint_stack, entrypoint_segment_idx)) =
185+
self.stacks.iter().find_map(|stack| {
186+
stack
187+
.segments
188+
.iter()
189+
.enumerate()
190+
.find_map(|(idx, segment)| segment.is_entrypoint.then_some((stack, idx)))
191+
})
192+
else {
176193
return false;
177194
};
178-
stack
195+
entrypoint_stack
179196
.segments
180-
.get(segment_idx..)
197+
.get(entrypoint_segment_idx..)
181198
.into_iter()
182199
.any(|segments| {
183200
segments

crates/but-workspace/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ flume = "0.11.1"
3535

3636
[dev-dependencies]
3737
but-testsupport.workspace = true
38-
pretty_assertions = "1.4.1"
3938
insta = "1.43.1"
4039
but-core = { workspace = true, features = ["testing"] }
4140
# for stable hashes in `gitbuter-` crates while we use them.

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

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::branch::checkout::UncommitedWorktreeChanges;
12
use std::borrow::Cow;
23

34
/// Returned by [function::apply()].
@@ -39,7 +40,7 @@ pub enum IntegrationMode {
3940
MergeIfNeeded,
4041
}
4142

42-
/// What to do if the applied branch conflicts?
43+
/// What to do if the applied branch conflicts with the existing branches?
4344
#[derive(Default, Debug, Copy, Clone)]
4445
pub enum OnWorkspaceConflict {
4546
/// Provide additional information about the stack that conflicted and the files involved in it,
@@ -63,18 +64,26 @@ pub enum WorkspaceReferenceNaming {
6364
pub struct Options {
6465
/// how the branch should be brought into the workspace.
6566
pub integration_mode: IntegrationMode,
66-
/// Decide how to deal with conflicts.
67+
/// Decide how to deal with conflicts when creating the workspace merge commit to bring in each stack.
6768
pub on_workspace_conflict: OnWorkspaceConflict,
6869
/// How the workspace reference should be named should it be created.
6970
/// The creation is always needed if there are more than one branch applied.
7071
pub workspace_reference_naming: WorkspaceReferenceNaming,
72+
/// How the worktree checkout should behave int eh light of uncommitted changes in the worktree.
73+
pub uncommitted_changes: UncommitedWorktreeChanges,
74+
/// If not `None`, the applied branch should be merged into the workspace commit at the N'th parent position.
75+
/// This is useful if the tip of a branc (at a specific position) was unapplied, and a segment within that branch
76+
/// should now be re-applied, but of course, be placed at the same spot and not end up at the end of the workspace.
77+
pub order: Option<usize>,
7178
}
7279

7380
pub(crate) mod function {
7481
use super::{Options, Outcome, WorkspaceReferenceNaming};
82+
use crate::branch::checkout;
7583
use crate::ref_info::WorkspaceExt;
76-
use anyhow::bail;
84+
use anyhow::{Context, bail};
7785
use but_core::RefMetadata;
86+
use but_graph::init::Overlay;
7887
use but_graph::projection::WorkspaceKind;
7988
use std::borrow::Cow;
8089

@@ -91,33 +100,64 @@ pub(crate) mod function {
91100
///
92101
/// On `error`, neither `repo` nor `meta` will have been changed, but `repo` may contain in-memory objects.
93102
/// Otherwise, objects will have been persisted, and references and metadata will have been updated.
94-
pub fn apply<'graph, T: RefMetadata>(
103+
pub fn apply<'graph>(
95104
branch: &gix::refs::FullNameRef,
96105
workspace: &but_graph::projection::Workspace<'graph>,
97106
repo: &mut gix::Repository,
98-
_meta: &mut T,
107+
meta: &mut impl RefMetadata,
99108
Options {
100109
integration_mode: _,
101110
on_workspace_conflict: _,
102111
workspace_reference_naming,
112+
uncommitted_changes,
113+
order: _to_be_used_in_merge,
103114
}: Options,
104115
) -> anyhow::Result<Outcome<'graph>> {
116+
if repo
117+
.try_find_reference(branch)?
118+
.is_some_and(|r| matches!(r.target(), gix::refs::TargetRef::Symbolic(_)))
119+
{
120+
bail!(
121+
"Refusing to apply symbolic ref '{}' due to potential ambiguity",
122+
branch.shorten()
123+
);
124+
}
105125
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-
}
126+
return Ok(Outcome {
127+
graph: Cow::Borrowed(workspace.graph),
128+
workspace_ref_created: false,
129+
});
118130
} else if workspace.refname_is_segment(branch) {
119-
todo!("checkout workspace so the to-be-applied branch becomes visible")
120-
}
131+
// This means our workspace encloses the desired branch, but it's not checked out yet.
132+
let commit_to_checkout = workspace
133+
.tip_commit()
134+
.context("Workspace must point to a commit to check out")?;
135+
let current_head_commit = workspace
136+
.graph
137+
.lookup_entrypoint()?
138+
.commit
139+
.context("The entrypoint must have a commit - it's equal to HEAD")?;
140+
crate::branch::safe_checkout(
141+
current_head_commit.id,
142+
commit_to_checkout.id,
143+
repo,
144+
checkout::Options {
145+
uncommitted_changes,
146+
},
147+
)?;
148+
let graph = workspace.graph.redo_traversal_with_overlay(
149+
repo,
150+
meta,
151+
Overlay::default().with_entrypoint(
152+
commit_to_checkout.id,
153+
workspace.ref_name().map(|rn| rn.to_owned()),
154+
),
155+
)?;
156+
return Ok(Outcome {
157+
graph: Cow::Owned(graph),
158+
workspace_ref_created: false,
159+
});
160+
};
121161

122162
if let Some(ws_ref_name) = workspace.ref_name()
123163
&& repo.try_find_reference(ws_ref_name)?.is_none()
@@ -133,6 +173,12 @@ pub(crate) mod function {
133173
bail!("Refusing to work on workspace whose workspace commit isn't at the top");
134174
}
135175

176+
if meta.workspace_opt(branch)?.is_some() {
177+
bail!(
178+
"Refusing to apply a reference that already is a workspace: '{}'",
179+
branch.shorten()
180+
);
181+
}
136182
// In general, we only have to deal with one branch to apply. But when we are on an adhoc workspace,
137183
// we need to assure both branches go into the existing or the new workspace.
138184
let (_workspace_ref_name_to_update, _branches_to_apply) = match &workspace.kind {

0 commit comments

Comments
 (0)