Repositories

grarr

(mirrored on github)

Wim Looman <wim@nemo157.com>
e651eb 2 spaces to 4 spaces
Wim Looman committed at 2017-05-16 19:16:51

Modified src/commit_tree.rs

@@ -2,80 +2,80 @@ use std::vec::IntoIter;
use git2::{ self, Oid, Repository, Commit };
pub struct CommitTree<'repo> {
repo: &'repo Repository,
next_after: Option<Commit<'repo>>,
next: Option<Commit<'repo>>,
commits: IntoIter<Commit<'repo>>,
ignored: Vec<Oid>,
len: usize,
repo: &'repo Repository,
next_after: Option<Commit<'repo>>,
next: Option<Commit<'repo>>,
commits: IntoIter<Commit<'repo>>,
ignored: Vec<Oid>,
len: usize,
}
impl<'repo> CommitTree<'repo> {
pub fn new(repo: &'repo Repository, commit: &Commit<'repo>, limit: usize) -> Result<CommitTree<'repo>, git2::Error> {
let mut walker = try!(repo.revwalk());
try!(walker.push(commit.id()));
walker.simplify_first_parent();
let mut all_commits = walker.map(|id| id.and_then(|id| repo.find_commit(id)));
let commits = try!((&mut all_commits).take(limit).collect());
let next_after = all_commits.next().and_then(|c| c.ok());
Ok(CommitTree::create(repo, commits, next_after, Vec::new()))
}
pub fn is_empty(&self) -> bool {
self.next.is_none()
}
pub fn new(repo: &'repo Repository, commit: &Commit<'repo>, limit: usize) -> Result<CommitTree<'repo>, git2::Error> {
let mut walker = try!(repo.revwalk());
try!(walker.push(commit.id()));
walker.simplify_first_parent();
let mut all_commits = walker.map(|id| id.and_then(|id| repo.find_commit(id)));
let commits = try!((&mut all_commits).take(limit).collect());
let next_after = all_commits.next().and_then(|c| c.ok());
Ok(CommitTree::create(repo, commits, next_after, Vec::new()))
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.next.is_none()
}
pub fn next_after(&self) -> Option<&Commit<'repo>> {
self.next_after.as_ref()
}
pub fn len(&self) -> usize {
self.len
}
fn between(repo: &'repo Repository, first: &Commit<'repo>, ignored: Vec<Oid>) -> CommitTree<'repo> {
let mut walker = repo.revwalk().unwrap();
for parent in first.parent_ids().skip(1) {
walker.push(parent).unwrap();
pub fn next_after(&self) -> Option<&Commit<'repo>> {
self.next_after.as_ref()
}
for ignored in ignored.clone() {
walker.hide(ignored).unwrap();
fn between(repo: &'repo Repository, first: &Commit<'repo>, ignored: Vec<Oid>) -> CommitTree<'repo> {
let mut walker = repo.revwalk().unwrap();
for parent in first.parent_ids().skip(1) {
walker.push(parent).unwrap();
}
for ignored in ignored.clone() {
walker.hide(ignored).unwrap();
}
walker.simplify_first_parent();
let commits = walker.map(|id| id.and_then(|id| repo.find_commit(id)).unwrap()).collect();
CommitTree::create(repo, commits, None, ignored)
}
walker.simplify_first_parent();
let commits = walker.map(|id| id.and_then(|id| repo.find_commit(id)).unwrap()).collect();
CommitTree::create(repo, commits, None, ignored)
}
fn create(repo: &'repo Repository, commits: Vec<Commit<'repo>>, next_after: Option<Commit<'repo>>, ignored: Vec<Oid>) -> CommitTree<'repo> {
let len = commits.len();
let mut iter = commits.into_iter();
CommitTree {
repo: repo,
next_after: next_after,
next: iter.next(),
commits: iter,
ignored: ignored,
len: len,
fn create(repo: &'repo Repository, commits: Vec<Commit<'repo>>, next_after: Option<Commit<'repo>>, ignored: Vec<Oid>) -> CommitTree<'repo> {
let len = commits.len();
let mut iter = commits.into_iter();
CommitTree {
repo: repo,
next_after: next_after,
next: iter.next(),
commits: iter,
ignored: ignored,
len: len,
}
}
}
}
impl<'repo> Iterator for CommitTree<'repo> {
type Item = (Commit<'repo>, CommitTree<'repo>);
type Item = (Commit<'repo>, CommitTree<'repo>);
fn next(&mut self) -> Option<Self::Item> {
match self.next.take() {
Some(commit) => {
self.next = self.commits.next();
let mut ignored = self.ignored.clone();
if self.next.is_some() {
ignored.push(self.next.as_ref().unwrap().id());
fn next(&mut self) -> Option<Self::Item> {
match self.next.take() {
Some(commit) => {
self.next = self.commits.next();
let mut ignored = self.ignored.clone();
if self.next.is_some() {
ignored.push(self.next.as_ref().unwrap().id());
}
let sub = CommitTree::between(self.repo, &commit, ignored);
self.len = self.len - 1;
Some((commit, sub))
},
None => None,
}
let sub = CommitTree::between(self.repo, &commit, ignored);
self.len = self.len - 1;
Some((commit, sub))
},
None => None,
}
}
}

Modified src/config.rs

@@ -6,68 +6,68 @@ use toml;
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub repos: Repos,
pub avatars: Avatars,
pub repos: Repos,
pub avatars: Avatars,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Repos {
pub root: PathBuf,
pub root: PathBuf,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Avatars {
pub cache: Cache,
pub gravatar: Gravatar,
pub cache: Cache,
pub gravatar: Gravatar,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Cache {
pub enable: bool,
pub capacity: usize,
pub ttl_seconds: u64,
pub enable: bool,
pub capacity: usize,
pub ttl_seconds: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Gravatar {
pub enable: bool,
pub enable: bool,
}
error_chain! {
foreign_links {
Io(io::Error);
Toml(toml::de::Error);
}
foreign_links {
Io(io::Error);
Toml(toml::de::Error);
}
}
pub fn load<S: AsRef<ffi::OsStr> + ?Sized>(from: Option<&S>) -> Result<Config> {
let path = if let Some(path) = from.map(Path::new) {
path.canonicalize()?
} else {
env::current_dir()?.canonicalize()?
};
let path = if let Some(path) = from.map(Path::new) {
path.canonicalize()?
} else {
env::current_dir()?.canonicalize()?
};
if path.is_dir() {
Ok(Config {
repos: Repos {
root: path,
},
avatars: Avatars {
cache: Cache {
enable: true,
capacity: 100,
ttl_seconds: 60,
},
gravatar: Gravatar {
enable: true,
},
},
})
} else {
let mut text = String::new();
File::open(path)?.read_to_string(&mut text)?;
let mut config: Config = toml::from_str(text.as_str())?;
config.repos.root = config.repos.root.canonicalize()?;
Ok(config)
}
if path.is_dir() {
Ok(Config {
repos: Repos {
root: path,
},
avatars: Avatars {
cache: Cache {
enable: true,
capacity: 100,
ttl_seconds: 60,
},
gravatar: Gravatar {
enable: true,
},
},
})
} else {
let mut text = String::new();
File::open(path)?.read_to_string(&mut text)?;
let mut config: Config = toml::from_str(text.as_str())?;
config.repos.root = config.repos.root.canonicalize()?;
Ok(config)
}
}

Modified src/error.rs

@@ -7,37 +7,37 @@ use git2;
pub struct Error(Box<error::Error + Send + Sync>);
impl error::Error for Error {
fn description(&self) -> &str {
self.0.description()
}
fn description(&self) -> &str {
self.0.description()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<git2::Error> for Error {
fn from(e: git2::Error) -> Error {
Error(e.into())
}
fn from(e: git2::Error) -> Error {
Error(e.into())
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error(e.into())
}
fn from(e: io::Error) -> Error {
Error(e.into())
}
}
impl From<&'static str> for Error {
fn from(s: &'static str) -> Error {
Error(s.into())
}
fn from(s: &'static str) -> Error {
Error(s.into())
}
}
impl From<String> for Error {
fn from(s: String) -> Error {
Error(s.into())
}
fn from(s: String) -> Error {
Error(s.into())
}
}

Modified src/handler/about.rs

@@ -4,22 +4,22 @@ use super::base::*;
pub struct About;
impl Handler for About {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
Html {
render: render::About(),
etag: None,
req: req,
}.into()
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
Html {
render: render::About(),
etag: None,
req: req,
}.into()
}
}
impl Route for About {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/-/about".into()
}
fn route() -> Cow<'static, str> {
"/-/about".into()
}
}

Modified src/handler/avatar.rs

@@ -16,99 +16,99 @@ use super::utils::{ self, sha1, File, CacheMatches };
use reqwest;
pub struct Avatars {
enable_gravatar: bool,
cache: Option<Mutex<LruCache<String, File>>>,
options: Options,
enable_gravatar: bool,
cache: Option<Mutex<LruCache<String, File>>>,
options: Options,
}
impl Clone for Avatars {
fn clone(&self) -> Avatars {
Avatars::new(self.options.clone())
}
fn clone(&self) -> Avatars {
Avatars::new(self.options.clone())
}
}
#[derive(Clone)]
pub struct Options {
pub enable_gravatar: bool,
pub enable_cache: bool,
pub cache_capacity: usize,
pub cache_time_to_live: Duration,
pub enable_gravatar: bool,
pub enable_cache: bool,
pub cache_capacity: usize,
pub cache_time_to_live: Duration,
}
impl Avatars {
pub fn new(options: Options) -> Avatars {
Avatars {
enable_gravatar: options.enable_gravatar,
cache: if options.enable_cache {
Some(Mutex::new(LruCache::with_expiry_duration_and_capacity(options.cache_time_to_live, options.cache_capacity)))
} else {
None
},
options: options,
pub fn new(options: Options) -> Avatars {
Avatars {
enable_gravatar: options.enable_gravatar,
cache: if options.enable_cache {
Some(Mutex::new(LruCache::with_expiry_duration_and_capacity(options.cache_time_to_live, options.cache_capacity)))
} else {
None
},
options: options,
}
}
}
fn find_image(&self, user: &str) -> File {
self.find_cached(user)
.unwrap_or_else(||
self.cache(user,
self.find_gravatar(user)
fn find_image(&self, user: &str) -> File {
self.find_cached(user)
.unwrap_or_else(||
self.default())))
}
self.cache(user,
self.find_gravatar(user)
.unwrap_or_else(||
self.default())))
}
fn cache(&self, user: &str, image: File) -> File {
if let Some(ref cache) = self.cache {
cache.lock().unwrap().insert(user.to_owned(), image.clone());
fn cache(&self, user: &str, image: File) -> File {
if let Some(ref cache) = self.cache {
cache.lock().unwrap().insert(user.to_owned(), image.clone());
}
image
}
image
}
fn find_cached(&self, user: &str) -> Option<File> {
self.cache.as_ref().and_then(|cache| cache.lock().unwrap().get(&user.to_owned()).cloned())
}
fn find_cached(&self, user: &str) -> Option<File> {
self.cache.as_ref().and_then(|cache| cache.lock().unwrap().get(&user.to_owned()).cloned())
}
fn find_gravatar(&self, user: &str) -> Option<File> {
if self.enable_gravatar {
use std::io::Read;
let mut gravatar = Gravatar::new(user);
gravatar.size = Some(30);
gravatar.default = Some(gravatar::Default::Identicon);
let mut res = reqwest::get(&gravatar.image_url()).unwrap();
if res.status().is_success() {
let mut buf = Vec::new();
res.read_to_end(&mut buf).unwrap();
let mime = res.headers().get::<ContentType>().unwrap().0.clone();
let entity_tag = EntityTag::strong(sha1(&buf));
return Some(File(mime, entity_tag, buf.into()));
}
fn find_gravatar(&self, user: &str) -> Option<File> {
if self.enable_gravatar {
use std::io::Read;
let mut gravatar = Gravatar::new(user);
gravatar.size = Some(30);
gravatar.default = Some(gravatar::Default::Identicon);
let mut res = reqwest::get(&gravatar.image_url()).unwrap();
if res.status().is_success() {
let mut buf = Vec::new();
res.read_to_end(&mut buf).unwrap();
let mime = res.headers().get::<ContentType>().unwrap().0.clone();
let entity_tag = EntityTag::strong(sha1(&buf));
return Some(File(mime, entity_tag, buf.into()));
}
}
None
}
None
}
fn default(&self) -> File {
file!("../static/images/default_avatar.png")
}
fn default(&self) -> File {
file!("../static/images/default_avatar.png")
}
}
impl Handler for Avatars {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let user = req.extensions.get::<Router>().unwrap().find("user").unwrap();
let File(mime, entity_tag, buffer) = self.find_image(user);
let cache_headers = utils::cache_headers_for(&entity_tag, Duration::from_secs(86400));
if req.cache_matches(&entity_tag) {
return Ok(Response::with((status::NotModified, cache_headers)));
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let user = req.extensions.get::<Router>().unwrap().find("user").unwrap();
let File(mime, entity_tag, buffer) = self.find_image(user);
let cache_headers = utils::cache_headers_for(&entity_tag, Duration::from_secs(86400));
if req.cache_matches(&entity_tag) {
return Ok(Response::with((status::NotModified, cache_headers)));
}
Ok(Response::with((status::Ok, mime, cache_headers, buffer.as_ref())))
}
Ok(Response::with((status::Ok, mime, cache_headers, buffer.as_ref())))
}
}
impl Route for Avatars {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/-/avatars/:user".into()
}
fn route() -> Cow<'static, str> {
"/-/avatars/:user".into()
}
}

Modified src/handler/blob.rs

@@ -7,41 +7,41 @@ use git2;
pub struct Blob;
impl Handler for Blob {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let path = router.find("path").unwrap_or("");
let entry = try!(tree_entry::get_tree_entry(&context, path));
let referenced_commit = itry!(context.referenced_commit(), status::NotFound);
let id = referenced_commit.commit.id();
match entry.entry.kind() {
Some(git2::ObjectType::Blob) => {
Html {
render: RepositoryWrapper(&context, render::Blob(entry.entry.as_blob().unwrap(), &entry), Some(render::Tab::Files)),
etag: Some(EntityTag::weak(versioned_sha1!(&id))),
req: req,
}.into()
},
Some(git2::ObjectType::Tree) => {
let new_url = Url::parse(&*req.url.to_string().replace("blob", "tree")).unwrap();
Ok(Response::with((status::TemporaryRedirect, Redirect(new_url))))
},
other => {
Err(IronError::new(Error::from(format!("Can only handle blobs and trees, not {:?}", other)), status::InternalServerError))
},
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let path = router.find("path").unwrap_or("");
let entry = try!(tree_entry::get_tree_entry(&context, path));
let referenced_commit = itry!(context.referenced_commit(), status::NotFound);
let id = referenced_commit.commit.id();
match entry.entry.kind() {
Some(git2::ObjectType::Blob) => {
Html {
render: RepositoryWrapper(&context, render::Blob(entry.entry.as_blob().unwrap(), &entry), Some(render::Tab::Files)),
etag: Some(EntityTag::weak(versioned_sha1!(&id))),
req: req,
}.into()
},
Some(git2::ObjectType::Tree) => {
let new_url = Url::parse(&*req.url.to_string().replace("blob", "tree")).unwrap();
Ok(Response::with((status::TemporaryRedirect, Redirect(new_url))))
},
other => {
Err(IronError::new(Error::from(format!("Can only handle blobs and trees, not {:?}", other)), status::InternalServerError))
},
}
}
}
}
impl Route for Blob {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn routes() -> Vec<Cow<'static, str>> {
vec![
"/blob/:ref".into(),
"/blob/:ref/*path".into(),
]
}
fn routes() -> Vec<Cow<'static, str>> {
vec![
"/blob/:ref".into(),
"/blob/:ref/*path".into(),
]
}
}

Modified src/handler/commit.rs

@@ -4,23 +4,23 @@ use super::base::*;
pub struct Commit;
impl Handler for Commit {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let commit = itry!(context.commit(), status::NotFound);
Html {
render: RepositoryWrapper(&context, render::Commit(&context, &commit), Some(render::Tab::Commits)),
etag: Some(EntityTag::weak(versioned_sha1!())),
req: req,
}.into()
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let commit = itry!(context.commit(), status::NotFound);
Html {
render: RepositoryWrapper(&context, render::Commit(&context, &commit), Some(render::Tab::Commits)),
etag: Some(EntityTag::weak(versioned_sha1!())),
req: req,
}.into()
}
}
impl Route for Commit {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/commit/:ref".into()
}
fn route() -> Cow<'static, str> {
"/commit/:ref".into()
}
}

