Skip to content

Commit ce26df5

Browse files
committed
feat: use gix-negotiate in fetch machinery.
Thanks to it we are finally able to do pack negotiations just like git can, as many rounds as it takes and with all available algorithms.
1 parent 0e8c05c commit ce26df5

File tree

7 files changed

+85
-17
lines changed

7 files changed

+85
-17
lines changed

Cargo.lock

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

gix/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ gix-object = { version = "^0.29.2", path = "../gix-object" }
131131
gix-actor = { version = "^0.20.0", path = "../gix-actor" }
132132
gix-pack = { version = "^0.35.0", path = "../gix-pack", features = ["object-cache-dynamic"] }
133133
gix-revision = { version = "^0.14.0", path = "../gix-revision" }
134+
gix-negotiate = { version = "0.1.0", path = "../gix-negotiate" }
134135

135136
gix-path = { version = "^0.8.0", path = "../gix-path" }
136137
gix-url = { version = "^0.18.0", path = "../gix-url" }

gix/src/remote/connection/fetch/negotiate.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ pub enum Error {
99
NegotiationFailed { rounds: usize },
1010
}
1111

12+
/// This function is modeled after the similarly named one in the git codebase and attempts to do the following:
13+
///
14+
/// ### Performance Notes
15+
///
16+
/// In `git`, the graph data structure is shared, whereas here we currently can't as the data we store is specific
17+
/// to the respective user. Git makes sure that the flags used there don't overlap, and we would have to do the same
18+
/// to get the same benefits. This explains why the negotiator code relies on commits being parsed already - it's a way
19+
/// to observe that the caller already traversed there.
20+
#[allow(dead_code)]
21+
pub(crate) fn mark_complete_and_common_ref(
22+
_negotiator: &mut dyn gix_negotiate::Negotiator,
23+
_ref_map: &fetch::RefMap,
24+
) -> Result<(), Error> {
25+
// TODO: we probably want a priority list here, that's mostly how their linked list is used. Doing so might be faster,
26+
// maybe their code is old enough not to use a prio list? Or maybe it's the opposite.
27+
todo!()
28+
}
29+
1230
/// Negotiate one round with `algo` by looking at `ref_map` and adjust `arguments` to contain the haves and wants.
1331
/// If this is not the first round, the `previous_response` is set with the last recorded server response.
1432
/// Returns `true` if the negotiation is done from our side so the server won't keep asking.
@@ -34,9 +52,7 @@ pub(crate) fn one_round(
3452

3553
match algo {
3654
Algorithm::Noop | Algorithm::Skipping | Algorithm::Consecutive => {
37-
todo!()
38-
}
39-
Algorithm::Naive => {
55+
// Use actual negotiation code here, this is the NAIVE implementation/hack
4056
assert_eq!(round, 1, "Naive always finishes after the first round, it claims.");
4157
let mut has_missing_tracking_branch = false;
4258
for mapping in &ref_map.mappings {

gix/src/remote/connection/fetch/receive_pack.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ where
119119
.map(|n| Fetch::NEGOTIATION_ALGORITHM.try_into_negotiation_algorithm(n))
120120
.transpose()
121121
.with_leniency(repo.config.lenient_config)?
122-
.unwrap_or(Algorithm::Naive); // TODO: use the default instead once consecutive is implemented
122+
.unwrap_or(Algorithm::Consecutive);
123123

124124
let reader = 'negotiation: loop {
125125
progress.step();

gix/src/remote/fetch.rs

+1-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
///
22
pub mod negotiate {
3-
/// The way the negotiation is performed.
4-
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
5-
pub enum Algorithm {
6-
/// Do not send any information at all, likely at cost of larger-than-necessary packs.
7-
Noop,
8-
/// Walk over consecutive commits and check each one. This can be costly be assures packs are exactly the size they need to be.
9-
#[default]
10-
Consecutive,
11-
/// Like `Consecutive`, but skips commits to converge faster, at the cost of receiving packs that are larger than they have to be.
12-
Skipping,
13-
/// Our very own implementation that probably should be replaced by one of the known algorithms soon.
14-
Naive,
15-
}
3+
pub use gix_negotiate::Algorithm;
164

175
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
186
pub use super::super::connection::fetch::negotiate::Error;

gix/tests/fixtures/make_remote_repos.sh

+42
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,45 @@ git clone --shared base detached-head
308308
(cd detached-head
309309
git checkout @~1
310310
)
311+
312+
function commit() {
313+
local message=${1:?first argument is the commit message}
314+
local file="$message.t"
315+
echo "$1" > "$file"
316+
git add -- "$file"
317+
tick
318+
git commit -m "$message"
319+
git tag "$message"
320+
}
321+
322+
function optimize_repo() {
323+
git commit-graph write --no-progress --reachable
324+
git repack -adq
325+
}
326+
327+
(mkdir multi_round && cd multi_round
328+
git init -q server && cd server
329+
commit to_fetch
330+
cd ..
331+
332+
git init -q client && cd client
333+
for i in $(seq 8); do
334+
git checkout --orphan b$i &&
335+
commit b$i.c0
336+
done
337+
338+
for j in $(seq 19); do
339+
for i in $(seq 8); do
340+
git checkout b$i &&
341+
commit b$i.c$j
342+
done
343+
done
344+
optimize_repo
345+
cd ..
346+
(cd server
347+
git fetch --no-tags "$PWD/../client" b1:refs/heads/b1
348+
git checkout b1
349+
commit commit-on-b1
350+
optimize_repo
351+
)
352+
)

gix/tests/remote/fetch.rs

+20
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod blocking_and_async_io {
1919
use gix_protocol::maybe_async;
2020

2121
use crate::{
22+
remote,
2223
remote::{into_daemon_remote_if_async, spawn_git_daemon_if_async},
2324
util::hex_to_id,
2425
};
@@ -106,6 +107,25 @@ mod blocking_and_async_io {
106107
Ok(())
107108
}
108109

110+
// TODO: try this as maybe-async
111+
#[test]
112+
#[cfg(feature = "blocking-network-client")]
113+
#[ignore = "fails because of improper negotiation (it's hacked to work for our cases)"]
114+
fn fetch_with_multi_round_negotiation() -> crate::Result {
115+
let repo = remote::repo("multi_round/client");
116+
let server_repo = remote::repo("multi_round/server");
117+
let changes = repo
118+
.remote_at(server_repo.work_dir().expect("non-bare"))?
119+
.with_refspecs(Some("refs/heads/*:refs/remotes/origin/*"), Fetch)?
120+
.connect(Fetch)?
121+
.prepare_fetch(gix::progress::Discard, Default::default())?
122+
.with_dry_run(true)
123+
.receive(gix::progress::Discard, &AtomicBool::default())?;
124+
125+
dbg!(changes);
126+
Ok(())
127+
}
128+
109129
#[maybe_async::test(
110130
feature = "blocking-network-client",
111131
async(feature = "async-network-client-async-std", async_std::test)

0 commit comments

Comments
 (0)