Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
fetch: integrate the latest `gix-protocol` into `radicle-fetch`
✗ CI failure Sebastian Thiel committed 1 year ago
commit 73d7db9d87ea624ec6414eec7d2255745f8b4d6a
parent 274d156297fe0ec28257bd382d4fa7223481f94e
1 failed (1 total) View logs
4 files changed +246 -369
modified Cargo.lock
@@ -836,9 +836,9 @@ dependencies = [

[[package]]
name = "gix-command"
-
version = "0.3.11"
+
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "6d7d6b8f3a64453fd7e8191eb80b351eb7ac0839b40a1237cd2c137d5079fe53"
+
checksum = "9405c0a56e17f8365a46870cd2c7db71323ecc8bda04b50cb746ea37bd091e90"
dependencies = [
 "bstr",
 "gix-path",
@@ -875,9 +875,9 @@ dependencies = [

[[package]]
name = "gix-credentials"
-
version = "0.25.1"
+
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "2be87bb8685fc7e6e7032ef71c45068ffff609724a0c897b8047fde10db6ae71"
+
checksum = "82a50c56b785c29a151ab4ccf74a83fe4e21d2feda0d30549504b4baed353e0a"
dependencies = [
 "bstr",
 "gix-command",
@@ -892,9 +892,9 @@ dependencies = [

[[package]]
name = "gix-date"
-
version = "0.9.2"
+
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "691142b1a34d18e8ed6e6114bc1a2736516c5ad60ef3aa9bd1b694886e3ca92d"
+
checksum = "c57c477b645ee248b173bb1176b52dd528872f12c50375801a58aaf5ae91113f"
dependencies = [
 "bstr",
 "itoa",
@@ -904,9 +904,9 @@ dependencies = [

[[package]]
name = "gix-diff"
-
version = "0.48.0"
+
version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a327be31a392144b60ab0b1c863362c32a1c8f7effdfa2141d5d5b6b916ef3bf"
+
checksum = "a8e92566eccbca205a0a0f96ffb0327c061e85bc5c95abbcddfe177498aa04f6"
dependencies = [
 "bstr",
 "gix-hash",
@@ -934,9 +934,9 @@ dependencies = [

[[package]]
name = "gix-fs"
-
version = "0.12.0"
+
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "34740384d8d763975858fa2c176b68652a6fcc09f616e24e3ce967b0d370e4d8"
+
checksum = "3b3d4fac505a621f97e5ce2c69fdc425742af00c0920363ca4074f0eb48b1db9"
dependencies = [
 "fastrand",
 "gix-features",
@@ -965,10 +965,37 @@ dependencies = [
]

[[package]]
+
name = "gix-lock"
+
version = "15.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1cd3ab68a452db63d9f3ebdacb10f30dba1fa0d31ac64f4203d395ed1102d940"
+
dependencies = [
+
 "gix-tempfile",
+
 "gix-utils",
+
 "thiserror 2.0.7",
+
]
+

+
[[package]]
+
name = "gix-negotiate"
+
version = "0.17.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d27f830a16405386e9c83b9d5be8261fe32bbd6b3caf15bd1b284c6b2b7ef1a8"
+
dependencies = [
+
 "bitflags 2.5.0",
+
 "gix-commitgraph",
+
 "gix-date",
+
 "gix-hash",
+
 "gix-object",
+
 "gix-revwalk",
+
 "smallvec",
+
 "thiserror 2.0.7",
+
]
+

+
[[package]]
name = "gix-object"
-
version = "0.46.0"
+
version = "0.46.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "65d93e2bbfa83a307e47f45e45de7b6c04d7375a8bd5907b215f4bf45237d879"
+
checksum = "e42d58010183ef033f31088479b4eb92b44fe341b35b62d39eb8b185573d77ea"
dependencies = [
 "bstr",
 "gix-actor",
@@ -976,6 +1003,7 @@ dependencies = [
 "gix-features",
 "gix-hash",
 "gix-hashtable",
+
 "gix-path",
 "gix-utils",
 "gix-validate",
 "itoa",
@@ -986,9 +1014,9 @@ dependencies = [

[[package]]
name = "gix-odb"
-
version = "0.65.0"
+
version = "0.66.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "93bed6e1b577c25a6bb8e6ecbf4df525f29a671ddf5f2221821a56a8dbeec4e3"
+
checksum = "cb780eceb3372ee204469478de02eaa34f6ba98247df0186337e0333de97d0ae"
dependencies = [
 "arc-swap",
 "gix-date",
@@ -1007,9 +1035,9 @@ dependencies = [

[[package]]
name = "gix-pack"
-
version = "0.55.0"
+
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9b91fec04d359544fecbb8e85117ec746fbaa9046ebafcefb58cb74f20dc76d4"
+
checksum = "4158928929be29cae7ab97afc8e820a932071a7f39d8ba388eed2380c12c566c"
dependencies = [
 "gix-chunk",
 "gix-diff",
@@ -1028,9 +1056,9 @@ dependencies = [

[[package]]
name = "gix-packetline"
-
version = "0.18.1"
+
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "8a720e5bebf494c3ceffa85aa89f57a5859450a0da0a29ebe89171e23543fa78"
+
checksum = "911aeea8b2dabeed2f775af9906152a1f0109787074daf9e64224e3892dde453"
dependencies = [
 "bstr",
 "faster-hex",
@@ -1053,9 +1081,9 @@ dependencies = [

[[package]]
name = "gix-prompt"
-
version = "0.8.9"
+
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "7a7822afc4bc9c5fbbc6ce80b00f41c129306b7685cac3248dbfa14784960594"
+
checksum = "82433a19aa44688e3bde05c692870eda50b5db053df53ed5ae6d8ea594a6babd"
dependencies = [
 "gix-command",
 "gix-config-value",
@@ -1066,15 +1094,23 @@ dependencies = [

[[package]]
name = "gix-protocol"
-
version = "0.46.1"
+
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "7a7e7e51a0dea531d3448c297e2fa919b2de187111a210c324b7e9f81508b8ca"
+
checksum = "c84642e8b6fed7035ce9cc449593019c55b0ec1af7a5dce1ab8a0636eaaeb067"
dependencies = [
 "bstr",
 "gix-credentials",
 "gix-date",
 "gix-features",
 "gix-hash",
+
 "gix-lock",
+
 "gix-negotiate",
+
 "gix-object",
+
 "gix-ref",
+
 "gix-refspec",
+
 "gix-revwalk",
+
 "gix-shallow",
+
 "gix-trace",
 "gix-transport",
 "gix-utils",
 "maybe-async",
@@ -1094,6 +1130,56 @@ dependencies = [
]

[[package]]
+
name = "gix-ref"
+
version = "0.49.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a91b61776c839d0f1b7114901179afb0947aa7f4d30793ca1c56d335dfef485f"
+
dependencies = [
+
 "gix-actor",
+
 "gix-features",
+
 "gix-fs",
+
 "gix-hash",
+
 "gix-lock",
+
 "gix-object",
+
 "gix-path",
+
 "gix-tempfile",
+
 "gix-utils",
+
 "gix-validate",
+
 "memmap2",
+
 "thiserror 2.0.7",
+
 "winnow",
+
]
+

+
[[package]]
+
name = "gix-refspec"
+
version = "0.27.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "00c056bb747868c7eb0aeb352c9f9181ab8ca3d0a2550f16470803500c6c413d"
+
dependencies = [
+
 "bstr",
+
 "gix-hash",
+
 "gix-revision",
+
 "gix-validate",
+
 "smallvec",
+
 "thiserror 2.0.7",
+
]
+

+
[[package]]
+
name = "gix-revision"
+
version = "0.31.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "61e1ddc474405a68d2ce8485705dd72fe6ce959f2f5fe718601ead5da2c8f9e7"
+
dependencies = [
+
 "bstr",
+
 "gix-commitgraph",
+
 "gix-date",
+
 "gix-hash",
+
 "gix-object",
+
 "gix-revwalk",
+
 "thiserror 2.0.7",
+
]
+

+
[[package]]
name = "gix-revwalk"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1121,6 +1207,18 @@ dependencies = [
]

[[package]]
+
name = "gix-shallow"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "88d2673242e87492cb6ff671f0c01f689061ca306c4020f137197f3abc84ce01"
+
dependencies = [
+
 "bstr",
+
 "gix-hash",
+
 "gix-lock",
+
 "thiserror 2.0.7",
+
]
+

+
[[package]]
name = "gix-tempfile"
version = "15.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1141,9 +1239,9 @@ checksum = "04bdde120c29f1fc23a24d3e115aeeea3d60d8e65bab92cc5f9d90d9302eb952"

[[package]]
name = "gix-transport"
-
version = "0.43.1"
+
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "39a1a41357b7236c03e0c984147f823d87c3e445a8581bac7006df141577200b"
+
checksum = "dd04d91e507a8713cfa2318d5a85d75b36e53a40379cc7eb7634ce400ecacbaf"
dependencies = [
 "bstr",
 "gix-command",
@@ -1157,9 +1255,9 @@ dependencies = [

[[package]]
name = "gix-traverse"
-
version = "0.43.0"
+
version = "0.43.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3ff2ec9f779680f795363db1c563168b32b8d6728ec58564c628e85c92d29faf"
+
checksum = "6ed47d648619e23e93f971d2bba0d10c1100e54ef95d2981d609907a8cabac89"
dependencies = [
 "bitflags 2.5.0",
 "gix-commitgraph",
@@ -1174,13 +1272,14 @@ dependencies = [

[[package]]
name = "gix-url"
-
version = "0.28.1"
+
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "e09f97db3618fb8e473d7d97e77296b50aaee0ddcd6a867f07443e3e87391099"
+
checksum = "d096fb733ba6bd3f5403dba8bd72bdd8809fe2b347b57844040b8f49c93492d9"
dependencies = [
 "bstr",
 "gix-features",
 "gix-path",
+
 "percent-encoding",
 "thiserror 2.0.7",
 "url",
]
modified radicle-fetch/src/transport.rs
@@ -10,9 +10,7 @@ use std::sync::Arc;
use bstr::BString;
use gix_features::progress::prodash::progress;
use gix_protocol::handshake;
-
use gix_protocol::FetchConnection;
use gix_transport::client;
-
use gix_transport::client::TransportWithoutIO as _;
use gix_transport::Protocol;
use gix_transport::Service;
use radicle::git::Oid;
@@ -85,7 +83,7 @@ where
        log::trace!(target: "fetch", "Performing handshake for {}", self.repo);
        let (read, write) = self.stream.open().map_err(io_other)?;
        gix_protocol::fetch::handshake(
-
            &mut Connection::new(read, write, FetchConnection::AllowReuse, self.repo.clone()),
+
            &mut Connection::new(read, write, self.repo.clone()),
            |_| Ok(None),
            vec![],
            &mut progress::Discard,
@@ -105,11 +103,10 @@ where
        ls_refs::run(
            ls_refs::Config {
                prefixes,
-
                extra_params: vec![],
                repo: self.repo.clone(),
            },
            handshake,
-
            Connection::new(read, write, FetchConnection::AllowReuse, self.repo.clone()),
+
            Connection::new(read, write, self.repo.clone()),
            &mut progress::Discard,
        )
        .map_err(io_other)
@@ -137,7 +134,7 @@ where
                    interrupt,
                },
                handshake,
-
                Connection::new(read, write, FetchConnection::AllowReuse, self.repo.clone()),
+
                Connection::new(read, write, self.repo.clone()),
                &mut progress::Discard,
            )
            .map_err(io_other)?
@@ -184,7 +181,6 @@ where

pub(crate) struct Connection<R, W> {
    inner: client::git::Connection<R, W>,
-
    mode: FetchConnection,
}

impl<R, W> Connection<R, W>
@@ -192,7 +188,7 @@ where
    R: io::Read,
    W: io::Write,
{
-
    pub fn new(read: R, write: W, mode: FetchConnection, repo: BString) -> Self {
+
    pub fn new(read: R, write: W, repo: BString) -> Self {
        Self {
            inner: client::git::Connection::new(
                read,
@@ -203,7 +199,6 @@ where
                client::git::ConnectMode::Daemon,
                false,
            ),
-
            mode,
        }
    }
}
@@ -256,20 +251,6 @@ where
    }
}

-
fn indicate_end_of_interaction<R, W>(transport: &mut Connection<R, W>) -> Result<(), client::Error>
-
where
-
    R: io::Read,
-
    W: io::Write,
-
{
-
    // An empty request marks the (early) end of the interaction. Only relevant in stateful transports though.
-
    if transport.connection_persists_across_multiple_requests() {
-
        transport
-
            .request(client::WriteMode::Binary, client::MessageKind::Flush, false)?
-
            .into_read()?;
-
    }
-
    Ok(())
-
}
-

fn io_other(err: impl std::error::Error + Send + Sync + 'static) -> io::Error {
    io::Error::new(io::ErrorKind::Other, err)
}
modified radicle-fetch/src/transport/fetch.rs
@@ -1,27 +1,19 @@
-
use std::borrow::Cow;
use std::io;
-
use std::io::BufRead;
use std::path::PathBuf;
use std::sync::{atomic::AtomicBool, Arc};

-
use gix_features::progress::NestedProgress;
+
use gix_features::progress::{DynNestedProgress, NestedProgress};
use gix_pack as pack;
use gix_protocol::fetch;
-
use gix_protocol::fetch::{Delegate, DelegateBlocking};
+
use gix_protocol::fetch::negotiate::one_round::State;
use gix_protocol::handshake;
use gix_protocol::handshake::Ref;
-
use gix_protocol::ls_refs;
-
use gix_protocol::FetchConnection;
-
use gix_transport::bstr::BString;
-
use gix_transport::client;
-
use gix_transport::client::{ExtendedBufRead, MessageKind};
-
use gix_transport::Protocol;

-
use crate::git::packfile;
+
use crate::git::{oid, packfile};

-
use super::{agent_name, indicate_end_of_interaction, Connection, WantsHaves};
+
use super::{agent_name, Connection, WantsHaves};

-
pub type Error = gix_protocol::fetch::Error;
+
pub type Error = fetch::Error;

pub mod error {
    use std::io;
@@ -50,15 +42,11 @@ pub struct PackWriter {
impl PackWriter {
    /// Write the packfile read from `pack` to the `objects/pack`
    /// directory.
-
    pub fn write_pack<P>(
+
    pub fn write_pack(
        &self,
-
        mut pack: impl BufRead,
-
        mut progress: P,
-
    ) -> Result<pack::bundle::write::Outcome, error::PackWriter>
-
    where
-
        P: NestedProgress,
-
        P::SubProgress: 'static,
-
    {
+
        pack: &mut dyn std::io::BufRead,
+
        progress: &mut dyn DynNestedProgress,
+
    ) -> Result<pack::bundle::write::Outcome, error::PackWriter> {
        let options = pack::bundle::write::Options {
            // N.b. use all cores. Can make configurable if needed
            // later.
@@ -80,9 +68,9 @@ impl PackWriter {
        )?);
        let thickener = thickener.to_handle_arc();
        Ok(pack::Bundle::write_to_directory(
-
            &mut pack,
+
            pack,
            Some(&self.git_dir.join("objects").join("pack")),
-
            &mut progress,
+
            progress,
            &self.interrupt,
            Some(thickener),
            options,
@@ -92,10 +80,8 @@ impl PackWriter {

/// The fetch [`Delegate`] that negotiates the fetch with the
/// server-side.
-
pub struct Fetch {
+
pub struct Negotiate {
    wants_haves: WantsHaves,
-
    pack_writer: PackWriter,
-
    out: FetchOut,
}

/// The result of running a fetch via [`run`].
@@ -105,77 +91,50 @@ pub struct FetchOut {
    pub keepfile: Option<packfile::Keepfile>,
}

-
// FIXME: the delegate pattern will be removed in the near future and
-
// we should look at the fetch code being used in gix to see how we
-
// can migrate to the proper form of fetching.
-
impl<'a> Delegate for &'a mut Fetch {
-
    fn receive_pack(
+
impl fetch::Negotiate for Negotiate {
+
    fn mark_complete_and_common_ref(
        &mut self,
-
        input: impl io::BufRead,
-
        progress: impl NestedProgress + 'static,
-
        _refs: &[handshake::Ref],
-
        previous_response: &fetch::Response,
-
    ) -> io::Result<()> {
-
        self.out
-
            .refs
-
            .extend(previous_response.wanted_refs().iter().map(
-
                |fetch::response::WantedRef { id, path }| Ref::Direct {
-
                    full_ref_name: path.clone(),
-
                    object: *id,
-
                },
-
            ));
-
        let pack = self
-
            .pack_writer
-
            .write_pack(input, progress)
-
            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
-
        self.out.keepfile = pack.keep_path.as_ref().and_then(packfile::Keepfile::new);
-
        self.out.pack = Some(pack);
-
        Ok(())
+
    ) -> Result<fetch::negotiate::Action, fetch::negotiate::Error> {
+
        Ok(fetch::negotiate::Action::MustNegotiate {
+
            remote_ref_target_known: vec![],
+
        })
    }
-
}

-
impl<'a> DelegateBlocking for &'a mut Fetch {
-
    fn negotiate(
+
    fn add_wants(
        &mut self,
-
        _refs: &[handshake::Ref],
        arguments: &mut fetch::Arguments,
-
        _previous_response: Option<&fetch::Response>,
-
    ) -> io::Result<fetch::Action> {
-
        use crate::git::oid;
-

+
        _remote_ref_target_known: &[bool],
+
    ) -> bool {
+
        let mut has_want = false;
        for oid in &self.wants_haves.wants {
            arguments.want(oid::to_object_id(*oid));
+
            has_want = true;
        }
+
        has_want
+
    }

+
    /// We don't actually negotiate, just provides all our haves and wants, while telling the
+
    /// server to make the best of it and just send a pack.
+
    /// Real Git negotiation can be done with calls to [`fetch::negotiate::one_round()`], but that
+
    /// requires a [`fetch::RefMap`] which can be instantiated with refspecs.
+
    fn one_round(
+
        &mut self,
+
        _state: &mut State,
+
        arguments: &mut fetch::Arguments,
+
        _previous_response: Option<&fetch::Response>,
+
    ) -> Result<(fetch::negotiate::Round, bool), fetch::negotiate::Error> {
        for oid in &self.wants_haves.haves {
            arguments.have(oid::to_object_id(*oid));
        }

-
        // N.b. sends `done` packet
-
        Ok(fetch::Action::Cancel)
-
    }
-

-
    fn prepare_ls_refs(
-
        &mut self,
-
        _server: &client::Capabilities,
-
        _arguments: &mut Vec<BString>,
-
        _features: &mut Vec<(&str, Option<Cow<'_, str>>)>,
-
    ) -> io::Result<ls_refs::Action> {
-
        // N.b. we performed ls-refs before the fetch already.
-
        Ok(ls_refs::Action::Skip)
-
    }
-

-
    fn prepare_fetch(
-
        &mut self,
-
        _version: Protocol,
-
        _server: &client::Capabilities,
-
        _features: &mut Vec<(&str, Option<Cow<'_, str>>)>,
-
        _refs: &[handshake::Ref],
-
    ) -> io::Result<fetch::Action> {
-
        if self.wants_haves.wants.is_empty() {
-
            return Err(io::Error::new(io::ErrorKind::InvalidData, "empty fetch"));
-
        }
-
        Ok(fetch::Action::Continue)
+
        let round = fetch::negotiate::Round {
+
            haves_sent: self.wants_haves.haves.len(),
+
            in_vain: 0,
+
            haves_to_send: 0,
+
            previous_response_had_at_least_one_in_common: false,
+
        };
+
        let is_done = true;
+
        Ok((round, is_done))
    }
}

@@ -200,116 +159,59 @@ where
{
    log::trace!(target: "fetch", "Performing fetch");

-
    let mut delegate = Fetch {
-
        wants_haves,
-
        pack_writer,
-
        out: FetchOut {
-
            refs: Vec::new(),
-
            pack: None,
-
            keepfile: None,
-
        },
-
    };
-

-
    let handshake::Outcome {
-
        server_protocol_version: protocol,
-
        refs: _refs,
-
        v1_shallow_updates: _,
-
        capabilities,
-
    } = handshake;
-
    let agent = agent_name()?;
-
    let fetch = gix_protocol::Command::Fetch;
-

-
    let mut features = fetch.default_features(*protocol, capabilities);
-
    match (&mut delegate).prepare_fetch(*protocol, capabilities, &mut features, &[]) {
-
        Ok(fetch::Action::Continue) => {
-
            // FIXME: this is a private function in gitoxide
-
            // fetch.validate_argument_prefixes_or_panic(protocol, &capabilities, &[], &features)
-
        }
-
        // N.b. we always return Action::Continue
-
        Ok(fetch::Action::Cancel) => unreachable!(),
-
        Err(err) => {
-
            indicate_end_of_interaction(&mut conn)?;
-
            return Err(err.into());
-
        }
-
    }
-

-
    gix_protocol::fetch::Response::check_required_features(*protocol, &features)?;
-
    let sideband_all = features.iter().any(|(n, _)| *n == "sideband-all");
-
    features.push(("agent", Some(Cow::Owned(agent))));
-
    let mut args = fetch::Arguments::new(*protocol, features, false);
-

-
    let mut previous_response = None::<fetch::Response>;
-
    let mut round = 1;
-
    'negotiation: loop {
-
        progress.step();
-
        progress.set_name(format!("negotiate (round {round})"));
-
        round += 1;
-
        let action = (&mut delegate).negotiate(&[], &mut args, previous_response.as_ref())?;
-
        let mut reader = args.send(&mut conn, action == fetch::Action::Cancel)?;
-
        if sideband_all {
-
            setup_remote_progress(progress, &mut reader);
-
        }
-
        let response = fetch::Response::from_line_reader(*protocol, &mut reader, true, false)?;
-
        previous_response = if response.has_pack() {
-
            progress.step();
-
            if !sideband_all {
-
                setup_remote_progress(progress, &mut reader);
-
            }
-
            let timer = std::time::Instant::now();
-
            // TODO: remove delegate in favor of functional style to fix progress-hack,
-
            //       needed as it needs `'static`. As the top-level seems to pass `Discard`,
-
            //       there should be no repercussions right now.
-
            (&mut delegate).receive_pack(
-
                &mut reader,
-
                progress.add_child("receiving pack"),
-
                &[],
-
                &response,
-
            )?;
-
            log::trace!(target: "fetch", "Received pack ({}ms)", timer.elapsed().as_millis());
-
            assert_eq!(
-
                reader.stopped_at(),
-
                None,
-
                "packs are read without 'overshooting', hence it never encountered EOF"
-
            );
-
            // Consume anything that might still be left on the wire - this is 'EOF' most of the time,
-
            // but some tests have 'garbage' here as well.
-
            std::io::copy(&mut reader, &mut std::io::sink())?;
-
            assert_eq!(
-
                reader.stopped_at(),
-
                Some(MessageKind::Flush),
-
                "the flush packet was now consumed"
-
            );
-
            break 'negotiation;
-
        } else {
-
            match action {
-
                fetch::Action::Cancel => break 'negotiation,
-
                fetch::Action::Continue => Some(response),
-
            }
-
        }
-
    }
-
    if matches!(protocol, Protocol::V2)
-
        && matches!(conn.mode, FetchConnection::TerminateOnSuccessfulCompletion)
-
    {
-
        log::trace!(target: "fetch", "Indicating end of interaction");
-
        indicate_end_of_interaction(&mut conn)?;
+
    if wants_haves.wants.is_empty() {
+
        return Err(Error::ReadRemainingBytes(io::Error::new(
+
            io::ErrorKind::InvalidData,
+
            "empty fetch",
+
        )));
    }
-

-
    log::trace!(target: "fetch", "fetched refs: {:?}", delegate.out.refs);
-
    Ok(delegate.out)
-
}
-

-
fn setup_remote_progress<'a, P>(
-
    progress: &mut P,
-
    reader: &mut Box<dyn gix_transport::client::ExtendedBufRead<'a> + Unpin + 'a>,
-
) where
-
    P: NestedProgress,
-
    P::SubProgress: 'static,
-
{
-
    reader.set_progress_handler(Some(Box::new({
-
        let mut remote_progress = progress.add_child("remote");
-
        move |is_err: bool, data: &[u8]| {
-
            gix_protocol::RemoteProgress::translate_to_progress(is_err, data, &mut remote_progress);
-
            gix_transport::packetline::read::ProgressAction::Continue
-
        }
-
    }) as gix_transport::client::HandleProgress<'a>));
+
    let mut out = FetchOut {
+
        refs: Vec::new(),
+
        pack: None,
+
        keepfile: None,
+
    };
+
    let mut negotiate = Negotiate { wants_haves };
+
    let agent = agent_name().map_err(Error::ReadRemainingBytes)?;
+

+
    let mut pack_out = None;
+
    let mut handshake = handshake.clone();
+
    let fetch_out = gix_protocol::fetch(
+
        &mut negotiate,
+
        |read_pack, progress, _should_interrupt| -> Result<_, error::PackWriter> {
+
            let res = pack_writer.write_pack(read_pack, progress)?;
+
            pack_out = Some(res);
+
            Ok(true)
+
        },
+
        progress,
+
        &pack_writer.interrupt,
+
        fetch::Context {
+
            handshake: &mut handshake,
+
            transport: &mut conn,
+
            user_agent: ("agent", Some(agent.into())),
+
            trace_packetlines: false,
+
        },
+
        fetch::Options {
+
            shallow_file: "no shallow file required as we reject shallow remotes (and we aren't shallow ourselves)".into(),
+
            reject_shallow_remote: true,
+
            shallow: &fetch::Shallow::NoChange,
+
            tags: fetch::Tags::None,
+
        },
+
    )?.expect("we always get a pack");
+

+
    out.refs
+
        .extend(fetch_out.last_response.wanted_refs().iter().map(
+
            |fetch::response::WantedRef { id, path }| Ref::Direct {
+
                full_ref_name: path.clone(),
+
                object: *id,
+
            },
+
        ));
+
    let pack_out = pack_out.expect("we always get a pack");
+
    out.keepfile = pack_out
+
        .keep_path
+
        .as_ref()
+
        .and_then(packfile::Keepfile::new);
+
    out.pack = Some(pack_out);
+

+
    log::trace!(target: "fetch", "fetched refs: {:?}", out.refs);
+
    Ok(out)
}
modified radicle-fetch/src/transport/ls_refs.rs
@@ -1,16 +1,13 @@
use std::borrow::Cow;
-
use std::io::{self, BufRead};
+
use std::io;

-
use bstr::ByteSlice;
use gix_features::progress::Progress;
-
use gix_protocol::fetch::{self, Delegate, DelegateBlocking};
use gix_protocol::handshake::{self, Ref};
+
use gix_protocol::ls_refs;
use gix_protocol::transport::Protocol;
-
use gix_protocol::{ls_refs, Command};
use gix_transport::bstr::{BString, ByteVec};
-
use gix_transport::client::{self, TransportV2Ext};

-
use super::{agent_name, indicate_end_of_interaction, Connection};
+
use super::{agent_name, Connection};

/// Configuration for running an ls-refs process.
///
@@ -19,84 +16,10 @@ pub struct Config {
    /// The repository name, i.e. `/<rid>`.
    #[allow(dead_code)]
    pub repo: BString,
-
    /// Extra parameters to pass to the ls-refs process.
-
    pub extra_params: Vec<(String, Option<String>)>,
    /// Ref prefixes for filtering the output of the ls-refs process.
    pub prefixes: Vec<BString>,
}

-
/// The Gitoxide delegate for running the ls-refs process.
-
struct LsRefs {
-
    /// Configuration for the ls-refs process.
-
    config: Config,
-
    /// The resulting references returned by the ls-refs process.
-
    refs: Vec<Ref>,
-
}
-

-
impl LsRefs {
-
    fn new(config: Config) -> Self {
-
        Self {
-
            config,
-
            refs: Vec::new(),
-
        }
-
    }
-
}
-

-
// FIXME: the delegate pattern will be removed in the near future and
-
// we should look at the fetch code being used in gix to see how we
-
// can migrate to the proper form of fetching.
-
impl DelegateBlocking for LsRefs {
-
    fn handshake_extra_parameters(&self) -> Vec<(String, Option<String>)> {
-
        self.config.extra_params.clone()
-
    }
-

-
    fn prepare_ls_refs(
-
        &mut self,
-
        _caps: &client::Capabilities,
-
        args: &mut Vec<BString>,
-
        _: &mut Vec<(&str, Option<Cow<'_, str>>)>,
-
    ) -> io::Result<ls_refs::Action> {
-
        for prefix in &self.config.prefixes {
-
            let mut arg = BString::from("ref-prefix ");
-
            arg.push_str(prefix);
-
            args.push(arg)
-
        }
-
        Ok(ls_refs::Action::Continue)
-
    }
-

-
    fn prepare_fetch(
-
        &mut self,
-
        _: Protocol,
-
        _: &client::Capabilities,
-
        _: &mut Vec<(&str, Option<Cow<'_, str>>)>,
-
        refs: &[Ref],
-
    ) -> io::Result<fetch::Action> {
-
        self.refs.extend_from_slice(refs);
-
        Ok(fetch::Action::Cancel)
-
    }
-

-
    fn negotiate(
-
        &mut self,
-
        _: &[Ref],
-
        _: &mut fetch::Arguments,
-
        _: Option<&fetch::Response>,
-
    ) -> io::Result<fetch::Action> {
-
        unreachable!("`negotiate` called even though no `fetch` command was sent")
-
    }
-
}
-

-
impl Delegate for LsRefs {
-
    fn receive_pack(
-
        &mut self,
-
        _: impl BufRead,
-
        _: impl Progress,
-
        _: &[Ref],
-
        _: &fetch::Response,
-
    ) -> io::Result<()> {
-
        unreachable!("`receive_pack` called even though no `fetch` command was sent")
-
    }
-
}
-

/// Run the ls-refs process using the provided `config`.
///
/// It is expected that the `handshake` was run outside of this
@@ -116,7 +39,6 @@ where
    W: io::Write,
{
    log::trace!(target: "fetch", "Performing ls-refs: {:?}", config.prefixes);
-
    let mut delegate = LsRefs::new(config);
    let handshake::Outcome {
        server_protocol_version: protocol,
        capabilities,
@@ -130,48 +52,21 @@ where
        )));
    }

-
    let ls = Command::LsRefs;
-
    let mut features = ls.default_features(Protocol::V2, capabilities);
-
    // N.b. copied from gitoxide
-
    let mut args = vec![
-
        b"symrefs".as_bstr().to_owned(),
-
        b"peel".as_bstr().to_owned(),
-
    ];
-
    if capabilities
-
        .capability("ls-refs")
-
        .and_then(|cap| cap.supports("unborn"))
-
        .unwrap_or_default()
-
    {
-
        args.push("unborn".into());
-
    }
-
    let refs = match delegate.prepare_ls_refs(capabilities, &mut args, &mut features) {
-
        Ok(ls_refs::Action::Skip) => Vec::new(),
-
        Ok(ls_refs::Action::Continue) => {
-
            // FIXME: this is a private function
-
            // ls.validate_argument_prefixes_or_panic(Protocol::V2, capabilities, &args, &features);
-

-
            let agent = agent_name()?;
-
            features.push(("agent", Some(Cow::Owned(agent))));
-

-
            progress.step();
-
            progress.set_name("list refs".into());
-
            let mut remote_refs = conn.invoke(
-
                ls.as_str(),
-
                features.clone().into_iter(),
-
                if args.is_empty() {
-
                    None
-
                } else {
-
                    Some(args.into_iter())
-
                },
-
                false,
-
            )?;
-
            handshake::refs::from_v2_refs(&mut remote_refs)?
-
        }
-
        Err(err) => {
-
            indicate_end_of_interaction(&mut conn)?;
-
            return Err(err.into());
-
        }
-
    };
+
    let refs = gix_protocol::ls_refs(
+
        &mut conn,
+
        capabilities,
+
        |_caps, args, features| {
+
            for prefix in &config.prefixes {
+
                let mut arg = BString::from("ref-prefix ");
+
                arg.push_str(prefix);
+
                args.push(arg)
+
            }
+
            features.push(("agent", Some(Cow::Owned(agent_name()?))));
+
            Ok(gix_protocol::ls_refs::Action::Continue)
+
        },
+
        progress,
+
        false, /* trace packetlines */
+
    )?;

    Ok(refs)
}