Modified src/handler/commits.rs

@@ -7,37 +7,37 @@ use commit_tree::CommitTree;
pub struct Commits;
impl Handler for Commits {
#[allow(deprecated)]
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let start_commit = req.url.clone().into_generic_url()
.query_pairs()
// .unwrap_or_default()
.into_iter()
.find(|&(ref key, _)| key == "start")
.map(|(_, ref id)| Oid::from_str(id)
.and_then(|id| context.repository.find_commit(id)));
let referenced_commit = itry!(context.referenced_commit(), status::InternalServerError);
let initial_commit = if let Some(Ok(ref commit)) = start_commit {
commit
} else {
&referenced_commit.commit
};
let commits = itry!(CommitTree::new(&context.repository, &initial_commit, 50), status::InternalServerError);
Html {
render: RepositoryWrapper(&context, render::Commits(&context, &referenced_commit, commits), Some(render::Tab::Commits)),
etag: Some(EntityTag::weak(versioned_sha1!(referenced_commit.commit.id().as_bytes()))),
req: req,
}.into()
}
#[allow(deprecated)]
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let start_commit = req.url.clone().into_generic_url()
.query_pairs()
// .unwrap_or_default()
.into_iter()
.find(|&(ref key, _)| key == "start")
.map(|(_, ref id)| Oid::from_str(id)
.and_then(|id| context.repository.find_commit(id)));
let referenced_commit = itry!(context.referenced_commit(), status::InternalServerError);
let initial_commit = if let Some(Ok(ref commit)) = start_commit {
commit
} else {
&referenced_commit.commit
};
let commits = itry!(CommitTree::new(&context.repository, &initial_commit, 50), status::InternalServerError);
Html {
render: RepositoryWrapper(&context, render::Commits(&context, &referenced_commit, commits), Some(render::Tab::Commits)),
etag: Some(EntityTag::weak(versioned_sha1!(referenced_commit.commit.id().as_bytes()))),
req: req,
}.into()
}
}
impl Route for Commits {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/commits/:ref".into()
}
fn route() -> Cow<'static, str> {
"/commits/:ref".into()
}
}

Modified src/handler/compare.rs

@@ -6,60 +6,60 @@ use referenced_commit::ReferencedCommit;
pub struct Compare;
impl Handler for Compare {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let new_commit = itry!(context.referenced_commit(), status::NotFound);
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let new_commit = itry!(context.referenced_commit(), status::NotFound);
// TODO: Read which old commit from url
let old_commit = itry!(
context.repository
.head().map_err(Error::from)
.and_then(|h|
h.resolve().map_err(Error::from)
.and_then(|h| h.target().ok_or(Error::from("<.< >.> <.<")))
.and_then(|id| context.repository.find_commit(id).map_err(Error::from))
.map(|commit| ReferencedCommit { commit: commit, reference: Some(h) })),
status::InternalServerError);
// TODO: Read which old commit from url
let old_commit = itry!(
context.repository
.head().map_err(Error::from)
.and_then(|h|
h.resolve().map_err(Error::from)
.and_then(|h| h.target().ok_or(Error::from("<.< >.> <.<")))
.and_then(|id| context.repository.find_commit(id).map_err(Error::from))
.map(|commit| ReferencedCommit { commit: commit, reference: Some(h) })),
status::InternalServerError);
let base = itry!(
context.repository
.merge_base(old_commit.commit.id(), new_commit.commit.id())
.and_then(|id| context.repository.find_commit(id)),
status::InternalServerError);
let base = itry!(
context.repository
.merge_base(old_commit.commit.id(), new_commit.commit.id())
.and_then(|id| context.repository.find_commit(id)),
status::InternalServerError);
let commits = itry!(
context.repository
.revwalk()
.and_then(|mut walker| walker.push(new_commit.commit.id()).map(|_| walker))
.and_then(|mut walker| walker.hide(base.id()).map(|_| walker))
.and_then(|walker|
walker.map(|id| id.and_then(|id| context.repository.find_commit(id)))
.collect()),
status::InternalServerError);
let commits = itry!(
context.repository
.revwalk()
.and_then(|mut walker| walker.push(new_commit.commit.id()).map(|_| walker))
.and_then(|mut walker| walker.hide(base.id()).map(|_| walker))
.and_then(|walker|
walker.map(|id| id.and_then(|id| context.repository.find_commit(id)))
.collect()),
status::InternalServerError);
Html {
render: RepositoryWrapper(&context, render::Compare {
context: &context,
new: new_commit,
old: old_commit,
base: base,
commits: commits,
}, None),
etag: Some(EntityTag::weak(versioned_sha1!())),
req: req,
}.into()
}
Html {
render: RepositoryWrapper(&context, render::Compare {
context: &context,
new: new_commit,
old: old_commit,
base: base,
commits: commits,
}, None),
etag: Some(EntityTag::weak(versioned_sha1!())),
req: req,
}.into()
}
}
impl Route for Compare {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn routes() -> Vec<Cow<'static, str>> {
vec![
"/compare/:ref".into(),
// "/compare/:new_ref...:old_ref".into(),
]
}
fn routes() -> Vec<Cow<'static, str>> {
vec![
"/compare/:ref".into(),
// "/compare/:new_ref...:old_ref".into(),
]
}
}

Modified src/handler/error.rs

@@ -3,22 +3,22 @@ use super::base::*;
use iron::middleware::AfterMiddleware;
macro_rules! error_handler {
($name:ident) => {
pub struct $name;
impl AfterMiddleware for $name {
fn catch(&self, req: &mut Request, err: IronError) -> IronResult<Response> {
if err.response.status == Some(status::$name) {
Ok(Response::with((status::$name, Html {
render: render::error::$name(&*err.error),
etag: None,
req: req,
})))
} else {
Err(err)
($name:ident) => {
pub struct $name;
impl AfterMiddleware for $name {
fn catch(&self, req: &mut Request, err: IronError) -> IronResult<Response> {
if err.response.status == Some(status::$name) {
Ok(Response::with((status::$name, Html {
render: render::error::$name(&*err.error),
etag: None,
req: req,
})))
} else {
Err(err)
}
}
}
}
}
}
}
error_handler!(BadRequest);

Modified src/handler/git_smart_http/refs.rs

@@ -7,70 +7,70 @@ use git2;
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))
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"));
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")));
// 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)
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")));
// 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)
}
#[allow(deprecated)]
fn find_service(req: &Request) -> Option<String> {
req.url.clone().into_generic_url()
.query_pairs()
.into_iter()
.find(|&(ref key, _)| key == "service")
.map(|(_, ref id)| id.to_string())
req.url.clone().into_generic_url()
.query_pairs()
.into_iter()
.find(|&(ref key, _)| key == "service")
.map(|(_, ref id)| id.to_string())
}
impl Handler for Refs {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), 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.")))
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), 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 method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/info/refs".into()
}
fn route() -> Cow<'static, str> {
"/info/refs".into()
}
}

Modified src/handler/git_smart_http/upload_pack.rs

