Skip to content

Commit ae845de

Browse files
committed
Merge branch 'integrate-gix-negotiate'
2 parents 42661c5 + 7983f6f commit ae845de

File tree

82 files changed

+2280
-1277
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+2280
-1277
lines changed

Diff for: Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: crate-status.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ Check out the [performance discussion][gix-traverse-performance] as well.
233233
* [x] nested traversal
234234
* **commits**
235235
* [x] ancestor graph traversal similar to `git revlog`
236+
* [ ] `commitgraph` support
236237
* [x] API documentation
237238
* [ ] Examples
238239

@@ -551,6 +552,7 @@ The git staging area.
551552

552553
* [x] read-only access
553554
* [x] Graph lookup of commit information to obtain timestamps, generation and parents, and extra edges
555+
* [ ] [Corrected generation dates](https://github.com/git/git/commit/e8b63005c48696a26f976f5f9b0ccaf1983e439d)
554556
* [ ] Bloom filter index
555557
* [ ] Bloom filter data
556558
* [ ] create and update graphs and graph files
@@ -669,8 +671,11 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/gix-lock/README.
669671
* [x] fetch
670672
* [x] shallow (remains shallow, options to adjust shallow boundary)
671673
* [ ] a way to auto-explode small packs to avoid them to pile up
672-
* [ ] 'ref-in-want'
673-
* [ ] standard negotiation algorithms (right now we only have a 'naive' one)
674+
* [x] 'ref-in-want'
675+
* [ ] 'wanted-ref'
676+
* [ ] standard negotiation algorithms `consecutive`, `skipping` and `noop`.
677+
* [ ] a more efficient way to deal [with common `have`](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L525)
678+
during negotiation - we would submit known non-common `haves` each round in stateless transports whereas git prunes the set to known common ones.
674679
* [ ] push
675680
* [x] ls-refs
676681
* [x] ls-refs with ref-spec filter

Diff for: gitoxide-core/src/repository/attributes/validate_baseline.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ pub(crate) mod function {
100100
});
101101

