Skip to content

Commit a21d8d0

Browse files
committed
Add missing tests for apply() to be sure it works without extra metadata.
Also, Make sure that worktree references are remapped with overrides. It's limited, but now correct for our simple cases.
1 parent 695e208 commit a21d8d0

File tree

8 files changed

+246
-78
lines changed

8 files changed

+246
-78
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,8 @@ impl Graph {
358358
let tip_is_not_workspace_commit = !workspaces
359359
.iter()
360360
.any(|(_, wsrn, _)| Some(wsrn) == ref_name.as_ref());
361-
let worktree_by_branch = worktree_branches(repo.for_worktree_only())?;
361+
let worktree_by_branch =
362+
repo.worktree_branches(graph.entrypoint_ref.as_ref().map(|r| r.as_ref()))?;
362363

363364
let mut ctx = post::Context {
364365
repo,

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

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
use crate::Worktree;
2+
use crate::init::walk::WorktreeByBranch;
3+
use crate::init::{Entrypoint, Overlay, walk::RefsById};
4+
use anyhow::bail;
5+
use but_core::{RefMetadata, ref_metadata};
6+
use gix::{prelude::ReferenceExt, refs::Target};
17
use std::{
28
borrow::Cow,
39
collections::{BTreeMap, BTreeSet},
410
};
511

6-
use but_core::{RefMetadata, ref_metadata};
7-
use gix::{prelude::ReferenceExt, refs::Target};
8-
9-
use crate::init::{Entrypoint, Overlay, walk::RefsById};
10-
1112
impl Overlay {
1213
/// Serve the given `refs` from memory, as if they would exist.
1314
/// This is true only, however, if a real reference doesn't exist.
@@ -103,10 +104,12 @@ impl Overlay {
103104
}
104105
}
105106

107+
type NameToReference = BTreeMap<gix::refs::FullName, gix::refs::Reference>;
108+
106109
pub(crate) struct OverlayRepo<'repo> {
107110
inner: &'repo gix::Repository,
108-
nonoverriding_references: BTreeMap<gix::refs::FullName, gix::refs::Reference>,
109-
overriding_references: BTreeMap<gix::refs::FullName, gix::refs::Reference>,
111+
nonoverriding_references: NameToReference,
112+
overriding_references: NameToReference,
110113
}
111114

112115
/// Note that functions with `'repo` in their return value technically leak the bare repo, and it's
@@ -182,10 +185,6 @@ impl<'repo> OverlayRepo<'repo> {
182185
self.inner
183186
}
184187

185-
pub fn for_worktree_only(&self) -> &'repo gix::Repository {
186-
self.inner
187-
}
188-
189188
pub fn remote_names(&self) -> gix::remote::Names<'repo> {
190189
self.inner.remote_names()
191190
}
@@ -269,6 +268,93 @@ impl<'repo> OverlayRepo<'repo> {
269268
all_refs_by_id.values_mut().for_each(|v| v.sort());
270269
Ok(all_refs_by_id)
271270
}
271+
272+
/// This is a bit tricky but aims to map the `HEAD` targets of the main worktree to what possibly was overridden
273+
/// via `main_head_referent`. The idea is that this is the entrypoint, which is assumed to be `HEAD`
274+
///
275+
/// ### Shortcoming
276+
///
277+
/// For now, it can only remap the first HEAD reference. For this to really work, we need proper in-memory overrides
278+
/// or a way to have overrides 'for real'.
279+
/// Also, we don't want `main_head_referent` to be initialised from the entrypoint, which we equal to be `HEAD`.
280+
/// But this invariant can fall apart easily and is caller dependent, as we use it to see the graph *as if* `HEAD` would
281+
/// be in another position - but that doesn't affect the worktree ref at all.
282+
pub fn worktree_branches(
283+
&self,
284+
main_head_referent: Option<&gix::refs::FullNameRef>,
285+
) -> anyhow::Result<WorktreeByBranch> {
286+
/// If `main_head_referent` is set, it means this is an overridden reference of the `HEAD` of the repo the graph is built in.
287+
/// If `None`, `head` belongs to another worktree. Completely unrelated to linked or main.
288+
fn maybe_insert_head(
289+
head: Option<gix::Head<'_>>,
290+
main_head_referent: Option<&gix::refs::FullNameRef>,
291+
overriding: &NameToReference,
292+
out: &mut WorktreeByBranch,
293+
) -> anyhow::Result<()> {
294+
let Some((head, wd)) = head.and_then(|head| {
295+
head.repo.worktree().map(|wt| {
296+
(
297+
head,
298+
match wt.id() {
299+
None => Worktree::Main,
300+
Some(id) => Worktree::LinkedId(id.to_owned()),
301+
},
302+
)
303+
})
304+
}) else {
305+
return Ok(());
306+
};
307+
308+
out.entry("HEAD".try_into().expect("valid"))
309+
.or_default()
310+
.push(wd.clone());
311+
let mut ref_chain = Vec::new();
312+
// Is this the repo that the overrides were applied on?
313+
let mut cursor = if let Some(head_name) = main_head_referent {
314+
overriding
315+
.get(head_name)
316+
.map(|overridden_head| overridden_head.clone().attach(head.repo))
317+
.or_else(|| head.try_into_referent())
318+
} else {
319+
head.try_into_referent()
320+
};
321+
while let Some(ref_) = cursor {
322+
ref_chain.push(ref_.name().to_owned());
323+
if overriding
324+
.get(ref_.name())
325+
.is_some_and(|r| r.target.try_name() != ref_.target().try_name())
326+
{
327+
bail!(
328+
"SHORTCOMING: cannot deal with {ref_:?} overridden to a different symbolic name to follow"
329+
)
330+
}
331+
cursor = ref_.follow().transpose()?;
332+
}
333+
for name in ref_chain {
334+
out.entry(name).or_default().push(wd.clone());
335+
}
336+
337+
Ok(())
338+
}
339+
340+
let mut map = BTreeMap::new();
341+
maybe_insert_head(
342+
self.inner.head().ok(),
343+
main_head_referent,
344+
&self.overriding_references,
345+
&mut map,
346+
)?;
347+
for proxy in self.inner.worktrees()? {
348+
let repo = proxy.into_repo_with_possibly_inaccessible_worktree()?;
349+
maybe_insert_head(
350+
repo.head().ok(),
351+
None,
352+
&self.overriding_references,
353+
&mut map,
354+
)?;
355+
}
356+
Ok(map)
357+
}
272358
}
273359