@@ -20,272 +20,272 @@ pub struct UploadPack;
#[derive(Debug, Eq, PartialEq)]
enum Capability {
SideBand,
SideBand64K,
Unknown(String),
SideBand,
SideBand64K,
Unknown(String),
}
#[derive(Debug)]
struct UploadPackRequest {
wants: Vec<Oid>,
haves: Vec<Oid>,
capabilities: Vec<Capability>,
done: bool,
context: RepositoryContext,
wants: Vec<Oid>,
haves: Vec<Oid>,
capabilities: Vec<Capability>,
done: bool,
context: RepositoryContext,
}
struct UploadPackContext<'a> {
repository: &'a Repository,
refs: HashSet<Oid>,
repository: &'a Repository,
refs: HashSet<Oid>,
}
enum UploadPackResponse<'a> {
Pack(Revwalk<'a>),
/* Continue( ... ), */
Pack(Revwalk<'a>),
/* Continue( ... ), */
}
fn parse_request(req: &mut Request, context: RepositoryContext) -> Result<UploadPackRequest, Error> {
let mut request = UploadPackRequest {
wants: Vec::new(),
haves: Vec::new(),
capabilities: Vec::new(),
done: false,
context: context,
};
let encoding = if let Some(&ContentEncoding(ref encodings)) = req.headers.get() {
if encodings.len() != 1 {
return Err(Error::from("Can't handle multiple encodings"));
}
encodings[0].clone()
} 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>,
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 }
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(' ').map(Capability::from));
},
"have" => {
let end = line.find(|c| c == '\n' || c == '\0').unwrap_or(line.len());
request.haves.push(try!(line[5..end].parse()));
},
"done" => {
request.done = true;
break;
},
_ => return Err(Error::from(format!("Unexpected pkt-line {}", line))),
let mut request = UploadPackRequest {
wants: Vec::new(),
haves: Vec::new(),
capabilities: Vec::new(),
done: false,
context: context,
};
let encoding = if let Some(&ContentEncoding(ref encodings)) = req.headers.get() {
if encodings.len() != 1 {
return Err(Error::from("Can't handle multiple encodings"));
}
encodings[0].clone()
} 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>,
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 }
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(' ').map(Capability::from));
},
"have" => {
let end = line.find(|c| c == '\n' || c == '\0').unwrap_or(line.len());
request.haves.push(try!(line[5..end].parse()));
},
"done" => {
request.done = true;
break;
},
_ => return Err(Error::from(format!("Unexpected pkt-line {}", line))),
}
}
}
Ok(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 })
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"));
}
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)));
for id in &request.wants {
if !context.refs.contains(&id) {
return Err(Error::from(format!("want missing from refs {}", id)));
}
}
}
Ok(())
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);
for descendant in descendants {
if try!(repository.graph_descendant_of(descendant, commit)) {
return Ok(true);
}
}
}
Ok(false)
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);
for ancestor in ancestors {
if try!(repository.graph_descendant_of(commit, ancestor)) {
return Ok(true);
}
}
}
Ok(false)
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())) {
return Ok(false);
for descendant in descendants {
if !try!(graph_descendant_of_any(repository, descendant, ancestors.clone())) {
return Ok(false);
}
}
}
Ok(true)
Ok(true)
}
#[allow(collapsible_if)]
fn compute_response<'a>(context: &'a UploadPackContext, request: &'a UploadPackRequest) -> Result<UploadPackResponse<'a>, Error> {
let mut common = HashSet::<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.insert(id);
}
}
}
if request.done || try!(is_closed(&context.repository, request.wants.iter().cloned(), common.iter().cloned())) {
let mut walker = try!(context.repository.revwalk());
for id in request.wants.iter().cloned() {
try!(walker.push(id));
let mut common = HashSet::<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.insert(id);
}
}
}
for id in common.iter().cloned() {
try!(walker.hide(id));
if request.done || try!(is_closed(&context.repository, request.wants.iter().cloned(), common.iter().cloned())) {
let mut walker = try!(context.repository.revwalk());
for id in request.wants.iter().cloned() {
try!(walker.push(id));
}
for id in common.iter().cloned() {
try!(walker.hide(id));
}
Ok(UploadPackResponse::Pack(walker))
} else {
Err(Error::from("TODO: ......"))
}
Ok(UploadPackResponse::Pack(walker))
} else {
Err(Error::from("TODO: ......"))
}
}
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());
{
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 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");
}
let output = Rc::new(Mutex::new(output));
let mut builder = try!(repository.packbuilder());
{
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 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");
}
}
}
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
}
}
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
if let Some(err) = error {
try!(err);
}
}));
if let Some(err) = error {
try!(err);
}
try!(output.lock().unwrap().close());
Ok(())
try!(output.lock().unwrap().close());
Ok(())
}
impl WriteBody for UploadPackRequest {
fn write_body(&mut self, mut res: &mut Write) -> io::Result<()> {
res.write_pkt_line("NAK")?;
let limit = if self.capabilities.contains(&Capability::SideBand64K) {
Some(65520)
} else if self.capabilities.contains(&Capability::SideBand) {
Some(1000)
} else {
None
};
let output = Multiplexer::new(res, limit);
println!( "Preparing context for {}", self.context.path);
let context2 = prepare_context(&self.context).unwrap();
println!( "Prepared context for {}", self.context.path);
validate_request(&context2, self).unwrap();
println!( "Validated request for {}", self.context.path);
let result = compute_response(&context2, self).unwrap();
println!( "Computed response for {}", self.context.path);
match result {
UploadPackResponse::Pack(revwalk) => {
build_pack(&self.context.repository, revwalk, output).unwrap();
},
fn write_body(&mut self, mut res: &mut Write) -> io::Result<()> {
res.write_pkt_line("NAK")?;
let limit = if self.capabilities.contains(&Capability::SideBand64K) {
Some(65520)
} else if self.capabilities.contains(&Capability::SideBand) {
Some(1000)
} else {
None
};
let output = Multiplexer::new(res, limit);
println!( "Preparing context for {}", self.context.path);
let context2 = prepare_context(&self.context).unwrap();
println!( "Prepared context for {}", self.context.path);
validate_request(&context2, self).unwrap();
println!( "Validated request for {}", self.context.path);
let result = compute_response(&context2, self).unwrap();
println!( "Computed response for {}", self.context.path);
match result {
UploadPackResponse::Pack(revwalk) => {
build_pack(&self.context.repository, revwalk, output).unwrap();
},
}
Ok(())
}
Ok(())
}
}
impl Handler for UploadPack {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let no_cache = (
Header(CacheControl(vec![
CacheDirective::NoCache,
CacheDirective::MaxAge(0),
CacheDirective::MustRevalidate,
])),
Header(Expires(HttpDate(time::empty_tm()))),
Header(Pragma::NoCache),
Header(Vary::Items(vec![
UniCase("accept-encoding".to_owned()),
])),
);
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>)))
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let no_cache = (
Header(CacheControl(vec![
CacheDirective::NoCache,
CacheDirective::MaxAge(0),
CacheDirective::MustRevalidate,
])),
Header(Expires(HttpDate(time::empty_tm()))),
Header(Pragma::NoCache),
Header(Vary::Items(vec![
UniCase("accept-encoding".to_owned()),
])),
);
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>)))
}
}
impl Route for UploadPack {
fn method() -> Method {
Method::Post
}
fn method() -> Method {
Method::Post
}
fn route() -> Cow<'static, str> {
"/git-upload-pack".into()
}
fn route() -> Cow<'static, str> {
"/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,
s => Capability::Unknown(s.to_owned()),
fn from(s: S) -> Capability {
match s.as_ref() {
"side-band" => Capability::SideBand,
"side-band-64k" => Capability::SideBand64K,
s => Capability::Unknown(s.to_owned()),
}
}
}
}

Modified src/handler/git_smart_http/utils.rs

@@ -3,144 +3,144 @@ use std::str;
use std::cmp;
pub struct Multiplexer<'a> {
stream: &'a mut io::Write,
limit: Option<usize>,
current_band: u8,
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")
}
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 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()
}
}
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<()>;
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(());
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")
}
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."));
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())
}
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")
}
fn write_pkt_line_flush(&mut self) -> io::Result<()> {
self.write_all(b"0000")
}
}
pub struct PktLines<'a> {
source: &'a mut io::Read,
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;
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"));
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)
}
}
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 }
}
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)),
},
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/handler/html.rs

@@ -10,36 +10,36 @@ use render::Wrapper;
use settings::Settings;
pub struct Html<'a, 'b: 'a, 'c: 'b, R: Render> {
pub req: &'a Request<'b, 'c>,
pub render: R,
pub etag: Option<EntityTag>,
pub req: &'a Request<'b, 'c>,
pub render: R,
pub etag: Option<EntityTag>,
}
impl<'a, 'b, 'c, R: Render> Into<Response> for Html<'a, 'b, 'c, R> {
fn into(self) -> Response {
Response::with((status::Ok, self))
}
fn into(self) -> Response {
Response::with((status::Ok, self))
}
}
impl<'a, 'b, 'c, R: Render> Into<IronResult<Response>> for Html<'a, 'b, 'c, R> {
fn into(self) -> IronResult<Response> {
Ok(Response::with((status::Ok, self)))
}
fn into(self) -> IronResult<Response> {
Ok(Response::with((status::Ok, self)))
}
}
impl<'a, 'b, 'c, R: Render> Modifier<Response> for Html<'a, 'b, 'c, R> {
fn modify(self, response: &mut Response) {
if let Some(ref etag) = self.etag {
let cache_headers = utils::cache_headers_for(&etag, Duration::from_secs(0));
cache_headers.modify(response);
if self.req.cache_matches(etag) {
status::NotModified.modify(response);
return;
}
fn modify(self, response: &mut Response) {
if let Some(ref etag) = self.etag {
let cache_headers = utils::cache_headers_for(&etag, Duration::from_secs(0));
cache_headers.modify(response);
if self.req.cache_matches(etag) {
status::NotModified.modify(response);
return;
}
}
let settings = self.req.extensions.get::<Settings>().cloned().unwrap_or_default();
let buffer = Wrapper(self.render, settings).render();
let mime = mime!(Text/Html; Charset=Utf8);
(mime, buffer).modify(response)
}
let settings = self.req.extensions.get::<Settings>().cloned().unwrap_or_default();
let buffer = Wrapper(self.render, settings).render();
let mime = mime!(Text/Html; Charset=Utf8);
(mime, buffer).modify(response)
}
}

Modified src/handler/pages.rs

@@ -9,67 +9,67 @@ use git2;
pub struct Pages;
fn get_response(context: &RepositoryContext, path: &str) -> Result<Response, ()> {
let entry = try!(tree_entry::get_tree_entry(&context, &path).map_err(|_| ())).entry;
let entry = try!(tree_entry::get_tree_entry(&context, &path).map_err(|_| ())).entry;
match entry.kind() {
Some(git2::ObjectType::Blob) => {
let blob = entry.as_blob().unwrap();
Ok(Response::with((status::Ok, utils::blob_mime(blob, &path), blob.content())))
},
_ => Err(()),
}
match entry.kind() {
Some(git2::ObjectType::Blob) => {
let blob = entry.as_blob().unwrap();
Ok(Response::with((status::Ok, utils::blob_mime(blob, &path), blob.content())))
},
_ => Err(()),
}
}
fn get_markdown_response(context: &RepositoryContext, path: &str) -> Result<Response, ()> {
if !path.ends_with(".html") {
return Err(());
}
if !path.ends_with(".html") {
return Err(());
}
let md_path = path[..path.len()-5].to_owned() + ".md";
let entry = try!(tree_entry::get_tree_entry(&context, &md_path).map_err(|_| ())).entry;
let md_path = path[..path.len()-5].to_owned() + ".md";
let entry = try!(tree_entry::get_tree_entry(&context, &md_path).map_err(|_| ())).entry;
match entry.kind() {
Some(git2::ObjectType::Blob) => {
let blob = entry.as_blob().unwrap();
let content = try!(str::from_utf8(blob.content()).map_err(|_| ()));
let mut buffer = String::with_capacity(content.len() * 3 / 2);
let parser = pulldown_cmark::Parser::new(&content);
pulldown_cmark::html::push_html(&mut buffer, parser);
Ok(Response::with((status::Ok, mime!(Text/Html), buffer)))
},
_ => Err(()),
}
match entry.kind() {
Some(git2::ObjectType::Blob) => {
let blob = entry.as_blob().unwrap();
let content = try!(str::from_utf8(blob.content()).map_err(|_| ()));
let mut buffer = String::with_capacity(content.len() * 3 / 2);
let parser = pulldown_cmark::Parser::new(&content);
pulldown_cmark::html::push_html(&mut buffer, parser);
Ok(Response::with((status::Ok, mime!(Text/Html), buffer)))
},
_ => Err(()),
}
}
impl Handler for Pages {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
{
let mut context = itry!(req.extensions.get_mut::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
context.reference = Some("gh-pages".to_owned());
}
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
fn handle(&self, req: &mut Request) -> IronResult<Response> {
{
let mut context = itry!(req.extensions.get_mut::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
context.reference = Some("gh-pages".to_owned());
}
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let mut path = router.find("path").unwrap_or("").to_owned();
if path == "" || path.ends_with('/') {
path = path + "index.html";
}
let mut path = router.find("path").unwrap_or("").to_owned();
if path == "" || path.ends_with('/') {
path = path + "index.html";
}
get_response(context, &*path)
.or_else(|_| get_markdown_response(context, &*path))
.or_else(|_| Err(IronError::new(Error::from("Not found"), status::NotFound)))
}
get_response(context, &*path)
.or_else(|_| get_markdown_response(context, &*path))
.or_else(|_| Err(IronError::new(Error::from("Not found"), status::NotFound)))
}
}
impl Route for Pages {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn routes() -> Vec<Cow<'static, str>> {
vec![
"/pages/".into(),
"/pages/*path".into(),
]
}
fn routes() -> Vec<Cow<'static, str>> {
vec![
"/pages/".into(),
"/pages/*path".into(),
]
}
}

Modified src/handler/register.rs

@@ -4,14 +4,14 @@ use iron::middleware::Handler;
use super::route::Route;
pub trait Register {
fn register<H: Route + Handler + Clone>(self, H) -> Self;
fn register<H: Route + Handler + Clone>(self, H) -> Self;
}
impl<'a> Register for &'a mut Router {
fn register<H: Route + Handler + Clone>(self, handler: H) -> &'a mut Router{
for route in H::routes() {
self.route(H::method(), route.clone(), handler.clone(), route);
fn register<H: Route + Handler + Clone>(self, handler: H) -> &'a mut Router{
for route in H::routes() {
self.route(H::method(), route.clone(), handler.clone(), route);
}
self
}
self
}
}

Modified src/handler/repositories.rs

