Repositories

grarr

(mirrored on github)

Added src/git_ship/error.rs

@@ -0,0 +1,44 @@
use std::{io, fmt, error, result};
use git2;
#[derive(Debug)]
pub enum Error {
Git2(git2::Error),
Io(io::Error),
}
pub type Result<T> = result::Result<T, Error>;
impl Error {
pub fn cause(&self) -> &error::Error {
match *self {
Error::Git2(ref err) => err,
Error::Io(ref err) => err,
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
self.cause().description()
}
fn cause(&self) -> Option<&error::Error> {
Some(self.cause())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "git_ship error: {}", self.cause())
}
}
impl From<git2::Error> for Error {
fn from(err: git2::Error) -> Error { Error::Git2(err) }
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error { Error::Io(err) }
}

Modified src/git_ship/mod.rs

@@ -1,11 +1,14 @@
extern crate url;
extern crate git2;
mod error;
pub mod multiplexer;
pub mod pkt_line;
pub mod capability;
pub mod refs;
pub mod upload_pack;
pub use self::error::{Error, Result};
pub use self::multiplexer::Multiplexer;
pub use self::pkt_line::PktLine;
pub use self::capability::{Capability, Capabilities};

Modified src/git_ship/refs.rs

@@ -4,7 +4,7 @@ use std::borrow::Borrow;
use super::url::Url;
use super::git2::{self, Oid};
use super::{pkt_line, Capability, Capabilities};
use super::{pkt_line, Capability, Capabilities, Result};
#[derive(Debug)]
pub struct UploadPack {
@@ -19,7 +19,7 @@ pub enum Response {
Error(&'static str),
}
pub fn prepare(repo: &git2::Repository, url: &Url) -> Result<Response, git2::Error> {
pub fn prepare(repo: &git2::Repository, url: &Url) -> Result<Response> {
let service = url.query_pairs()
.find(|&(ref key, _)| key == "service")
.map(|(_, id)| id.clone());
@@ -38,7 +38,7 @@ pub fn prepare(repo: &git2::Repository, url: &Url) -> Result<Response, git2::Err
.expect("Resolved references always have a target");
Ok((name, target))
})
.collect::<Result<Vec<_>, git2::Error>>()?;
.collect::<::std::result::Result<_, git2::Error>>()?;
// TODO: Sort refs by name in C locale
let capabilities = Capabilities::new(vec![
Capability::SideBand,
@@ -58,7 +58,7 @@ pub fn prepare(repo: &git2::Repository, url: &Url) -> Result<Response, git2::Err
}
impl UploadPack {
pub fn write_to(&self, mut writer: &mut io::Write) -> io::Result<()> {
pub fn write_to(&self, mut writer: &mut io::Write) -> Result<()> {
pkt_line::write_str(&mut writer, "# service=git-upload-pack")?;
pkt_line::flush(&mut writer)?;
pkt_line::write_str(&mut writer, format!("{} HEAD\0{}", self.head, self.capabilities))?;
@@ -85,10 +85,11 @@ impl Response {
}
}
pub fn write_to(&self, mut writer: &mut io::Write) -> io::Result<()> {
pub fn write_to(&self, mut writer: &mut io::Write) -> Result<()> {
match *self {
Response::UploadPack(ref pack) => pack.write_to(writer),
Response::Error(ref msg) => writer.write_all(msg.as_bytes()),
Response::UploadPack(ref pack) => pack.write_to(writer)?,
Response::Error(ref msg) => writer.write_all(msg.as_bytes())?,
}
Ok(())
}
}

Added src/git_ship/upload_pack.rs

@@ -0,0 +1,285 @@
use std::fmt;
use std::rc::Rc;
use std::sync::Mutex;
use std::io;
use std::borrow::Cow;
use super::git2::{self, Oid, PackBuilderStage};
use super::{pkt_line, PktLine, Capability, Capabilities, Multiplexer, Result};
pub struct Pack {
repo: git2::Repository,
commits: Vec<Oid>,
common: Vec<Oid>,
capabilities: Capabilities,
}
#[derive(Debug)]
pub struct Continue {
common: Vec<Oid>,
capabilities: Capabilities,
}
#[derive(Debug)]
pub struct Request {
wants: Vec<Oid>,
haves: Vec<Oid>,
capabilities: Capabilities,
done: bool,
}
#[derive(Debug)]
pub enum Response {
Pack(Pack),
Continue(Continue),
Error(Cow<'static, str>),
}
fn parse(body: &mut io::Read) -> Result<Request> {
let mut request = Request {
wants: Vec::new(),
haves: Vec::new(),
capabilities: Capabilities::empty(),
done: false,
};
pkt_line::each_str(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(want.parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?);
if !caps.is_empty() {
request.capabilities = caps.parse().unwrap();
}
},
"have" => {
request.haves.push(line[5..].trim().parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?);
},
"done" => {
request.done = true;
},
_ => return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Unexpected pkt-line {}", line))),
}
Ok(())
})?;
println!("request: {:?}", request);
Ok(request)
}
fn refs(repo: &git2::Repository) -> Result<Vec<Oid>> {
repo.references()?
.map(|r| {
let r = r?;
Ok(r.resolve()?
.target()
.expect("Resolved references always have a target"))
})
.collect()
}
fn graph_ancestor_of_any(repo: &git2::Repository, commit: Oid, descendants: &[Oid]) -> Result<bool> {
for &descendant in descendants {
if repo.graph_descendant_of(descendant, commit)? {
return Ok(true);
}
}
Ok(false)
}
fn graph_descendant_of_any(repo: &git2::Repository, commit: Oid, ancestors: &[Oid]) -> Result<bool> {
for &ancestor in ancestors {
if repo.graph_descendant_of(commit, ancestor)? {
return Ok(true);
}
}
Ok(false)
}
// a commit set is closed if every commit in `descendants` has at least one ancestor in `ancestors`
fn is_closed(repo: &git2::Repository, descendants: &[Oid], ancestors: &[Oid]) -> Result<bool> {
for &descendant in descendants {
if !graph_descendant_of_any(repo, descendant, ancestors)? {
println!("{:?} has no ancestors in {:?}", descendant, ancestors);
return Ok(false);
}
}
Ok(true)
}
#[allow(collapsible_if)]
fn compute_response(repo: git2::Repository, refs: Vec<Oid>, request: Request) -> Result<Response> {
let mut common = Vec::<Oid>::new();
// for each id given in have
for id in request.haves {
// if it is an ancestor of a ref
if graph_ancestor_of_any(&repo, id, &refs)? {
// and is not an ancestor of a common
if !graph_ancestor_of_any(&repo, id, &common)? {
// add it to common
common.push(id);
}
}
}
println!("common: {:?}", common);
if request.done || is_closed(&repo, &request.wants, &common)? {
let commits = {
let mut revwalk = repo.revwalk()?;
for id in request.wants {
revwalk.push(id)?;
}
for &id in &common {
revwalk.hide(id)?;
}
revwalk.collect::<::std::result::Result<_, git2::Error>>()?
};
Ok(Response::Pack(Pack {
repo: repo,
commits: commits,
common: common,
capabilities: request.capabilities
}))
} else {
Ok(Response::Continue(Continue {
common: common,
capabilities: request.capabilities
}))
}
}
pub fn prepare(repo: git2::Repository, body: &mut io::Read) -> Result<Response> {
let request = parse(body)?;
let refs = refs(&repo)?;
if request.wants.is_empty() {
return Ok(Response::Error("need wants".into()));
}
for id in &request.wants {
if !refs.contains(&id) {
return Ok(Response::Error(format!("want missing from refs {}", id).into()));
}
}
Ok(compute_response(repo, refs, request)?)
}
impl Pack {
pub fn write_to(&mut self, mut writer: &mut io::Write) -> Result<()> {
if !self.common.is_empty() && self.capabilities.contains(Capability::MultiAckDetailed) {
for id in &self.common {
let line = format!("ACK {} common", id);
println!("{}", line);
pkt_line::write_str(&mut writer, line)?;
}
let line = format!("ACK {}", self.common.iter().last().unwrap());
println!("{}", line);
pkt_line::write_str(&mut writer, line)?;
} else {
pkt_line::write_str(&mut writer, "NAK")?;
}
let mut output = Multiplexer::new(writer, &self.capabilities)?;
{
let output = Rc::new(Mutex::new(&mut output));
let mut builder = self.repo.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));
}
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));
}
}
}
true
})?;
}
for &id in &self.commits {
builder.insert_commit(id)?;
}
builder.foreach(|object| output.lock().unwrap().write_packfile(object).is_ok())?;
}
pkt_line::flush(output.into_inner())?;
Ok(())
}
}
impl Continue {
pub fn write_to(&self, mut writer: &mut io::Write) -> Result<()> {
if self.capabilities.contains(Capability::MultiAckDetailed) {
for id in &self.common {
let line = format!("ACK {} common", id);
println!("{}", line);
pkt_line::write_str(&mut writer, line)?;
}
} else {
// TODO
}
pkt_line::write_str(&mut writer, "NAK")?;
Ok(())
}
}
impl Response {
pub fn status_code(&self) -> u16 {
match *self {
Response::Pack(_) => 200,
Response::Continue(_) => 200,
Response::Error(_) => 403,
}
}
pub fn write_to(&mut self, mut writer: &mut io::Write) -> Result<()> {
match *self {
Response::Pack(ref mut pack) => pack.write_to(writer)?,
Response::Continue(ref c) => c.write_to(writer)?,
Response::Error(ref msg) => writer.write_all(msg.as_bytes())?,
}
Ok(())
}
}
impl fmt::Debug for Pack {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
struct R<'a>(&'a git2::Repository);
impl<'a> fmt::Debug for R<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("git2::Repository")
.field("path", &self.0.path())
.finish()
}
}
f.debug_struct("Pack")
.field("repo", &R(&self.repo))
.field("commits", &self.commits)
.field("common", &self.common)
.field("capabilities", &self.capabilities)
.finish()
}
}

