Repositories

grarr

(mirrored on github)

Added src/git_ship/capability.rs

@@ -0,0 +1,51 @@
use std::str::FromStr;
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Void {
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Capability {
SideBand,
SideBand64K,
MultiAck,
MultiAckDetailed,
Unknown(String),
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Capabilities(Vec<Capability>);
impl Capabilities {
pub fn empty() -> Capabilities {
Capabilities(Vec::new())
}
pub fn contains(&self, cap: Capability) -> bool {
self.0.contains(&cap)
}
}
impl FromStr for Capability {
type Err = Void;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"side-band" => Capability::SideBand,
"side-band-64k" => Capability::SideBand64K,
"multi_ack" => Capability::MultiAck,
"multi_ack_detailed" => Capability::MultiAckDetailed,
s => Capability::Unknown(s.to_owned()),
})
}
}
impl FromStr for Capabilities {
type Err = Void;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Capabilities(s
.split(' ')
.filter(|s| !s.is_empty())
.map(|s| s.parse().unwrap())
.collect()))
}
}

Added src/git_ship/mod.rs

@@ -0,0 +1,10 @@
pub mod multiplexer;
pub mod pkt_line;
pub mod capability;
pub use self::multiplexer::Multiplexer;
pub use self::pkt_line::PktLine;
pub use self::capability::{Capability, Capabilities};
// pub use self::refs::Refs;
// pub use self::upload_pack::UploadPack;

Added src/git_ship/multiplexer.rs

@@ -0,0 +1,77 @@
use std::io;
use std::cmp;
use super::pkt_line;
use super::{Capability, Capabilities};
/// A pkt-line multiplexer for the `side-band` and `side-band-64k` capabilities.
/// Also supports being a transparent passthrough when neither capability is
/// specified to make usage easier.
/// https://github.com/git/git/blob/10c78a162fa821ee85203165b805ff46be454091/Documentation/technical/protocol-capabilities.txt#L119
pub struct Multiplexer<'a> {
writer: &'a mut io::Write,
limit: Option<usize>,
buffer: [u8; pkt_line::MAX_PKT_LINE_DATA_LEN],
}
impl<'a> Multiplexer<'a> {
pub fn new(writer: &'a mut io::Write, caps: &Capabilities) -> io::Result<Multiplexer<'a>> {
let limit = match (caps.contains(Capability::SideBand), caps.contains(Capability::SideBand64K)) {
(true, true) => return Err(io::Error::new(io::ErrorKind::InvalidInput, "client cannot send both side-band and side-band-64k")),
(true, false) => Some(1000),
(false, true) => Some(pkt_line::MAX_PKT_LINE_DATA_LEN),
(false, false) => None,
};
Ok(Multiplexer { writer: writer, limit: limit, buffer: [0; pkt_line::MAX_PKT_LINE_DATA_LEN] })
}
pub fn write_packfile<B: AsRef<[u8]>>(&mut self, bytes: B) -> io::Result<()> {
let bytes = bytes.as_ref();
if let Some(limit) = self.limit {
let mut offset = 0;
while offset < bytes.len() {
let len = cmp::min(bytes.len() - offset, limit - 1);
self.buffer[0] = 1;
self.buffer[1..len + 1].copy_from_slice(&bytes[offset..offset + len]);
pkt_line::write(&mut self.writer, &self.buffer[0..len + 1])?;
offset += len;
}
Ok(())
} else {
self.writer.write_all(bytes)
}
}
pub fn write_progress<S: AsRef<str>>(&mut self, msg: S) -> io::Result<()> {
let msg = msg.as_ref();
if let Some(limit) = self.limit {
if msg.len() > limit {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "msg too long"));
}
self.buffer[0] = 2;
self.buffer[1..msg.len() + 1].copy_from_slice(msg.as_bytes());
pkt_line::write(&mut self.writer, &self.buffer[0..msg.len() + 1])?;
self.writer.flush()?;
}
Ok(())
}
#[allow(unused)]
pub fn write_error<S: AsRef<str>>(&mut self, msg: S) -> io::Result<()> {
let msg = msg.as_ref();
if let Some(limit) = self.limit {
if msg.len() > limit {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "msg too long"));
}
self.buffer[0] = 3;
self.buffer[1..msg.len() + 1].copy_from_slice(msg.as_bytes());
pkt_line::write(&mut self.writer, &self.buffer[0..msg.len() + 1])?;
self.writer.flush()?;
}
Ok(())
}
pub fn into_inner(self) -> &'a mut io::Write {
self.writer
}
}

