Skip to content

Commit 0f513e1

Browse files
authored
Merge pull request #10995 from Byron/fix
applyv3: tracking-branch usability
2 parents 64c794d + ee8217d commit 0f513e1

File tree

7 files changed

+212
-7
lines changed

7 files changed

+212
-7
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ impl Options {
128128
self.commits_limit_recharge_location.extend(commits);
129129
self
130130
}
131+
132+
/// Set the extra-target which should be included in the workspace.
133+
pub fn with_extra_target_commit_id(mut self, id: impl Into<gix::ObjectId>) -> Self {
134+
self.extra_target_commit_id = Some(id.into());
135+
self
136+
}
131137
}
132138

133139
/// Lifecycle

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ impl Graph {
7777
// We perform view-related updates here for convenience, but also because the graph
7878
// traversal should have nothing to do with workspace details. It's just about laying
7979
// the foundation for figuring out our workspaces more easily.
80-
self.workspace_upgrades(meta, repo)?;
80+
self.workspace_upgrades(meta, repo, &refs_by_id)?;
8181

8282
// Point entrypoint to the right spot after all the virtual branches were added.
8383
self.set_entrypoint_to_ref_name(meta)?;
@@ -352,6 +352,23 @@ impl Graph {
352352
);
353353
self.inner.remove_edge(edge.id);
354354
}
355+
356+
if cidx_for_new_segment == 0 {
357+
// The top-segment is still connected with edges that think they link to a commit, so adjust them.
358+
let edges_to_adjust: Vec<_> = self
359+
.inner
360+
.edges_directed(sidx, Direction::Incoming)
361+
.map(|e| e.id())
362+
.collect();
363+
for edge_id in edges_to_adjust {
364+
let edge = self
365+
.inner
366+
.edge_weight_mut(edge_id)
367+
.expect("still present as we just saw it");
368+
edge.dst = None;
369+
edge.dst_id = None;
370+
}
371+
}
355372
Ok(new_segment_sidx)
356373
}
357374

