Skip to content

Commit 470b1bb

Browse files
committed
Improve tip-pruning when extra-commits are set.
1 parent ec33a5a commit 470b1bb

File tree

4 files changed

+79
-49
lines changed

4 files changed

+79
-49
lines changed

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,6 @@ impl Graph {
253253
} = options;
254254

255255
let max_limit = Limit::new(limit);
256-
// TODO: also traverse (outside)-branches that ought to be in the workspace. That way we have the desired ones
257-
// automatically and just have to find a way to prune the undesired ones.
258256
let ref_name = ref_name.into();
259257
if ref_name
260258
.as_ref()
@@ -454,7 +452,6 @@ impl Graph {
454452
// have to adjust the existing queue item.
455453
existing_segment
456454
} else {
457-
// TODO: test in graph
458455
let extra_target_sidx = graph.insert_root(branch_segment_from_name_and_meta(
459456
None,
460457
meta,

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ impl Limit {
2727
/// Keep queueing without limit until `goal` is seen in a commit that has **it ahead of itself**.
2828
/// Then stop searching for that goal.
2929
/// `goals` are used to keep track of existing bitflags.
30-
/// `origin` is used to know where the search for `goal` came from.
3130
///
3231
/// ### Note
3332
///
@@ -331,6 +330,9 @@ pub struct TopoWalk {
331330
direction: Direction,
332331
/// If `true`, don't return the first segment which is always the starting point.
333332
skip_tip: Option<()>,
333+
/// If this is set during the iteration, we will store segment ids which didn't have any outgoing or
334+
/// incoming connections, depending on the direction of traversal.
335+
pub leafs: Option<Vec<SegmentIndex>>,
334336
}
335337

336338
/// Lifecycle
@@ -351,6 +353,7 @@ impl TopoWalk {
351353
seen_empty_segments: Default::default(),
352354
direction,
353355
skip_tip: None,
356+
leafs: None,
354357
}
355358
}
356359
}
@@ -392,7 +395,9 @@ impl TopoWalk {
392395
let (segment, first_commit_index) = self.next.pop_front()?;
393396
let available_range = self.select_range(graph, segment, first_commit_index)?;
394397

398+
let mut count = 0;
395399
for edge in graph.edges_directed(segment, self.direction) {
400+
count += 1;
396401
match self.direction {
397402
Direction::Outgoing => {
398403
if edge
@@ -417,6 +422,9 @@ impl TopoWalk {
417422
}
418423
}
419424
}
425+
if let Some(leafs) = self.leafs.as_mut().filter(|_| count == 0) {
426+
leafs.push(segment);
427+
}
420428
Some((segment, available_range))
421429
}
422430

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -679,13 +679,16 @@ pub fn propagate_flags_downward(
679679
flags_to_add: CommitFlags,
680680
dst_sidx: SegmentIndex,
681681
dst_commit: Option<CommitIndex>,
682-
) {
682+
needs_leafs: bool,
683+
) -> Option<Vec<SegmentIndex>> {
683684
let mut topo = TopoWalk::start_from(dst_sidx, dst_commit, petgraph::Direction::Outgoing);
685+
topo.leafs = needs_leafs.then(Vec::new);
684686
while let Some((segment, commit_range)) = topo.next(graph) {
685687
for commit in &mut graph[segment].commits[commit_range] {
686688
commit.flags |= flags_to_add;
687689
}
688690
}
691+
topo.leafs.take().filter(|v| !v.is_empty())
689692
}
690693

691694
/// Check `refs` for refs with remote tracking branches, and return a queue for them for traversal after creating a segment
@@ -814,12 +817,33 @@ pub fn possibly_split_occupied_segment(
814817
let new_flags = propagated_flags | top_flags | bottom_flags;
815818

816819
// Only propagate if there is something new as propagation is slow
817-
if new_flags != bottom_flags {
818-
propagate_flags_downward(&mut graph.inner, new_flags, bottom_sidx, Some(bottom_cidx));
820+
// Note that we currently assume that the flags are different also to get leafs, even though these
821+
// depend on flags to propagate. This will be an issue, but seems to work well for all known cases.
822+
let needs_leafs = !limit.goal_reached();
823+
let leafs = if new_flags != bottom_flags
824+
|| (needs_leafs
825+
&& next
826+
.iter()
827+
.any(|(_, _, _, tip_limit)| !tip_limit.goal_flags().contains(limit.goal_flags())))
828+
{
829+
propagate_flags_downward(
830+
&mut graph.inner,
831+
new_flags,
832+
bottom_sidx,
833+
Some(bottom_cidx),
834+
needs_leafs,
835+
)
836+
} else {
837+
None
838+
};
839+
840+
if let Some(leafs) = leafs {
841+
propagate_goals_of_reachable_tips(next, leafs, limit.goal_flags());
819842
}
820843

821844
// Find the tips that saw this commit, and adjust their limit it that would extend it.
822845
// The commit is the one we hit, but seen from the newly split segment which should never be empty.
846+
// TODO: merge this into the new propagation based one.
823847
let bottom_commit_goals = Limit::new(None)
824848
.additional_goal(
825849
graph[bottom_sidx]
@@ -840,7 +864,21 @@ pub fn possibly_split_occupied_segment(
840864
Ok(())
841865
}
842866

843-
/// Remove if there are only tips with integrated commits and delete empty segments of pruned tips,
867+
// Find all tips that are scheduled to be put into one of the `reachable_segments`
868+
// (reachable from the commit we just ran into)
869+
fn propagate_goals_of_reachable_tips(
870+
next: &mut Queue,
871+
reachable_segments: Vec<SegmentIndex>,
872+
goal_flags: CommitFlags,
873+
) {
874+
for (_, _, instruction, limit) in next.iter_mut() {
875+
if reachable_segments.contains(&instruction.segment_idx()) {
876+
*limit = limit.additional_goal(goal_flags);
877+
}
878+
}
879+
}
880+
881+
/// Remove tips with only integrated commits and delete empty segments of pruned tips,
844882
/// as these are uninteresting.
845883
/// However, do so only if our entrypoint isn't integrated itself and is not in a workspace. The reason for this is that we
846884
/// always also traverse workspaces and their targets, even if the traversal starts outside a workspace.

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

Lines changed: 28 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,39 +1777,32 @@ fn integrated_tips_stop_early_if_remote_is_not_configured() -> anyhow::Result<()
17771777
│ └── ·777b552 (⌂|🏘|✓|1)
17781778
│ └── ►:6[3]:anon:
17791779
│ └── ·ce4a760 (⌂|🏘|✓|1)
1780-
│ ├── ►:7[5]:anon:
1781-
│ │ └── ·01d0e1e (⌂|🏘|✓|1)
1782-
│ │ └── ►:5[6]:main
1783-
│ │ ├── ·4b3e5a8 (⌂|🏘|✓|1)
1784-
│ │ ├── ·34d0715 (⌂|🏘|✓|1)
1785-
│ │ └── ·eb5f731 (⌂|🏘|✓|1)
1780+
│ ├── ►:7[4]:anon:
1781+
│ │ └── ✂·01d0e1e (⌂|🏘|✓|1)
17861782
│ └── ►:8[4]:A-feat
1787-
│ ├── ·fea59b5 (⌂|🏘|✓|1)
1788-
│ └── ·4deea74 (⌂|🏘|✓|1)
1789-
│ └── →:7:
1783+
│ └── ✂·fea59b5 (⌂|🏘|✓|1)
17901784
└── ►:2[0]:origin/main
17911785
├── 🟣d0df794 (✓)
17921786
└── 🟣09c6e08 (✓)
17931787
└── ►:4[1]:anon:
17941788
└── 🟣7b9f260 (✓)
1795-
├── →:5: (main)
1789+
├── ►:5[2]:main
1790+
│ ├── 🟣4b3e5a8 (✓)
1791+
│ ├── 🟣34d0715 (✓)
1792+
│ └── 🟣eb5f731 (✓)
17961793
└── →:0: (A)
17971794
");
17981795
// Because the branch is integrated, the surrounding workspace isn't shown.
17991796
insta::assert_snapshot!(graph_workspace(&graph.to_workspace()?), @r"
18001797
⌂:0:A <> ✓!
18011798
└── ≡:0:A
1802-
├── :0:A
1803-
│ ├── ·79bbb29 (🏘️|✓)
1804-
│ ├── ·fc98174 (🏘️|✓)
1805-
│ ├── ·a381df5 (🏘️|✓)
1806-
│ ├── ·777b552 (🏘️|✓)
1807-
│ ├── ·ce4a760 (🏘️|✓)
1808-
│ └── ·01d0e1e (🏘️|✓)
1809-
└── :5:main
1810-
├── ·4b3e5a8 (🏘️|✓)
1811-
├── ·34d0715 (🏘️|✓)
1812-
└── ·eb5f731 (🏘️|✓)
1799+
└── :0:A
1800+
├── ·79bbb29 (🏘️|✓)
1801+
├── ·fc98174 (🏘️|✓)
1802+
├── ·a381df5 (🏘️|✓)
1803+
├── ·777b552 (🏘️|✓)
1804+
├── ·ce4a760 (🏘️|✓)
1805+
└── ✂️·01d0e1e (🏘️|✓)
18131806
");
18141807

18151808
// See what happens with an out-of-workspace HEAD and an arbitrary extra target.
@@ -2169,8 +2162,7 @@ fn workspace_obeys_limit_when_target_branch_is_missing() -> anyhow::Result<()> {
21692162
│ └── ·03ad472 (⌂|🏘|1)
21702163
│ └── ►:4[2]:A
21712164
│ ├── ·79bbb29 (⌂|🏘|✓|1)
2172-
│ ├── ·fc98174 (⌂|🏘|✓|1)
2173-
│ └── ✂·a381df5 (⌂|🏘|✓|1)
2165+
│ └── ✂·fc98174 (⌂|🏘|✓|1)
21742166
└── ►:1[0]:origin/main
21752167
├── 🟣d0df794 (✓)
21762168
└── 🟣09c6e08 (✓)
@@ -2403,12 +2395,13 @@ fn partitions_with_long_and_short_connections_to_each_other() -> anyhow::Result<
24032395
");
24042396

24052397
add_workspace(&mut meta);
2406-
let (id, ref_name) = id_at(&repo, "main");
2398+
let (main_id, main_ref_name) = id_at(&repo, "main");
24072399
// Validate that we will perform long searches to connect connectable segments, without interfering
24082400
// with other searches that may take even longer.
24092401
// Also, without limit, we should be able to see all of 'main' without cut-off
2410-
let graph = Graph::from_commit_traversal(id, ref_name.clone(), &*meta, standard_options())?
2411-
.validated()?;
2402+
let graph =
2403+
Graph::from_commit_traversal(main_id, main_ref_name.clone(), &*meta, standard_options())?
2404+
.validated()?;
24122405
insta::assert_snapshot!(graph_tree(&graph), @r"
24132406
├── 📕►►►:1[0]:gitbutler/workspace
24142407
│ └── ·41ed0e4 (⌂|🏘)
@@ -2473,9 +2466,13 @@ fn partitions_with_long_and_short_connections_to_each_other() -> anyhow::Result<
24732466
// When setting a limit when traversing 'main', it is respected.
24742467
// We still want it to be found and connected though, and it's notable that the limit kicks in
24752468
// once everything reconciled.
2476-
let graph =
2477-
Graph::from_commit_traversal(id, ref_name, &*meta, standard_options().with_limit_hint(1))?
2478-
.validated()?;
2469+
let graph = Graph::from_commit_traversal(
2470+
main_id,
2471+
main_ref_name,
2472+
&*meta,
2473+
standard_options().with_limit_hint(1),
2474+
)?
2475+
.validated()?;
24792476
insta::assert_snapshot!(graph_tree(&graph), @r"
24802477
├── 📕►►►:1[0]:gitbutler/workspace
24812478
│ └── ·41ed0e4 (⌂|🏘)
@@ -2488,12 +2485,7 @@ fn partitions_with_long_and_short_connections_to_each_other() -> anyhow::Result<
24882485
│ │ ├── ·f49c977 (⌂|🏘|✓|1)
24892486
│ │ ├── ·7b7ebb2 (⌂|🏘|✓|1)
24902487
│ │ ├── ·dca4960 (⌂|🏘|✓|1)
2491-
│ │ ├── ·11c29b8 (⌂|🏘|✓|1)
2492-
│ │ ├── ·c32dd03 (⌂|🏘|✓|1)
2493-
│ │ ├── ·b625665 (⌂|🏘|✓|1)
2494-
│ │ ├── ·a821094 (⌂|🏘|✓|1)
2495-
│ │ ├── ·bce0c5e (⌂|🏘|✓|1)
2496-
│ │ └── ·3183e43 (⌂|🏘|✓|1)
2488+
│ │ └── ✂·11c29b8 (⌂|🏘|✓|1)
24972489
│ └── ►:7[3]:long-main-to-workspace
24982490
│ ├── ·77f31a0 (⌂|🏘|✓)
24992491
│ ├── ·eb17e31 (⌂|🏘|✓)
@@ -2529,12 +2521,7 @@ fn partitions_with_long_and_short_connections_to_each_other() -> anyhow::Result<
25292521
├── ·f49c977 (🏘️|✓)
25302522
├── ·7b7ebb2 (🏘️|✓)
25312523
├── ·dca4960 (🏘️|✓)
2532-
├── ·11c29b8 (🏘️|✓)
2533-
├── ·c32dd03 (🏘️|✓)
2534-
├── ·b625665 (🏘️|✓)
2535-
├── ·a821094 (🏘️|✓)
2536-
├── ·bce0c5e (🏘️|✓)
2537-
└── ·3183e43 (🏘️|✓)
2524+
└── ✂️·11c29b8 (🏘️|✓)
25382525
");
25392526

25402527
// From the workspace, even without limit, we don't traverse all of 'main' as it's uninteresting.

0 commit comments

Comments
 (0)