Skip to content

Commit 42f3e10

Browse files
committed
Basic apply and unapply (heavily WIP)
The idea is to develop both at the same time so it's easier to test that 'unapply' truly goes back to the (mostly) original state.
1 parent 2242956 commit 42f3e10

16 files changed

+826
-65
lines changed

crates/but-graph/src/init/mod.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ mod remotes;
2121
mod overlay;
2222
mod post;
2323

24+
pub(crate) type Entrypoint = Option<(gix::ObjectId, Option<gix::refs::FullName>)>;
25+
2426
/// A way to define information to be served from memory, instead of from the underlying data source, when
2527
/// [initializing](Graph::from_commit_traversal()) the graph.
2628
#[derive(Debug, Default)]
2729
pub struct Overlay {
30+
entrypoint: Entrypoint,
2831
nonoverriding_references: Vec<gix::refs::Reference>,
2932
meta_branches: Vec<(gix::refs::FullName, ref_metadata::Branch)>,
3033
workspace: Option<(gix::refs::FullName, ref_metadata::Workspace)>,
@@ -141,7 +144,7 @@ impl Graph {
141144
let mut graph = Graph::default();
142145
// It's OK to default-initialise this here as overlays are only used when redoing
143146
// the traversal.
144-
let (_repo, meta) = Overlay::default().into_parts(repo, meta);
147+
let (_repo, meta, _entrypoint) = Overlay::default().into_parts(repo, meta);
145148
graph.insert_root(branch_segment_from_name_and_meta(
146149
Some((ref_name, None)),
147150
&meta,
@@ -228,7 +231,7 @@ impl Graph {
228231
meta: &impl RefMetadata,
229232
options: Options,
230233
) -> anyhow::Result<Self> {
231-
let (repo, meta) = Overlay::default().into_parts(tip.repo, meta);
234+
let (repo, meta, _entrypoint) = Overlay::default().into_parts(tip.repo, meta);
232235
Graph::from_commit_traversal_inner(tip.detach(), &repo, ref_name, &meta, options)
233236
}
234237

@@ -654,16 +657,22 @@ impl Graph {
654657
meta: &impl RefMetadata,
655658
overlay: Overlay,
656659
) -> anyhow::Result<Self> {
657-
let (repo, meta) = overlay.into_parts(repo, meta);
658-
let tip_sidx = self
659-
.entrypoint
660-
.context("BUG: entrypoint must always be set")?
661-
.0;
662-
let tip = self
663-
.tip_skip_empty(tip_sidx)
664-
.context("BUG: entrypoint must eventually point to a commit")?
665-
.id;
666-
let ref_name = self[tip_sidx].ref_name.clone();
660+
let (repo, meta, entrypoint) = overlay.into_parts(repo, meta);
661+
let (tip, ref_name) = match entrypoint {
662+
Some(t) => t,
663+
None => {
664+
let tip_sidx = self
665+
.entrypoint
666+
.context("BUG: entrypoint must always be set")?
667+
.0;
668+
let tip = self
669+
.tip_skip_empty(tip_sidx)
670+
.context("BUG: entrypoint must eventually point to a commit")?
671+
.id;
672+
let ref_name = self[tip_sidx].ref_name.clone();
673+
(tip, ref_name)
674+
}
675+
};
667676
Graph::from_commit_traversal_inner(tip, &repo, ref_name, &meta, self.options.clone())
668677
}
669678

crates/but-graph/src/init/overlay.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::init::Overlay;
21
use crate::init::walk::RefsById;
2+
use crate::init::{Entrypoint, Overlay};
33
use crate::is_workspace_ref_name;
44
use but_core::{RefMetadata, ref_metadata};
55
use gix::prelude::ReferenceExt;
@@ -17,6 +17,17 @@ impl Overlay {
1717
self
1818
}
1919

20+
/// Override the starting position of the traversal by setting it to `id`,
21+
/// and optionally, by providing the `ref_name` that points to `id`.
22+
pub fn with_entrypoint(
23+
mut self,
24+
id: gix::ObjectId,
25+
ref_name: Option<gix::refs::FullName>,
26+
) -> Self {
27+
self.entrypoint = Some((id, ref_name));
28+
self
29+
}
30+
2031
/// Serve the given `branches` metadata from memory, as if they would exist,
2132
/// possibly overriding metadata of a ref that already exists.
2233
pub fn with_branch_metadata_override(
@@ -43,14 +54,15 @@ impl Overlay {
4354
self,
4455
repo: &'repo gix::Repository,
4556
meta: &'meta T,
46-
) -> (OverlayRepo<'repo>, OverlayMetadata<'meta, T>)
57+
) -> (OverlayRepo<'repo>, OverlayMetadata<'meta, T>, Entrypoint)
4758
where
4859
T: RefMetadata,
4960
{
5061
let Overlay {
5162
nonoverriding_references,
5263
meta_branches,
5364
workspace,
65+
entrypoint,
5466
} = self;
5567
(
5668
OverlayRepo {
@@ -62,6 +74,7 @@ impl Overlay {
6274
meta_branches,
6375
workspace,
6476
},
77+
entrypoint,
6578
)
6679
}
6780
}

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

Lines changed: 41 additions & 3 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]
@@ -149,6 +156,37 @@ impl Workspace<'_> {
149156
})
150157
}
151158

