Repositories

grarr

(mirrored on github)

Wim Looman <wim@nemo157.com>
b81af3 2 spaces to 4 spaces
Wim Looman committed at 2017-05-15 20:46:03

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

@@ -5,46 +5,46 @@ use git2;
#[derive(Debug)]
pub enum Error {
MissingExtension,
MissingPathComponent,
String(Cow<'static, str>),
Git(git2::Error),
MissingExtension,
MissingPathComponent,
String(Cow<'static, str>),
Git(git2::Error),
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::MissingExtension => "Missing request extension",
Error::MissingPathComponent => "Missing path component",
Error::String(ref s) => s.borrow(),
Error::Git(ref e) => e.description(),
fn description(&self) -> &str {
match *self {
Error::MissingExtension => "Missing request extension",
Error::MissingPathComponent => "Missing path component",
Error::String(ref s) => s.borrow(),
Error::Git(ref e) => e.description(),
}
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Git(ref e) => e.fmt(f),
_ => f.write_str(error::Error::description(self)),
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Git(ref e) => e.fmt(f),
_ => f.write_str(error::Error::description(self)),
}
}
}
}
impl From<git2::Error> for Error {
fn from(e: git2::Error) -> Error {
Error::Git(e)
}
fn from(e: git2::Error) -> Error {
Error::Git(e)
}
}
impl From<&'static str> for Error {
fn from(s: &'static str) -> Error {
Error::String(s.into())
}
fn from(s: &'static str) -> Error {
Error::String(s.into())
}
}
impl From<String> for Error {
fn from(s: String) -> Error {
Error::String(s.into())
}
fn from(s: String) -> Error {
Error::String(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::MissingExtension), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), 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::MissingExtension), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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/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::MissingExtension), status::InternalServerError);
context.reference = Some("gh-pages".to_owned());
}
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), status::InternalServerError);
let router = itry!(req.extensions.get::<Router>().ok_or(Error::MissingExtension), status::InternalServerError);
fn handle(&self, req: &mut Request) -> IronResult<Response> {
{
let mut context = itry!(req.extensions.get_mut::<RepositoryContext>().ok_or(Error::MissingExtension), status::InternalServerError);
context.reference = Some("gh-pages".to_owned());
}
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), status::InternalServerError);
let router = itry!(req.extensions.get::<Router>().ok_or(Error::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), status::InternalServerError);
let commit = itry!(router.find("ref").ok_or(Error::MissingPathComponent), 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::MissingExtension), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), status::InternalServerError);
let commit = itry!(router.find("ref").ok_or(Error::MissingPathComponent), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), 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::MissingExtension), status::InternalServerError);
let path = Path::new(itry!(router.find("path").ok_or(Error::MissingPathComponent), 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::MissingExtension), status::InternalServerError);
let path = Path::new(itry!(router.find("path").ok_or(Error::MissingPathComponent), 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::MissingExtension), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), 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::MissingExtension), status::InternalServerError);
let context = itry!(req.extensions.get::<RepositoryContext>().ok_or(Error::MissingExtension), 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

@@ -64,67 +64,67 @@ 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(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(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))
}
}
}
}
pub fn InternalServerError(error: &iron::Error) -> ::maud::Markup {
html! {
div.block {
div.block-header {
h2 "Internal Server Error"
}
(Error(error))
html! {
div.block {
div.block-header {
h2 "Internal Server Error"
}
(Error(error))
}
}
}
}

Modified src/render/event.rs

@@ -1,26 +1,26 @@
use git_appraise;
pub fn Events(root: String, events: git_appraise::Events) -> ::maud::Markup {
html! {
@for event in events {
(Event(root.clone(), event))
html! {
@for event in events {
(Event(root.clone(), event))
}
}
}
}
pub fn Event(root: String, event: Box<git_appraise::Event>) -> ::maud::Markup {
html! {
@if let Some(request) = event.as_request() {
(super::Request(&root, request))
html! {
@if let Some(request) = event.as_request() {
(super::Request(&root, request))
}
@if let Some(comment) = event.as_comment() {
(super::Comment(comment))
}
@if let Some(analysis) = event.as_analysis() {
(super::Analysis(analysis))
}
@if let Some(ci_status) = event.as_ci_status() {
(super::CIStatus(ci_status))
}
}
@if let Some(comment) = event.as_comment() {
(super::Comment(comment))
}
@if let Some(analysis) = event.as_analysis() {
(super::Analysis(analysis))
}
@if let Some(ci_status) = event.as_ci_status() {
(super::CIStatus(ci_status))
}
}
}

Modified src/render/fa.rs

@@ -1,62 +1,62 @@
use maud::{ Render, Markup };
macro_rules! fa {
($($e:ident => $v:expr,)*) => {
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub enum FA { $($e,)* }
impl FA {
fn class(self) -> &'static str {
match self {
$(FA::$e => concat!("fa fa-", $v),)*
($($e:ident => $v:expr,)*) => {
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub enum FA { $($e,)* }
impl FA {
fn class(self) -> &'static str {
match self {
$(FA::$e => concat!("fa fa-", $v),)*
}
}
}
}
}
};
};
}
fa! {
Book => "book",
CodeFork => "code-fork",
Cog => "cog",
File => "file",
GitSquare => "git-square",
Home => "home",
Info => "info",
LevelUp => "level-up",
Question => "question",
Sitemap => "sitemap",
Tag => "tag",
Book => "book",
CodeFork => "code-fork",
Cog => "cog",
File => "file",
GitSquare => "git-square",
Home => "home",
Info => "info",
LevelUp => "level-up",
Question => "question",
Sitemap => "sitemap",
Tag => "tag",
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub enum FAM {
FixedWidth(FA),
Lg(FA),
Li(FA),
X(u8, FA),
FixedWidth(FA),
Lg(FA),
Li(FA),
X(u8, FA),
}
impl FAM {
fn class(self) -> String {
match self {
FAM::FixedWidth(fa) => format!("fa-fw {}", fa.class()),
FAM::Lg(fa) => format!("fa-fw fa-lg {}", fa.class()),
FAM::Li(fa) => format!("fa-li {}", fa.class()),
FAM::X(mul, fa) => format!("fa-fw fa-{}x {}", mul, fa.class()),
fn class(self) -> String {
match self {
FAM::FixedWidth(fa) => format!("fa-fw {}", fa.class()),
FAM::Lg(fa) => format!("fa-fw fa-lg {}", fa.class()),
FAM::Li(fa) => format!("fa-li {}", fa.class()),
FAM::X(mul, fa) => format!("fa-fw fa-{}x {}", mul, fa.class()),
}
}
}
}
impl Render for FA {
fn render(&self) -> Markup {
html!({ i class=(self.class()) { } })
}
fn render(&self) -> Markup {
html!({ i class=(self.class()) { } })
}
}
impl Render for FAM {
fn render(&self) -> Markup {
html!({ i class=(self.class()) { } })
}
fn render(&self) -> Markup {
html!({ i class=(self.class()) { } })
}
}

Modified src/render/highlight.rs

@@ -1,7 +1,7 @@
pub fn HighlightJS() -> ::maud::Markup {
html! {
link rel="stylesheet" href="/-/static/css/highlight-solarized-light.css" {}
script src="/-/static/js/highlight.js" {}
script { "hljs.initHighlightingOnLoad()" }
}
html! {
link rel="stylesheet" href="/-/static/css/highlight-solarized-light.css" {}
script src="/-/static/js/highlight.js" {}
script { "hljs.initHighlightingOnLoad()" }
}
}

Modified src/render/macros.rs

@@ -1,6 +1,6 @@
#[macro_export]
macro_rules! to_string {
($($x:tt)*) => {{
html!($($x)*).into_string();
}}
($($x:tt)*) => {{
html!($($x)*).into_string();
}}
}

Modified src/render/reference.rs

@@ -3,20 +3,20 @@ use referenced_commit::ReferencedCommit;
const HEX: &'static [u8; 0x10] = b"0123456789abcdef";
fn short(oid: git2::Oid) -> String {
oid.as_bytes().iter().take(3).flat_map(|b| vec![HEX[((b >> 4) & 0xFu8) as usize] as char, HEX[(b & 0xFu8) as usize] as char]).collect()
oid.as_bytes().iter().take(3).flat_map(|b| vec![HEX[((b >> 4) & 0xFu8) as usize] as char, HEX[(b & 0xFu8) as usize] as char]).collect()
}
pub fn Commit(commit: &git2::Commit) -> ::maud::Markup {
html! {
span.id title=(commit.id()) { (short(commit.id())) }
}
html! {
span.id title=(commit.id()) { (short(commit.id())) }
}
}
pub fn Reference(commit: &ReferencedCommit) -> ::maud::Markup {
html! {
@match commit.reference.as_ref().and_then(|r| r.shorthand()) {
Some(ref reff) => span.ref title=(commit.commit.id()) { (reff) },
None => (Commit(&commit.commit)),
html! {
@match commit.reference.as_ref().and_then(|r| r.shorthand()) {
Some(ref reff) => span.ref title=(commit.commit.id()) { (reff) },
None => (Commit(&commit.commit)),
}
}
}
}

Modified src/render/repository.rs

@@ -8,123 +8,123 @@ use super::fa::{ FA, FAM };
use { RepositoryExtension };
fn find_readme(head_id: Oid, repo: &git2::Repository) -> Option<String> {
let head = try_expect!(repo.find_commit(head_id));
let tree = try_expect!(head.tree());
let entry = expect!(tree.get_name("README").or_else(|| tree.get_name("README.md")));
let object = try_expect!(entry.to_object(repo));
let blob = expect!(object.as_blob());
str::from_utf8(blob.content()).ok().map(|s| s.to_owned())
let head = try_expect!(repo.find_commit(head_id));
let tree = try_expect!(head.tree());
let entry = expect!(tree.get_name("README").or_else(|| tree.get_name("README.md")));
let object = try_expect!(entry.to_object(repo));
let blob = expect!(object.as_blob());
str::from_utf8(blob.content()).ok().map(|s| s.to_owned())
}
fn description(repo: &git2::Repository) -> Option<PreEscaped<String>> {
let head_id = expect!(try_expect!(try_expect!(repo.head()).resolve()).target());
// Render the readme and grab the first <p> element from it.
find_readme(head_id, repo)
.map(|readme| {
let mut unsafe_html = String::new();
html::push_html(
&mut unsafe_html,
Parser::new(&*readme)
.skip_while(|ev| match *ev {
Event::Start(Tag::Paragraph) => false,
_ => true,
})
.take_while(|ev| match *ev {
Event::End(Tag::Paragraph) => false,
_ => true,
}));
let safe_html = ammonia::clean(&unsafe_html);
PreEscaped(safe_html)
})
let head_id = expect!(try_expect!(try_expect!(repo.head()).resolve()).target());
// Render the readme and grab the first <p> element from it.
find_readme(head_id, repo)
.map(|readme| {
let mut unsafe_html = String::new();
html::push_html(
&mut unsafe_html,
Parser::new(&*readme)
.skip_while(|ev| match *ev {
Event::Start(Tag::Paragraph) => false,
_ => true,
})
.take_while(|ev| match *ev {
Event::End(Tag::Paragraph) => false,
_ => true,
}));
let safe_html = ammonia::clean(&unsafe_html);
PreEscaped(safe_html)
})
}
pub fn Repository(repo: &git2::Repository, head_id: &Oid) -> ::maud::Markup {
html! {
@if let Some(readme) = find_readme(*head_id, repo) {
div.block {
div.block-details {
(Markdown(&*readme))
html! {
@if let Some(readme) = find_readme(*head_id, repo) {
div.block {
div.block-details {
(Markdown(&*readme))
}
}
}
}
}
}
}
pub fn RepositoryIcon(mul: &u8, repo: &git2::Repository) -> ::maud::Markup {
html! {
@match repo.origin_url() {
Some(_) => (FAM::X(*mul, FA::CodeFork)),
None => (FAM::X(*mul, FA::Home)),
html! {
@match repo.origin_url() {
Some(_) => (FAM::X(*mul, FA::CodeFork)),
None => (FAM::X(*mul, FA::Home)),
}
}
}
}
pub fn RepositoryHeader(path: &str, repo: &git2::Repository) -> ::maud::Markup {
html! {
div.block-header {
div.row.center {
(RepositoryIcon(&3, repo))
div.column {
h1 { a href={ "/" (path) } { (path) } }
@if let Some(origin) = repo.origin_url() {
h4 { "(fork of " (super::MaybeLink(&origin, &origin)) ")" }
}
@if let Some(mirrors) = repo.mirrors() {
h4 {
"(mirrored on"
@for (name, url) in mirrors {
" " a href=(url) { (name) }
}
")"
}
}
}
@if repo.find_branch("gh-pages", git2::BranchType::Local).is_ok() {
div.column.fixed {
h3 {
a href={ "/" (path) "/pages/" } {
(FAM::Lg(FA::Book))
" Pages"
}
html! {
div.block-header {
div.row.center {
(RepositoryIcon(&3, repo))
div.column {
h1 { a href={ "/" (path) } { (path) } }
@if let Some(origin) = repo.origin_url() {
h4 { "(fork of " (super::MaybeLink(&origin, &origin)) ")" }
}
@if let Some(mirrors) = repo.mirrors() {
h4 {
"(mirrored on"
@for (name, url) in mirrors {
" " a href=(url) { (name) }
}
")"
}
}
}
@if repo.find_branch("gh-pages", git2::BranchType::Local).is_ok() {
div.column.fixed {
h3 {
a href={ "/" (path) "/pages/" } {
(FAM::Lg(FA::Book))
" Pages"
}
}
}
}
}
}
}
}
}
}
}
pub fn RepositoryStub(path: &str, repo: &git2::Repository) -> ::maud::Markup {
html! {
div.block {
div.block-header {
div.row.center {
(RepositoryIcon(&2, repo))
div.column {
h3 { a href={ "/" (path) } { (path) } }
@if let Some(origin) = repo.origin_url() {
h6 { "(fork of " (super::MaybeLink(&origin, &origin)) ")" }
html! {
div.block {
div.block-header {
div.row.center {
(RepositoryIcon(&2, repo))
div.column {
h3 { a href={ "/" (path) } { (path) } }
@if let Some(origin) = repo.origin_url() {
h6 { "(fork of " (super::MaybeLink(&origin, &origin)) ")" }
}
}
}
}
@if let Some(description) = description(repo) {
div.block-details {
(description)
}
}
}
}
}
@if let Some(description) = description(repo) {
div.block-details {
(description)
}
}
}
}
}
pub fn Repositories(repos: Vec<(String, git2::Repository)>) -> ::maud::Markup {
html! {
@for (path, repo) in repos {
(RepositoryStub(&path, &repo))
html! {
@for (path, repo) in repos {
(RepositoryStub(&path, &repo))
}
}
}
}
// impl<'a> super::repository_wrapper::RepositoryTab for &'a Repository<'a> {
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Overview) }
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Overview) }
// }

Modified src/render/repository_wrapper.rs

@@ -3,34 +3,34 @@ use { RepositoryContext };
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Tab {
Overview,
Files,
Commits,
Reviews,
Overview,
Files,
Commits,
Reviews,
}
pub struct RepositoryWrapper<'a, R: Render>(pub &'a RepositoryContext, pub R, pub Option<Tab>);
impl<'a, R: Render> Render for RepositoryWrapper<'a, R> {
fn render(&self) -> Markup {
let &RepositoryWrapper(ref context, ref content, ref tab) = self;
html!({
div.block {
(super::RepositoryHeader(&context.path, &context.repository))
(RepositoryWrapperTabs(&tab, &context.path, context.repository.head().unwrap().shorthand().unwrap()))
}
(content)
})
}
fn render(&self) -> Markup {
let &RepositoryWrapper(ref context, ref content, ref tab) = self;
html!({
div.block {
(super::RepositoryHeader(&context.path, &context.repository))
(RepositoryWrapperTabs(&tab, &context.path, context.repository.head().unwrap().shorthand().unwrap()))
}
(content)
})
}
}
pub fn RepositoryWrapperTabs(tab: &Option<Tab>, path: &str, head: &str) -> ::maud::Markup {
html! {
div.tabs {
div class={ "overview" @if *tab == Some(Tab::Overview) { " selected" } } { a href={ "/" (path) } { "Overview" } }
div class={ "files" @if *tab == Some(Tab::Files) { " selected" } } { a href={ "/" (path) "/tree/" (head) } { "Files" } }
div class={ "commits" @if *tab == Some(Tab::Commits) { " selected" } } { a href={ "/" (path) "/commits/" (head) } { "Commits" } }
div class={ "reviews" @if *tab == Some(Tab::Reviews) { " selected" } } { a href={ "/" (path) "/reviews" } { "Reviews" } }
html! {
div.tabs {
div class={ "overview" @if *tab == Some(Tab::Overview) { " selected" } } { a href={ "/" (path) } { "Overview" } }
div class={ "files" @if *tab == Some(Tab::Files) { " selected" } } { a href={ "/" (path) "/tree/" (head) } { "Files" } }
div class={ "commits" @if *tab == Some(Tab::Commits) { " selected" } } { a href={ "/" (path) "/commits/" (head) } { "Commits" } }
div class={ "reviews" @if *tab == Some(Tab::Reviews) { " selected" } } { a href={ "/" (path) "/reviews" } { "Reviews" } }
}
}
}
}

Modified src/render/request.rs

@@ -4,77 +4,77 @@ use super::utils::Markdown;
use chrono::naive::datetime::NaiveDateTime;
fn summary(request: &git_appraise::Request) -> Option<&str> {
request.description()
.and_then(|description| description.lines().nth(0))
request.description()
.and_then(|description| description.lines().nth(0))
}
const HEX: &'static [u8; 0x10] = b"0123456789abcdef";
fn short(oid: Oid) -> String {
oid.as_bytes().iter().take(3).flat_map(|b| vec![HEX[((b >> 4) & 0xFu8) as usize] as char, HEX[(b & 0xFu8) as usize] as char]).collect()
oid.as_bytes().iter().take(3).flat_map(|b| vec![HEX[((b >> 4) & 0xFu8) as usize] as char, HEX[(b & 0xFu8) as usize] as char]).collect()
}
pub fn Request(root: &str, request: &git_appraise::Request) -> ::maud::Markup {
html! {
div.block.request {
(RequestHeader(root, request))
(RequestDetails(request))
html! {
div.block.request {
(RequestHeader(root, request))
(RequestDetails(request))
}
}
}
}
pub fn RequestStub(root: &str, request: &git_appraise::Request) -> ::maud::Markup {
html! {
div.block.request {
(RequestHeader(root, request))
html! {
div.block.request {
(RequestHeader(root, request))
}
}
}
}
pub fn RequestHeader(root: &str, request: &git_appraise::Request) -> ::maud::Markup {
html! {
div.block-header {
div.row {
(super::Avatar(request.requester().unwrap_or("unknown@example.org"), &None))
div.column {
div {
a href={ (root) "/review/" (request.commit_id()) } {
span.id (short(request.commit_id()))
" "
@match summary(request) {
Some(summary) => (summary),
None => "<No summary provided>",
}
html! {
div.block-header {
div.row {
(super::Avatar(request.requester().unwrap_or("unknown@example.org"), &None))
div.column {
div {
a href={ (root) "/review/" (request.commit_id()) } {
span.id (short(request.commit_id()))
" "
@match summary(request) {
Some(summary) => (summary),
None => "<No summary provided>",
}
}
}
small {
span.user
(request.requester().unwrap_or("<unknown requester>"))
" wants to merge "
span.ref
(request.review_ref().unwrap_or("<unknown ref>"))
" into "
span.ref
(request.target_ref().unwrap_or("<unknown ref>"))
}
}
div.column.fixed {
@if let Some(timestamp) = request.timestamp() {
small.timestamp
(NaiveDateTime::from_timestamp(timestamp.seconds(), 0))
}
}
}
}
small {
span.user
(request.requester().unwrap_or("<unknown requester>"))
" wants to merge "
span.ref
(request.review_ref().unwrap_or("<unknown ref>"))
" into "
span.ref
(request.target_ref().unwrap_or("<unknown ref>"))
}
}
div.column.fixed {
@if let Some(timestamp) = request.timestamp() {
small.timestamp
(NaiveDateTime::from_timestamp(timestamp.seconds(), 0))
}
}
}
}
}
}
pub fn RequestDetails(request: &git_appraise::Request) -> ::maud::Markup {
html! {
div.block-details.request-details {
@match request.description() {
Some(description) => (Markdown(description)),
None => i "No description provided",
}
html! {
div.block-details.request-details {
@match request.description() {
Some(description) => (Markdown(description)),
None => i "No description provided",
}
}
}
}
}

Modified src/render/review.rs

@@ -1,31 +1,31 @@
use git_appraise;
pub fn Reviews(root: &str, reviews: &Vec<git_appraise::Review>) -> ::maud::Markup {
html! {
@for review in reviews {
(ReviewStub(root, review))
html! {
@for review in reviews {
(ReviewStub(root, review))
}
}
}
}
pub fn ReviewStub(root: &str, review: &git_appraise::Review) -> ::maud::Markup {
html! {
(super::RequestStub(root, review.request()))
}
html! {
(super::RequestStub(root, review.request()))
}
}
pub fn Review(root: &str, review: &git_appraise::Review) -> ::maud::Markup {
html! {
div.review {
(super::Events(root.to_owned(), review.events()))
html! {
div.review {
(super::Events(root.to_owned(), review.events()))
}
}
}
}
// impl<'a> super::repository_wrapper::RepositoryTab for &'a Review<'a> {
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Reviews) }
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Reviews) }
// }
//
// impl<'a> super::repository_wrapper::RepositoryTab for &'a Reviews<'a> {
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Reviews) }
// fn tab() -> Option<super::repository_wrapper::Tab> { Some(super::repository_wrapper::Tab::Reviews) }
// }

Modified src/render/settings.rs

@@ -1,23 +1,23 @@
use settings::{ self, Theme };
pub fn Settings(settings: &settings::Settings) -> ::maud::Markup {
html! {
div.block {
div.block-header {
h3 { "Settings" }
}
div.block-details {
form method="POST" {
label for="theme" { "Theme" }
select id="theme" name="theme" required? {
option value=(Theme::SolarizedDark) selected?[settings.theme == Theme::SolarizedDark] { "Solarized Dark" }
option value=(Theme::SolarizedLight) selected?[settings.theme == Theme::SolarizedLight] { "Solarized Light" }
}
button type="submit" {
"Submit"
}
html! {
div.block {
div.block-header {
h3 { "Settings" }
}
div.block-details {
form method="POST" {
label for="theme" { "Theme" }
select id="theme" name="theme" required? {
option value=(Theme::SolarizedDark) selected?[settings.theme == Theme::SolarizedDark] { "Solarized Dark" }
option value=(Theme::SolarizedLight) selected?[settings.theme == Theme::SolarizedLight] { "Solarized Light" }
}
button type="submit" {
"Submit"
}
}
}
}
}
}
}
}

Modified src/render/signature.rs

@@ -2,19 +2,19 @@ use git2;
use maud::PreEscaped;
pub fn Signature(signature: git2::Signature, include_avatar: bool) -> ::maud::Markup {
html! {
@if include_avatar {
@if let Some(email) = signature.email() {
(super::Avatar(email, &signature.name()))
}
html! {
@if include_avatar {
@if let Some(email) = signature.email() {
(super::Avatar(email, &signature.name()))
}
}
@if let Some(name) = signature.name() {
@if let Some(email) = signature.email() {
a.user href={ "mailto:" (email) } title={ "<" (email) ">" } (name)
} @else {
span.user (name)
}
(PreEscaped("&nbsp;"))
}
}
@if let Some(name) = signature.name() {
@if let Some(email) = signature.email() {
a.user href={ "mailto:" (email) } title={ "<" (email) ">" } (name)
} @else {
span.user (name)
}
(PreEscaped("&nbsp;"))
}
}
}

Modified src/render/style.rs

@@ -1,9 +1,9 @@
use settings::Settings;
pub fn Style(settings: &Settings) -> ::maud::Markup {
html! {
link rel="stylesheet" href="/-/static/css/font-awesome.min.css" { }
link rel="stylesheet" href="/-/static/css/layout.css" { }
link rel="stylesheet" href=(format!("/-/static/css/theme-{}.css", settings.theme)) { }
}
html! {
link rel="stylesheet" href="/-/static/css/font-awesome.min.css" { }
link rel="stylesheet" href="/-/static/css/layout.css" { }
link rel="stylesheet" href=(format!("/-/static/css/theme-{}.css", settings.theme)) { }
}
}

Modified src/render/tree.rs

@@ -6,169 +6,169 @@ use tree_entry::TreeEntryContext;
use maud::{ Render };
pub fn TreeEntryStub(parent: &TreeEntryContext, entry: &git2::TreeEntry) -> ::maud::Markup {
html! {
@if let Some(name) = entry.name() {
li {
@match entry.kind() {
Some(ObjectType::Tree) => {
(FAM::Li(FA::Sitemap))
a href={ "/" (parent.repo_path) "/tree/" (parent.reff) (parent.entry_path.trim_right_matches('/')) "/" (name) } { (name) }
},
Some(ObjectType::Blob) => {
(FAM::Li(FA::File))
a href={ "/" (parent.repo_path) "/blob/" (parent.reff) (parent.entry_path.trim_right_matches('/')) "/" (name) } { (name) }
},
_ => {
(FAM::Li(FA::Question))
(name)
},
html! {
@if let Some(name) = entry.name() {
li {
@match entry.kind() {
Some(ObjectType::Tree) => {
(FAM::Li(FA::Sitemap))
a href={ "/" (parent.repo_path) "/tree/" (parent.reff) (parent.entry_path.trim_right_matches('/')) "/" (name) } { (name) }
},
Some(ObjectType::Blob) => {
(FAM::Li(FA::File))
a href={ "/" (parent.repo_path) "/blob/" (parent.reff) (parent.entry_path.trim_right_matches('/')) "/" (name) } { (name) }
},
_ => {
(FAM::Li(FA::Question))
(name)
},
}
}
}
}
}
}
}
pub fn TreeEntry(context: &TreeEntryContext) -> ::maud::Markup {
html! {
div {
@match context.entry.kind() {
Some(ObjectType::Tree) => (Tree(context.entry.as_tree().unwrap(), context)),
Some(ObjectType::Blob) => (Blob(context.entry.as_blob().unwrap(), context)),
Some(ObjectType::Tag) => "Can't render ObjectType::Tag yet",
Some(ObjectType::Commit) => "Can't render ObjectType::Commit yet",
Some(ObjectType::Any) => "Can't render ObjectType::Any yet",
None => "Can't render without an ObjectType",
}
html! {
div {
@match context.entry.kind() {
Some(ObjectType::Tree) => (Tree(context.entry.as_tree().unwrap(), context)),
Some(ObjectType::Blob) => (Blob(context.entry.as_blob().unwrap(), context)),
Some(ObjectType::Tag) => "Can't render ObjectType::Tag yet",
Some(ObjectType::Commit) => "Can't render ObjectType::Commit yet",
Some(ObjectType::Any) => "Can't render ObjectType::Any yet",
None => "Can't render without an ObjectType",
}
}
}
}
}
pub fn Tree(tree: &git2::Tree, context: &TreeEntryContext) -> ::maud::Markup {
html! {
div.block {
div.block-header {
h2 {
(FAM::FixedWidth(FA::File)) " "
span.path (Components(context))
" at "
(super::Reference(&context.commit))
}
}
div.block-details {
ul.fa-ul {
@if let Some(parent) = context.parent() {
li { (FAM::Li(FA::LevelUp)) a href={ "/" (context.repo_path) "/tree/" (context.reff) (parent) } { ".." } }
}
@for entry in tree.iter().collect::<Vec<_>>().tap(|v| v.sort_by_key(|e| Sorter(e.kind()))) {
(TreeEntryStub(context, &entry))
}
html! {
div.block {
div.block-header {
h2 {
(FAM::FixedWidth(FA::File)) " "
span.path (Components(context))
" at "
(super::Reference(&context.commit))
}
}
div.block-details {
ul.fa-ul {
@if let Some(parent) = context.parent() {
li { (FAM::Li(FA::LevelUp)) a href={ "/" (context.repo_path) "/tree/" (context.reff) (parent) } { ".." } }
}
@for entry in tree.iter().collect::<Vec<_>>().tap(|v| v.sort_by_key(|e| Sorter(e.kind()))) {
(TreeEntryStub(context, &entry))
}
}
}
}
}
}
}
}
pub fn Blob(blob: &git2::Blob, context: &TreeEntryContext) -> ::maud::Markup {
html! {
div.block {
div.block-header {
h2 {
(FAM::FixedWidth(FA::File)) " "
span.path (Components(context))
" at "
(super::Reference(&context.commit))
}
}
pre.block-details {
@match blob.is_binary() {
true => code { "Binary file" },
false => code class={ "hljs lang-" (context.extension().unwrap_or("")) } {
@for (i, line) in str::from_utf8(blob.content()).unwrap().lines().enumerate() {
div.line {
a.line-num id={ "L" (i + 1) } href={ "#L" (i + 1) } data-line-num=(format!("{: >4}", i + 1)) { " " }
span.text (line)
}
html! {
div.block {
div.block-header {
h2 {
(FAM::FixedWidth(FA::File)) " "
span.path (Components(context))
" at "
(super::Reference(&context.commit))
}
}
},
pre.block-details {
@match blob.is_binary() {
true => code { "Binary file" },
false => code class={ "hljs lang-" (context.extension().unwrap_or("")) } {
@for (i, line) in str::from_utf8(blob.content()).unwrap().lines().enumerate() {
div.line {
a.line-num id={ "L" (i + 1) } href={ "#L" (i + 1) } data-line-num=(format!("{: >4}", i + 1)) { " " }
span.text (line)
}
}
},
}
}
(super::HighlightJS())
}
}
(super::HighlightJS())
}
}
}
pub struct Components<'a>(pub &'a TreeEntryContext<'a>);
impl<'a> Render for Components<'a> {
#[allow(cyclomatic_complexity)]
fn render_to(&self, buffer: &mut String) {
let context = self.0;
let is_blob = context.entry.kind() == Some(ObjectType::Blob);
buffer.push_str(&html!({ a.path-component href={ "/" (context.repo_path) "/tree/" (context.reff) } (context.repo_path) }).into_string());
let mut parent = "/".to_owned();
let components: Vec<_> = context.entry_path.split_terminator('/').collect();
for (i, component) in components.iter().enumerate() {
if *component == "" {
continue;
} else if is_blob && i == components.len() - 1 {
buffer.push_str(&html!({ "/" a.path-component href={ "/" (context.repo_path) "/blob/" (context.reff) (parent) (component) } (component) }).into_string());
} else {
buffer.push_str(&html!({ "/" a.path-component href={ "/" (context.repo_path) "/tree/" (context.reff) (parent) (component) } (component) }).into_string());
parent.push_str(component);
parent.push('/');
}
#[allow(cyclomatic_complexity)]
fn render_to(&self, buffer: &mut String) {
let context = self.0;
let is_blob = context.entry.kind() == Some(ObjectType::Blob);
buffer.push_str(&html!({ a.path-component href={ "/" (context.repo_path) "/tree/" (context.reff) } (context.repo_path) }).into_string());
let mut parent = "/".to_owned();
let components: Vec<_> = context.entry_path.split_terminator('/').collect();
for (i, component) in components.iter().enumerate() {
if *component == "" {
continue;
} else if is_blob && i == components.len() - 1 {
buffer.push_str(&html!({ "/" a.path-component href={ "/" (context.repo_path) "/blob/" (context.reff) (parent) (component) } (component) }).into_string());
} else {
buffer.push_str(&html!({ "/" a.path-component href={ "/" (context.repo_path) "/tree/" (context.reff) (parent) (component) } (component) }).into_string());
parent.push_str(component);
parent.push('/');
}
}
}
}
}
trait Tapable {
fn tap<F: Fn(&mut Self)>(self, f: F) -> Self;
fn tap<F: Fn(&mut Self)>(self, f: F) -> Self;
}