Added src/git_ship/pkt_line.rs

@@ -0,0 +1,106 @@
use std::io;
use std::str;
const MAX_PKT_LINE_LEN: usize = 65520;
const PKT_LINE_SIZE_LEN: usize = 4;
pub const MAX_PKT_LINE_DATA_LEN: usize = MAX_PKT_LINE_LEN - PKT_LINE_SIZE_LEN;
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum PktLine<T> {
Flush,
Line(T),
}
pub fn write<W: io::Write, B: AsRef<[u8]>>(mut writer: W, buf: B) -> io::Result<()> {
let buf = buf.as_ref();
if buf.is_empty() {
return Ok(());
}
if buf.len() > MAX_PKT_LINE_DATA_LEN {
return Err(io::Error::new(io::ErrorKind::InvalidData, "The maximum length of a pkt-line's data component is 65516 bytes."));
}
write!(writer, "{:04x}", buf.len() + PKT_LINE_SIZE_LEN)?;
writer.write_all(buf.as_ref())
}
pub fn write_str<W: io::Write, S: AsRef<str>>(mut writer: W, buf: S) -> io::Result<()> {
let buf = buf.as_ref().as_bytes();
if buf.is_empty() {
return Ok(());
}
let len = buf.len();
if buf.len() > MAX_PKT_LINE_DATA_LEN {
return Err(io::Error::new(io::ErrorKind::InvalidData, "The maximum length of a pkt-line's data component is 65516 bytes."));
}
let inject_line_feed = buf[buf.len() - 1] != b'\n' && buf.len() < MAX_PKT_LINE_DATA_LEN;
write!(writer, "{:04x}", len + if inject_line_feed { 5 } else { 4 })?;
writer.write_all(buf)?;
if inject_line_feed {
writer.write_all(b"\n")?;
}
Ok(())
}
pub fn flush<W: io::Write>(mut writer: W) -> io::Result<()> {
writer.write_all(b"0000")
}
fn read_pkt_line_size<R: io::Read>(mut reader: R) -> io::Result<PktLine<usize>> {
let mut size_buf = [0; PKT_LINE_SIZE_LEN];
reader.read_exact(&mut size_buf)?;
let size_str = str::from_utf8(&size_buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let size = usize::from_str_radix(size_str, 16)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
if size == 0 {
return Ok(PktLine::Flush);
}
if size < PKT_LINE_SIZE_LEN {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Size less than 4 and not equal to 0"));
}
if size > MAX_PKT_LINE_LEN {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Size greater than 65520"));
}
Ok(PktLine::Line(size - PKT_LINE_SIZE_LEN))
}
pub fn read<'a, R: io::Read>(mut reader: R, buf: &'a mut [u8; MAX_PKT_LINE_DATA_LEN]) -> io::Result<PktLine<&'a [u8]>> {
let size = read_pkt_line_size(&mut reader)?;
let size = match size {
PktLine::Line(size) => size,
PktLine::Flush => return Ok(PktLine::Flush),
};
reader.read_exact(&mut buf[..size])?;
if size > 0 && buf[size - 1] == b'\n' {
Ok(PktLine::Line(&buf[..size - 1]))
} else {
Ok(PktLine::Line(&buf[..size]))
}
}
pub fn each<R: io::Read, F: FnMut(PktLine<&[u8]>) -> io::Result<()>>(mut reader: R, mut callback: F) -> io::Result<()> {
let mut buffer = [0; MAX_PKT_LINE_DATA_LEN];
loop {
match read(&mut reader, &mut buffer) {
Ok(line) => {
callback(line)?;
}
Err(e) => match e.kind() {
io::ErrorKind::UnexpectedEof => return Ok(()),
_ => return Err(e),
}
}
}
}
pub fn each_str<R: io::Read, F: FnMut(PktLine<&str>) -> io::Result<()>>(reader: R, mut callback: F) -> io::Result<()>{
each(reader, |line| {
callback(match line {
PktLine::Flush => PktLine::Flush,
PktLine::Line(buf) => {
let line = str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
PktLine::Line(line)
}
})
})
}

Modified src/handler/git_smart_http/mod.rs

@@ -1,5 +1,4 @@
mod refs;
mod utils;
mod upload_pack;
pub use self::refs::Refs;

Deleted src/handler/git_smart_http/protocols/mod.rs

@@ -1 +0,0 @@
pub mod dumb;

Modified src/handler/git_smart_http/refs.rs