@@ -532,13 +549,22 @@ impl Graph {
532549
&mut self,
533550
meta: &OverlayMetadata<'_, T>,
534551
repo: &OverlayRepo<'_>,
552+
refs_by_id: &RefsById,
535553
) -> anyhow::Result<()> {
536-
let Some((ws_sidx, ws_stacks, ws_data, ws_target)) = self
554+
let Some((ws_sidx, ws_stacks, ws_data, ws_target, ws_low_bound_sidx)) = self
537555
.to_workspace_inner(workspace::Downgrade::Disallow)
538556
.ok()
539557
.and_then(|mut ws| {
540558
let md = ws.metadata.take();
541-
md.map(|md| (ws.id, ws.stacks, md, ws.target))
559+
md.map(|md| {
560+
let lower_bound_if_in_workspace = ws.lower_bound_segment_id.filter(|lb_sidx| {
561+
ws.stacks
562+
.iter()
563+
.flat_map(|s| s.segments.iter().map(|s| s.id))
564+
.any(|sid| sid == *lb_sidx)
565+
});
566+
(ws.id, ws.stacks, md, ws.target, lower_bound_if_in_workspace)
567+
})
542568
})
543569
else {
544570
return Ok(());
@@ -810,6 +836,16 @@ impl Graph {
810836
});
811837
self[sidx].sibling_segment_id = named_segment_id;
812838
}
839+
840+
// The named-segment check is needed as we don't want to double-split unnamed segments.
841+
// What this really does is to pass ownership of the base commit from a named segment to an unnamed one,
842+
// as all algorithms kind of rely on it.
843+
// So if this ever becomes a problem, we can also try to adjust said algorithms downstream.
844+
if let Some(low_bound_segment_id) = ws_low_bound_sidx
845+
&& self[low_bound_segment_id].ref_name.is_some()
846+
{
847+
self.split_segment(low_bound_segment_id, 0, None, Some(refs_by_id), meta)?;
848+
}
813849
Ok(())
814850
}
815851

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl Stack {
7070
}
7171
if let Some((last_segment, last_aggregated_sidx)) = segments.last_mut().and_then(|s| {
7272
let sidx = s.commits_by_segment.last().map(|t| t.0)?;
73-
(s, sidx).into()
73+
Some((s, sidx))
7474
}) {
7575
let first_parent_sidx = graph
7676
.neighbors_directed(last_aggregated_sidx, Direction::Outgoing)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ impl Graph {
584584
return stop;
585585
}
586586
// Check for anonymous segments with sibling ID - these know their
587-
// named counterparts and we want to set the name, but they must
587+
// named counterparts, and we want to set the name, but they must
588588
// be in their own stack-segment.
589589
if s.ref_name.is_none() && s.sibling_segment_id.is_some() {
590590
return stop;
@@ -851,7 +851,7 @@ impl Graph {
851851
fn collect_stack_segments(
852852
&self,
853853
from: SegmentIndex,
854-
entrypoint_sidx: Option<SegmentIndex>,
854+
mut entrypoint_sidx: Option<SegmentIndex>,
855855
mut is_one_past_end_of_stack_segment: impl FnMut(&Segment) -> bool,
856856
mut starts_next_stack_segment: impl FnMut(&Segment) -> bool,
857857
mut discard_stack: impl FnMut(&StackSegment) -> bool,
@@ -866,6 +866,7 @@ impl Graph {
866866
let mut segment = StackSegment::from_graph_segments(&segments, self)?;
867867
if entrypoint_sidx.is_some_and(|id| segment.id == id) {
868868
segment.is_entrypoint = true;
869+
entrypoint_sidx = None;
869870
}
870871
out.push(segment);
871872
next = stopped_at

crates/but-graph/tests/fixtures/scenarios.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,65 @@ git init special-branches
205205

206206
mkdir ws
207207
(cd ws
208+
git init remote-and-integrated-tracking-linear
209+
(cd remote-and-integrated-tracking-linear
210+
commit M1
211+
commit M-base
212+
git branch A
213+
git checkout -b soon-origin-A main
214+
commit A-remote
215+
git checkout main
216+
commit M-advanced
217+
setup_target_to_match_main
218+
219+
git checkout A
220+
create_workspace_commit_once A
221+
setup_remote_tracking soon-origin-A A "move"
222+
)
223+
224+
git init remote-and-integrated-tracking
225+
(cd remote-and-integrated-tracking
226+
commit M1
227+
commit M2
228+
git checkout -b tmp1
229+
commit X
230+
git checkout main
231+
commit Y
232+
git merge --no-ff tmp1 -m "M-base"
233+
git branch A
234+
git checkout -b soon-origin-A main
235+
commit A-remote
236+
git checkout main
237+
commit M-advanced
238+
setup_target_to_match_main
239+
240+
git checkout A
241+
create_workspace_commit_once A
242+
setup_remote_tracking soon-origin-A A "move"
243+
)
244+
245+
git init remote-and-integrated-tracking-extra-commit
246+
(cd remote-and-integrated-tracking-extra-commit
247+
commit M1
248+
commit M2
249+
git checkout -b tmp1
250+
commit X
251+
git checkout main
252+
commit Y
253+
git merge --no-ff tmp1 -m "M-base"
254+
git checkout -b A
255+
commit A-local
256+
git checkout -b soon-origin-A main
257+
commit A-remote
258+
git checkout main
259+
commit M-advanced
260+
setup_target_to_match_main
261+
262+
git checkout A
263+
create_workspace_commit_once A
264+
setup_remote_tracking soon-origin-A A "move"
265+
)
266+
208267
git init single-stack-ambiguous
209268
(cd single-stack-ambiguous
210269
commit init

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5218,6 +5218,109 @@ fn dependent_branch_on_base() -> anyhow::Result<()> {
52185218
Ok(())
52195219
}
52205220

5221+
#[test]
5222+
fn remote_and_integrated_tracking_branch_on_merge() -> anyhow::Result<()> {
5223+
let (repo, mut meta) = read_only_in_memory_scenario("ws/remote-and-integrated-tracking")?;
5224+
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r"
5225+
* d018f71 (HEAD -> gitbutler/workspace) GitButler Workspace Commit
5226+
| * c1e26b0 (origin/main, main) M-advanced
5227+
|/
5228+
| * 2181501 (origin/A) A-remote
5229+
|/
5230+
* 1ee1e34 (A) M-base
5231+
|\
5232+
| * efc3b77 (tmp1) X
5233+
* | c822d66 Y
5234+
|/
5235+
* bce0c5e M2
5236+
* 3183e43 M1
5237+
");
5238+
add_stack_with_segments(&mut meta, 1, "A", StackState::InWorkspace, &[]);
5239+
5240+
let graph = Graph::from_head(
5241+
&repo,
5242+
&*meta,
5243+
standard_options().with_extra_target_commit_id(repo.rev_parse_single("origin/main")?),
5244+
)?
5245+
.validated()?;
5246+
insta::assert_snapshot!(graph_workspace(&graph.to_workspace()?), @r"
5247+
📕🏘️:0:gitbutler/workspace <> ✓refs/remotes/origin/main⇣1 on 1ee1e34
5248+
└── ≡📙:3:A <> origin/A →:4:⇣1 on 1ee1e34 {1}
5249+
└── 📙:3:A <> origin/A →:4:⇣1
5250+
└── 🟣2181501
5251+
");
5252+
5253+
Ok(())
5254+
}
5255+
5256+
#[test]
5257+
fn remote_and_integrated_tracking_branch_on_linear_segment() -> anyhow::Result<()> {
5258+
let (repo, mut meta) =
5259+
read_only_in_memory_scenario("ws/remote-and-integrated-tracking-linear")?;
5260+
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r"
5261+
* 21e584f (HEAD -> gitbutler/workspace) GitButler Workspace Commit
5262+
| * 8dc508f (origin/main, main) M-advanced
5263+
|/
5264+
| * 197ddce (origin/A) A-remote
5265+
|/
5266+
* 081bae9 (A) M-base
5267+
* 3183e43 M1
5268+
");
5269+
add_stack_with_segments(&mut meta, 1, "A", StackState::InWorkspace, &[]);
5270+
5271+
let graph = Graph::from_head(
5272+
&repo,
5273+
&*meta,
5274+
standard_options().with_extra_target_commit_id(repo.rev_parse_single("origin/main")?),
5275+
)?
5276+
.validated()?;
5277+
insta::assert_snapshot!(graph_workspace(&graph.to_workspace()?), @r"
5278+
📕🏘️:0:gitbutler/workspace <> ✓refs/remotes/origin/main⇣1 on 081bae9
5279+
└── ≡📙:3:A <> origin/A →:4:⇣1 on 081bae9 {1}
5280+
└── 📙:3:A <> origin/A →:4:⇣1
5281+
└── 🟣197ddce
5282+
");
5283+
5284+
Ok(())
5285+
}
5286+
5287+
#[test]
5288+
fn remote_and_integrated_tracking_branch_on_merge_extra_target() -> anyhow::Result<()> {
5289+
let (repo, mut meta) =
5290+
read_only_in_memory_scenario("ws/remote-and-integrated-tracking-extra-commit")?;
5291+
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r"
5292+
* 5f2810f (HEAD -> gitbutler/workspace) GitButler Workspace Commit
5293+
* 9f47a25 (A) A-local
5294+
| * c1e26b0 (origin/main, main) M-advanced
5295+
|/
5296+
| * 2181501 (origin/A) A-remote
5297+
|/
5298+
* 1ee1e34 M-base
5299+
|\
5300+
| * efc3b77 (tmp1) X
5301+
* | c822d66 Y
5302+
|/
5303+
* bce0c5e M2
5304+
* 3183e43 M1
5305+
");
5306+
add_stack_with_segments(&mut meta, 1, "A", StackState::InWorkspace, &[]);
5307+
let graph = Graph::from_head(
5308+
&repo,
5309+
&*meta,
5310+
standard_options().with_extra_target_commit_id(repo.rev_parse_single("origin/main")?),
5311+
)?
5312+
.validated()?;
5313+
insta::assert_snapshot!(graph_workspace(&graph.to_workspace()?), @r"
5314+
📕🏘️:0:gitbutler/workspace <> ✓refs/remotes/origin/main⇣1 on 1ee1e34
5315+
└── ≡📙:3:A <> origin/A →:4:⇡1⇣1 on 1ee1e34 {1}
5316+
└── 📙:3:A <> origin/A →:4:⇡1⇣1
5317+
├── 🟣2181501
5318+
└── ·9f47a25 (🏘️)
5319+
");
5320+
5321+
Ok(())
5322+
}
5323+
52215324
#[test]
52225325
fn unapplied_branch_on_base() -> anyhow::Result<()> {
52235326
let (repo, mut meta) = read_only_in_memory_scenario("ws/unapplied-branch-on-base")?;

crates/gitbutler-project/src/project.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ impl Project {
202202
let mut projects = crate::dangerously_list_projects_without_migration()?;
203203
// Sort projects by longest pathname to shortest.
204204
// We need to do this because users might have one gitbutler project
205-
// nexted insided of another via a gitignored folder.
205+
// nexted inside another via a gitignored folder.
206206
// We want to match on the longest project path.
207207
projects.sort_by(|a, b| {
208208
a.worktree_dir

0 commit comments

Comments
 (0)