@@ -7,53 +7,53 @@ use std::path::{ Path, PathBuf };
#[derive(Clone)]
pub struct Repositories {
pub root: PathBuf,
pub root: PathBuf,
}
fn get_repo(root: &Path, dir: DirEntry) -> Option<(String, git2::Repository)> {
let path = dir.path();
let relative_dir = expect!(path.strip_prefix(root).ok());
let relative = expect!(relative_dir.to_str()).to_owned();
let repo = expect!(git2::Repository::open(&path).ok());
Some((relative, repo))
let path = dir.path();
let relative_dir = expect!(path.strip_prefix(root).ok());
let relative = expect!(relative_dir.to_str()).to_owned();
let repo = expect!(git2::Repository::open(&path).ok());
Some((relative, repo))
}
fn get_repos(root: &Path) -> Vec<(String, git2::Repository)> {
let mut repos = Vec::new();
let mut it = WalkDir::new(root).into_iter();
loop {
let entry = match it.next() {
None => break,
Some(Err(_)) => continue,
Some(Ok(entry)) => entry,
};
if entry.file_type().is_dir() {
if let Some(repo) = get_repo(root, entry) {
repos.push(repo);
it.skip_current_dir();
}
let mut repos = Vec::new();
let mut it = WalkDir::new(root).into_iter();
loop {
let entry = match it.next() {
None => break,
Some(Err(_)) => continue,
Some(Ok(entry)) => entry,
};
if entry.file_type().is_dir() {
if let Some(repo) = get_repo(root, entry) {
repos.push(repo);
it.skip_current_dir();
}
}
}
}
repos
repos
}
impl Handler for Repositories {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let repos = get_repos(&self.root);
Html {
render: render::Repositories(repos),
etag: None,
req: req,
}.into()
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let repos = get_repos(&self.root);
Html {
render: render::Repositories(repos),
etag: None,
req: req,
}.into()
}
}
impl Route for Repositories {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/".into()
}
fn route() -> Cow<'static, str> {
"/".into()
}
}

Modified src/handler/repository.rs

@@ -4,25 +4,25 @@ use super::base::*;
pub struct Repository;
impl Handler for Repository {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let head_ref = itry!(context.repository.head(), status::InternalServerError);
let resolved_head = itry!(head_ref.resolve(), status::InternalServerError);
let head_id = itry!(resolved_head.target().ok_or(Error::from("Couldn't resolve head")), status::InternalServerError);
Html {
render: RepositoryWrapper(&context, render::Repository(&context.repository, &head_id), Some(render::Tab::Overview)),
etag: Some(EntityTag::weak(versioned_sha1!(head_id.as_bytes()))),
req: req,
}.into()
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let head_ref = itry!(context.repository.head(), status::InternalServerError);
let resolved_head = itry!(head_ref.resolve(), status::InternalServerError);
let head_id = itry!(resolved_head.target().ok_or(Error::from("Couldn't resolve head")), status::InternalServerError);
Html {
render: RepositoryWrapper(&context, render::Repository(&context.repository, &head_id), Some(render::Tab::Overview)),
etag: Some(EntityTag::weak(versioned_sha1!(head_id.as_bytes()))),
req: req,
}.into()
}
}
impl Route for Repository {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"".into()
}
fn route() -> Cow<'static, str> {
"".into()
}
}

Modified src/handler/review.rs

@@ -7,27 +7,27 @@ use git_appraise::AppraisedRepository;
pub struct Review;
impl Handler for Review {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let commit = itry!(router.find("ref").ok_or(Error::from("missing path component")), status::InternalServerError);
let id = itry!(Oid::from_str(commit), status::BadRequest);
let review = itry!(context.repository.review_for(id), status::NotFound);
let root = format!("/{}", context.path);
Html {
render: RepositoryWrapper(&context, render::Review(&root, &review), Some(render::Tab::Reviews)),
etag: None,
req: req,
}.into()
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let commit = itry!(router.find("ref").ok_or(Error::from("missing path component")), status::InternalServerError);
let id = itry!(Oid::from_str(commit), status::BadRequest);
let review = itry!(context.repository.review_for(id), status::NotFound);
let root = format!("/{}", context.path);
Html {
render: RepositoryWrapper(&context, render::Review(&root, &review), Some(render::Tab::Reviews)),
etag: None,
req: req,
}.into()
}
}
impl Route for Review {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/review/:ref".into()
}
fn route() -> Cow<'static, str> {
"/review/:ref".into()
}
}

Modified src/handler/reviews.rs

@@ -5,26 +5,26 @@ use git_appraise::AppraisedRepository;
pub struct Reviews;
impl Handler for Reviews {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let mut reviews: Vec<_> = context.repository.all_reviews().and_then(|revs| revs.collect()).ok().unwrap_or_default();
reviews.sort_by(|a, b| a.request().timestamp().cmp(&b.request().timestamp()));
reviews.reverse();
let root = format!("/{}", context.path);
Html {
render: RepositoryWrapper(&context, render::Reviews(&root, &reviews), Some(render::Tab::Reviews)),
etag: None,
req: req,
}.into()
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let mut reviews: Vec<_> = context.repository.all_reviews().and_then(|revs| revs.collect()).ok().unwrap_or_default();
reviews.sort_by(|a, b| a.request().timestamp().cmp(&b.request().timestamp()));
reviews.reverse();
let root = format!("/{}", context.path);
Html {
render: RepositoryWrapper(&context, render::Reviews(&root, &reviews), Some(render::Tab::Reviews)),
etag: None,
req: req,
}.into()
}
}
impl Route for Reviews {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/reviews".into()
}
fn route() -> Cow<'static, str> {
"/reviews".into()
}
}

Modified src/handler/route.rs

@@ -2,13 +2,13 @@ use std::borrow::Cow;
use iron::method::Method;
pub trait Route {
fn method() -> Method;
fn method() -> Method;
fn route() -> Cow<'static, str> {
"".into()
}
fn route() -> Cow<'static, str> {
"".into()
}
fn routes() -> Vec<Cow<'static, str>> {
vec![Self::route()]
}
fn routes() -> Vec<Cow<'static, str>> {
vec![Self::route()]
}
}

Modified src/handler/settings.rs

@@ -8,56 +8,56 @@ pub struct Settings;
pub struct SettingsPost;
impl Handler for Settings {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let settings = itry!(req.extensions.get::<settings::Settings>().ok_or(Error::from("missing extension")), status::InternalServerError);
Html {
render: render::Settings(settings),
etag: None,
req: req,
}.into()
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let settings = itry!(req.extensions.get::<settings::Settings>().ok_or(Error::from("missing extension")), status::InternalServerError);
Html {
render: render::Settings(settings),
etag: None,
req: req,
}.into()
}
}
impl Route for Settings {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/-/settings".into()
}
fn route() -> Cow<'static, str> {
"/-/settings".into()
}
}
impl Handler for SettingsPost {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
/*
let settings = {
let map: Map = itry!(req.get::<Params>(), status::InternalServerError);
let settings = itry!(req.extensions.get::<settings::Settings>().ok_or(Error::from("missing extension")), status::InternalServerError);
settings.with(
map.iter().filter_map(|(key, value)| match *value {
Value::String(ref value) => Some((&**key, &**value)),
_ => None,
}))
};
println!("{:?}", settings);
let html = Html {
render: &render::Settings(&settings),
etag: None,
req: req,
};
Ok(Response::with((status::SeeOther, Redirect(req.url.clone()), html, &settings)))
*/
Ok(Response::with((status::SeeOther, Redirect(req.url.clone()))))
}
fn handle(&self, req: &mut Request) -> IronResult<Response> {
/*
let settings = {
let map: Map = itry!(req.get::<Params>(), status::InternalServerError);
let settings = itry!(req.extensions.get::<settings::Settings>().ok_or(Error::from("missing extension")), status::InternalServerError);
settings.with(
map.iter().filter_map(|(key, value)| match *value {
Value::String(ref value) => Some((&**key, &**value)),
_ => None,
}))
};
println!("{:?}", settings);
let html = Html {
render: &render::Settings(&settings),
etag: None,
req: req,
};
Ok(Response::with((status::SeeOther, Redirect(req.url.clone()), html, &settings)))
*/
Ok(Response::with((status::SeeOther, Redirect(req.url.clone()))))
}
}
impl Route for SettingsPost {
fn method() -> Method {
Method::Post
}
fn method() -> Method {
Method::Post
}
fn route() -> Cow<'static, str> {
"/-/settings".into()
}
fn route() -> Cow<'static, str> {
"/-/settings".into()
}
}

Modified src/handler/statics.rs

@@ -16,56 +16,56 @@ use super::utils::{ self, File, CacheMatches };
#[derive(Debug)]
pub struct Static {
files: Mutex<HashMap<PathBuf, File>>,
files: Mutex<HashMap<PathBuf, File>>,
}
impl Clone for Static {
fn clone(&self) -> Static {
Static::new(self.files.lock().unwrap().clone())
}
fn clone(&self) -> Static {
Static::new(self.files.lock().unwrap().clone())
}
}
#[macro_export]
macro_rules! statics {
(prefix: $prefix:expr; $($x:expr),*) => (
$crate::handler::statics::Static::new(vec![
$(($x.trim_left_matches($prefix).into(), file!($x))),*
].into_iter().collect())
);
(prefix: $prefix:expr; $($x:expr,)*) => (statics![prefix: $prefix; $($x),*]);
(prefix: $prefix:expr; $($x:expr),*) => (
$crate::handler::statics::Static::new(vec![
$(($x.trim_left_matches($prefix).into(), file!($x))),*
].into_iter().collect())
);
(prefix: $prefix:expr; $($x:expr,)*) => (statics![prefix: $prefix; $($x),*]);
}
impl Static {
pub fn new(files: HashMap<PathBuf, File>) -> Static {
Static {
files: Mutex::new(files),
pub fn new(files: HashMap<PathBuf, File>) -> Static {
Static {
files: Mutex::new(files),
}
}
}
fn find_file(&self, path: &Path) -> Option<File> {
self.files.lock().unwrap().get(path).cloned()
}
fn find_file(&self, path: &Path) -> Option<File> {
self.files.lock().unwrap().get(path).cloned()
}
}
impl Handler for Static {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let path = Path::new(itry!(router.find("path").ok_or(Error::from("missing path component")), status::InternalServerError));
let File(mime, entity_tag, buffer) = itry!(self.find_file(path).ok_or(Error::from("Static file not found")), status::NotFound);
let cache_headers = utils::cache_headers_for(&entity_tag, Duration::from_secs(86400));
if req.cache_matches(&entity_tag) {
return Ok(Response::with((status::NotModified, cache_headers)));
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let path = Path::new(itry!(router.find("path").ok_or(Error::from("missing path component")), status::InternalServerError));
let File(mime, entity_tag, buffer) = itry!(self.find_file(path).ok_or(Error::from("Static file not found")), status::NotFound);
let cache_headers = utils::cache_headers_for(&entity_tag, Duration::from_secs(86400));
if req.cache_matches(&entity_tag) {
return Ok(Response::with((status::NotModified, cache_headers)));
}
Ok(Response::with((status::Ok, mime, cache_headers, &*buffer)))
}
Ok(Response::with((status::Ok, mime, cache_headers, &*buffer)))
}
}
impl Route for Static {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn route() -> Cow<'static, str> {
"/-/static/*path".into()
}
fn route() -> Cow<'static, str> {
"/-/static/*path".into()
}
}

Modified src/handler/tree.rs

@@ -7,38 +7,38 @@ use git2;
pub struct Tree;
impl Handler for Tree {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let entry = try!(tree_entry::get_tree_entry(&context, router.find("path").unwrap_or("")));
match entry.entry.kind() {
Some(git2::ObjectType::Tree) => {
Html {
render: RepositoryWrapper(&context, render::Tree(entry.entry.as_tree().unwrap(), &entry), Some(render::Tab::Files)),
etag: Some(EntityTag::weak(versioned_sha1!(&entry.commit.commit.id()))),
req: req,
}.into()
},
Some(git2::ObjectType::Blob) => {
let new_url = Url::parse(&*req.url.to_string().replace("tree", "blob")).unwrap();
Ok(Response::with((status::TemporaryRedirect, Redirect(new_url))))
},
other => {
Err(IronError::new(Error::from(format!("Can only handle blobs and trees, not {:?}", other)), status::InternalServerError))
},
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let router = itry!(req.extensions.get::<Router>().ok_or(Error::from("missing extension")), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::from("missing extension")), status::InternalServerError);
let entry = try!(tree_entry::get_tree_entry(&context, router.find("path").unwrap_or("")));
match entry.entry.kind() {
Some(git2::ObjectType::Tree) => {
Html {
render: RepositoryWrapper(&context, render::Tree(entry.entry.as_tree().unwrap(), &entry), Some(render::Tab::Files)),
etag: Some(EntityTag::weak(versioned_sha1!(&entry.commit.commit.id()))),
req: req,
}.into()
},
Some(git2::ObjectType::Blob) => {
let new_url = Url::parse(&*req.url.to_string().replace("tree", "blob")).unwrap();
Ok(Response::with((status::TemporaryRedirect, Redirect(new_url))))
},
other => {
Err(IronError::new(Error::from(format!("Can only handle blobs and trees, not {:?}", other)), status::InternalServerError))
},
}
}
}
}
impl Route for Tree {
fn method() -> Method {
Method::Get
}
fn method() -> Method {
Method::Get
}
fn routes() -> Vec<Cow<'static, str>> {
vec![
"/tree/:ref".into(),
"/tree/:ref/*path".into(),
]
}
fn routes() -> Vec<Cow<'static, str>> {
vec![
"/tree/:ref".into(),
"/tree/:ref/*path".into(),
]
}
}

Modified src/handler/utils.rs

