Skip to content

Commit 7f90c8d

Browse files
committed
feat: implement consecutive algorithm.
This is the default negotiation algorithm.
1 parent 5e59fea commit 7f90c8d

File tree

6 files changed

+107
-23
lines changed

6 files changed

+107
-23
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-negotiate/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ gix-hash = { version = "^0.11.1", path = "../gix-hash" }
1717
gix-object = { version = "^0.29.2", path = "../gix-object" }
1818
gix-commitgraph = { version = "^0.15.0", path = "../gix-commitgraph" }
1919
gix-revision = { version = "^0.14.0", path = "../gix-revision" }
20+
thiserror = "1.0.40"
21+
bitflags = "2"
2022

2123
[dev-dependencies]
2224
gix-testtools = { path = "../tests/tools" }

gix-negotiate/src/consecutive.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,67 @@
1-
// TODO: make this the actual bitflags
2-
pub(crate) type Flags = u32;
1+
use crate::{Error, Negotiator};
2+
use gix_hash::ObjectId;
3+
use gix_revision::graph::CommitterTimestamp;
4+
bitflags::bitflags! {
5+
/// Whether something can be read or written.
6+
#[derive(Debug, Default, Copy, Clone)]
7+
pub struct Flags: u8 {
8+
/// The revision is known to be in common with the remote
9+
const COMMON = 1 << 0;
10+
/// The revision is common and was set by merit of a remote tracking ref (e.g. `refs/heads/origin/main`).
11+
const COMMON_REF = 1 << 1;
12+
/// The revision was processed by us and used to avoid processing it again.
13+
const SEEN = 1 << 2;
14+
/// The revision was popped off our primary priority queue, used to avoid double-counting of `non_common_revs`
15+
const POPPED = 1 << 3;
16+
}
17+
}
18+
19+
pub(crate) struct Algorithm<'find> {
20+
graph: gix_revision::Graph<'find, Flags>,
21+
revs: gix_revision::PriorityQueue<CommitterTimestamp, ObjectId>,
22+
non_common_revs: usize,
23+
}
24+
25+
impl<'a> Algorithm<'a> {
26+
pub fn new(graph: gix_revision::Graph<'a, Flags>) -> Self {
27+
Self {
28+
graph,
29+
revs: gix_revision::PriorityQueue::new(),
30+
non_common_revs: 0,
31+
}
32+
}
33+
34+
/// Add `id` to our priority queue and *add* `flags` to it.
35+
fn add_to_queue(&mut self, id: ObjectId, flags: Flags) -> Result<(), Error> {
36+
let mut is_common = false;
37+
let commit = self.graph.try_lookup_and_insert(id, |current| {
38+
*current |= flags;
39+
is_common = current.contains(Flags::COMMON);
40+
})?;
41+
if let Some(timestamp) = commit.map(|c| c.committer_timestamp()).transpose()? {
42+
self.revs.insert(timestamp, id);
43+
if !is_common {
44+
self.non_common_revs += 1;
45+
}
46+
}
47+
Ok(())
48+
}
49+
}
50+
51+
impl<'a> Negotiator for Algorithm<'a> {
52+
fn known_common(&mut self, _id: ObjectId) -> Result<(), Error> {
53+
todo!()
54+
}
55+
56+
fn add_tip(&mut self, id: ObjectId) -> Result<(), Error> {
57+
self.add_to_queue(id, Flags::SEEN)
58+
}
59+
60+
fn next_have(&mut self) -> Option<Result<ObjectId, Error>> {
61+
todo!()
62+
}
63+
64+
fn in_common_with_remote(&mut self, _id: ObjectId) -> Result<bool, Error> {
65+
todo!()
66+
}
67+
}

