Skip to content

Commit 6e8a298

Browse files
committed
SQUASH ME
1 parent 9d869da commit 6e8a298

File tree

9 files changed

+345
-69
lines changed

9 files changed

+345
-69
lines changed

crates/but-core/src/ref_metadata.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ pub struct RefInfo {
120120
pub updated_at: Option<gix::date::Time>,
121121
}
122122

123+
/// Mutations
124+
impl RefInfo {
125+
/// Set the `updated_at` field to the current time.
126+
pub fn update(&mut self) {
127+
self.updated_at = Some(gix::date::Time::now_local_or_utc());
128+
}
129+
}
130+
123131
impl std::fmt::Debug for RefInfo {
124132
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125133
let format = gix::date::time::format::ISO8601;

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

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::{Context, bail};
2-
use but_core::RefMetadata;
2+
use but_core::{RefMetadata, ref_metadata};
33
use gix::{
44
hashtable::hash_map::Entry,
55
prelude::{ObjectIdExt, ReferenceExt},
@@ -13,12 +13,22 @@ mod walk;
1313
use walk::*;
1414

1515
pub(crate) mod types;
16+
use crate::init::overlay::{OverlayMetadata, OverlayRepo};
1617
use types::{Goals, Instruction, Limit, Queue};
1718

1819
mod remotes;
1920

21+
mod overlay;
2022
mod post;
2123

24+
/// A way to define information to be served from memory, instead of from the underlying data source, when
25+
/// [initializing](Graph::from_commit_traversal()) the graph.
26+
#[derive(Debug, Default)]
27+
pub struct Overlay {
28+
references: Vec<gix::refs::Reference>,
29+
meta_branches: Vec<(gix::refs::FullName, ref_metadata::Branch)>,
30+
}
31+
2232
pub(super) type PetGraph = petgraph::stable_graph::StableGraph<Segment, Edge>;
2333

2434
/// Options for use in [`Graph::from_head()`] and [`Graph::from_commit_traversal()`].
@@ -123,9 +133,12 @@ impl Graph {
123133
let (tip, maybe_name) = match head.kind {
124134
gix::head::Kind::Unborn(ref_name) => {
125135
let mut graph = Graph::default();
136+
// It's OK to default-initialise this here as overlays are only used when redoing
137+
// the traversal.
138+
let (_repo, meta) = Overlay::default().into_parts(repo, meta);
126139
graph.insert_root(branch_segment_from_name_and_meta(
127140
Some((ref_name, None)),
128-
meta,
141+
&meta,
129142
None,
130143
)?);
131144
return Ok(graph);
@@ -208,6 +221,19 @@ impl Graph {
208221
ref_name: impl Into<Option<gix::refs::FullName>>,
209222
meta: &impl RefMetadata,
210223
options: Options,
224+
) -> anyhow::Result<Self> {
225+
let (repo, meta) = Overlay::default().into_parts(tip.repo, meta);
226+
// TODO: use repo
227+
Graph::from_commit_traversal_inner(tip.detach(), tip.repo, ref_name, &meta, options)
228+
}
229+
230+
fn from_commit_traversal_inner<T: RefMetadata>(
231+
tip: gix::ObjectId,
232+
// repo: &OverlayRepo<'_>,
233+
repo: &gix::Repository,
234+
ref_name: impl Into<Option<gix::refs::FullName>>,
235+
meta: &OverlayMetadata<'_, T>,
236+
options: Options,
211237
) -> anyhow::Result<Self> {
212238
let mut graph = Graph {
213239
options: options.clone(),
@@ -221,7 +247,6 @@ impl Graph {
221247
hard_limit,
222248
} = options;
223249

224-
let repo = tip.repo;
225250
let max_limit = Limit::new(limit);
226251
// TODO: also traverse (outside)-branches that ought to be in the workspace. That way we have the desired ones
227252
// automatically and just have to find a way to prune the undesired ones.
@@ -255,7 +280,7 @@ impl Graph {
255280
// The tip transports itself.
256281
let tip_flags = CommitFlags::NotInRemote
257282
| goals
258-
.flag_for(tip.detach())
283+
.flag_for(tip)
259284
.expect("we more than one bitflags for this");
260285

261286
let target_symbolic_remote_names = {
@@ -288,10 +313,10 @@ impl Graph {
288313
let current = graph.insert_root(branch_segment_from_name_and_meta(
289314
ref_name.clone().map(|rn| (rn, None)),
290315
meta,
291-
Some((&ctx.refs_by_id, tip.detach())),
316+
Some((&ctx.refs_by_id, tip)),
292317
)?);
293318
_ = next.push_back_exhausted((
294-
tip.detach(),
319+
tip,
295320
tip_flags,
296321
Instruction::CollectCommit { into: current },
297322
max_limit,
@@ -331,7 +356,7 @@ impl Graph {
331356
} else {
332357
(
333358
CommitFlags::empty(),
334-
max_limit.with_indirect_goal(tip.detach(), &mut goals),
359+
max_limit.with_indirect_goal(tip, &mut goals),
335360
)
336361
};
337362
let mut ws_segment =
@@ -374,10 +399,10 @@ impl Graph {
374399
CommitFlags::NotInRemote | goal,
375400
Instruction::CollectCommit { into: local_sidx },
376401
max_limit
377-
.with_indirect_goal(tip.detach(), &mut goals)
402+
.with_indirect_goal(tip, &mut goals)
378403
.without_allowance(),
379404
));
380-
next.add_goal_to(tip.detach(), goal);
405+
next.add_goal_to(tip, goal);
381406
(Some(local_sidx), goal)
382407
} else {
383408
(None, CommitFlags::empty())
@@ -391,7 +416,7 @@ impl Graph {
391416
// Once the goal was found, be done immediately,
392417
// we are not interested in these.
393418
max_limit
394-
.with_indirect_goal(tip.detach(), &mut goals)
419+
.with_indirect_goal(tip, &mut goals)
395420
.additional_goal(local_goal)
396421
.without_allowance(),
397422
));
@@ -421,7 +446,7 @@ impl Graph {
421446
into: extra_target_sidx,
422447
},
423448
max_limit
424-
.with_indirect_goal(tip.detach(), &mut goals)
449+
.with_indirect_goal(tip, &mut goals)
425450
.without_allowance(),
426451
));
427452
extra_target_sidx
@@ -571,7 +596,7 @@ impl Graph {
571596
limit,
572597
);
573598
if hard_limit_hit {
574-
return graph.post_processed(meta, tip.detach(), ctx.with_hard_limit());
599+
return graph.post_processed(meta, tip, ctx.with_hard_limit());
575600
}
576601

577602
segment.commits.push(
@@ -592,14 +617,36 @@ impl Graph {
592617

593618
for item in remote_items {
594619
if next.push_back_exhausted(item) {
595-
return graph.post_processed(meta, tip.detach(), ctx.with_hard_limit());
620+
return graph.post_processed(meta, tip, ctx.with_hard_limit());
596621
}
597622
}
598623

599624
prune_integrated_tips(&mut graph, &mut next);
600625
}
601626

602-
graph.post_processed(meta, tip.detach(), ctx)
627+
graph.post_processed(meta, tip, ctx)
628+
}
629+
630+
/// Repeat the traversal that generated this graph using `repo` and `meta`, but allow to set an in-memory
631+
/// `overlay` to amend the data available from `repo` and `meta`.
632+
/// This way, one can see this graph as it will be in the future once the changes to `repo` and `meta` are actually made.
633+
pub fn redo_traversal_with_overlay(
634+
&self,
635+
repo: &gix::Repository,
636+
meta: &impl RefMetadata,
637+
overlay: Overlay,
638+
) -> anyhow::Result<Self> {
639+
let (_repo, meta) = overlay.into_parts(repo, meta);
640+
let tip_sidx = self
641+
.entrypoint
642+
.context("BUG: entrypoint must always be set")?
643+
.0;
644+
let tip = self
645+
.tip_skip_empty(tip_sidx)
646+
.context("BUG: entrypoint must eventually point to a commit")?
647+
.id;
648+
let ref_name = self[tip_sidx].ref_name.clone();
649+
Graph::from_commit_traversal_inner(tip, repo, ref_name, &meta, self.options.clone())
603650
}
604651
}
605652

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use crate::init::Overlay;
2+
use but_core::{RefMetadata, ref_metadata};
3+
4+
impl Overlay {
5+
/// Serve the given `refs` from memory, as if they would exist.
6+
pub fn with_references(mut self, refs: impl IntoIterator<Item = gix::refs::Reference>) -> Self {
7+
self.references = refs.into_iter().collect();
8+
self
9+
}
10+
11+
/// Serve the given `branches` metadata from memory, as if they would exist.
12+
pub fn with_branch_metadata(
13+
mut self,
14+
refs: impl IntoIterator<Item = (gix::refs::FullName, ref_metadata::Branch)>,
15+
) -> Self {
16+
self.meta_branches = refs.into_iter().collect();
17+
self
18+
}
19+
}
20+
21+
impl Overlay {
22+
pub(crate) fn into_parts<'repo, 'meta, T>(
23+
self,
24+
repo: &'repo gix::Repository,
25+
meta: &'meta T,
26+
) -> (OverlayRepo<'repo>, OverlayMetadata<'meta, T>)
27+
where
28+
T: RefMetadata,
29+
{
30+
let Overlay {
31+
references,
32+
meta_branches,
33+
} = self;
34+
(
35+
OverlayRepo {
36+
references,
37+
inner: repo,
38+
},
39+
OverlayMetadata {
40+
inner: meta,
41+
meta_branches,
42+
},
43+
)
44+
}
45+
}
46+
47+
pub(crate) struct OverlayRepo<'repo> {
48+
inner: &'repo gix::Repository,
49+
references: Vec<gix::refs::Reference>,
50+
}
51+
52+
pub(crate) struct OverlayMetadata<'meta, T> {
53+
inner: &'meta T,
54+
meta_branches: Vec<(gix::refs::FullName, ref_metadata::Branch)>,
55+
}
56+
57+
impl<T> OverlayMetadata<'_, T>
58+
where
59+
T: RefMetadata,
60+
{
61+
pub fn iter_workspaces(
62+
&self,
63+
) -> impl Iterator<Item = (gix::refs::FullName, ref_metadata::Workspace)> {
64+
self.inner
65+
.iter()
66+
.filter_map(Result::ok)
67+
.filter_map(|(ref_name, item)| {
68+
item.downcast::<ref_metadata::Workspace>()
69+
.ok()
70+
.map(|ws| (ref_name, ws))
71+
})
72+
.map(|(ref_name, ws)| (ref_name, (*ws).clone()))
73+
}
74+
75+
pub fn workspace_opt(
76+
&self,
77+
ref_name: &gix::refs::FullNameRef,
78+
) -> anyhow::Result<Option<ref_metadata::Workspace>> {
79+
let opt = self.inner.workspace_opt(ref_name)?;
80+
Ok(opt.map(|ws_data| ws_data.clone()))
81+
}
82+
83+
pub fn branch_opt(
84+
&self,
85+
ref_name: &gix::refs::FullNameRef,
86+
) -> anyhow::Result<Option<ref_metadata::Branch>> {
87+
if let Some(overlay_branch) = self
88+
.meta_branches
89+
.iter()
90+
.find_map(|(rn, branch)| (rn.as_ref() == ref_name).then(|| branch.clone()))
91+
{
92+
return Ok(Some(overlay_branch));
93+
}
94+
let opt = self.inner.branch_opt(ref_name)?;
95+
Ok(opt.map(|data| data.clone()))
96+
}
97+
}

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::init::overlay::OverlayMetadata;
12
use crate::init::types::{EdgeOwned, TopoWalk};
23
use crate::init::walk::{RefsById, disambiguate_refs_by_branch_metadata};
34
use crate::init::{PetGraph, branch_segment_from_name_and_meta, remotes};
@@ -34,9 +35,9 @@ impl Graph {
3435
/// the requirement of them to be computationally cheap.
3536
#[instrument(skip(self, meta, repo, refs_by_id), err(Debug))]
3637
#[allow(clippy::too_many_arguments)]
37-
pub(super) fn post_processed(
38+
pub(super) fn post_processed<T: RefMetadata>(
3839
mut self,
39-
meta: &impl RefMetadata,
40+
meta: &OverlayMetadata<'_, T>,
4041
tip: gix::ObjectId,
4142
Context {
4243
repo,
@@ -87,11 +88,11 @@ impl Graph {
8788

8889
/// Assure that workspace segments with managed commits only have that commit, and move all others
8990
/// into a new segment.
90-
fn fixup_workspace_segments(
91+
fn fixup_workspace_segments<T: RefMetadata>(
9192
&mut self,
9293
repo: &gix::Repository,
9394
refs_by_id: &RefsById,
94-
meta: &impl RefMetadata,
95+
meta: &OverlayMetadata<'_, T>,
9596
) -> anyhow::Result<()> {
9697
let workspace_segments_with_multiple_commits: Vec<_> = self
9798
.inner
@@ -156,9 +157,9 @@ impl Graph {
156157
/// * segments have a name, but the same name is still visible in the refs of the first commit.
157158
///
158159
/// Only perform disambiguation on proxy segments (i.e. those inserted segments to prevent commit-ownership).
159-
fn fixup_segment_names(
160+
fn fixup_segment_names<T: RefMetadata>(
160161
&mut self,
161-
meta: &impl RefMetadata,
162+
meta: &OverlayMetadata<'_, T>,
162163
inserted_proxy_segments: &[SegmentIndex],
163164
) {
164165
let segments_with_refs_on_first_commit: Vec<_> = self
@@ -307,9 +308,9 @@ impl Graph {
307308
/// * insert empty segments as defined by the workspace that affects its downstream.
308309
/// * put workspace connection into the order defined in the workspace metadata.
309310
/// * set sibling segment IDs for unnamed segments that are descendents of an out-of-workspace but known segment.
310-
fn workspace_upgrades(
311+
fn workspace_upgrades<T: RefMetadata>(
311312
&mut self,
312-
meta: &impl RefMetadata,
313+
meta: &OverlayMetadata<'_, T>,
313314
repo: &gix::Repository,
314315
) -> anyhow::Result<()> {
315316
let Some((ws_sidx, ws_stacks, ws_data, ws_target)) = self
@@ -740,12 +741,12 @@ fn delete_anon_if_empty_and_reconnect(graph: &mut Graph, sidx: SegmentIndex) {
740741

741742
/// Create as many new segments as refs in `matching_refs`, connect them to each other in order, and finally connect them
742743
/// with `above_idx` and `below_idx` to integrate them into the workspace that is bounded by these segments.
743-
fn create_independent_segments(
744+
fn create_independent_segments<T: RefMetadata>(
744745
graph: &mut Graph,
745746
above_idx: SegmentIndex,
746747
below_idx: SegmentIndex,
747748
matching_refs: Vec<gix::refs::FullName>,
748-
meta: &impl RefMetadata,
749+
meta: &OverlayMetadata<'_, T>,
749750
) -> anyhow::Result<()> {
750751
assert!(!matching_refs.is_empty());
751752

@@ -803,13 +804,13 @@ fn create_independent_segments(
803804
/// Note that the Segment at `bottom_segment_index` will own `commit`.
804805
/// Also note that we reconnect commit-by-commit, so the outer processing has to do that.
805806
/// Note that it may avoid creating a new segment.
806-
fn maybe_create_multiple_segments(
807+
fn maybe_create_multiple_segments<T: RefMetadata>(
807808
graph: &mut Graph,
808809
mut above_idx: SegmentIndex,
809810
commit_parent: SegmentIndex,
810811
commit_idx: CommitIndex,
811812
mut matching_refs: Vec<gix::refs::FullName>,
812-
meta: &impl RefMetadata,
813+
meta: &OverlayMetadata<'_, T>,
813814
) -> anyhow::Result<Option<SegmentIndex>> {
814815
assert!(!matching_refs.is_empty());
815816
let commit = &graph[commit_parent].commits[commit_idx];

0 commit comments

Comments
 (0)