@@ -10,142 +10,142 @@ use git2;
#[macro_export]
macro_rules! file {
($x:expr) => ({
let bytes = include_bytes!($x);
$crate::handler::utils::File(
$crate::handler::utils::mime($x),
::iron::headers::EntityTag::strong(sha1!(bytes as &[u8])),
::std::borrow::Cow::Borrowed(bytes))
});
($x:expr) => ({
let bytes = include_bytes!($x);
$crate::handler::utils::File(
$crate::handler::utils::mime($x),
::iron::headers::EntityTag::strong(sha1!(bytes as &[u8])),
::std::borrow::Cow::Borrowed(bytes))
});
}
#[macro_export]
macro_rules! sha1 {
($($x:expr),*) => ({
use ::crypto::digest::Digest;
let mut hasher = ::crypto::sha1::Sha1::new();
$(hasher.input(::std::convert::AsRef::<[u8]>::as_ref($x));)*
hasher.result_str()
});
($($x:expr),*) => ({
use ::crypto::digest::Digest;
let mut hasher = ::crypto::sha1::Sha1::new();
$(hasher.input(::std::convert::AsRef::<[u8]>::as_ref($x));)*
hasher.result_str()
});
}
#[macro_export]
macro_rules! versioned_sha1 {
() => ({
sha1!(env!("CARGO_PKG_VERSION"), ::REVISION.unwrap_or_default())
});
($($x:expr),+) => ({
sha1!(env!("CARGO_PKG_VERSION"), ::REVISION.unwrap_or_default(), $($x),*)
});
() => ({
sha1!(env!("CARGO_PKG_VERSION"), ::REVISION.unwrap_or_default())
});
($($x:expr),+) => ({
sha1!(env!("CARGO_PKG_VERSION"), ::REVISION.unwrap_or_default(), $($x),*)
});
}
#[derive(Clone)]
pub struct File(pub Mime, pub EntityTag, pub Cow<'static, [u8]>);
impl fmt::Debug for File {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
write!(w, "File{:?}", (&self.0, &self.1, &self.2.len()))
}
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
write!(w, "File{:?}", (&self.0, &self.1, &self.2.len()))
}
}
pub fn mime(path: &str) -> Mime {
match Path::new(path).extension().and_then(|s| s.to_str()) {
Some("css") => mime!(Text/Css),
Some("html") => mime!(Text/Html),
Some("js") => mime!(Text/Javascript),
None | Some(_) => mime!(Application/("octet-stream")),
}
match Path::new(path).extension().and_then(|s| s.to_str()) {
Some("css") => mime!(Text/Css),
Some("html") => mime!(Text/Html),
Some("js") => mime!(Text/Javascript),
None | Some(_) => mime!(Application/("octet-stream")),
}
}
pub fn blob_mime(blob: &git2::Blob, path: &str) -> Mime {
match Path::new(path).extension().and_then(|s| s.to_str()) {
Some("css") => mime!(Text/Css),
Some("html") => mime!(Text/Html),
Some("js") => mime!(Text/Javascript),
None | Some(_) => {
if blob.is_binary() {
mime!(Application/("octet-stream"))
} else {
mime!(Text/Plain)
}
},
}
match Path::new(path).extension().and_then(|s| s.to_str()) {
Some("css") => mime!(Text/Css),
Some("html") => mime!(Text/Html),
Some("js") => mime!(Text/Javascript),
None | Some(_) => {
if blob.is_binary() {
mime!(Application/("octet-stream"))
} else {
mime!(Text/Plain)
}
},
}
}
pub fn sha1<T: AsRef<[u8]>>(file: T) -> String {
let mut hasher = Sha1::new();
hasher.input(file.as_ref());
hasher.result_str()
let mut hasher = Sha1::new();
hasher.input(file.as_ref());
hasher.result_str()
}
pub trait CacheMatches {
fn cache_matches(&self, etag: &EntityTag) -> bool;
fn cache_matches(&self, etag: &EntityTag) -> bool;
}
#[cfg(not(all(feature = "maybe_cache", feature = "cache")))]
mod caching {
use std::time::Duration;
use iron::headers::EntityTag;
use iron::headers::Vary;
use iron::modifiers::Header;
use iron::request::Request;
use unicase::UniCase;
use super::CacheMatches;
// In debug mode assume the etag never matches so we
// don't have to bump version numbers for dynamic content
impl<'a, 'b> CacheMatches for Request<'a, 'b> {
fn cache_matches(&self, _etag: &EntityTag) -> bool {
false
use std::time::Duration;
use iron::headers::EntityTag;
use iron::headers::Vary;
use iron::modifiers::Header;
use iron::request::Request;
use unicase::UniCase;
use super::CacheMatches;
// In debug mode assume the etag never matches so we
// don't have to bump version numbers for dynamic content
impl<'a, 'b> CacheMatches for Request<'a, 'b> {
fn cache_matches(&self, _etag: &EntityTag) -> bool {
false
}
}
// Should return () once https://github.com/reem/rust-modifier/pull/19 is merged
pub fn cache_headers_for(_entity_tag: &EntityTag, _duration: Duration) -> Header<Vary> {
Header(Vary::Items(vec![
UniCase("accept-encoding".to_owned()),
]))
}
}
// Should return () once https://github.com/reem/rust-modifier/pull/19 is merged
pub fn cache_headers_for(_entity_tag: &EntityTag, _duration: Duration) -> Header<Vary> {
Header(Vary::Items(vec![
UniCase("accept-encoding".to_owned()),
]))
}
}
#[cfg(all(feature = "maybe_cache", feature = "cache"))]
mod caching {
use std::time::Duration;
use iron::headers::EntityTag;
use iron::headers::{ ETag, CacheControl, CacheDirective, Vary };
use iron::modifiers::Header;
use iron::request::Request;
use unicase::UniCase;
use super::CacheMatches;
impl<'a, 'b> CacheMatches for Request<'a, 'b> {
fn cache_matches(&self, etag: &EntityTag) -> bool {
use iron::headers::IfNoneMatch;
if let Some(&IfNoneMatch::Items(ref items)) = self.headers.get() {
if items.len() == 1 && items[0] == *etag {
return true;
use std::time::Duration;
use iron::headers::EntityTag;
use iron::headers::{ ETag, CacheControl, CacheDirective, Vary };
use iron::modifiers::Header;
use iron::request::Request;
use unicase::UniCase;
use super::CacheMatches;
impl<'a, 'b> CacheMatches for Request<'a, 'b> {
fn cache_matches(&self, etag: &EntityTag) -> bool {
use iron::headers::IfNoneMatch;
if let Some(&IfNoneMatch::Items(ref items)) = self.headers.get() {
if items.len() == 1 && items[0] == *etag {
return true;
}
}
false
}
}
false
}
}
// Where's my abstract return types....
pub fn cache_headers_for(entity_tag: &EntityTag, duration: Duration)
-> (Header<CacheControl>, Header<ETag>, Header<Vary>)
{
(
Header(CacheControl(vec![
CacheDirective::Public,
CacheDirective::MaxAge(duration.as_secs() as u32),
])),
Header(ETag(entity_tag.clone())),
Header(Vary::Items(vec![
UniCase("accept-encoding".to_owned()),
])),
)
}
// Where's my abstract return types....
pub fn cache_headers_for(entity_tag: &EntityTag, duration: Duration)
-> (Header<CacheControl>, Header<ETag>, Header<Vary>)
{
(
Header(CacheControl(vec![
CacheDirective::Public,
CacheDirective::MaxAge(duration.as_secs() as u32),
])),
Header(ETag(entity_tag.clone())),
Header(Vary::Items(vec![
UniCase("accept-encoding".to_owned()),
])),
)
}
}
pub use self::caching::*;

Modified src/macros.rs

@@ -1,19 +1,19 @@
#[macro_export]
macro_rules! expect {
($expr:expr) => ({
match $expr {
::std::option::Option::Some(x) => x,
::std::option::Option::None => return None,
}
})
($expr:expr) => ({
match $expr {
::std::option::Option::Some(x) => x,
::std::option::Option::None => return None,
}
})
}
#[macro_export]
macro_rules! try_expect {
($expr:expr) => ({
match $expr {
::std::result::Result::Ok(x) => x,
::std::result::Result::Err(_) => return None,
}
})
($expr:expr) => ({
match $expr {
::std::result::Result::Ok(x) => x,
::std::result::Result::Err(_) => return None,
}
})
}

Modified src/main.rs

@@ -66,69 +66,69 @@ pub use repository_extension::RepositoryExtension;
include!(concat!(env!("OUT_DIR"), "/version.rs"));
fn main() {
let config = match config::load(env::args_os().nth(1).as_ref()) {
Ok(config) => config,
Err(err) => {
println!("Failed to load config:\n{}", err);
std::process::exit(1)
},
};
let config = match config::load(env::args_os().nth(1).as_ref()) {
Ok(config) => config,
Err(err) => {
println!("Failed to load config:\n{}", err);
std::process::exit(1)
},
};
println!("Running with config");
println!("===================");
println!("{}", toml::to_string(&config).unwrap());
println!("===================");
println!("Running with config");
println!("===================");
println!("{}", toml::to_string(&config).unwrap());
println!("===================");
let mut router = Router::new();
let mut router = Router::new();
router
.register(inject_repository_context(&config.repos.root, handler::Review))
.register(inject_repository_context(&config.repos.root, handler::Reviews))
.register(inject_repository_context(&config.repos.root, handler::Commit))
.register(inject_repository_context(&config.repos.root, handler::Commits))
.register(inject_repository_context(&config.repos.root, handler::Repository))
.register(handler::Repositories { root: config.repos.root.clone() })
.register(handler::Settings)
.register(handler::SettingsPost)
.register(handler::About)
.register(inject_repository_context(&config.repos.root, handler::Tree))
.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_smart_http::Refs))
.register(inject_repository_context(&config.repos.root, handler::git_smart_http::UploadPack))
.register(statics![
prefix: "./static/";
"./static/js/highlight.js",
"./static/css/highlight-solarized-light.css",
"./static/css/layout.css",
"./static/css/theme-solarized-dark.css",
"./static/css/theme-solarized-light.css",
"./static/css/font-awesome.min.css",
"./static/fonts/FontAwesome.otf",
"./static/fonts/fontawesome-webfont.eot",
"./static/fonts/fontawesome-webfont.svg",
"./static/fonts/fontawesome-webfont.ttf",
"./static/fonts/fontawesome-webfont.woff",
"./static/fonts/fontawesome-webfont.woff2",
])
.register(handler::Avatars::new(handler::avatar::Options {
enable_gravatar: config.avatars.gravatar.enable,
enable_cache: config.avatars.cache.enable,
cache_capacity: config.avatars.cache.capacity,
cache_time_to_live: Duration::from_secs(config.avatars.cache.ttl_seconds),
}));
router
.register(inject_repository_context(&config.repos.root, handler::Review))
.register(inject_repository_context(&config.repos.root, handler::Reviews))
.register(inject_repository_context(&config.repos.root, handler::Commit))
.register(inject_repository_context(&config.repos.root, handler::Commits))
.register(inject_repository_context(&config.repos.root, handler::Repository))
.register(handler::Repositories { root: config.repos.root.clone() })
.register(handler::Settings)
.register(handler::SettingsPost)
.register(handler::About)
.register(inject_repository_context(&config.repos.root, handler::Tree))
.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_smart_http::Refs))
.register(inject_repository_context(&config.repos.root, handler::git_smart_http::UploadPack))
.register(statics![
prefix: "./static/";
"./static/js/highlight.js",
"./static/css/highlight-solarized-light.css",
"./static/css/layout.css",
"./static/css/theme-solarized-dark.css",
"./static/css/theme-solarized-light.css",
"./static/css/font-awesome.min.css",
"./static/fonts/FontAwesome.otf",
"./static/fonts/fontawesome-webfont.eot",
"./static/fonts/fontawesome-webfont.svg",
"./static/fonts/fontawesome-webfont.ttf",
"./static/fonts/fontawesome-webfont.woff",
"./static/fonts/fontawesome-webfont.woff2",
])
.register(handler::Avatars::new(handler::avatar::Options {
enable_gravatar: config.avatars.gravatar.enable,
enable_cache: config.avatars.cache.enable,
cache_capacity: config.avatars.cache.capacity,
cache_time_to_live: Duration::from_secs(config.avatars.cache.ttl_seconds),
}));
let (logger_before, logger_after) = Logger::new(None);
let (logger_before, logger_after) = Logger::new(None);
let mut chain = Chain::new(router);
let mut chain = Chain::new(router);
chain.link_before(logger_before);
chain.link_before(settings::Settings::default());
chain.link_after(handler::error::NotFound);
chain.link_after(handler::error::BadRequest);
chain.link_after(handler::error::InternalServerError);
chain.link_after(logger_after);
chain.link_before(logger_before);
chain.link_before(settings::Settings::default());
chain.link_after(handler::error::NotFound);
chain.link_after(handler::error::BadRequest);
chain.link_after(handler::error::InternalServerError);
chain.link_after(logger_after);
Iron::new(chain).http("localhost:3000").unwrap();
Iron::new(chain).http("localhost:3000").unwrap();
}

Modified src/referenced_commit.rs

