Skip to content

Commit

Permalink
[clone] support automatic downgrade to protocol version 1
Browse files Browse the repository at this point in the history
This allows us to always ask for V2 to give delegates more capabilities
and make them fit(ter) for the future.
  • Loading branch information
Byron committed Sep 4, 2020
1 parent 1925d02 commit 4cf3643
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 15 deletions.
6 changes: 6 additions & 0 deletions git-protocol/src/fetch/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ pub enum Action {
Close,
}

/// The protocol delegate is the bare minimal interface needed to fully control the 'fetch' operation.
/// It's controlled by code with intricate knowledge about how that works in protocol version V1 and V2,
/// so you don't have to.
/// Everything is tucked away behind type-safety so nothing can go wrong. Runtime assertions assure invalid
/// features or arguments don't make it to the server in the first place.
/// Please note that this trait mostly corresponds to what V2 would look like.
pub trait Delegate {
/// Called before invoking ls-refs to allow providing it with additional `arguments` and to enable `features`.
/// Note that some arguments are preset based on typical use, and `features` are preset to maximize options.
Expand Down
31 changes: 19 additions & 12 deletions git-transport/src/client/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,32 @@ pub mod message {
}
pub(crate) mod recv {
use crate::{client, client::Capabilities, Protocol};
use bstr::ByteSlice;
use std::io;

pub fn capabilities_and_possibly_refs<'a, T: io::Read>(
rd: &'a mut git_packetline::Provider<T>,
version: Protocol,
) -> Result<(Capabilities, Option<Box<dyn io::BufRead + 'a>>, Protocol), client::Error> {
rd.fail_on_err_lines(true);
let capabilities_or_version = rd
.peek_line()
.ok_or(client::Error::ExpectedLine("capabilities or version"))???;
let first_line = capabilities_or_version
.to_text()
.ok_or(client::Error::ExpectedLine("text"))?;

let version = if first_line.as_bstr().starts_with_str("version ") {
if first_line.as_bstr().ends_with_str(" 1") {
Protocol::V1
} else {
Protocol::V2
}
} else {
Protocol::V1
};
match version {
Protocol::V1 => {
let capabilities = rd
.peek_line()
.ok_or(client::Error::ExpectedLine("capabilities or version"))???;
let (capabilities, delimiter_position) = Capabilities::from_bytes(
capabilities
.to_text()
.ok_or(client::Error::ExpectedLine("text"))?
.as_slice(),
)?;
let (capabilities, delimiter_position) = Capabilities::from_bytes(first_line.0)?;
rd.peek_buffer_replace_and_truncate(delimiter_position, b'\n');
Ok((capabilities, Some(Box::new(rd.as_read())), Protocol::V1))
}
Expand Down Expand Up @@ -99,8 +107,7 @@ where
line_writer.flush()?;
}

let (capabilities, refs, actual_protocol) =
recv::capabilities_and_possibly_refs(&mut self.line_provider, self.desired_version)?;
let (capabilities, refs, actual_protocol) = recv::capabilities_and_possibly_refs(&mut self.line_provider)?;
Ok(SetServiceResponse {
actual_protocol,
capabilities,
Expand Down
3 changes: 1 addition & 2 deletions git-transport/src/client/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ impl<H: Http> client::Transport for Transport<H> {
))));
}

let (capabilities, refs, actual_protocol) =
git::recv::capabilities_and_possibly_refs(line_reader, self.desired_version)?;
let (capabilities, refs, actual_protocol) = git::recv::capabilities_and_possibly_refs(line_reader)?;
self.service = Some(service);
Ok(client::SetServiceResponse {
actual_protocol,
Expand Down
24 changes: 24 additions & 0 deletions git-transport/tests/client/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,27 @@ fn handshake_v2_and_request() -> crate::Result {
);
Ok(())
}

#[test]
fn handshake_v2_downgrade_to_v1() -> crate::Result {
let mut out = Vec::new();
let input = fixture_bytes("v1/clone.response");
let mut c = git::Connection::new(
input.as_slice(),
&mut out,
Protocol::V2,
"/bar.git",
Some(("example.org", None)),
git::ConnectMode::Daemon,
);
let res = c.handshake(Service::UploadPack)?;
assert_eq!(res.actual_protocol, Protocol::V1);
assert!(
res.refs.is_some(),
"V1 downgrades 'just happen', so we should have refs as part of the handshake"
);
drop(res);

assert_eq!(c.desired_protocol_version(), Protocol::V2);
Ok(())
}
2 changes: 1 addition & 1 deletion tasks.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Cloning
* **protocol**
* [ ] allow automatic downgrade (request V2, deal with that not supported and fallback to V1)
* [x] allow automatic downgrade (request V2, deal with that not supported and fallback to V1)
* [x] basic progress
* **gixp-ls-remote**
* [ ] A V1/V2 version of a delegate to list remotes of a given remote, attempting to use as many features as possible
Expand Down

0 comments on commit 4cf3643

Please sign in to comment.