Skip to content

Commit 52903a6

Browse files
committed
fix commits_ahead computation in Target.
Previously it was dependent on counting remote commits, but these are dependent on the respective local tracking branch not being caught up, but that really is up to the user and shouldn't affect the count.
1 parent 16f9845 commit 52903a6

File tree

6 files changed

+105
-67
lines changed

6 files changed

+105
-67
lines changed

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

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,65 @@ pub struct Target {
9797
impl Target {
9898
/// Return `None` if `ref_name` wasn't found as segment in `graph`.
9999
/// This can happen if a reference is configured, but not actually present as reference.
100-
fn from_ref_name(ref_name: &gix::refs::FullName, graph: &Graph) -> Option<Self> {
100+
/// Note that `commits_ahead` isn't set yet, see [`Self::compute_and_set_commits_ahead()`].
101+
fn from_ref_name_without_commits_ahead(
102+
ref_name: &gix::refs::FullName,
103+
graph: &Graph,
104+
) -> Option<Self> {
101105
let target_segment = graph.inner.node_indices().find_map(|n| {
102106
let s = &graph[n];
103107
(s.ref_name.as_ref() == Some(ref_name)).then_some(s)
104108
})?;
105109
Some(Target {
106110
ref_name: ref_name.to_owned(),
107111
segment_index: target_segment.id,
108-
commits_ahead: {
109-
// Find all remote commits but stop traversing when there is segments without remotes.
110-
let mut count = 0;
111-
graph.visit_all_segments_until(target_segment.id, Direction::Outgoing, |s| {
112-
let remote_commits = s.commits.iter().filter(|c| c.flags.is_remote()).count();
113-
count += remote_commits;
114-
remote_commits != s.commits.len()
115-
});
116-
count
117-
},
112+
commits_ahead: 0,
113+
})
114+
}
115+
116+
fn compute_and_set_commits_ahead(
117+
&mut self,
118+
graph: &Graph,
119+
lower_bound_segment: Option<SegmentIndex>,
120+
) {
121+
let lower_bound = lower_bound_segment.map(|sidx| (sidx, graph[sidx].generation));
122+
self.commits_ahead = 0;
123+
Self::visit_upstream_commits(graph, self.segment_index, lower_bound, |s| {
124+
self.commits_ahead += s.commits.len();
118125
})
119126
}
120127
}
121128

129+
/// Utilities
130+
impl Target {
131+
/// Visit all segments whose commits would be considered 'upstream', or part of the target branch
132+
/// whose tip is identified with `target_segment`. The `lower_bound_segment_and_generation` is another way
133+
/// to stop the traversal.
134+
pub fn visit_upstream_commits(
135+
graph: &Graph,
136+
target_segment: SegmentIndex,
137+
lower_bound_segment_and_generation: Option<(SegmentIndex, usize)>,
138+
mut visit: impl FnMut(&Segment),
139+
) {
140+
graph.visit_all_segments_until(target_segment, Direction::Outgoing, |s| {
141+
let prune = true;
142+
if lower_bound_segment_and_generation.is_some_and(
143+
|(lower_bound, lower_bound_generation)| {
144+
s.id == lower_bound || s.generation > lower_bound_generation
145+
},
146+
) || s
147+
.commits
148+
.iter()
149+
.any(|c| c.flags.contains(CommitFlags::InWorkspace))
150+
{
151+
return prune;
152+
}
153+
visit(s);
154+
!prune
155+
});
156+
}
157+
}
158+
122159
impl Graph {
123160
/// Analyse the current graph starting at its [entrypoint](Self::lookup_entrypoint()).
124161
///
@@ -196,9 +233,9 @@ impl Graph {
196233
id: ws_tip_segment.id,
197234
kind,
198235
stacks: vec![],
199-
target: metadata
200-
.as_ref()
201-
.and_then(|md| Target::from_ref_name(md.target_ref.as_ref()?, self)),
236+
target: metadata.as_ref().and_then(|md| {
237+
Target::from_ref_name_without_commits_ahead(md.target_ref.as_ref()?, self)
238+
}),
202239
extra_target: self.extra_target,
203240
metadata,
204241
lower_bound_segment_id: None,
@@ -352,6 +389,9 @@ impl Graph {
352389
);
353390
}
354391

392+
if let Some(target) = ws.target.as_mut() {
393+
target.compute_and_set_commits_ahead(self, ws.lower_bound_segment_id);
394+
}
355395
ws.mark_remote_reachability()?;
356396
Ok(ws)
357397
}
@@ -834,6 +874,8 @@ impl std::fmt::Debug for Workspace<'_> {
834874
.field("id", &self.id.index())
835875
.field("stacks", &self.stacks)
836876
.field("metadata", &self.metadata)
877+
.field("target", &self.target)
878+
.field("extra_target", &self.extra_target)
837879
.finish()
838880
}
839881
}