@@ -2,15 +2,15 @@ use std::borrow::Cow;
use git2;
pub struct ReferencedCommit<'a> {
pub commit: git2::Commit<'a>,
pub reference: Option<git2::Reference<'a>>,
pub commit: git2::Commit<'a>,
pub reference: Option<git2::Reference<'a>>,
}
impl<'a> ReferencedCommit<'a> {
pub fn shorthand_or_id(&self) -> Cow<str> {
match self.reference.as_ref().and_then(|r| r.shorthand()) {
Some(reff) => reff.into(),
None => self.commit.id().to_string().into(),
pub fn shorthand_or_id(&self) -> Cow<str> {
match self.reference.as_ref().and_then(|r| r.shorthand()) {
Some(reff) => reff.into(),
None => self.commit.id().to_string().into(),
}
}
}
}

Modified src/render/about.rs

@@ -2,27 +2,27 @@ use { REVISION, DATE };
use super::utils::Markdown;
pub fn About() -> ::maud::Markup {
html! {
div.block {
div.block-header h3 "About"
div.block-details {
(Markdown(include_str!("../../README.md")))
}
}
div.block {
div.block-header h3 "Version"
div.block-details {
"Website generated using "
a href="https://git.nemo157.com/grarr" "grarr"
" version "
(env!("CARGO_PKG_VERSION"))
@match (REVISION, DATE) {
(Some(rev), None) => " (" a href={ "https://git.nemo157.com/grarr/commits/" (rev) } (rev) ")",
(None, Some(date)) => " (" (date) ")",
(Some(rev), Some(date)) => " (" a href={ "https://git.nemo157.com/grarr/commits/" (rev) } (rev) " " (date) ")",
(None, None) => {},
html! {
div.block {
div.block-header h3 "About"
div.block-details {
(Markdown(include_str!("../../README.md")))
}
}
div.block {
div.block-header h3 "Version"
div.block-details {
"Website generated using "
a href="https://git.nemo157.com/grarr" "grarr"
" version "
(env!("CARGO_PKG_VERSION"))
@match (REVISION, DATE) {
(Some(rev), None) => " (" a href={ "https://git.nemo157.com/grarr/commits/" (rev) } (rev) ")",
(None, Some(date)) => " (" (date) ")",
(Some(rev), Some(date)) => " (" a href={ "https://git.nemo157.com/grarr/commits/" (rev) } (rev) " " (date) ")",
(None, None) => {},
}
}
}
}
}
}
}

Modified src/render/analysis.rs

@@ -2,23 +2,23 @@ use git_appraise;
use chrono::naive::datetime::NaiveDateTime;
pub fn Analysis(analysis: &git_appraise::Analysis) -> ::maud::Markup {
html! {
@if let Some(url) = analysis.url() {
div.block.analysis {
div.block-header {
small {
a href=(url) {
"External analysis"
@if let Some(timestamp) = analysis.timestamp() {
" submitted at "
span.timestamp {
(NaiveDateTime::from_timestamp(timestamp.seconds(), 0))
html! {
@if let Some(url) = analysis.url() {
div.block.analysis {
div.block-header {
small {
a href=(url) {
"External analysis"
@if let Some(timestamp) = analysis.timestamp() {
" submitted at "
span.timestamp {
(NaiveDateTime::from_timestamp(timestamp.seconds(), 0))
}
}
}
}
}
}
}
}
}
}
}
}
}

Modified src/render/avatar.rs

@@ -1,11 +1,11 @@
pub fn Avatar(email: &str, name: &Option<&str>) -> ::maud::Markup {
html! {
img.avatar
width="30"
height="30"
alt={ @if let Some(name) = *name { (name) " " } "<" (email) ">" }
title={ @if let Some(name) = *name { (name) " " } "<" (email) ">" }
src={ "/-/avatars/" (email) }
{}
}
html! {
img.avatar
width="30"
height="30"
alt={ @if let Some(name) = *name { (name) " " } "<" (email) ">" }
title={ @if let Some(name) = *name { (name) " " } "<" (email) ">" }
src={ "/-/avatars/" (email) }
{}
}
}

Modified src/render/ci_status.rs

@@ -2,47 +2,47 @@ use git_appraise::{ self, Status };
use chrono::naive::datetime::NaiveDateTime;
pub fn CIStatus(ci_status: &git_appraise::CIStatus) -> ::maud::Markup {
html! {
div.block.ci-status {
div.block-header {
small {
@match ci_status.url() {
Some(url) => {
a href=(url) {
(CIStatusText(ci_status))
}
},
None => (CIStatusText(ci_status))
}
html! {
div.block.ci-status {
div.block-header {
small {
@match ci_status.url() {
Some(url) => {
a href=(url) {
(CIStatusText(ci_status))
}
},
None => (CIStatusText(ci_status))
}
}
}
}
}
}
}
}
pub fn CIStatusText(ci_status: &git_appraise::CIStatus) -> ::maud::Markup {
html! {
span.agent {
(ci_status.agent().unwrap_or("<Unknown agent>"))
}
" reported status "
span class={
"status "
@match ci_status.status() {
Some(Status::Success) => "success",
Some(Status::Failure) => "failure",
None => "running",
}
} {
@match ci_status.status() {
Some(Status::Success) => "success",
Some(Status::Failure) => "failure",
None => "running",
}
}
@if let Some(timestamp) = ci_status.timestamp() {
" at "
span.timestamp (NaiveDateTime::from_timestamp(timestamp.seconds(), 0))
html! {
span.agent {
(ci_status.agent().unwrap_or("<Unknown agent>"))
}
" reported status "
span class={
"status "
@match ci_status.status() {
Some(Status::Success) => "success",
Some(Status::Failure) => "failure",
None => "running",
}
} {
@match ci_status.status() {
Some(Status::Success) => "success",
Some(Status::Failure) => "failure",
None => "running",
}
}
@if let Some(timestamp) = ci_status.timestamp() {
" at "
span.timestamp (NaiveDateTime::from_timestamp(timestamp.seconds(), 0))
}
}
}
}

Modified src/render/comment.rs

@@ -3,61 +3,61 @@ use super::utils::Markdown;
use chrono::naive::datetime::NaiveDateTime;
pub fn CommentHeader(comment: &git_appraise::Comment) -> ::maud::Markup {
html! {
div.block-header.row.center {
(super::Avatar(comment.author().unwrap_or("unknown@example.org"), &None))
div.column {
div {
span.user
(comment.author().unwrap_or("<unknown author>"))
" commented "
@if let Some(timestamp) = comment.timestamp() {
"on "
span.timestamp
(NaiveDateTime::from_timestamp(timestamp.seconds(), 0))
" "
}
"with status "
span class={
"resolved "
@match comment.resolved() {
Some(true) => "lgtm",
Some(false) => "nmw",
None => "fyi",
html! {
div.block-header.row.center {
(super::Avatar(comment.author().unwrap_or("unknown@example.org"), &None))
div.column {
div {
span.user
(comment.author().unwrap_or("<unknown author>"))
" commented "
@if let Some(timestamp) = comment.timestamp() {
"on "
span.timestamp
(NaiveDateTime::from_timestamp(timestamp.seconds(), 0))
" "
}
"with status "
span class={
"resolved "
@match comment.resolved() {
Some(true) => "lgtm",
Some(false) => "nmw",
None => "fyi",
}
} {
@match comment.resolved() {
Some(true) => "👍",
Some(false) => "👎",
None => "ℹī¸",
}
}
}
}
} {
@match comment.resolved() {
Some(true) => "👍",
Some(false) => "👎",
None => "ℹī¸",
}
}
}
}
}
}
}
pub fn CommentDetails(comment: &git_appraise::Comment) -> ::maud::Markup {
html! {
@if let Some(location) = comment.location() {
div.block-details.comment-details {
pre { code { ((format!("{:?}", location))) } }
}
}
@if let Some(description) = comment.description() {
div.block-details.comment-details {
(Markdown(description))
}
html! {
@if let Some(location) = comment.location() {
div.block-details.comment-details {
pre { code { ((format!("{:?}", location))) } }
}
}
@if let Some(description) = comment.description() {
div.block-details.comment-details {
(Markdown(description))
}
}
}
}
}
pub fn Comment(comment: &git_appraise::Comment) -> ::maud::Markup {
html! {
div.block.comment {
(CommentHeader(comment))
(CommentDetails(comment))
html! {
div.block.comment {
(CommentHeader(comment))
(CommentDetails(comment))
}
}
}
}

Modified src/render/commit.rs

@@ -8,180 +8,180 @@ use repository_context::RepositoryContext;
use super::reference;
fn summary<'a>(commit: &'a git2::Commit<'a>) -> Option<&'a str> {
commit.message()
.and_then(|message| message.lines().nth(0))
commit.message()
.and_then(|message| message.lines().nth(0))
}
fn non_summary<'a>(commit: &'a git2::Commit<'a>) -> Option<&'a str> {
commit.message()
.and_then(|message| message.splitn(2, '\n').map(|l| if l.starts_with('\r') { &l[1..] } else { l }).nth(1))
commit.message()
.and_then(|message| message.splitn(2, '\n').map(|l| if l.starts_with('\r') { &l[1..] } else { l }).nth(1))
}
pub fn CommitStub(context: &RepositoryContext, commit: &git2::Commit) -> ::maud::Markup {
html! {
div.block {
(CommitHeader(context, commit))
html! {
div.block {
(CommitHeader(context, commit))
}
}
}
}
pub fn CommitHeader(context: &RepositoryContext, commit: &git2::Commit) -> ::maud::Markup {
html! {
div.block-header {
div.row {
@if commit.author().email() == commit.committer().email() {
@if let Some(email) = commit.author().email() {
(super::Avatar(email, &commit.author().name()))
}
} @else {
div.column.fixed {
@if let Some(email) = commit.author().email() {
(super::Avatar(email, &commit.author().name()))
}
@if let Some(email) = commit.committer().email() {
(super::Avatar(email, &commit.committer().name()))
}
}
}
div.column {
div {
a href={ "/" (context.path) "/commit/" (commit.id()) } {
(super::reference::Commit(commit))
" "
@match summary(commit) {
Some(summary) => (summary),
None => "<No summary provided>",
}
}
}
@if (commit.author().name(), commit.author().email()) == (commit.committer().name(), commit.committer().email()) {
small {
(super::Signature(commit.author(), false))
"committed at" (PreEscaped("&nbsp;"))
span.timestamp { (NaiveDateTime::from_timestamp(commit.time().seconds(), 0)) }
}
} @else {
small {
(super::Signature(commit.author(), false))
"authored at" (PreEscaped("&nbsp;"))
span.timestamp { (NaiveDateTime::from_timestamp(commit.time().seconds(), 0)) }
}
small {
(super::Signature(commit.committer(), false))
"committed at" (PreEscaped("&nbsp;"))
span.timestamp { (NaiveDateTime::from_timestamp(commit.author().when().seconds(), 0)) }
html! {
div.block-header {
div.row {
@if commit.author().email() == commit.committer().email() {
@if let Some(email) = commit.author().email() {
(super::Avatar(email, &commit.author().name()))
}
} @else {
div.column.fixed {
@if let Some(email) = commit.author().email() {
(super::Avatar(email, &commit.author().name()))
}
@if let Some(email) = commit.committer().email() {
(super::Avatar(email, &commit.committer().name()))
}
}
}
div.column {
div {
a href={ "/" (context.path) "/commit/" (commit.id()) } {
(super::reference::Commit(commit))
" "
@match summary(commit) {
Some(summary) => (summary),
None => "<No summary provided>",
}
}
}
@if (commit.author().name(), commit.author().email()) == (commit.committer().name(), commit.committer().email()) {
small {
(super::Signature(commit.author(), false))
"committed at" (PreEscaped("&nbsp;"))
span.timestamp { (NaiveDateTime::from_timestamp(commit.time().seconds(), 0)) }
}
} @else {
small {
(super::Signature(commit.author(), false))
"authored at" (PreEscaped("&nbsp;"))
span.timestamp { (NaiveDateTime::from_timestamp(commit.time().seconds(), 0)) }
}
small {
(super::Signature(commit.committer(), false))
"committed at" (PreEscaped("&nbsp;"))
span.timestamp { (NaiveDateTime::from_timestamp(commit.author().when().seconds(), 0)) }
}
}
}
}
}
}
}
}
}
}
pub fn CommitDetails(context: &RepositoryContext, commit: &git2::Commit) -> ::maud::Markup {
html! {
div.commit.block {
(CommitHeader(context, commit))
@if let Some(non_summary) = non_summary(commit) {
@if !non_summary.is_empty() {
div.block-details.message {
(Markdown(non_summary))
}
html! {
div.commit.block {
(CommitHeader(context, commit))
@if let Some(non_summary) = non_summary(commit) {
@if !non_summary.is_empty() {
div.block-details.message {
(Markdown(non_summary))
}
}
}
}
}
}
}
}
pub fn Commit(context: &RepositoryContext, commit: &git2::Commit) -> ::maud::Markup {
html! {
(CommitDetails(context, commit))
(super::DiffCommit(context, commit))
}
html! {
(CommitDetails(context, commit))
(super::DiffCommit(context, commit))
}
}
pub fn NextPage(context: &RepositoryContext, commit: &ReferencedCommit, next: &Option<&git2::Commit>) -> ::maud::Markup {
html! {
div.block div.block-header.row {
div.column.fixed {
a href={
"/"
(context.path)
"/commits/"
(commit.shorthand_or_id())
} {
"Back to beginning (" (reference::Commit(&commit.commit)) ")"
}
}
div.column {}
@if let Some(ref next) = *next {
div.column.fixed {
a.float-right href={
"/"
(context.path)
"/commits/"
(commit.shorthand_or_id())
"?start=" (next.id())
} {
"Next page (" (reference::Commit(next)) ")"
}
html! {
div.block div.block-header.row {
div.column.fixed {
a href={
"/"
(context.path)
"/commits/"
(commit.shorthand_or_id())
} {
"Back to beginning (" (reference::Commit(&commit.commit)) ")"
}
}
div.column {}
@if let Some(ref next) = *next {
div.column.fixed {
a.float-right href={
"/"
(context.path)
"/commits/"
(commit.shorthand_or_id())
"?start=" (next.id())
} {
"Next page (" (reference::Commit(next)) ")"
}
}
}
}
}
}
}
}
pub fn Commits(context: &RepositoryContext, commit: &ReferencedCommit, mut commits: commit_tree::CommitTree) -> ::maud::Markup {
let first = commits.next();
let mut id = 0;
html!({
@if let Some((first, mut sub)) = first {
div.block {
div.block-header {
h3 {
"Commits for ref " (super::Reference(commit))
@if first.id() != commit.commit.id() {
small { " (showing from " (reference::Commit(&first)) ")" }
let first = commits.next();
let mut id = 0;
html!({
@if let Some((first, mut sub)) = first {
div.block {
div.block-header {
h3 {
"Commits for ref " (super::Reference(commit))
@if first.id() != commit.commit.id() {
small { " (showing from " (reference::Commit(&first)) ")" }
}
}
}
}
}
}
}
(CommitStub(context, &first))
@if !sub.is_empty() {
div.subtree {
input.expander disabled?[sub.len() == 1] id={ "commits-expander-" (id) } type="checkbox" checked? { }
label for={ "commits-expander-" (id) } { i.fa.fa-fw.chevron {} }
(CommitTree(context, &mut sub, &mut id))
(CommitStub(context, &first))
@if !sub.is_empty() {
div.subtree {
input.expander disabled?[sub.len() == 1] id={ "commits-expander-" (id) } type="checkbox" checked? { }
label for={ "commits-expander-" (id) } { i.fa.fa-fw.chevron {} }
(CommitTree(context, &mut sub, &mut id))
}
}
(CommitTree(context, &mut commits, &mut id))
(NextPage(context, commit, &commits.next_after()))
}
}
(CommitTree(context, &mut commits, &mut id))
(NextPage(context, commit, &commits.next_after()))
}
})
})
}
pub fn CommitTree(context: &RepositoryContext, commits: &mut commit_tree::CommitTree, id: &mut u32) -> ::maud::Markup {
*id = *id + 1;
html!({
div.commits {
@for (commit, mut sub) in commits {
(CommitStub(context, &commit))
@if !sub.is_empty() {
div.subtree {
input.expander disabled?[sub.len() == 1] id={ "commits-expander-" (id) } type="checkbox" checked? { }
label for={ "commits-expander-" (id) } { i.fa.fa-fw.chevron {} }
(CommitTree(context, &mut sub, id))
}
*id = *id + 1;
html!({
div.commits {
@for (commit, mut sub) in commits {
(CommitStub(context, &commit))
@if !sub.is_empty() {
div.subtree {
input.expander disabled?[sub.len() == 1] id={ "commits-expander-" (id) } type="checkbox" checked? { }
label for={ "commits-expander-" (id) } { i.fa.fa-fw.chevron {} }
(CommitTree(context, &mut sub, id))
}
}
}
}
}
}
})
})
}
// impl<'a> super::repository_wrapper::RepositoryTab for &'a Commit<'a> {
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Commits) }
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Commits) }
// }
//
// impl<'a, 'b> super::repository_wrapper::RepositoryTab for Commits<'a, 'b> {
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Commits) }
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Commits) }
// }

Modified src/render/compare.rs

@@ -4,31 +4,31 @@ use repository_context::RepositoryContext;
use referenced_commit::ReferencedCommit;
pub struct Compare<'r> {
pub context: &'r RepositoryContext,
pub new: ReferencedCommit<'r>,
pub old: ReferencedCommit<'r>,
pub base: git2::Commit<'r>,
pub commits: Vec<git2::Commit<'r>>,
pub context: &'r RepositoryContext,
pub new: ReferencedCommit<'r>,
pub old: ReferencedCommit<'r>,
pub base: git2::Commit<'r>,
pub commits: Vec<git2::Commit<'r>>,
}
impl<'r> Render for Compare<'r> {
fn render(&self) -> Markup {
html!({
div.block div.block-header h2 { "Comparing base " (super::Reference(&self.old)) " to " (super::Reference(&self.new)) }
div.block {
div.block-header h3 { "Commits" }
div.block-details {
@for commit in &self.commits {
(super::CommitStub(&self.context, commit))
}
}
}
div.block {
div.block-header h3 { "File changes" }
div.block-details {
(super::DiffCommits(&self.context, &Some(&self.base), &self.new.commit))
}
}
})
}
fn render(&self) -> Markup {
html!({
div.block div.block-header h2 { "Comparing base " (super::Reference(&self.old)) " to " (super::Reference(&self.new)) }
div.block {
div.block-header h3 { "Commits" }
div.block-details {
@for commit in &self.commits {
(super::CommitStub(&self.context, commit))
}
}
}
div.block {
div.block-header h3 { "File changes" }
div.block-details {
(super::DiffCommits(&self.context, &Some(&self.base), &self.new.commit))
}
}
})
}
}

Modified src/render/diff.rs

@@ -6,149 +6,149 @@ use git2;
use repository_context::RepositoryContext;
pub fn DiffCommits(context: &RepositoryContext, old_commit: &Option<&git2::Commit>, new_commit: &git2::Commit) -> ::maud::Markup {
html! {
@match context.repository.diff_tree_to_tree(old_commit.map(|commit| commit.tree().unwrap()).as_ref(), Some(&new_commit.tree().unwrap()), None) {
Ok(ref diff) => (Diff(context, new_commit, diff)),
Err(ref error) => (super::Error(error)),
html! {
@match context.repository.diff_tree_to_tree(old_commit.map(|commit| commit.tree().unwrap()).as_ref(), Some(&new_commit.tree().unwrap()), None) {
Ok(ref diff) => (Diff(context, new_commit, diff)),
Err(ref error) => (super::Error(error)),
}
}
}
}
pub fn DiffCommit(context: &RepositoryContext, commit: &git2::Commit) -> ::maud::Markup {
html! {
(DiffCommits(context, &commit.parents().nth(0).as_ref(), commit))
}
html! {
(DiffCommits(context, &commit.parents().nth(0).as_ref(), commit))
}
}
pub fn DiffHeader(context: &RepositoryContext, new_commit: &git2::Commit, delta: &DiffDelta) -> ::maud::Markup {
html! {
div.block-header.row {
div.column {
@match (delta.status.0, delta.new_file.as_ref(), delta.old_file.as_ref()) {
(git2::Delta::Added, Some(ref new_file), _) => {
h3 { span { "Added " span.path (new_file.to_string_lossy()) } }
},
(git2::Delta::Deleted, _, Some(ref old_file)) => {
h3 { span { "Deleted " span.path (old_file.to_string_lossy()) } }
},
(git2::Delta::Modified, Some(ref new_file), Some(ref old_file)) if old_file == new_file => {
h3 { span { "Modified " span.path (new_file.to_string_lossy()) } }
},
(git2::Delta::Modified, Some(ref new_file), Some(ref old_file)) if old_file != new_file => {
h3 { span { "Modified " span.path (new_file.to_string_lossy()) "(Previously " span.path (old_file.to_string_lossy()) ")" } }
},
(git2::Delta::Renamed, Some(ref new_file), Some(ref old_file)) => {
h3 { span { "Renamed " span.path (old_file.to_string_lossy()) " to " span.path (new_file.to_string_lossy()) } }
},
(git2::Delta::Copied, Some(ref new_file), Some(ref old_file)) => {
h3 { span { "Copied " span.path (old_file.to_string_lossy()) " to " span.path (new_file.to_string_lossy()) } }
},
(status, ref new_file, ref old_file) => (format!("{:?} ({:?} -> {:?}) (should not happen)", status, old_file, new_file))
}
}
@if let Some(new_file) = delta.new_file.as_ref() {
div.column.fixed {
a href={ "/" (context.path) "/blob/" (new_commit.id()) "/" (new_file.to_string_lossy()) } { "View" }
html! {
div.block-header.row {
div.column {
@match (delta.status.0, delta.new_file.as_ref(), delta.old_file.as_ref()) {
(git2::Delta::Added, Some(ref new_file), _) => {
h3 { span { "Added " span.path (new_file.to_string_lossy()) } }
},
(git2::Delta::Deleted, _, Some(ref old_file)) => {
h3 { span { "Deleted " span.path (old_file.to_string_lossy()) } }
},
(git2::Delta::Modified, Some(ref new_file), Some(ref old_file)) if old_file == new_file => {
h3 { span { "Modified " span.path (new_file.to_string_lossy()) } }
},
(git2::Delta::Modified, Some(ref new_file), Some(ref old_file)) if old_file != new_file => {
h3 { span { "Modified " span.path (new_file.to_string_lossy()) "(Previously " span.path (old_file.to_string_lossy()) ")" } }
},
(git2::Delta::Renamed, Some(ref new_file), Some(ref old_file)) => {
h3 { span { "Renamed " span.path (old_file.to_string_lossy()) " to " span.path (new_file.to_string_lossy()) } }
},
(git2::Delta::Copied, Some(ref new_file), Some(ref old_file)) => {
h3 { span { "Copied " span.path (old_file.to_string_lossy()) " to " span.path (new_file.to_string_lossy()) } }
},
(status, ref new_file, ref old_file) => (format!("{:?} ({:?} -> {:?}) (should not happen)", status, old_file, new_file))
}
}
@if let Some(new_file) = delta.new_file.as_ref() {
div.column.fixed {
a href={ "/" (context.path) "/blob/" (new_commit.id()) "/" (new_file.to_string_lossy()) } { "View" }
}
}
}
}
}
}
}
pub fn DiffLineNums(id: &str, old_lineno: &Option<u32>, new_lineno: &Option<u32>) -> ::maud::Markup {
html! {
@if let Some(num) = *old_lineno {
a.line-num
id={ (id) "L" (num) }
href={ "#" (id) "L" (num) }
data-line-num=(format!("{: >4}", num))
{ }
} @else {
span.line-num { }
}
@if let Some(num) = *new_lineno {
a.line-num
id={ (id) "R" (num) }
href={ "#" (id) "R" (num) }
data-line-num=(format!("{: >4}", num))
{ " " }
} @else {
span.line-num { " " }
html! {
@if let Some(num) = *old_lineno {
a.line-num
id={ (id) "L" (num) }
href={ "#" (id) "L" (num) }
data-line-num=(format!("{: >4}", num))
{ }
} @else {
span.line-num { }
}
@if let Some(num) = *new_lineno {
a.line-num
id={ (id) "R" (num) }
href={ "#" (id) "R" (num) }
data-line-num=(format!("{: >4}", num))
{ " " }
} @else {
span.line-num { " " }
}
}
}
}
pub fn DiffDetails(id: String, extension: Option<String>, hunks: Vec<(DiffHunk, Vec<DiffLine>)>) -> ::maud::Markup {
html! {
pre.block-details code class={ "hljs lang-" (extension.unwrap_or("".to_owned())) } {
@if hunks.is_empty() {
div.line.hunk-header span.text "No content"
}
@for (hunk, lines) in hunks {
div.line.hunk-header {
(DiffLineNums(&*id, &None, &None))
span.text (hunk.header.unwrap())
}
@for line in lines {
@match (line.origin, line.content) {
(Origin::LineContext, Some(ref content)) => {
div.line.context {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text (content)
}
},
(Origin::LineAddition, Some(ref content)) => {
div.line.addition {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text (content)
}
},
(Origin::LineDeletion, Some(ref content)) => {
div.line.deletion {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text (content)
}
},
(Origin::AddEOF, _) => {
div.line.add-eof {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text "Added EOF"
}
},
(Origin::RemoveEOF, _) => {
div.line.remove-eof {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text "Removed EOF"
}
},
(Origin::LineBinary, _) => {
div.line.binary {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text "Binary file changed"
}
},
(Origin::ContextEOF, _) | (Origin::FileHeader, _) | (Origin::HunkHeader, _) => {
},
(_, _) => {
"UNREACHABLE"
html! {
pre.block-details code class={ "hljs lang-" (extension.unwrap_or("".to_owned())) } {
@if hunks.is_empty() {
div.line.hunk-header span.text "No content"
}
@for (hunk, lines) in hunks {
div.line.hunk-header {
(DiffLineNums(&*id, &None, &None))
span.text (hunk.header.unwrap())
}
@for line in lines {
@match (line.origin, line.content) {
(Origin::LineContext, Some(ref content)) => {
div.line.context {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text (content)
}
},
(Origin::LineAddition, Some(ref content)) => {
div.line.addition {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text (content)
}
},
(Origin::LineDeletion, Some(ref content)) => {
div.line.deletion {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text (content)
}
},
(Origin::AddEOF, _) => {
div.line.add-eof {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text "Added EOF"
}
},
(Origin::RemoveEOF, _) => {
div.line.remove-eof {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text "Removed EOF"
}
},
(Origin::LineBinary, _) => {
div.line.binary {
(DiffLineNums(&*id, &line.old_lineno, &line.new_lineno))
span.text "Binary file changed"
}
},
(Origin::ContextEOF, _) | (Origin::FileHeader, _) | (Origin::HunkHeader, _) => {
},
(_, _) => {
"UNREACHABLE"
}
}
}
}
}
}
}
}
}
}
pub fn Diff(context: &RepositoryContext, new_commit: &git2::Commit, diff: &git2::Diff) -> ::maud::Markup {
html! {
@for (delta, hunks) in group(diff).unwrap() {
div.diff.block id=(delta.id()) {
(DiffHeader(context, new_commit, &delta))
(DiffDetails(delta.id(), delta.new_file.or(delta.old_file).and_then(|path| path.extension().map(|s| s.to_string_lossy().into_owned())), hunks))
}
html! {
@for (delta, hunks) in group(diff).unwrap() {
div.diff.block id=(delta.id()) {
(DiffHeader(context, new_commit, &delta))
(DiffDetails(delta.id(), delta.new_file.or(delta.old_file).and_then(|path| path.extension().map(|s| s.to_string_lossy().into_owned())), hunks))
}
}
(super::HighlightJS())
}
(super::HighlightJS())
}
}
#[derive(Clone, Copy, Debug)]
@@ -156,170 +156,170 @@ pub struct Delta(pub git2::Delta);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Origin {
LineContext,
LineAddition,
LineDeletion,
ContextEOF,
AddEOF,
RemoveEOF,
FileHeader,
HunkHeader,
LineBinary,
LineContext,
LineAddition,
LineDeletion,
ContextEOF,
AddEOF,
RemoveEOF,
FileHeader,
HunkHeader,
LineBinary,
}
impl From<char> for Origin {
fn from(c: char) -> Origin {
match c {
' ' => Origin::LineContext,
'+' => Origin::LineAddition,
'-' => Origin::LineDeletion,
'=' => Origin::ContextEOF,
'>' => Origin::AddEOF,
'<' => Origin::RemoveEOF,
'F' => Origin::FileHeader,
'H' => Origin::HunkHeader,
'B' => Origin::LineBinary,
_ => panic!(),
fn from(c: char) -> Origin {
match c {
' ' => Origin::LineContext,
'+' => Origin::LineAddition,
'-' => Origin::LineDeletion,
'=' => Origin::ContextEOF,
'>' => Origin::AddEOF,
'<' => Origin::RemoveEOF,
'F' => Origin::FileHeader,
'H' => Origin::HunkHeader,
'B' => Origin::LineBinary,
_ => panic!(),
}
}
}
}
#[derive(Eq, PartialEq, Debug)]
pub struct DiffDelta {
pub status: Delta,
pub old_file: Option<PathBuf>,
pub new_file: Option<PathBuf>,
pub status: Delta,
pub old_file: Option<PathBuf>,
pub new_file: Option<PathBuf>,
}
#[derive(Eq, PartialEq, Debug)]
pub struct DiffHunk {
pub old_start: u32,
pub old_lines: u32,
pub new_start: u32,
pub new_lines: u32,
pub header: Option<String>,
pub old_start: u32,
pub old_lines: u32,
pub new_start: u32,
pub new_lines: u32,
pub header: Option<String>,
}
#[derive(Eq, PartialEq, Debug)]
pub struct DiffLine {
pub old_lineno: Option<u32>,
pub new_lineno: Option<u32>,
pub num_lines: u32,
pub content_offset: i64,
pub content: Option<String>,
pub origin: Origin,
pub old_lineno: Option<u32>,
pub new_lineno: Option<u32>,
pub num_lines: u32,
pub content_offset: i64,
pub content: Option<String>,
pub origin: Origin,
}
impl DiffDelta {
fn id(&self) -> String {
self.new_file.as_ref()
.or(self.old_file.as_ref())
.map_or(
"".to_owned(),
|f| f.to_string_lossy()
.chars()
.map(|c| if c.is_whitespace() || c == '/' || !c.is_ascii() { '-' } else { c.to_ascii_lowercase() })
.collect())
}
fn id(&self) -> String {
self.new_file.as_ref()
.or(self.old_file.as_ref())
.map_or(
"".to_owned(),
|f| f.to_string_lossy()
.chars()
.map(|c| if c.is_whitespace() || c == '/' || !c.is_ascii() { '-' } else { c.to_ascii_lowercase() })
.collect())
}
}
impl<'a> From<git2::DiffDelta<'a>> for DiffDelta {
fn from(delta: git2::DiffDelta<'a>) -> DiffDelta {
DiffDelta {
status: Delta(delta.status()),
old_file: delta.old_file().path().map(|p| p.to_owned()),
new_file: delta.new_file().path().map(|p| p.to_owned()),
fn from(delta: git2::DiffDelta<'a>) -> DiffDelta {
DiffDelta {
status: Delta(delta.status()),
old_file: delta.old_file().path().map(|p| p.to_owned()),
new_file: delta.new_file().path().map(|p| p.to_owned()),
}
}
}
}
impl<'a> From<git2::DiffHunk<'a>> for DiffHunk {
fn from(hunk: git2::DiffHunk<'a>) -> DiffHunk {
DiffHunk {
old_start: hunk.old_start(),
old_lines: hunk.old_lines(),
new_start: hunk.new_start(),
new_lines: hunk.new_lines(),
header: String::from_utf8(hunk.header().into()).ok(),
fn from(hunk: git2::DiffHunk<'a>) -> DiffHunk {
DiffHunk {
old_start: hunk.old_start(),
old_lines: hunk.old_lines(),
new_start: hunk.new_start(),
new_lines: hunk.new_lines(),
header: String::from_utf8(hunk.header().into()).ok(),
}
}
}
}
impl<'a> From<git2::DiffLine<'a>> for DiffLine {
fn from(line: git2::DiffLine<'a>) -> DiffLine {
DiffLine {
old_lineno: line.old_lineno(),
new_lineno: line.new_lineno(),
num_lines: line.num_lines(),
content_offset: line.content_offset(),
content: String::from_utf8(line.content().into()).ok(),
origin: line.origin().into(),
fn from(line: git2::DiffLine<'a>) -> DiffLine {
DiffLine {
old_lineno: line.old_lineno(),
new_lineno: line.new_lineno(),
num_lines: line.num_lines(),
content_offset: line.content_offset(),
content: String::from_utf8(line.content().into()).ok(),
origin: line.origin().into(),
}
}
}
}
#[allow(type_complexity)] // This is temporary till I figure out a nicer way to do this without all the allocation
fn group(diff: &git2::Diff) -> Result<Vec<(DiffDelta, Vec<(DiffHunk, Vec<DiffLine>)>)>, git2::Error> {
let mut deltas = Vec::new();
let hunks = RefCell::new(Vec::new());
let lines = RefCell::new(Vec::new());
let last_delta = RefCell::new(None);
let last_hunk = RefCell::new(None);
try!(diff.foreach(
&mut |delta, _progress| {
if let Some(last_hunk) = last_hunk.borrow_mut().take() {
let mut new_lines = vec![];
mem::swap(&mut *lines.borrow_mut(), &mut new_lines);
hunks.borrow_mut().push((last_hunk, new_lines));
}
if let Some(last_delta) = last_delta.borrow_mut().take() {
let mut new_hunks = vec![];
mem::swap(&mut *hunks.borrow_mut(), &mut new_hunks);
deltas.push((last_delta, new_hunks));
}
*last_delta.borrow_mut() = Some(delta.into());
true
},
None,
Some(&mut |_delta, hunk| {
if let Some(last_hunk) = last_hunk.borrow_mut().take() {
let mut new_lines = vec![];
mem::swap(&mut *lines.borrow_mut(), &mut new_lines);
hunks.borrow_mut().push((last_hunk, new_lines));
}
*last_hunk.borrow_mut() = Some(hunk.into());
true
}),
Some(&mut |_delta, _hunk, line| {
lines.borrow_mut().push(line.into());
true
})));
if let Some(last_hunk) = last_hunk.into_inner() {
hunks.borrow_mut().push((last_hunk, lines.into_inner()));
}
if let Some(last_delta) = last_delta.into_inner() {
deltas.push((last_delta, hunks.into_inner()));
}
Ok(deltas)
let mut deltas = Vec::new();
let hunks = RefCell::new(Vec::new());
let lines = RefCell::new(Vec::new());
let last_delta = RefCell::new(None);
let last_hunk = RefCell::new(None);
try!(diff.foreach(
&mut |delta, _progress| {
if let Some(last_hunk) = last_hunk.borrow_mut().take() {
let mut new_lines = vec![];
mem::swap(&mut *lines.borrow_mut(), &mut new_lines);
hunks.borrow_mut().push((last_hunk, new_lines));
}
if let Some(last_delta) = last_delta.borrow_mut().take() {
let mut new_hunks = vec![];
mem::swap(&mut *hunks.borrow_mut(), &mut new_hunks);
deltas.push((last_delta, new_hunks));
}
*last_delta.borrow_mut() = Some(delta.into());
true
},
None,
Some(&mut |_delta, hunk| {
if let Some(last_hunk) = last_hunk.borrow_mut().take() {
let mut new_lines = vec![];
mem::swap(&mut *lines.borrow_mut(), &mut new_lines);
hunks.borrow_mut().push((last_hunk, new_lines));
}
*last_hunk.borrow_mut() = Some(hunk.into());
true
}),
Some(&mut |_delta, _hunk, line| {
lines.borrow_mut().push(line.into());
true
})));
if let Some(last_hunk) = last_hunk.into_inner() {
hunks.borrow_mut().push((last_hunk, lines.into_inner()));
}
if let Some(last_delta) = last_delta.into_inner() {
deltas.push((last_delta, hunks.into_inner()));
}
Ok(deltas)
}
impl Eq for Delta { }
impl PartialEq<Delta> for Delta {
#[allow(match_same_arms)]
fn eq(&self, other: &Delta) -> bool {
match (self.0, other.0) {
(git2::Delta::Unmodified, git2::Delta::Unmodified) => true,
(git2::Delta::Added, git2::Delta::Added) => true,
(git2::Delta::Deleted, git2::Delta::Deleted) => true,
(git2::Delta::Modified, git2::Delta::Modified) => true,
(git2::Delta::Renamed, git2::Delta::Renamed) => true,
(git2::Delta::Copied, git2::Delta::Copied) => true,
(git2::Delta::Ignored, git2::Delta::Ignored) => true,
(git2::Delta::Untracked, git2::Delta::Untracked) => true,
(git2::Delta::Typechange, git2::Delta::Typechange) => true,
(git2::Delta::Unreadable, git2::Delta::Unreadable) => true,
(git2::Delta::Conflicted, git2::Delta::Conflicted) => true,
(_, _) => false,
#[allow(match_same_arms)]
fn eq(&self, other: &Delta) -> bool {
match (self.0, other.0) {
(git2::Delta::Unmodified, git2::Delta::Unmodified) => true,
(git2::Delta::Added, git2::Delta::Added) => true,
(git2::Delta::Deleted, git2::Delta::Deleted) => true,
(git2::Delta::Modified, git2::Delta::Modified) => true,
(git2::Delta::Renamed, git2::Delta::Renamed) => true,
(git2::Delta::Copied, git2::Delta::Copied) => true,
(git2::Delta::Ignored, git2::Delta::Ignored) => true,
(git2::Delta::Untracked, git2::Delta::Untracked) => true,
(git2::Delta::Typechange, git2::Delta::Typechange) => true,
(git2::Delta::Unreadable, git2::Delta::Unreadable) => true,
(git2::Delta::Conflicted, git2::Delta::Conflicted) => true,
(_, _) => false,
}
}
}
}

Modified src/render/error.rs

@@ -1,40 +1,40 @@
use iron;
pub fn Error(error: &iron::Error) -> ::maud::Markup {
html! {
pre.block-details code (error)
}
html! {
pre.block-details code (error)
}
}
pub fn BadRequest(error: &iron::Error) -> ::maud::Markup {
html! {
div.block {
div.block-header {
h2 "Bad Request"
}
(Error(error))
html! {
div.block {
div.block-header {
h2 "Bad Request"
}
(Error(error))
}
}
}
}
pub fn NotFound(error: &iron::Error) -> ::maud::Markup {
html! {
div.block {
div.block-header {
h2 "Not Found"
}
(Error(error))
html! {
div.block {
div.block-header {
h2 "Not Found"
}
(Error(error))
}
}
}
}