102102
let stdout = std::io::BufReader::new(child.stdout.take().expect("we configured it"));
103-
let mut lines = stdout.lines().filter_map(Result::ok).peekable();
103+
let mut lines = stdout.lines().map_while(Result::ok).peekable();
104104
while let Some(baseline) = parse_attributes(&mut lines) {
105105
if tx_base.send(baseline).is_err() {
106106
child.kill().ok();

Diff for: gitoxide-core/src/repository/clone.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,24 @@ pub(crate) mod function {
9292
writeln!(err, "The cloned repository appears to be empty")?;
9393
}
9494
Status::DryRun { .. } => unreachable!("dry-run unsupported"),
95-
Status::Change { update_refs, .. } => {
95+
Status::Change {
96+
update_refs,
97+
negotiation_rounds,
98+
..
99+
} => {
96100
let remote = repo
97101
.find_default_remote(gix::remote::Direction::Fetch)
98102
.expect("one origin remote")?;
99103
let ref_specs = remote.refspecs(gix::remote::Direction::Fetch);
100-
print_updates(&repo, update_refs, ref_specs, fetch_outcome.ref_map, &mut out, &mut err)?;
104+
print_updates(
105+
&repo,
106+
negotiation_rounds,
107+
update_refs,
108+
ref_specs,
109+
fetch_outcome.ref_map,
110+
&mut out,
111+
&mut err,
112+
)?;
101113
}
102114
};
103115

Diff for: gitoxide-core/src/repository/fetch.rs

+27-3
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,34 @@ pub(crate) mod function {
6363
let ref_specs = remote.refspecs(gix::remote::Direction::Fetch);
6464
match res.status {
6565
Status::NoPackReceived { update_refs } => {
66-
print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err)
66+
print_updates(&repo, 1, update_refs, ref_specs, res.ref_map, &mut out, err)
6767
}
68-
Status::DryRun { update_refs } => print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err),
68+
Status::DryRun {
69+
update_refs,
70+
negotiation_rounds,
71+
} => print_updates(
72+
&repo,
73+
negotiation_rounds,
74+
update_refs,
75+
ref_specs,
76+
res.ref_map,
77+
&mut out,
78+
err,
79+
),
6980
Status::Change {
7081
update_refs,
7182
write_pack_bundle,
83+
negotiation_rounds,
7284
} => {
73-
print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err)?;
85+
print_updates(
86+
&repo,
87+
negotiation_rounds,
88+
update_refs,
89+
ref_specs,
90+
res.ref_map,
91+
&mut out,
92+
err,
93+
)?;
7494
if let Some(data_path) = write_pack_bundle.data_path {
7595
writeln!(out, "pack file: \"{}\"", data_path.display()).ok();
7696
}
@@ -88,6 +108,7 @@ pub(crate) mod function {
88108

89109
pub(crate) fn print_updates(
90110
repo: &gix::Repository,
111+
negotiation_rounds: usize,
91112
update_refs: gix::remote::fetch::refs::update::Outcome,
92113
refspecs: &[gix::refspec::RefSpec],
93114
mut map: gix::remote::fetch::RefMap,
@@ -191,6 +212,9 @@ pub(crate) mod function {
191212
refspecs.len()
192213
)?;
193214
}
215+
if negotiation_rounds != 1 {
216+
writeln!(err, "needed {negotiation_rounds} rounds of pack-negotiation")?;
217+
}
194218
Ok(())
195219
}
196220
}

Diff for: gix-actor/tests/identity/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use gix_actor::Identity;
33

44
#[test]
55
fn round_trip() -> gix_testtools::Result {
6-
static DEFAULTS: &'static [&'static [u8]] = &[
6+
static DEFAULTS: &[&[u8]] = &[
77
b"Sebastian Thiel <[email protected]>",
88
".. ☺️Sebastian 王知明 Thiel🙌 .. <[email protected]>".as_bytes(),
99
b".. whitespace \t is explicitly allowed - unicode aware trimming must be done elsewhere <[email protected]>"

Diff for: gix-actor/tests/signature/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fn trim() {
6464

6565
#[test]
6666
fn round_trip() -> Result<(), Box<dyn std::error::Error>> {
67-
static DEFAULTS: &'static [&'static [u8]] = &[
67+
static DEFAULTS: &[&[u8]] = &[
6868
b"Sebastian Thiel <[email protected]> 1 -0030",
6969
".. ☺️Sebastian 王知明 Thiel🙌 .. <[email protected]> 1528473343 +0230".as_bytes(),
7070
b".. whitespace \t is explicitly allowed - unicode aware trimming must be done elsewhere <[email protected]> 1528473343 +0230"

Diff for: gix-commitgraph/src/file/commit.rs

+3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ impl<'a> Commit<'a> {
5151
root_tree_id: gix_hash::oid::from_bytes_unchecked(&bytes[..file.hash_len]),
5252
parent1: ParentEdge::from_raw(read_u32(&bytes[file.hash_len..][..4])),
5353
parent2: ParentEdge::from_raw(read_u32(&bytes[file.hash_len + 4..][..4])),
54+
// TODO: Add support for corrected commit date offset overflow.
55+
// See https://github.com/git/git/commit/e8b63005c48696a26f976f5f9b0ccaf1983e439d and
56+
// https://github.com/git/git/commit/f90fca638e99a031dce8e3aca72427b2f9b4bb38 for more details and hints at a test.
5457
generation: read_u32(&bytes[file.hash_len + 8..][..4]) >> 2,
5558
commit_timestamp: u64::from_be_bytes(bytes[file.hash_len + 8..][..8].try_into().unwrap())
5659
& 0x0003_ffff_ffff,

Diff for: gix-features/tests/pipe.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ mod io {
4949
writer.write_all(b"b\nc\n").expect("success");
5050
drop(writer);
5151
assert_eq!(
52-
reader.lines().flat_map(Result::ok).collect::<Vec<_>>(),
52+
reader.lines().map_while(Result::ok).collect::<Vec<_>>(),
5353
vec!["a", "b", "c"]
5454
)
5555
}

Diff for: gix-negotiate/src/consecutive.rs

+66-90
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,82 @@
1-
use crate::{Error, Negotiator};
1+
use crate::{Error, Flags, Negotiator};
22
use gix_hash::ObjectId;
33
use gix_revision::graph::CommitterTimestamp;
4-
use smallvec::SmallVec;
5-
bitflags::bitflags! {
6-
/// Whether something can be read or written.
7-
#[derive(Debug, Default, Copy, Clone)]
8-
pub struct Flags: u8 {
9-
/// The revision is known to be in common with the remote.
10-
const COMMON = 1 << 0;
11-
/// The revision is common and was set by merit of a remote tracking ref (e.g. `refs/heads/origin/main`).
12-
const COMMON_REF = 1 << 1;
13-
/// The revision has entered the priority queue.
14-
const SEEN = 1 << 2;
15-
/// The revision was popped off our primary priority queue, used to avoid double-counting of `non_common_revs`
16-
const POPPED = 1 << 3;
17-
}
18-
}
194

20-
pub(crate) struct Algorithm<'find> {
21-
graph: gix_revision::Graph<'find, Flags>,
5+
pub(crate) struct Algorithm {
226
revs: gix_revision::PriorityQueue<CommitterTimestamp, ObjectId>,
237
non_common_revs: usize,
248
}
259

26-
impl<'a> Algorithm<'a> {
27-
pub fn new(graph: gix_revision::Graph<'a, Flags>) -> Self {
10+
impl Default for Algorithm {
11+
fn default() -> Self {
2812
Self {
29-
graph,
3013
revs: gix_revision::PriorityQueue::new(),
3114
non_common_revs: 0,
3215
}
3316
}
17+
}
3418

19+
impl Algorithm {
3520
/// Add `id` to our priority queue and *add* `flags` to it.
36-
fn add_to_queue(&mut self, id: ObjectId, mark: Flags) -> Result<(), Error> {
21+
fn add_to_queue(&mut self, id: ObjectId, mark: Flags, graph: &mut crate::Graph<'_>) -> Result<(), Error> {
3722
let mut is_common = false;
38-
if self.graph.get(&id).map_or(false, |flags| flags.intersects(mark)) {
39-
return Ok(());
40-
}
41-
let commit = self.graph.try_lookup_and_insert(id, |current| {
42-
*current |= mark;
43-
is_common = current.contains(Flags::COMMON);
44-
})?;
45-
if let Some(timestamp) = commit.map(|c| c.committer_timestamp()).transpose()? {
46-
self.revs.insert(timestamp, id);
23+
let mut has_mark = false;
24+
if let Some(commit) = graph
25+
.try_lookup_or_insert_commit(id, |data| {
26+
has_mark = data.flags.intersects(mark);
27+
data.flags |= mark;
28+
is_common = data.flags.contains(Flags::COMMON);
29+
})?
30+
.filter(|_| !has_mark)
31+
{
32+
self.revs.insert(commit.commit_time, id);
4733
if !is_common {
4834
self.non_common_revs += 1;
4935
}
5036
}
5137
Ok(())
5238
}
5339

54-
fn mark_common(&mut self, id: ObjectId, mode: Mark, ancestors: Ancestors) -> Result<(), Error> {
40+
fn mark_common(
41+
&mut self,
42+
id: ObjectId,
43+
mode: Mark,
44+
ancestors: Ancestors,
45+
graph: &mut crate::Graph<'_>,
46+
) -> Result<(), Error> {
5547
let mut is_common = false;
56-
if let Some(commit) = self
57-
.graph
58-
.try_lookup_and_insert(id, |current| is_common = current.contains(Flags::COMMON))?
48+
if let Some(commit) = graph
49+
.try_lookup_or_insert_commit(id, |data| is_common = data.flags.contains(Flags::COMMON))?
5950
.filter(|_| !is_common)
6051
{
61-
let mut queue =
62-
gix_revision::PriorityQueue::from_iter(Some((commit.committer_timestamp()?, (id, 0_usize))));
52+
let mut queue = gix_revision::PriorityQueue::from_iter(Some((commit.commit_time, (id, 0_usize))));
6353
if let Mark::ThisCommitAndAncestors = mode {
64-
let current = self.graph.get_mut(&id).expect("just inserted");
65-
*current |= Flags::COMMON;
66-
if current.contains(Flags::SEEN) && !current.contains(Flags::POPPED) {
54+
commit.data.flags |= Flags::COMMON;
55+
if commit.data.flags.contains(Flags::SEEN) && !commit.data.flags.contains(Flags::POPPED) {
6756
self.non_common_revs -= 1;
6857
}
6958
}
70-
let mut parents = SmallVec::new();
7159
while let Some((id, generation)) = queue.pop() {
72-
if self.graph.get(&id).map_or(true, |d| !d.contains(Flags::SEEN)) {
73-
self.add_to_queue(id, Flags::SEEN)?;
60+
if graph
61+
.get(&id)
62+
.map_or(true, |commit| !commit.data.flags.contains(Flags::SEEN))
63+
{
64+
self.add_to_queue(id, Flags::SEEN, graph)?;
7465
} else if matches!(ancestors, Ancestors::AllUnseen) || generation < 2 {
75-
if let Some(commit) = self.graph.try_lookup_and_insert(id, |_| {})? {
76-
collect_parents(commit.iter_parents(), &mut parents)?;
77-
for parent_id in parents.drain(..) {
66+
if let Some(commit) = graph.try_lookup_or_insert_commit(id, |_| {})? {
67+
for parent_id in commit.parents.clone() {
7868
let mut prev_flags = Flags::default();
79-
if let Some(parent) = self
80-
.graph
81-
.try_lookup_and_insert(parent_id, |d| {
82-
prev_flags = *d;
83-
*d |= Flags::COMMON;
69+
if let Some(parent) = graph
70+
.try_lookup_or_insert_commit(parent_id, |data| {
71+
prev_flags = data.flags;
72+
data.flags |= Flags::COMMON;
8473
})?
8574
.filter(|_| !prev_flags.contains(Flags::COMMON))
8675
{
8776
if prev_flags.contains(Flags::SEEN) && !prev_flags.contains(Flags::POPPED) {
8877
self.non_common_revs -= 1;
8978
}
90-
queue.insert(parent.committer_timestamp()?, (parent_id, generation + 1))
79+
queue.insert(parent.commit_time, (parent_id, generation + 1))
9180
}
9281
}
9382
}
@@ -98,38 +87,27 @@ impl<'a> Algorithm<'a> {
9887
}
9988
}
10089

101-
pub(crate) fn collect_parents(
102-
parents: gix_revision::graph::commit::Parents<'_>,
103-
out: &mut SmallVec<[ObjectId; 2]>,
104-
) -> Result<(), Error> {
105-
out.clear();
106-
for parent in parents {
107-
out.push(parent.map_err(|err| match err {
108-
gix_revision::graph::commit::iter_parents::Error::DecodeCommit(err) => Error::DecodeCommit(err),
109-
gix_revision::graph::commit::iter_parents::Error::DecodeCommitGraph(err) => Error::DecodeCommitInGraph(err),
110-
})?);
111-
}
112-
Ok(())
113-
}
114-
115-
impl<'a> Negotiator for Algorithm<'a> {
116-
fn known_common(&mut self, id: ObjectId) -> Result<(), Error> {
117-
if self.graph.get(&id).map_or(true, |d| !d.contains(Flags::SEEN)) {
118-
self.add_to_queue(id, Flags::COMMON_REF | Flags::SEEN)?;
119-
self.mark_common(id, Mark::AncestorsOnly, Ancestors::DirectUnseen)?;
90+
impl Negotiator for Algorithm {
91+
fn known_common(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<(), Error> {
92+
if graph
93+
.get(&id)
94+
.map_or(true, |commit| !commit.data.flags.contains(Flags::SEEN))
95+
{
96+
self.add_to_queue(id, Flags::COMMON_REF | Flags::SEEN, graph)?;
97+
self.mark_common(id, Mark::AncestorsOnly, Ancestors::DirectUnseen, graph)?;
12098
}
12199
Ok(())
122100
}
123101

124-
fn add_tip(&mut self, id: ObjectId) -> Result<(), Error> {
125-
self.add_to_queue(id, Flags::SEEN)
102+
fn add_tip(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<(), Error> {
103+
self.add_to_queue(id, Flags::SEEN, graph)
126104
}
127105

128-
fn next_have(&mut self) -> Option<Result<ObjectId, Error>> {
129-
let mut parents = SmallVec::new();
106+
fn next_have(&mut self, graph: &mut crate::Graph<'_>) -> Option<Result<ObjectId, Error>> {
130107
loop {
131108
let id = self.revs.pop().filter(|_| self.non_common_revs != 0)?;
132-
let flags = self.graph.get_mut(&id).expect("it was added to the graph by now");
109+
let commit = graph.get_mut(&id).expect("it was added to the graph by now");
110+
let flags = &mut commit.data.flags;
133111
*flags |= Flags::POPPED;
134112

135113
if !flags.contains(Flags::COMMON) {
@@ -144,21 +122,17 @@ impl<'a> Negotiator for Algorithm<'a> {
144122
(Some(id), Flags::SEEN)
145123
};
146124

147-
let commit = match self.graph.try_lookup(&id) {
148-
Ok(c) => c.expect("it was found before, must still be there"),
149-
Err(err) => return Some(Err(err.into())),
150-
};
151-
if let Err(err) = collect_parents(commit.iter_parents(), &mut parents) {
152-
return Some(Err(err));
153-
}
154-
for parent_id in parents.drain(..) {
155-
if self.graph.get(&parent_id).map_or(true, |d| !d.contains(Flags::SEEN)) {
156-
if let Err(err) = self.add_to_queue(parent_id, mark) {
125+
for parent_id in commit.parents.clone() {
126+
if graph
127+
.get(&parent_id)
128+
.map_or(true, |commit| !commit.data.flags.contains(Flags::SEEN))
129+
{
130+
if let Err(err) = self.add_to_queue(parent_id, mark, graph) {
157131
return Some(Err(err));
158132
}
159133
}
160134
if mark.contains(Flags::COMMON) {
161-
if let Err(err) = self.mark_common(parent_id, Mark::AncestorsOnly, Ancestors::AllUnseen) {
135+
if let Err(err) = self.mark_common(parent_id, Mark::AncestorsOnly, Ancestors::AllUnseen, graph) {
162136
return Some(Err(err));
163137
}
164138
}
@@ -170,9 +144,11 @@ impl<'a> Negotiator for Algorithm<'a> {
170144
}
171145
}
172146

173-
fn in_common_with_remote(&mut self, id: ObjectId) -> Result<bool, Error> {
174-
let known_to_be_common = self.graph.get(&id).map_or(false, |d| d.contains(Flags::COMMON));
175-
self.mark_common(id, Mark::ThisCommitAndAncestors, Ancestors::DirectUnseen)?;
147+
fn in_common_with_remote(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<bool, Error> {
148+
let known_to_be_common = graph
149+
.get(&id)
150+
.map_or(false, |commit| commit.data.flags.contains(Flags::COMMON));
151+
self.mark_common(id, Mark::ThisCommitAndAncestors, Ancestors::DirectUnseen, graph)?;
176152
Ok(known_to_be_common)
177153
}
178154
}

0 commit comments

Comments
 (0)