Modified src/handler/git_smart_http/refs.rs

@@ -23,6 +23,7 @@ struct W(refs::Response);
impl WriteBody for W {
fn write_body(&mut self, res: &mut io::Write) -> io::Result<()> {
self.0.write_to(res)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
}

Modified src/handler/git_smart_http/upload_pack.rs

@@ -1,9 +1,6 @@
use handler::base::*;
use std::rc::Rc;
use std::sync::Mutex;
use std::io::{ self, Read, Write };
use std::collections::HashSet;
use std::io;
use iron::headers::{ CacheControl, CacheDirective, Vary, Pragma, Expires, HttpDate, ContentEncoding, Encoding };
use iron::modifiers::Header;
@@ -12,44 +9,12 @@ use unicase::UniCase;
use time;
use flate2::FlateReadExt;
use git2::{ self, Oid, Repository, Revwalk, PackBuilderStage };
use git_ship::{pkt_line, PktLine, Multiplexer, Capability, Capabilities};
use git_ship::upload_pack;
#[derive(Clone)]
pub struct UploadPack;
#[derive(Debug)]
struct UploadPackRequest {
wants: Vec<Oid>,
haves: Vec<Oid>,
capabilities: Capabilities,
done: bool,
context: RepositoryContext,
}
struct UploadPackContext<'a> {
repository: &'a Repository,
refs: HashSet<Oid>,
}
enum UploadPackResponse<'a> {
Pack {
revwalk: Revwalk<'a>,
common: Vec<Oid>,
},
Continue {
common: Vec<Oid>,
},
}
fn parse_request(req: &mut Request, context: RepositoryContext) -> Result<UploadPackRequest, Error> {
let mut request = UploadPackRequest {
wants: Vec::new(),
haves: Vec::new(),
capabilities: Capabilities::empty(),
done: false,
context: context,
};
fn body_thing<'a>(req: &'a mut Request) -> Result<Box<io::Read + 'a>, Error> {
let encoding = if let Some(&ContentEncoding(ref encodings)) = req.headers.get() {
if encodings.len() != 1 {
return Err(Error::from("Can't handle multiple encodings"));
@@ -58,205 +23,12 @@ fn parse_request(req: &mut Request, context: RepositoryContext) -> Result<Upload
} else {
Encoding::Identity
};
let mut body = match encoding {
Encoding::Identity => Box::new(&mut req.body) as Box<Read>,
Encoding::Gzip => Box::new(try!((&mut req.body).gz_decode())) as Box<Read>,
Encoding::Deflate => Box::new((&mut req.body).deflate_decode()) as Box<Read>,
Ok(match encoding {
Encoding::Identity => Box::new(&mut req.body) as Box<io::Read>,
Encoding::Gzip => Box::new((&mut req.body).gz_decode()?) as Box<io::Read>,
Encoding::Deflate => Box::new((&mut req.body).deflate_decode()) as Box<io::Read>,
encoding => return Err(Error::from(format!("Can't handle encoding {}", encoding))),
};
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(want.parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?);
if !caps.is_empty() {
request.capabilities = caps.parse().unwrap();
}
},
"have" => {
request.haves.push(line[5..].trim().parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?);
},
"done" => {
request.done = true;
},
_ => return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Unexpected pkt-line {}", line))),
}
Ok(())
})?;
println!("request: {:?}", request);
Ok(request)
}
fn prepare_context(context: &RepositoryContext) -> Result<UploadPackContext, Error> {
let mut refs = HashSet::new();
for reff in try!(context.repository.references()) {
let reff = try!(reff);
let reff = try!(reff.resolve());
refs.insert(try!(reff.target().ok_or("ref missing target")));
}
Ok(UploadPackContext { repository: &context.repository, refs: refs })
}
fn validate_request(context: &UploadPackContext, request: &UploadPackRequest) -> Result<(), Error> {
if request.wants.is_empty() {
return Err(Error::from("need wants"));
}
for id in &request.wants {
if !context.refs.contains(&id) {
return Err(Error::from(format!("want missing from refs {}", id)));
}
}
Ok(())
}
fn graph_ancestor_of_any<I: Iterator<Item=Oid>>(repository: &Repository, commit: Oid, descendants: I) -> Result<bool, git2::Error> {
for descendant in descendants {
if try!(repository.graph_descendant_of(descendant, commit)) {
return Ok(true);
}
}
Ok(false)
}
fn graph_descendant_of_any<I: Iterator<Item=Oid>>(repository: &Repository, commit: Oid, ancestors: I) -> Result<bool, git2::Error> {
for ancestor in ancestors {
if try!(repository.graph_descendant_of(commit, ancestor)) {
return Ok(true);
}
}
Ok(false)
}
// a commit set is closed if every commit in `descendants` has at least one ancestor in `ancestors`
fn is_closed<I1: Iterator<Item=Oid>, I2: Iterator<Item=Oid> + Clone>(repository: &Repository, descendants: I1, ancestors: I2) -> Result<bool, Error> {
for descendant in descendants {
if !try!(graph_descendant_of_any(repository, descendant, ancestors.clone())) {
println!("{:?} has no ancestors in {:?}", descendant, ancestors.clone().collect::<Vec<_>>());
return Ok(false);
}
}
Ok(true)
}
#[allow(collapsible_if)]
fn compute_response<'a>(context: &'a UploadPackContext, request: &'a UploadPackRequest) -> Result<UploadPackResponse<'a>, Error> {
let mut common = Vec::<Oid>::new();
// for each id given in have
for id in request.haves.iter().cloned() {
// if it is an ancestor of a ref
if try!(graph_ancestor_of_any(&context.repository, id, context.refs.iter().cloned())) {
// and is not an ancestor of a common
if !try!(graph_ancestor_of_any(&context.repository, id, common.iter().cloned())) {
// add it to common
common.push(id);
}
}
}
println!("common: {:?}", common);
if request.done || try!(is_closed(&context.repository, request.wants.iter().cloned(), common.iter().cloned())) {
let mut revwalk = try!(context.repository.revwalk());
for id in request.wants.iter().cloned() {
try!(revwalk.push(id));
}
for id in common.iter().cloned() {
try!(revwalk.hide(id));
}
Ok(UploadPackResponse::Pack { revwalk: revwalk, common: common })
} else {
Ok(UploadPackResponse::Continue { common: common })
}
}
fn build_pack<'a>(repository: &'a Repository, mut revwalk: Revwalk<'a>, mut output: Multiplexer) -> Result<(), Error> {
{
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));
}
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));
}
}
}
true
})?;
}
builder.insert_walk(&mut revwalk)?;
builder.foreach(|object| output.lock().unwrap().write_packfile(object).is_ok())?;
}
pkt_line::flush(output.into_inner())?;
Ok(())
}
impl WriteBody for UploadPackRequest {
fn write_body(&mut self, mut res: &mut Write) -> io::Result<()> {
let context2 = prepare_context(&self.context).unwrap();
validate_request(&context2, self).unwrap();
let result = compute_response(&context2, self).unwrap();
match result {
UploadPackResponse::Pack { revwalk, common } => {
if !common.is_empty() && self.capabilities.contains(Capability::MultiAckDetailed) {
for id in &common {
let line = format!("ACK {} common", id);
println!("{}", line);
pkt_line::write_str(&mut res, line)?;
}
let line = format!("ACK {}", common.iter().last().unwrap());
println!("{}", line);
pkt_line::write_str(&mut res, line)?;
} else {
pkt_line::write_str(&mut res, "NAK")?;
}
let output = Multiplexer::new(res, &self.capabilities)?;
build_pack(&self.context.repository, revwalk, output).unwrap();
},
UploadPackResponse::Continue { common } => {
if self.capabilities.contains(Capability::MultiAckDetailed) {
for id in common {
let line = format!("ACK {} common", id);
println!("{}", line);
pkt_line::write_str(&mut res, line)?;
}
} else {
// TODO
}
pkt_line::write_str(&mut res, "NAK")?;
},
}
Ok(())
}
})
}
impl Handler for UploadPack {
@@ -274,9 +46,18 @@ impl Handler for UploadPack {
])),
);
let context = itry!(req.extensions.remove::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let request = itry!(parse_request(req, context), (status::BadRequest, no_cache));
println!("Prepared request for {}", request.context.path);
Ok(Response::with((status::Ok, no_cache, Box::new(request) as Box<WriteBody>)))
let mut body = itry!(body_thing(req), (status::BadRequest, no_cache));
let response = itry!(upload_pack::prepare(context.repository, &mut *body), (status::BadRequest, no_cache));
let status_code = status::Unregistered(response.status_code());
Ok(Response::with((status_code, no_cache, Box::new(W(response)) as Box<WriteBody>)))
}
}
struct W(upload_pack::Response);
impl WriteBody for W {
fn write_body(&mut self, res: &mut io::Write) -> io::Result<()> {
self.0.write_to(res)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
}