274360
pub(crate) struct OverlayMetadata<'meta, T> {

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

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -973,50 +973,6 @@ pub fn prune_integrated_tips(graph: &mut Graph, next: &mut Queue) -> anyhow::Res
973973

974974
pub(crate) type WorktreeByBranch = BTreeMap<gix::refs::FullName, Vec<Worktree>>;
975975

976-
pub fn worktree_branches(repo: &gix::Repository) -> anyhow::Result<WorktreeByBranch> {
977-
fn maybe_insert_head(
978-
head: Option<gix::Head<'_>>,
979-
out: &mut WorktreeByBranch,
980-
) -> anyhow::Result<()> {
981-
let Some((head, wd)) = head.and_then(|head| {
982-
head.repo.worktree().map(|wt| {
983-
(
984-
head,
985-
match wt.id() {
986-
None => Worktree::Main,
987-
Some(id) => Worktree::LinkedId(id.to_owned()),
988-
},
989-
)
990-
})
991-
}) else {
992-
return Ok(());
993-
};
994-
995-
out.entry("HEAD".try_into().expect("valid"))
996-
.or_default()
997-
.push(wd.to_owned());
998-
let mut ref_chain = Vec::new();
999-
let mut cursor = head.try_into_referent();
1000-
while let Some(ref_) = cursor {
1001-
ref_chain.push(ref_.name().to_owned());
1002-
cursor = ref_.follow().transpose()?;
1003-
}
1004-
for name in ref_chain {
1005-
out.entry(name).or_default().push(wd.to_owned());
1006-
}
1007-
1008-
Ok(())
1009-
}
1010-
1011-
let mut map = BTreeMap::new();
1012-
maybe_insert_head(repo.head().ok(), &mut map)?;
1013-
for proxy in repo.worktrees()? {
1014-
let repo = proxy.into_repo_with_possibly_inaccessible_worktree()?;
1015-
maybe_insert_head(repo.head().ok(), &mut map)?;
1016-
}
1017-
Ok(map)
1018-
}
1019-
1020976
impl crate::RefInfo {
1021977
pub(crate) fn from_ref(
1022978
ref_name: gix::refs::FullName,

crates/but-graph/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ pub struct Graph {
237237
hard_limit_hit: bool,
238238
/// The options used to create the graph, which allows it to regenerate itself after something
239239
/// possibly changed. This can also be used to simulate changes by injecting would-be information.
240-
options: init::Options,
240+
/// Public to be able to change it before calling [Graph::redo_traversal_with_overlay()].
241+
pub options: init::Options,
241242
}
242243

243244
/// A resolved entry point into the graph for easy access to the segment, commit,

crates/but-graph/src/segment.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,14 +263,14 @@ impl std::fmt::Debug for Segment {
263263
sibling_segment_id,
264264
metadata,
265265
} = self;
266-
f.debug_struct("StackSegment")
266+
f.debug_struct("Segment")
267267
.field("id", id)
268268
.field("generation", generation)
269269
.field(
270-
"ref_name",
270+
"ref_info",
271271
&match ref_info.as_ref() {
272272
None => "None".to_string(),
273-
Some(name) => name.debug_string(),
273+
Some(info) => info.debug_string(),
274274
},
275275
)
276276
.field(

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ fn unborn() -> anyhow::Result<()> {
1414
node_count: 1,
1515
edge_count: 0,
1616
node weights: {
17-
0: StackSegment {
17+
0: Segment {
1818
id: NodeIndex(0),
1919
generation: 0,
20-
ref_name: "►main[🌳]",
20+
ref_info: "►main[🌳]",
2121
remote_tracking_ref_name: "None",
2222
sibling_segment_id: "None",
2323
commits: [],
@@ -89,21 +89,21 @@ fn detached() -> anyhow::Result<()> {
8989
edge_count: 1,
9090
edges: (0, 1),
9191
node weights: {
92-
0: StackSegment {
92+
0: Segment {
9393
id: NodeIndex(0),
9494
generation: 0,
95-
ref_name: "None",
95+
ref_info: "None",
9696
remote_tracking_ref_name: "None",
9797
sibling_segment_id: "None",
9898
commits: [
9999
Commit(541396b, ⌂|1►annotated, ►release/v1, ►main),
100100
],
101101
metadata: "None",
102102
},
103-
1: StackSegment {
103+
1: Segment {
104104
id: NodeIndex(1),
105105
generation: 1,
106-
ref_name: "►other",
106+
ref_info: "►other",
107107
remote_tracking_ref_name: "None",
108108
sibling_segment_id: "None",
109109
commits: [
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
3+
set -eu -o pipefail
4+
5+
source "${BASH_SOURCE[0]%/*}/shared.sh"
6+
7+
### General Description
8+
9+
# A pushed main branch with two forked-off feature branches with the same base. One of these branches is a remote tracking branch.
10+
git init
11+
commit-file init
12+
setup_target_to_match_main
13+
14+
git branch B
15+
git checkout -b A
16+
commit-file A
17+
git checkout B
18+
commit-file B
19+
git checkout main
20+
commit M
21+
22+
setup_remote_tracking B B "move"
23+
echo 'ref: refs/remotes/origin/main' > .git/refs/remotes/origin/HEAD

0 commit comments

Comments
 (0)