Repositories

grarr

(mirrored on github)

Will probably be easier to implement with libgit2's pack builder than figuring out how to serve individual objects

Modified src/error.rs

@@ -1,5 +1,6 @@
use std::error;
use std::fmt;
use std::io;
use std::borrow::{ Cow, Borrow };
use git2;
@@ -9,6 +10,7 @@ pub enum Error {
MissingPathComponent,
String(Cow<'static, str>),
Git(git2::Error),
Io(io::Error),
}
impl error::Error for Error {
@@ -18,6 +20,7 @@ impl error::Error for Error {
Error::MissingPathComponent => "Missing path component",
Error::String(ref s) => s.borrow(),
Error::Git(ref e) => e.description(),
Error::Io(ref e) => e.description(),
}
}
}
@@ -26,6 +29,7 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Git(ref e) => e.fmt(f),
Error::Io(ref e) => e.fmt(f),
_ => f.write_str(error::Error::description(self)),
}
}
@@ -37,6 +41,12 @@ impl From<git2::Error> for Error {
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::Io(e)
}
}
impl From<&'static str> for Error {
fn from(s: &'static str) -> Error {
Error::String(s.into())

Deleted src/handler/git/mod.rs

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

Deleted src/handler/git/protocols/dumb/head.rs

@@ -1,32 +0,0 @@
use handler::base::*;
#[derive(Clone)]
pub struct Head;
impl Handler for Head {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), status::InternalServerError);
let head = itry!(
context.repository
.find_reference("HEAD")
.map_err(Error::from)
.and_then(|head|
head.symbolic_target()
.ok_or(Error::from("HEAD should be a symbolic ref"))
.map(|target| format!("ref: {}", target))),
status::InternalServerError);
Ok(Response::with((status::Ok, mime!(Text/Plain; Charset=Utf8), head)))
}
}
impl Route for Head {
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/HEAD".into()
}
}

Deleted src/handler/git/protocols/dumb/mod.rs

@@ -1,5 +0,0 @@
mod refs;
mod head;
pub use self::refs::Refs;
pub use self::head::Head;

Deleted src/handler/git/protocols/dumb/refs.rs

@@ -1,40 +0,0 @@
use handler::base::*;
use git2;
#[derive(Clone)]
pub struct Refs;
fn format_ref(reff: git2::Reference) -> Result<String, Error> {
let target = try!(try!(reff.resolve()).target().ok_or(Error::from("Ref missing target")));
let name = try!(reff.name().ok_or(Error::from("Ref missing name")));
Ok(format!("{}\t{}", target, name))
}
fn format_refs(refs: git2::References) -> Result<String, Error> {
let mut result = String::new();
for reff in refs {
result.push_str(&try!(format_ref(try!(reff))));
result.push('\n');
}
Ok(result)
}
impl Handler for Refs {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), status::InternalServerError);
let mime = mime!(Text/Plain; Charset=Utf8);
let buffer = itry!(context.repository.references().map_err(From::from).and_then(format_refs), status::InternalServerError);
Ok(Response::with((status::Ok, mime, buffer)))
}
}
impl Route for Refs {
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/info/refs".into()
}
}

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

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

Added src/handler/git_smart_http/mod.rs

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

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

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

Added src/handler/git_smart_http/refs.rs

@@ -0,0 +1,75 @@
use handler::base::*;
use super::utils::*;
use git2;
#[derive(Clone)]
pub struct Refs;
fn format_ref(reff: git2::Reference) -> Result<String, Error> {
let target = try!(try!(reff.resolve()).target().ok_or(Error::from("Ref missing target")));
let name = try!(reff.name().ok_or(Error::from("Ref missing name")));
Ok(format!("{} {}", target, name))
}
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"));
let head_id = try!(head.target().ok_or(Error::from("HEAD missing target")));
try!(result.write_pkt_line(format!("{} HEAD\0{}", head_id, "i-am-incapable")));
// TODO: Sort refs by name in C locale
for reff in refs {
try!(result.write_pkt_line(try!(format_ref(try!(reff)))));
}
try!(result.write_pkt_line_flush());
Ok(result)
}
fn find_service(req: &Request) -> Option<String> {
req.url.clone().into_generic_url()
.query_pairs()
.unwrap_or_default()
.into_iter()
.find(|&(ref key, _)| key == "service")
.map(|(_, ref id)| id.clone())
}
impl Handler for Refs {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), status::InternalServerError);
match find_service(req).as_ref().map(|s| &**s) {
Some("git-upload-pack") => {
let head = itry!(context.repository.head());
let refs = itry!(context.repository.references());
let buffer = itry!(format_refs(head, refs), status::InternalServerError);
Ok(Response::with((
status::Ok,
mime!(Application/("x-git-upload-pack-advertisement")),
buffer)))
},
Some(_) => {
Ok(Response::with((
status::Forbidden,
mime!(Text/Plain; Charset=Utf8),
"Unknown git service name")))
}
None => {
// Assumed dumb client
Ok(Response::with((
status::Forbidden,
mime!(Text/Plain; Charset=Utf8),
"Please upgrade your git client.")))
}
}
}
}
impl Route for Refs {
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/info/refs".into()
}
}

Added src/handler/git_smart_http/utils.rs

@@ -0,0 +1,42 @@
use std::io::{ self, Write };
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 WritePktLine for Vec<u8> {
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")
}
}

Modified src/handler/mod.rs

@@ -21,7 +21,7 @@ mod tree;
mod blob;
mod pages;
mod compare;
pub mod git;
pub mod git_smart_http;
pub use self::avatar::Avatars;
pub use self::review::Review;

Modified src/main.rs

@@ -90,8 +90,7 @@ fn main() {
.register(inject_repository_context(&config.repos.root, handler::Blob))
.register(inject_repository_context(&config.repos.root, handler::Pages))
.register(inject_repository_context(&config.repos.root, handler::Compare))
.register(inject_repository_context(&config.repos.root, handler::git::protocols::dumb::Refs))
.register(inject_repository_context(&config.repos.root, handler::git::protocols::dumb::Head))
.register(inject_repository_context(&config.repos.root, handler::git_smart_http::Refs))
.register(statics![
prefix: "./static/";
"./static/js/highlight.js",