Skip to content

Commit 1cfe577

Browse files
authored
Merge pull request #1564 from Byron/improvements
improvements
2 parents 750e268 + 0fe5133 commit 1cfe577

File tree

10 files changed

+170
-128
lines changed

10 files changed

+170
-128
lines changed

gitoxide-core/src/repository/merge_base.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ pub fn merge_base(
2020
.collect::<Result<_, _>>()?;
2121

2222
let cache = repo.commit_graph_if_enabled()?;
23-
let bases = repo.merge_bases_many_with_cache(first_id, &other_ids, cache.as_ref())?;
23+
let mut graph = repo.revision_graph(cache.as_ref());
24+
let bases = repo.merge_bases_many_with_graph(first_id, &other_ids, &mut graph)?;
2425
if bases.is_empty() {
2526
bail!("No base found for {first} and {others}", others = others.join(", "))
2627
}

gix-negotiate/src/consecutive.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl Algorithm {
2323
let mut is_common = false;
2424
let mut has_mark = false;
2525
if let Some(commit) = graph
26-
.try_lookup_or_insert_commit(id, |data| {
26+
.get_or_insert_commit(id, |data| {
2727
has_mark = data.flags.intersects(mark);
2828
data.flags |= mark;
2929
is_common = data.flags.contains(Flags::COMMON);
@@ -47,7 +47,7 @@ impl Algorithm {
4747
) -> Result<(), Error> {
4848
let mut is_common = false;
4949
if let Some(commit) = graph
50-
.try_lookup_or_insert_commit(id, |data| is_common = data.flags.contains(Flags::COMMON))?
50+
.get_or_insert_commit(id, |data| is_common = data.flags.contains(Flags::COMMON))?
5151
.filter(|_| !is_common)
5252
{
5353
let mut queue = gix_revwalk::PriorityQueue::from_iter(Some((commit.commit_time, (id, 0_usize))));
@@ -64,11 +64,11 @@ impl Algorithm {
6464
{
6565
self.add_to_queue(id, Flags::SEEN, graph)?;
6666
} else if matches!(ancestors, Ancestors::AllUnseen) || generation < 2 {
67-
if let Some(commit) = graph.try_lookup_or_insert_commit(id, |_| {})? {
67+
if let Some(commit) = graph.get_or_insert_commit(id, |_| {})? {
6868
for parent_id in commit.parents.clone() {
6969
let mut prev_flags = Flags::default();
7070
if let Some(parent) = graph
71-
.try_lookup_or_insert_commit(parent_id, |data| {
71+
.get_or_insert_commit(parent_id, |data| {
7272
prev_flags = data.flags;
7373
data.flags |= Flags::COMMON;
7474
})?

gix-negotiate/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,4 @@ pub trait Negotiator {
144144
}
145145

146146
/// An error that happened during any of the methods on a [`Negotiator`].
147-
pub type Error = gix_revwalk::graph::try_lookup_or_insert_default::Error;
147+
pub type Error = gix_revwalk::graph::get_or_insert_default::Error;

gix-negotiate/src/skipping.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl Default for Algorithm {
2020
impl Algorithm {
2121
/// Add `id` to our priority queue and *add* `flags` to it.
2222
fn add_to_queue(&mut self, id: ObjectId, mark: Flags, graph: &mut crate::Graph<'_, '_>) -> Result<(), Error> {
23-
let commit = graph.try_lookup_or_insert_commit(id, |entry| {
23+
let commit = graph.get_or_insert_commit(id, |entry| {
2424
entry.flags |= mark | Flags::SEEN;
2525
})?;
2626
if let Some(timestamp) = commit.map(|c| c.commit_time) {
@@ -35,15 +35,15 @@ impl Algorithm {
3535
fn mark_common(&mut self, id: ObjectId, graph: &mut crate::Graph<'_, '_>) -> Result<(), Error> {
3636
let mut is_common = false;
3737
if let Some(commit) = graph
38-
.try_lookup_or_insert_commit(id, |entry| {
38+
.get_or_insert_commit(id, |entry| {
3939
is_common = entry.flags.contains(Flags::COMMON);
4040
entry.flags |= Flags::COMMON;
4141
})?
4242
.filter(|_| !is_common)
4343
{
4444
let mut queue = gix_revwalk::PriorityQueue::from_iter(Some((commit.commit_time, id)));
4545
while let Some(id) = queue.pop_value() {
46-
if let Some(commit) = graph.try_lookup_or_insert_commit(id, |entry| {
46+
if let Some(commit) = graph.get_or_insert_commit(id, |entry| {
4747
if !entry.flags.contains(Flags::POPPED) {
4848
self.non_common_revs -= 1;
4949
}
@@ -56,7 +56,7 @@ impl Algorithm {
5656
}
5757
let mut was_unseen_or_common = false;
5858
if let Some(parent) = graph
59-
.try_lookup_or_insert_commit(parent_id, |entry| {
59+
.get_or_insert_commit(parent_id, |entry| {
6060
was_unseen_or_common =
6161
!entry.flags.contains(Flags::SEEN) || entry.flags.contains(Flags::COMMON);
6262
entry.flags |= Flags::COMMON;

gix-revision/src/merge_base.rs

Lines changed: 83 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,15 @@ bitflags::bitflags! {
1818
#[derive(Debug, thiserror::Error)]
1919
#[allow(missing_docs)]
2020
pub enum Error {
21-
#[error(transparent)]
22-
IterParents(#[from] gix_revwalk::graph::commit::iter_parents::Error),
23-
#[error("A commit could not be found")]
24-
FindExistingCommit(#[from] gix_object::find::existing_iter::Error),
25-
#[error("A commit could not be decoded during traversal")]
26-
Decode(#[from] gix_object::decode::Error),
21+
#[error("A commit could not be inserted into the graph")]
22+
InsertCommit(#[from] gix_revwalk::graph::get_or_insert_default::Error),
2723
}
2824

2925
pub(crate) mod function {
3026
use super::Error;
3127
use crate::{merge_base::Flags, Graph, PriorityQueue};
3228
use gix_hash::ObjectId;
33-
use gix_revwalk::graph::LazyCommit;
29+
use gix_revwalk::graph;
3430
use std::cmp::Ordering;
3531

3632
/// Given a commit at `first` id, traverse the commit `graph` and return all possible merge-base between it and `others`,
@@ -39,19 +35,23 @@ pub(crate) mod function {
3935
///
4036
/// Note that this function doesn't do any work if `first` is contained in `others`, which is when `first` will be returned
4137
/// as only merge-base right away. This is even the case if some commits of `others` are disjoint.
38+
///
39+
/// # Performance
40+
///
41+
/// For repeated calls, be sure to re-use `graph` as its content will be kept and reused for a great speed-up. The contained flags
42+
/// will automatically be cleared.
4243
pub fn merge_base(
4344
first: ObjectId,
4445
others: &[ObjectId],
45-
graph: &mut Graph<'_, '_, Flags>,
46+
graph: &mut Graph<'_, '_, graph::Commit<Flags>>,
4647
) -> Result<Option<Vec<ObjectId>>, Error> {
4748
let _span = gix_trace::coarse!("gix_revision::merge_base()", ?first, ?others);
4849
if others.is_empty() || others.contains(&first) {
4950
return Ok(Some(vec![first]));
5051
}
5152

52-
graph.clear();
53+
graph.clear_commit_data(|f| *f = Flags::empty());
5354
let bases = paint_down_to_common(first, others, graph)?;
54-
graph.clear();
5555

5656
let bases = remove_redundant(&bases, graph)?;
5757
Ok((!bases.is_empty()).then_some(bases))
@@ -61,11 +61,12 @@ pub(crate) mod function {
6161
/// That way, we return only the topologically most recent commits in `commits`.
6262
fn remove_redundant(
6363
commits: &[(ObjectId, GenThenTime)],
64-
graph: &mut Graph<'_, '_, Flags>,
64+
graph: &mut Graph<'_, '_, graph::Commit<Flags>>,
6565
) -> Result<Vec<ObjectId>, Error> {
6666
if commits.is_empty() {
6767
return Ok(Vec::new());
6868
}
69+
graph.clear_commit_data(|f| *f = Flags::empty());
6970
let _span = gix_trace::detail!("gix_revision::remove_redundant()", num_commits = %commits.len());
7071
let sorted_commits = {
7172
let mut v = commits.to_vec();
@@ -77,36 +78,46 @@ pub(crate) mod function {
7778

7879
let mut walk_start = Vec::with_capacity(commits.len());
7980
for (id, _) in commits {
80-
graph.insert_parents_with_lookup(id, &mut |parent_id, parent_data, maybe_flags| -> Result<_, Error> {
81-
if maybe_flags.map_or(true, |flags| !flags.contains(Flags::STALE)) {
82-
walk_start.push((parent_id, GenThenTime::try_from(parent_data)?));
83-
}
84-
Ok(Flags::empty())
85-
})?;
86-
graph.insert(*id, Flags::RESULT);
81+
let commit = graph.get_mut(id).expect("previously added");
82+
commit.data |= Flags::RESULT;
83+
for parent_id in commit.parents.clone() {
84+
graph.get_or_insert_full_commit(parent_id, |parent| {
85+
// prevent double-addition
86+
if !parent.data.contains(Flags::STALE) {
87+
parent.data |= Flags::STALE;
88+
walk_start.push((parent_id, GenThenTime::from(&*parent)));
89+
}
90+
})?;
91+
}
8792
}
8893
walk_start.sort_by(|a, b| a.0.cmp(&b.0));
94+
// allow walking everything at first.
95+
walk_start
96+
.iter_mut()
97+
.for_each(|(id, _)| graph.get_mut(id).expect("added previously").data.remove(Flags::STALE));
8998
let mut count_still_independent = commits.len();
9099

91100
let mut stack = Vec::new();
92101
while let Some((commit_id, commit_info)) = walk_start.pop().filter(|_| count_still_independent > 1) {
93102
stack.clear();
94-
graph.insert(commit_id, Flags::STALE);
103+
graph.get_mut(&commit_id).expect("added").data |= Flags::STALE;
95104
stack.push((commit_id, commit_info));
96105

97106
while let Some((commit_id, commit_info)) = stack.last().copied() {
98-
let flags = graph.get_mut(&commit_id).expect("all commits have been added");
99-
if flags.contains(Flags::RESULT) {
100-
flags.remove(Flags::RESULT);
107+
let commit = graph.get_mut(&commit_id).expect("all commits have been added");
108+
let commit_parents = commit.parents.clone();
109+
if commit.data.contains(Flags::RESULT) {
110+
commit.data.remove(Flags::RESULT);
101111
count_still_independent -= 1;
102112
if count_still_independent <= 1 {
103113
break;
104114
}
105-
if commit_id == sorted_commits[min_gen_pos].0 {
115+
if *commit_id == *sorted_commits[min_gen_pos].0 {
106116
while min_gen_pos < commits.len() - 1
107117
&& graph
108118
.get(&sorted_commits[min_gen_pos].0)
109119
.expect("already added")
120+
.data
110121
.contains(Flags::STALE)
111122
{
112123
min_gen_pos += 1;
@@ -120,87 +131,81 @@ pub(crate) mod function {
120131
continue;
121132
}
122133

123-
let mut pushed_one_parent = false;
124-
graph.insert_parents_with_lookup(&commit_id, &mut |parent_id,
125-
parent_data,
126-
maybe_flags|
127-
-> Result<_, Error> {
128-
let is_new_parent = !pushed_one_parent
129-
&& maybe_flags.map_or(true, |flags| {
130-
let res = !flags.contains(Flags::STALE);
131-
*flags |= Flags::STALE;
132-
res
133-
});
134-
if is_new_parent {
135-
stack.push((parent_id, GenThenTime::try_from(parent_data)?));
136-
pushed_one_parent = true;
134+
let previous_len = stack.len();
135+
for parent_id in &commit_parents {
136+
if graph
137+
.get_or_insert_full_commit(*parent_id, |parent| {
138+
if !parent.data.contains(Flags::STALE) {
139+
parent.data |= Flags::STALE;
140+
stack.push((*parent_id, GenThenTime::from(&*parent)));
141+
}
142+
})?
143+
.is_some()
144+
{
145+
break;
137146
}
138-
Ok(Flags::STALE)
139-
})?;
147+
}
140148

141-
if !pushed_one_parent {
149+
if previous_len == stack.len() {
142150
stack.pop();
143151
}
144152
}
145153
}
146154

147155
Ok(commits
148156
.iter()
149-
.filter_map(|(id, _info)| graph.get(id).filter(|flags| !flags.contains(Flags::STALE)).map(|_| *id))
157+
.filter_map(|(id, _info)| {
158+
graph
159+
.get(id)
160+
.filter(|commit| !commit.data.contains(Flags::STALE))
161+
.map(|_| *id)
162+
})
150163
.collect())
151164
}
152165

153166
fn paint_down_to_common(
154167
first: ObjectId,
155168
others: &[ObjectId],
156-
graph: &mut Graph<'_, '_, Flags>,
169+
graph: &mut Graph<'_, '_, graph::Commit<Flags>>,
157170
) -> Result<Vec<(ObjectId, GenThenTime)>, Error> {
158171
let mut queue = PriorityQueue::<GenThenTime, ObjectId>::new();
159-
graph.insert_data(first, |commit| -> Result<_, Error> {
160-
queue.insert(commit.try_into()?, first);
161-
Ok(Flags::COMMIT1)
172+
graph.get_or_insert_full_commit(first, |commit| {
173+
commit.data |= Flags::COMMIT1;
174+
queue.insert(GenThenTime::from(&*commit), first);
162175
})?;
163176

164177
for other in others {
165-
graph.insert_data(*other, |commit| -> Result<_, Error> {
166-
queue.insert(commit.try_into()?, *other);
167-
Ok(Flags::COMMIT2)
178+
graph.get_or_insert_full_commit(*other, |commit| {
179+
commit.data |= Flags::COMMIT2;
180+
queue.insert(GenThenTime::from(&*commit), *other);
168181
})?;
169182
}
170183

171184
let mut out = Vec::new();
172-
while queue
173-
.iter_unordered()
174-
.any(|id| graph.get(id).map_or(false, |data| !data.contains(Flags::STALE)))
175-
{
185+
while queue.iter_unordered().any(|id| {
186+
graph
187+
.get(id)
188+
.map_or(false, |commit| !commit.data.contains(Flags::STALE))
189+
}) {
176190
let (info, commit_id) = queue.pop().expect("we have non-stale");
177-
let flags_mut = graph.get_mut(&commit_id).expect("everything queued is in graph");
178-
let mut flags_without_result = *flags_mut & (Flags::COMMIT1 | Flags::COMMIT2 | Flags::STALE);
191+
let commit = graph.get_mut(&commit_id).expect("everything queued is in graph");
192+
let mut flags_without_result = commit.data & (Flags::COMMIT1 | Flags::COMMIT2 | Flags::STALE);
179193
if flags_without_result == (Flags::COMMIT1 | Flags::COMMIT2) {
180-
if !flags_mut.contains(Flags::RESULT) {
181-
*flags_mut |= Flags::RESULT;
194+
if !commit.data.contains(Flags::RESULT) {
195+
commit.data |= Flags::RESULT;
182196
out.push((commit_id, info));
183197
}
184198
flags_without_result |= Flags::STALE;
185199
}
186200

187-
graph.insert_parents_with_lookup(&commit_id, &mut |parent_id, parent, ex_flags| -> Result<_, Error> {
188-
let queue_info = match ex_flags {
189-
Some(ex_flags) => {
190-
if (*ex_flags & flags_without_result) != flags_without_result {
191-
*ex_flags |= flags_without_result;
192-
Some(GenThenTime::try_from(parent)?)
193-
} else {
194-
None
195-
}
201+
for parent_id in commit.parents.clone() {
202+
graph.get_or_insert_full_commit(parent_id, |parent| {
203+
if (parent.data & flags_without_result) != flags_without_result {
204+
parent.data |= flags_without_result;
205+
queue.insert(GenThenTime::from(&*parent), parent_id);
196206
}
197-
None => Some(GenThenTime::try_from(parent)?),
198-
};
199-
if let Some(info) = queue_info {
200-
queue.insert(info, parent_id);
201-
}
202-
Ok(flags_without_result)
203-
})?;
207+
})?;
208+
}
204209
}
205210

206211
Ok(out)
@@ -215,15 +220,12 @@ pub(crate) mod function {
215220
time: gix_date::SecondsSinceUnixEpoch,
216221
}
217222

218-
impl TryFrom<gix_revwalk::graph::LazyCommit<'_, '_>> for GenThenTime {
219-
type Error = gix_object::decode::Error;
220-
221-
fn try_from(commit: LazyCommit<'_, '_>) -> Result<Self, Self::Error> {
222-
let (generation, timestamp) = commit.generation_and_timestamp()?;
223-
Ok(GenThenTime {
224-
generation: generation.unwrap_or(gix_commitgraph::GENERATION_NUMBER_INFINITY),
225-
time: timestamp,
226-
})
223+
impl From<&graph::Commit<Flags>> for GenThenTime {
224+
fn from(commit: &graph::Commit<Flags>) -> Self {
225+
GenThenTime {
226+
generation: commit.generation.unwrap_or(gix_commitgraph::GENERATION_NUMBER_INFINITY),
227+
time: commit.commit_time,
228+
}
227229
}
228230
}
229231

gix-revision/tests/merge_base/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ mod baseline {
1717
.then(|| gix_commitgraph::Graph::from_info_dir(&odb.store_ref().path().join("info")).unwrap());
1818
for expected in parse_expectations(&baseline_path)? {
1919
let mut graph = gix_revision::Graph::new(&odb, cache.as_ref());
20-
2120
let actual = merge_base(expected.first, &expected.others, &mut graph)?;
2221
assert_eq!(
2322
actual,
@@ -27,6 +26,17 @@ mod baseline {
2726
input = expected.plain_input
2827
);
2928
}
29+
let mut graph = gix_revision::Graph::new(&odb, cache.as_ref());
30+
for expected in parse_expectations(&baseline_path)? {
31+
let actual = merge_base(expected.first, &expected.others, &mut graph)?;
32+
assert_eq!(
33+
actual,
34+
expected.bases,
35+
"sample (reused graph) {file:?}:{input}",
36+
file = baseline_path.with_extension("").file_name(),
37+
input = expected.plain_input
38+
);
39+
}
3040
}
3141
}
3242
assert_ne!(count, 0, "there must be at least one baseline");

0 commit comments

Comments
 (0)