crates/but-workspace/src/changeset.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use bstr::BString;
1616
use but_core::{ChangeState, commit::TreeKind};
1717
use gix::diff::tree::recorder::Change;
1818
use gix::{ObjectId, Repository, object::tree::EntryKind, prelude::ObjectIdExt};
19-
use itertools::Itertools;
2019
use std::ops::Deref;
2120
use std::{
2221
borrow::Borrow,
@@ -59,10 +58,7 @@ impl RefInfo {
5958
.target
6059
.as_ref()
6160
.map(|t| t.segment_index)
62-
.into_iter()
63-
.chain(self.extra_target)
64-
.sorted_by_key(|sidx| graph[*sidx].generation)
65-
.next();
61+
.or(self.extra_target);
6662
let mut upstream_commits = Vec::new();
6763
let Some(target_tip) = topmost_target_sidx else {
6864
// Without any notion of 'target' we can't do anything here.

crates/but-workspace/tests/workspace/ref_info/with_workspace_commit/journey/exhaustive_with_squash_merges.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ Ok(
757757
"refs/remotes/origin/main",
758758
),
759759
segment_index: NodeIndex(1),
760-
commits_ahead: 3,
760+
commits_ahead: 5,
761761
},
762762
),
763763
extra_target: None,
@@ -880,7 +880,7 @@ Ok(
880880
"refs/remotes/origin/main",
881881
),
882882
segment_index: NodeIndex(1),
883-
commits_ahead: 2,
883+
commits_ahead: 7,
884884
},
885885
),
886886
extra_target: None,

crates/but-workspace/tests/workspace/ref_info/with_workspace_commit/journey/integrate_with_merges.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ fn remote_diverged_merge() -> anyhow::Result<()> {
277277
"refs/remotes/origin/main",
278278
),
279279
segment_index: NodeIndex(1),
280-
commits_ahead: 0,
280+
commits_ahead: 2,
281281
},
282282
),
283283
extra_target: Some(
@@ -416,7 +416,7 @@ fn remote_behind_merge_no_ff() -> anyhow::Result<()> {
416416
"refs/remotes/origin/main",
417417
),
418418
segment_index: NodeIndex(1),
419-
commits_ahead: 0,
419+
commits_ahead: 1,
420420
},
421421
),
422422
extra_target: Some(
@@ -552,7 +552,7 @@ fn remote_ahead_merge_ff() -> anyhow::Result<()> {
552552
"refs/remotes/origin/main",
553553
),
554554
segment_index: NodeIndex(1),
555-
commits_ahead: 0,
555+
commits_ahead: 1,
556556
},
557557
),
558558
extra_target: Some(

crates/but-workspace/tests/workspace/ref_info/with_workspace_commit/journey/integrate_with_rebase.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ fn two_commits_rebased_onto_target() -> anyhow::Result<()> {
6262
"refs/remotes/origin/main",
6363
),
6464
segment_index: NodeIndex(1),
65-
commits_ahead: 0,
65+
commits_ahead: 5,
6666
},
6767
),
6868
extra_target: None,
@@ -137,7 +137,7 @@ fn two_commits_rebased_onto_target_with_changeset_check() -> anyhow::Result<()>
137137
"refs/remotes/origin/main",
138138
),
139139
segment_index: NodeIndex(1),
140-
commits_ahead: 0,
140+
commits_ahead: 5,
141141
},
142142
),
143143
extra_target: None,

0 commit comments

Comments
 (0)