159+
/// Return `true` if `name` is contained in the workspace as segment.
160+
pub fn refname_is_segment(&self, name: &gix::refs::FullNameRef) -> bool {
161+
self.find_segment_and_stack_by_refname(name).is_some()
162+
}
163+
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+
152190
/// Try to find `name` in any named [`StackSegment`] and return it along with the stack containing it.
153191
pub fn find_segment_and_stack_by_refname(
154192
&self,
@@ -189,9 +227,9 @@ pub enum WorkspaceKind {
189227
/// The name of the reference pointing to the workspace commit. Useful for deriving the workspace name.
190228
ref_name: gix::refs::FullName,
191229
},
192-
/// Information for when a workspace reference was advanced by hand and does not point to a
193-
/// managed workspace commit anymore.
194-
/// That commit, however, is reachable by following the first parent from the workspace reference.
230+
/// Information for when a workspace reference was *possibly* advanced by hand and does not point to a
231+
/// managed workspace commit (anymore).
232+
/// That workspace commit, may be reachable by following the first parent from the workspace reference.
195233
///
196234
/// Note that the stacks that follow *will* be in unusable if the workspace commit is in a segment below,
197235
/// but typically is usable if there is just a single real stack, or any amount of virtual stacks below

crates/but-graph/tests/graph/init/with_workspace.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ fn stack_configuration_is_respected_if_one_of_them_is_an_entrypoint() -> anyhow:
699699
└── 👉📕►►►:0[0]:gitbutler/workspace
700700
├── 📙►:2[1]:A
701701
│ └── ►:1[2]:anon:
702-
│ └── ·fafd9d0 (⌂|🏘|1) ►main
702+
│ └── ·fafd9d0 (⌂|🏘|1) ►main
703703
└── 📙►:3[1]:B
704704
└── →:1:
705705
");
@@ -720,7 +720,7 @@ fn stack_configuration_is_respected_if_one_of_them_is_an_entrypoint() -> anyhow:
720720
└── 📕►►►:1[0]:gitbutler/workspace
721721
└── 👉📙►:0[1]:B
722722
└── 📙►:2[2]:A
723-
└── ·fafd9d0 (⌂|🏘|1) ►main
723+
└── ·fafd9d0 (⌂|🏘|1) ►main
724724
");
725725
insta::assert_snapshot!(graph_workspace(&graph.to_workspace()?), @r"
726726
📕🏘️⚠️:1:gitbutler/workspace <> ✓!

0 commit comments

Comments
 (0)