Skip to content

Commit

Permalink
[clone] a little closer to handling the client handshake
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Aug 20, 2020
1 parent e1100c8 commit 1a4f84d
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 76 deletions.
2 changes: 1 addition & 1 deletion git-transport/src/client/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub fn connect(url: &[u8], version: crate::Protocol) -> Result<Box<dyn Transport
Box::new(
crate::client::git::connect(
&url.host.as_ref().expect("host is present in url"),
url.path.to_path()?,
url.path,
version,
url.port,
)
Expand Down
60 changes: 40 additions & 20 deletions git-transport/src/client/git.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::{
client::{Capabilities, SetServiceResponse},
Protocol, Service,
};
use std::{io, net::TcpStream, path::Path};
use crate::{client::SetServiceResponse, Protocol, Service};
use bstr::{BString, ByteVec};
use std::{io, net::TcpStream};

pub struct Connection<R, W> {
read: R,
write: W,
path: BString,
virtual_host: Option<(String, Option<u16>)>,
protocol: Protocol,
}

Expand All @@ -15,26 +15,38 @@ where
R: io::Read,
W: io::Write,
{
fn set_service(&self) -> &[&str] {
unimplemented!("cached capabilities")
}

fn command_capabilities(&self, _command: &str, _out: &mut Vec<&str>) -> bool {
unimplemented!("command capabilities")
}
}

impl<R, W> crate::client::TransportSketch for Connection<R, W>
where
R: io::Read,
W: io::Write,
{
fn set_service(
&mut self,
_service: Service,
_protocol: Protocol,
) -> Result<SetServiceResponse, crate::client::Error> {
unimplemented!()
fn set_service(&mut self, service: Service) -> Result<SetServiceResponse, crate::client::Error> {
let mut out = bstr::BString::from(service.as_str());
out.push(b' ');
out.extend_from_slice(&self.path);
out.push(0);
if let Some((host, port)) = self.virtual_host.as_ref() {
out.push_str("host=");
out.extend_from_slice(host.as_bytes());
out.push(0);
if let Some(port) = port {
out.push_byte(b':');
out.push_str(&format!("{}", port));
}
}
out.push(0);
out.push_str(format!("version={}", self.protocol as usize));
out.push(0);
self.write.write_all(&out)?;
self.write.flush()?;

Ok(SetServiceResponse {
actual_protocol: Protocol::V1, // TODO
capabilities: vec![], // TODO
refs: None, // TODO
})
}
}

Expand All @@ -43,10 +55,18 @@ where
R: io::Read,
W: io::Write,
{
pub fn new(read: R, write: W, desired_version: Protocol) -> Self {
pub fn new(
read: R,
write: W,
desired_version: Protocol,
repository_path: impl Into<BString>,
virtual_host: Option<(impl Into<String>, Option<u16>)>,
) -> Self {
Connection {
read,
write,
path: repository_path.into(),
virtual_host: virtual_host.map(|(h, p)| (h.into(), p)),
protocol: desired_version,
}
}
Expand All @@ -64,7 +84,7 @@ quick_error! {

pub fn connect(
_host: &str,
_path: &Path,
_path: BString,
_version: crate::Protocol,
_port: Option<u16>,
) -> Result<Connection<TcpStream, TcpStream>, Error> {
Expand Down
10 changes: 1 addition & 9 deletions git-transport/src/client/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,7 @@ quick_error! {

pub struct Transport {}

impl crate::client::Transport for Transport {
fn set_service(&self) -> &[&str] {
unimplemented!("cached capabilities")
}

fn command_capabilities(&self, _command: &str, _out: &mut Vec<&str>) -> bool {
unimplemented!("command capabilities")
}
}
impl crate::client::Transport for Transport {}

pub fn connect(
_host: &str,
Expand Down
20 changes: 7 additions & 13 deletions git-transport/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ quick_error! {

pub struct SetServiceResponse {
/// The protocol the service can provide. May be different from the requested one
actual_protocol: Protocol,
capabilities: Capabilities,
pub actual_protocol: Protocol,
pub capabilities: Capabilities,
/// In protocol version one, this is set to a list of refs and their peeled counterparts.
refs: Option<Box<dyn io::BufRead>>,
pub refs: Option<Box<dyn io::BufRead>>,
}

/// All methods provided here must be called in the correct order according to the communication protocol used to connect to them.
Expand All @@ -44,15 +44,9 @@ pub trait TransportSketch {
/// This means that asking for an unsupported protocol will result in a protocol downgrade to the given one.
/// using the `read_line(…)` function of the given BufReader. It must be exhausted, that is, read to the end,
/// before the next method can be invoked.
fn set_service(&mut self, service: Service, protocol: crate::Protocol) -> Result<SetServiceResponse, Error>;
}

pub trait Transport {
/// a listing of the Server capabilities, as received with the first request
/// These are provided in both V1 and V2
fn set_service(&self) -> &[&str];
fn set_service(&mut self, service: Service) -> Result<SetServiceResponse, Error>;

/// List capabilities for the given `command`, if any. Return true if some were added, false otherwise.
/// This allows to use the command-like interface of protocol V2.
fn command_capabilities(&self, command: &str, out: &mut Vec<&str>) -> bool;
//TODO: A way to terminate the connection gracefully with 'flush' (V1) and noop in V2
}

pub trait Transport {}
13 changes: 11 additions & 2 deletions git-transport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Protocol {
V1,
V2,
V1 = 1,
V2 = 2,
}

#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
Expand All @@ -16,6 +16,15 @@ pub enum Service {
ReceivePack,
}

impl Service {
pub fn as_str(&self) -> &'static str {
match self {
Service::ReceivePack => "git-receive-pack",
Service::UploadPack => "git-upload-pack",
}
}
}

pub mod client;

#[doc(inline)]
Expand Down
60 changes: 38 additions & 22 deletions git-transport/tests/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
mod git {
use git_transport::Protocol;
use std::path::PathBuf;
mod upload_pack {
use crate::fixture_bytes;
use git_transport::{client::TransportSketch, Protocol, Service};
use std::io::BufRead;

pub fn fixture_path(path: &str) -> PathBuf {
PathBuf::from("tests").join("fixtures").join(path)
}
fn fixture_bytes(path: &str) -> Vec<u8> {
std::fs::read(fixture_path(path)).expect("fixture to be present and readable")
}
#[test]
#[ignore]
fn clone_v1() -> crate::Result {
let mut out = Vec::new();
let input = fixture_bytes("v1/clone.response");
let mut c = git_transport::client::git::Connection::new(
input.as_slice(),
&mut out,
Protocol::V1,
"/foo.git",
Some(("example.org", None)),
);
let res = c.set_service(Service::UploadPack)?;
assert_eq!(res.actual_protocol, Protocol::V1);
// assert_eq!(res.capabilities, vec!["hello"].into());
let refs = res
.refs
.expect("v1 protocol provides refs")
.lines()
.flat_map(Result::ok)
.collect::<Vec<_>>();
assert_eq!(refs, vec!["HEAD"]);
Ok(())
}

#[test]
fn upload_pack_clone_v1() {
let mut out = Vec::new();
let input = fixture_bytes("v1/clone.response");
git_transport::client::git::Connection::new(input.as_slice(), &mut out, Protocol::V1);
}
#[test]
fn upload_pack_clone_v2() {
// it lists the version in the first line
}
#[test]
#[ignore]
fn upload_pack_clone_version_unsupported() {
// it replies with version 1, but doesn't list the version number, we can't test it actually, that's alright
#[test]
fn upload_pack_clone_v2() {
// With port
// it lists the version in the first line
}
#[test]
#[ignore]
fn upload_pack_clone_version_unsupported() {
// it replies with version 1, but doesn't list the version number, we can't test it actually, that's alright
}
}
}
Binary file modified git-transport/tests/fixtures/v1/clone.response
Binary file not shown.
9 changes: 9 additions & 0 deletions git-transport/tests/transport.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
use std::path::PathBuf;

pub type Result = std::result::Result<(), Box<dyn std::error::Error>>;

pub fn fixture_path(path: &str) -> PathBuf {
PathBuf::from("tests").join("fixtures").join(path)
}
pub fn fixture_bytes(path: &str) -> Vec<u8> {
std::fs::read(fixture_path(path)).expect("fixture to be present and readable")
}

mod client;
17 changes: 8 additions & 9 deletions tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@
* [x] Disable sideband support (e.g. github http V2 doesn't have it)
* [x] don't coerce line delimiters into empty slices.
* [x] Make 'ERR <error>' handling as error optional, as it may occur only in certain contexts.
* [ ] Allow to enable handling it in `Read` adapter, if configured
* [x] `Write` with packet line encoding
* **git-url**
* [ ] parse into components to make them easy to understand
* **connect**
* [ ] via file and `git-upload-pack`
* [ ] receive pack with V1 protocol
* [ ] receive pack with V2 protocol
* [ ] via https
* [ ] receive pack with V1 protocol
* [ ] receive pack with V2 protocol
* **git-transport**
* [ ] git://git-upload-pack
* [ ] handshake
* [ ] receive V1 response
* [ ] receige V2 response
* [ ] http://git-upload-pack
* [ ] handshake and response handling
* **git-refs**
* [ ] a way to know if a ref needs to be parsed (like `ref/name^{}`)
* [ ] create ref pointing to ID
Expand Down

0 comments on commit 1a4f84d

Please sign in to comment.