gix-negotiate/src/lib.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ impl Algorithm {
4747
self,
4848
find: Find,
4949
cache: impl Into<Option<gix_commitgraph::Graph>>,
50-
) -> Box<dyn Negotiator>
50+
) -> Box<dyn Negotiator + 'find>
5151
where
5252
Find:
5353
for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Result<Option<gix_object::CommitRefIter<'a>>, E> + 'find,
@@ -56,8 +56,8 @@ impl Algorithm {
5656
match &self {
5757
Algorithm::Noop => Box::new(noop::Noop) as Box<dyn Negotiator>,
5858
Algorithm::Consecutive => {
59-
let _graph = gix_revision::Graph::<'_, consecutive::Flags>::new(find, cache);
60-
todo!()
59+
let graph = gix_revision::Graph::<'_, consecutive::Flags>::new(find, cache);
60+
Box::new(consecutive::Algorithm::new(graph))
6161
}
6262
Algorithm::Skipping => todo!(),
6363
}
@@ -69,18 +69,30 @@ pub trait Negotiator {
6969
/// Mark `id` as common between the remote and us.
7070
///
7171
/// These ids are typically the local tips of remote tracking branches.
72-
fn known_common(&mut self, id: &gix_hash::oid);
72+
fn known_common(&mut self, id: gix_hash::ObjectId) -> Result<(), Error>;
7373

7474
/// Add `id` as starting point of a traversal across commits that aren't necessarily common between the remote and us.
7575
///
7676
/// These tips are usually the commits of local references whose tips should lead to objects that we have in common with the remote.
77-
fn add_tip(&mut self, id: &gix_hash::oid);
77+
fn add_tip(&mut self, id: gix_hash::ObjectId) -> Result<(), Error>;
7878

7979
/// Produce the next id of an object that we want the server to know we have. It's an object we don't know we have in common or not.
8080
///
8181
/// Returns `None` if we have exhausted all options, which might mean we have traversed the entire commit graph.
82-
fn next_have(&mut self) -> Option<gix_hash::ObjectId>;
82+
fn next_have(&mut self) -> Option<Result<gix_hash::ObjectId, Error>>;
8383

8484
/// Mark `id` as being common with the remote (as informed by the remote itself) and return `true` if we knew it was common already.
85-
fn in_common_with_remote(&mut self, id: &gix_hash::oid) -> bool;
85+
fn in_common_with_remote(&mut self, id: gix_hash::ObjectId) -> Result<bool, Error>;
86+
}
87+
88+
/// An error that happened during any of the methods on a [`Negotiator`].
89+
#[derive(Debug, thiserror::Error)]
90+
#[allow(missing_docs)]
91+
pub enum Error {
92+
#[error(transparent)]
93+
DecodeCommit(#[from] gix_object::decode::Error),
94+
#[error(transparent)]
95+
DecodeCommitInGraph(#[from] gix_commitgraph::file::commit::Error),
96+
#[error(transparent)]
97+
LookupCommitInGraph(#[from] gix_revision::graph::lookup::Error),
8698
}

gix-negotiate/src/noop.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
use crate::Negotiator;
2-
use gix_hash::{oid, ObjectId};
1+
use crate::{Error, Negotiator};
2+
use gix_hash::ObjectId;
33

44
pub(crate) struct Noop;
55

66
impl Negotiator for Noop {
7-
fn known_common(&mut self, _id: &oid) {}
7+
fn known_common(&mut self, _id: ObjectId) -> Result<(), Error> {
8+
Ok(())
9+
}
810

9-
fn add_tip(&mut self, _id: &oid) {}
11+
fn add_tip(&mut self, _id: ObjectId) -> Result<(), Error> {
12+
Ok(())
13+
}
1014

11-
fn next_have(&mut self) -> Option<ObjectId> {
15+
fn next_have(&mut self) -> Option<Result<ObjectId, Error>> {
1216
None
1317
}
1418

15-
fn in_common_with_remote(&mut self, _id: &oid) -> bool {
16-
false
19+
fn in_common_with_remote(&mut self, _id: ObjectId) -> Result<bool, Error> {
20+
Ok(false)
1721
}
1822
}

gix-negotiate/tests/baseline/mod.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ fn run() -> crate::Result {
1111

1212
for (algo_name, algo) in [
1313
("noop", Algorithm::Noop),
14-
// ("consecutive", Algorithm::Consecutive),
14+
("consecutive", Algorithm::Consecutive),
1515
// ("skipping", Algorithm::Skipping),
1616
] {
1717
let buf = std::fs::read(base.join(format!("baseline.{algo_name}")))?;
@@ -31,7 +31,7 @@ fn run() -> crate::Result {
3131
cache,
3232
);
3333
for tip in &tips {
34-
negotiator.add_tip(tip);
34+
negotiator.add_tip(*tip)?;
3535
}
3636
for Round { haves, common } in ParseRounds::new(buf.lines()) {
3737
for have in haves {
@@ -40,16 +40,15 @@ fn run() -> crate::Result {
4040
"{algo_name}: one have per baseline: {have} missing or in wrong order, left: {:?}",
4141
std::iter::from_fn(|| negotiator.next_have()).collect::<Vec<_>>()
4242
)
43-
});
43+
})?;
4444
assert_eq!(actual, have, "{algo_name}: order and commit matches exactly");
4545
}
4646
for common_revision in common {
47-
negotiator.in_common_with_remote(&common_revision);
47+
negotiator.in_common_with_remote(common_revision)?;
4848
}
4949
}
50-
assert_eq!(
51-
negotiator.next_have(),
52-
None,
50+
assert!(
51+
negotiator.next_have().is_none(),
5352
"{algo_name}: negotiator should be depleted after all recorded baseline rounds"
5453
);
5554
}

0 commit comments

Comments
 (0)