@@ -1,5 +1,5 @@
use handler::base::*;
use super::utils::*;
use git_ship::pkt_line;
use git2;
@@ -14,15 +14,15 @@ fn format_ref(reff: git2::Reference) -> Result<String, Error> {
fn format_refs(head: git2::Reference, refs: git2::References) -> Result<Vec<u8>, Error> {
let mut result = Vec::new();
try!(result.write_pkt_line("# service=git-upload-pack"));
try!(result.write_pkt_line_flush());
let head_id = try!(head.target().ok_or(Error::from("HEAD missing target")));
try!(result.write_pkt_line(format!("{} HEAD\0{}", head_id, "side-band side-band-64k multi_ack_detailed")));
pkt_line::write_str(&mut result, "# service=git-upload-pack")?;
pkt_line::flush(&mut result)?;
let head_id = head.target().ok_or(Error::from("HEAD missing target"))?;
pkt_line::write_str(&mut result, format!("{} HEAD\0{}", head_id, "side-band side-band-64k multi_ack_detailed"))?;
// TODO: Sort refs by name in C locale
for reff in refs {
try!(result.write_pkt_line(try!(format_ref(try!(reff)))));
pkt_line::write_str(&mut result, try!(format_ref(try!(reff))))?;
}
try!(result.write_pkt_line_flush());
pkt_line::flush(&mut result)?;
Ok(result)
}

Modified src/handler/git_smart_http/upload_pack.rs

@@ -1,5 +1,4 @@
use handler::base::*;
use super::utils::*;
use std::rc::Rc;
use std::sync::Mutex;
@@ -14,24 +13,16 @@ use time;
use flate2::FlateReadExt;
use git2::{ self, Oid, Repository, Revwalk, PackBuilderStage };
use git_ship::{pkt_line, PktLine, Multiplexer, Capability, Capabilities};
#[derive(Clone)]
pub struct UploadPack;
#[derive(Debug, Eq, PartialEq)]
enum Capability {
SideBand,
SideBand64K,
MultiAck,
MultiAckDetailed,
Unknown(String),
}
#[derive(Debug)]
struct UploadPackRequest {
wants: Vec<Oid>,
haves: Vec<Oid>,
capabilities: Vec<Capability>,
capabilities: Capabilities,
done: bool,
context: RepositoryContext,
}
@@ -55,7 +46,7 @@ fn parse_request(req: &mut Request, context: RepositoryContext) -> Result<Upload
let mut request = UploadPackRequest {
wants: Vec::new(),
haves: Vec::new(),
capabilities: Vec::new(),
capabilities: Capabilities::empty(),
done: false,
context: context,
};
@@ -73,27 +64,34 @@ fn parse_request(req: &mut Request, context: RepositoryContext) -> Result<Upload
Encoding::Deflate => Box::new((&mut req.body).deflate_decode()) as Box<Read>,
encoding => return Err(Error::from(format!("Can't handle encoding {}", encoding))),
};
for line in body.pkt_lines() {
let line = try!(line);
if line.len() < 4 { continue }
pkt_line::each_str(&mut body, |line| {
println!("line {:?}", line);
let line = match line {
PktLine::Flush => return Ok(()),
PktLine::Line(line) => line,
};
if line.len() < 4 {
return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Unexpected pkt-line {}", line)));
}
match &line[0..4] {
"want" => {
let line = line[5..].trim();
let (want, caps) = line.split_at(line.find(' ').unwrap_or(line.len()));
request.wants.push(try!(want.parse()));
request.capabilities.extend(caps.split(' ').filter(|s| !s.is_empty()).map(Capability::from));
request.wants.push(want.parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?);
if !caps.is_empty() {
request.capabilities = caps.parse().unwrap();
}
},
"have" => {
let end = line.find(|c| c == '\n' || c == '\0').unwrap_or(line.len());
request.haves.push(try!(line[5..end].parse()));
request.haves.push(line[5..].trim().parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?);
},
"done" => {
request.done = true;
break;
},
_ => return Err(Error::from(format!("Unexpected pkt-line {}", line))),
_ => return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Unexpected pkt-line {}", line))),
}
}
Ok(())
})?;
println!("request: {:?}", request);
Ok(request)
}
@@ -182,55 +180,43 @@ fn compute_response<'a>(context: &'a UploadPackContext, request: &'a UploadPackR
}
}
fn build_pack<'a>(repository: &'a Repository, mut revwalk: Revwalk<'a>, output: Multiplexer) -> Result<(), Error> {
let output = Rc::new(Mutex::new(output));
let mut builder = try!(repository.packbuilder());
fn build_pack<'a>(repository: &'a Repository, mut revwalk: Revwalk<'a>, mut output: Multiplexer) -> Result<(), Error> {
{
let output = output.clone();
let mut first_delta = true;
try!(builder.set_progress_callback(move |stage, current, total| {
let mut output = output.lock().unwrap();
match stage {
PackBuilderStage::AddingObjects => {
let _ = write!(output.progress(), "Counting objects {}\r", current);
}
PackBuilderStage::Deltafication => {
if first_delta {
let _ = write!(output.progress(), "\n");
first_delta = false;
let output = Rc::new(Mutex::new(&mut output));
let mut builder = repository.packbuilder()?;
{
let output = output.clone();
let mut first_delta = true;
builder.set_progress_callback(move |stage, current, total| {
let mut output = output.lock().unwrap();
match stage {
PackBuilderStage::AddingObjects => {
let _ = output.write_progress(format!("Counting objects {}\r", current));
}
let percent = (current as f64 / total as f64) * 100.0;
let _ = write!(
output.progress(),
"Compressing objects: {:.0}% ({}/{})",
percent, current, total);
if current == total {
let _ = write!(output.progress(), ", done\n");
} else {
let _ = write!(output.progress(), "\r");
PackBuilderStage::Deltafication => {
if first_delta {
let _ = output.write_progress("\n");
first_delta = false;
}
let percent = (current as f64 / total as f64) * 100.0;
if current == total {
let _ = output.write_progress(format!(
"Compressing objects: {:.0}% ({}/{}), done\n",
percent, current, total));
} else {
let _ = output.write_progress(format!(
"Compressing objects: {:.0}% ({}/{})\r",
percent, current, total));
}
}
}
}
let _ = output.flush();
true
}));
}
try!(builder.insert_walk(&mut revwalk));
let mut error = None;
try!(builder.foreach(|object| {
let mut output = output.lock().unwrap();
match output.packfile().write_all(object) {
err @ Err(_) => {
error = Some(err);
false
}
Ok(()) => true
true
})?;
}
}));
if let Some(err) = error {
try!(err);
builder.insert_walk(&mut revwalk)?;
builder.foreach(|object| output.lock().unwrap().write_packfile(object).is_ok())?;
}
try!(output.lock().unwrap().close());
pkt_line::flush(output.into_inner())?;
Ok(())
}
@@ -241,39 +227,32 @@ impl WriteBody for UploadPackRequest {
let result = compute_response(&context2, self).unwrap();
match result {
UploadPackResponse::Pack { revwalk, common } => {
if self.capabilities.contains(&Capability::MultiAckDetailed) {
if !common.is_empty() && self.capabilities.contains(Capability::MultiAckDetailed) {
for id in &common {
let line = format!("ACK {} common", id);
println!("{}", line);
res.write_pkt_line(line)?;
pkt_line::write_str(&mut res, line)?;
}
let line = format!("ACK {}", common.iter().last().unwrap());
println!("{}", line);
res.write_pkt_line(line)?;
pkt_line::write_str(&mut res, line)?;
} else {
res.write_pkt_line("NAK")?;
pkt_line::write_str(&mut res, "NAK")?;
}
let limit = if self.capabilities.contains(&Capability::SideBand64K) {
Some(65520)
} else if self.capabilities.contains(&Capability::SideBand) {
Some(1000)
} else {
None
};
let mut output = Multiplexer::new(res, limit);
let output = Multiplexer::new(res, &self.capabilities)?;
build_pack(&self.context.repository, revwalk, output).unwrap();
},
UploadPackResponse::Continue { common } => {
if self.capabilities.contains(&Capability::MultiAckDetailed) {
if self.capabilities.contains(Capability::MultiAckDetailed) {
for id in common {
let line = format!("ACK {} common", id);
println!("{}", line);
res.write_pkt_line(line)?;
pkt_line::write_str(&mut res, line)?;
}
} else {
// TODO
}
res.write_pkt_line("NAK")?;
pkt_line::write_str(&mut res, "NAK")?;
},
}
Ok(())
@@ -310,15 +289,3 @@ impl Route for UploadPack {
"/git-upload-pack".into()
}
}
impl<S: AsRef<str>> From<S> for Capability {
fn from(s: S) -> Capability {
match s.as_ref() {
"side-band" => Capability::SideBand,
"side-band-64k" => Capability::SideBand64K,
"multi_ack" => Capability::MultiAck,
"multi_ack_detailed" => Capability::MultiAckDetailed,
s => Capability::Unknown(s.to_owned()),
}
}
}

Deleted src/handler/git_smart_http/utils.rs

@@ -1,146 +0,0 @@
use std::io;
use std::str;
use std::cmp;
pub struct Multiplexer<'a> {
stream: &'a mut io::Write,
limit: Option<usize>,
current_band: u8,
}
impl<'a> Multiplexer<'a> {
pub fn new(stream: &mut io::Write, limit: Option<usize>) -> Multiplexer {
Multiplexer { stream: stream, limit: limit, current_band: 0 }
}
pub fn packfile(&mut self) -> &mut io::Write {
self.current_band = 1;
self
}
pub fn progress(&mut self) -> &mut io::Write {
self.current_band = 2;
self
}
#[allow(dead_code)]
pub fn error(&mut self) -> &mut io::Write {
self.current_band = 3;
self
}
pub fn close(&mut self) -> io::Result<()> {
self.stream.write_all(b"0000")
}
}
impl<'a> io::Write for Multiplexer<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if let Some(limit) = self.limit {
let len = cmp::min(limit - 2, buf.len());
try!(write!(self.stream, "{:04x}", len + 5));
try!(self.stream.write(&[self.current_band]));
try!(self.stream.write_all(&buf[0..len]));
Ok(len)
} else if self.current_band == 1 {
self.stream.write(buf)
} else {
Ok(buf.len())
}
}
fn flush(&mut self) -> io::Result<()> {
self.stream.flush()
}
}
pub trait WritePktLine {
fn write_pkt_line<S: AsRef<str>>(&mut self, buf: S) -> io::Result<()>;
fn write_pkt_line_binary<B: AsRef<[u8]>>(&mut self, buf: B) -> io::Result<()>;
fn write_pkt_line_flush(&mut self) -> io::Result<()>;
}
impl<W: io::Write> WritePktLine for W {
fn write_pkt_line<S: AsRef<str>>(&mut self, buf: S) -> io::Result<()> {
let buf = buf.as_ref().as_bytes();
if buf.len() >= 65520 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"The maximum length of a pkt-line's data component is 65520 bytes (including LF)."));
}
if buf.is_empty() {
return Ok(());
}
try!(write!(self, "{:04x}", buf.len() + 5));
try!(self.write_all(buf));
self.write_all(b"\n")
}
fn write_pkt_line_binary<B: AsRef<[u8]>>(&mut self, buf: B) -> io::Result<()> {
let buf = buf.as_ref();
if buf.len() > 65520 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"The maximum length of a pkt-line's data component is 65520 bytes."));
}
if buf.is_empty() {
return Ok(());
}
try!(write!(self, "{:04x}", buf.len() + 4));
self.write_all(buf.as_ref())
}
fn write_pkt_line_flush(&mut self) -> io::Result<()> {
self.write_all(b"0000")
}
}
pub struct PktLines<'a> {
source: &'a mut io::Read,
}
pub trait ReadPktLine {
fn read_pkt_line(&mut self, buf: &mut [u8]) -> io::Result<usize>;
fn pkt_lines(&mut self) -> PktLines;
}
impl<T: io::Read> ReadPktLine for T {
fn read_pkt_line(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut size_buf = [0; 4];
try!(self.read_exact(&mut size_buf));
let size_str = try!(str::from_utf8(&size_buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)));
let size = try!(usize::from_str_radix(size_str, 16).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)));
if size == 0 {
return Ok(0);
}
if size < 4 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Size less than 4 and not equal to 0"));
}
let size = size - 4;
if size > buf.len() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "Buffer was not large enough"));
}
try!(self.read_exact(&mut buf[..size]));
if size > 0 && buf[size - 1] == b'\n' {
Ok(size - 1)
} else {
Ok(size)
}
}
fn pkt_lines(&mut self) -> PktLines {
PktLines { source: self }
}
}
impl<'a> Iterator for PktLines<'a> {
type Item = Result<String, io::Error>;
fn next(&mut self) -> Option<Result<String, io::Error>> {
let mut buf = vec![0; 65520];
match self.source.read_pkt_line(&mut buf) {
Ok(len) => {
buf.truncate(len);
Some(String::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)))
},
Err(e) => match e.kind() {
io::ErrorKind::UnexpectedEof => None,
_ => Some(Err(e)),
},
}
}
}

Modified src/main.rs

@@ -51,6 +51,7 @@ mod settings;
mod referenced_commit;
mod config;
mod tree_entry;
mod git_ship;
use std::time::Duration;
use std::env;