Nicer error handling when calling out to git2
Diff
Cargo.lock | 19 +++++++++++++++++++
Cargo.toml | 2 ++
src/git.rs | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
src/main.rs | 15 ++++++++++++---
src/methods/repo.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
5 files changed, 286 insertions(+), 185 deletions(-)
@@ -200,6 +200,18 @@
]
[[package]]
name = "axum-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6293dae2ec708e679da6736e857cf8532886ef258e92930f38279c12641628b8"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -948,6 +960,12 @@
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
@@ -1918,6 +1936,7 @@
"anyhow",
"askama",
"axum",
"axum-macros",
"bat",
"bincode",
"bytes",
@@ -7,7 +7,9 @@
[dependencies]
askama = "0.11"
anyhow = "1.0"
axum = "0.5"
axum-macros = "0.2"
bat = { version = "0.21", default-features = false, features = ["build-assets"] }
bytes = "1.1"
bincode = "1.3"
@@ -1,8 +1,9 @@
use std::ffi::OsStr;
use std::path::Path;
use std::{borrow::Cow, fmt::Write, path::PathBuf, sync::Arc, time::Duration};
use crate::syntax_highlight::ComrakSyntectAdapter;
use anyhow::{Context, Result};
use bytes::{Bytes, BytesMut};
use comrak::{ComrakOptions, ComrakPlugins};
use git2::{
@@ -46,19 +47,20 @@
impl Git {
#[instrument(skip(self))]
pub async fn repo(self: Arc<Self>, repo_path: PathBuf) -> Arc<OpenRepository> {
pub async fn repo(self: Arc<Self>, repo_path: PathBuf) -> Result<Arc<OpenRepository>> {
let repo = tokio::task::spawn_blocking({
let repo_path = repo_path.clone();
move || git2::Repository::open(repo_path).unwrap()
move || git2::Repository::open(repo_path)
})
.await
.unwrap();
.context("Failed to join Tokio task")?
.context("Failed to open repository")?;
Arc::new(OpenRepository {
Ok(Arc::new(OpenRepository {
git: self,
cache_key: repo_path,
repo: Mutex::new(repo),
})
}))
}
}
@@ -74,59 +76,72 @@
path: Option<PathBuf>,
tree_id: Option<&str>,
branch: Option<String>,
) -> PathDestination {
let tree_id = tree_id.map(Oid::from_str).transpose().unwrap();
) -> Result<PathDestination> {
let tree_id = tree_id
.map(Oid::from_str)
.transpose()
.context("Failed to parse tree hash")?;
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let mut tree = if let Some(tree_id) = tree_id {
repo.find_tree(tree_id).unwrap()
repo.find_tree(tree_id)
.context("Couldn't find tree with given id")?
} else if let Some(branch) = branch {
let branch = repo.find_branch(&branch, BranchType::Local).unwrap();
branch.get().peel_to_tree().unwrap()
let branch = repo.find_branch(&branch, BranchType::Local)?;
branch
.get()
.peel_to_tree()
.context("Couldn't find tree for branch")?
} else {
let head = repo.head().unwrap();
head.peel_to_tree().unwrap()
let head = repo.head()?;
head.peel_to_tree()
.context("Couldn't find tree from HEAD")?
};
if let Some(path) = path.as_ref() {
let item = tree.get_path(path).unwrap();
let object = item.to_object(&repo).unwrap();
let item = tree.get_path(path).context("Path doesn't exist in tree")?;
let object = item
.to_object(&repo)
.context("Path in tree isn't an object")?;
if let Some(blob) = object.as_blob() {
let name = item.name().unwrap().to_string();
let path = path.clone().join(&name);
let name = String::from_utf8_lossy(item.name_bytes());
let path = path.clone().join(&*name);
let extension = path
.extension()
.or_else(|| path.file_name())
.unwrap()
.to_string_lossy();
let content = format_file(blob.content(), &extension, &self.git.syntax_set);
return PathDestination::File(FileWithContent {
.map(|v| v.to_string_lossy())
.unwrap_or_else(|| Cow::Borrowed(""));
let content = format_file(blob.content(), &extension, &self.git.syntax_set)?;
return Ok(PathDestination::File(FileWithContent {
metadata: File {
mode: item.filemode(),
size: blob.size(),
path,
name,
name: name.into_owned(),
},
content,
});
}));
} else if let Ok(new_tree) = object.into_tree() {
tree = new_tree;
} else {
panic!("unknown item kind");
anyhow::bail!("Given path not tree nor blob... what is it?!");
}
}
let mut tree_items = Vec::new();
for item in tree.iter() {
let object = item.to_object(&repo).unwrap();
let name = item.name().unwrap().to_string();
let object = item
.to_object(&repo)
.context("Expected item in tree to be object but it wasn't")?;
let name = String::from_utf8_lossy(item.name_bytes()).into_owned();
let path = path.clone().unwrap_or_default().join(&name);
if let Some(blob) = object.as_blob() {
@@ -145,14 +160,14 @@
}
}
PathDestination::Tree(tree_items)
Ok(PathDestination::Tree(tree_items))
})
.await
.unwrap()
.context("Failed to join Tokio task")?
}
#[instrument(skip(self))]
pub async fn tag_info(self: Arc<Self>, tag_name: &str) -> DetailedTag {
pub async fn tag_info(self: Arc<Self>, tag_name: &str) -> Result<DetailedTag> {
let reference = format!("refs/tags/{tag_name}");
let tag_name = tag_name.to_string();
@@ -161,10 +176,10 @@
let tag = repo
.find_reference(&reference)
.unwrap()
.context("Given reference does not exist in repository")?
.peel_to_tag()
.unwrap();
let tag_target = tag.target().unwrap();
.context("Couldn't get to a tag from the given reference")?;
let tag_target = tag.target().context("Couldn't find tagged object")?;
let tagged_object = match tag_target.kind() {
Some(ObjectType::Commit) => Some(TaggedObject::Commit(tag_target.id().to_string())),
@@ -172,45 +187,49 @@
None | Some(_) => None,
};
DetailedTag {
Ok(DetailedTag {
name: tag_name,
tagger: tag.tagger().map(Into::into),
message: tag.message().unwrap().to_string(),
tagger: tag.tagger().map(TryInto::try_into).transpose()?,
message: tag
.message_bytes()
.map(String::from_utf8_lossy)
.unwrap_or_else(|| Cow::Borrowed(""))
.into_owned(),
tagged_object,
}
})
})
.await
.unwrap()
.context("Failed to join Tokio task")?
}
#[instrument(skip(self))]
pub async fn refs(self: Arc<Self>) -> Arc<Refs> {
pub async fn refs(self: Arc<Self>) -> Result<Arc<Refs>, Arc<anyhow::Error>> {
let git = self.git.clone();
git.refs
.get_with(self.cache_key.clone(), async move {
.try_get_with(self.cache_key.clone(), async move {
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let ref_iter = repo.references().unwrap();
let ref_iter = repo.references().context("Couldn't get list of references for repository")?;
let mut built_refs = Refs::default();
for ref_ in ref_iter {
let ref_ = ref_.unwrap();
let ref_ = ref_?;
if ref_.is_branch() {
let commit = ref_.peel_to_commit().unwrap();
let commit = ref_.peel_to_commit().context("Reference is apparently a branch but I couldn't get to the HEAD of it")?;
built_refs.branch.push(Branch {
name: ref_.shorthand().unwrap().to_string(),
commit: commit.into(),
name: String::from_utf8_lossy(ref_.shorthand_bytes()).into_owned(),
commit: commit.try_into()?,
});
} else if ref_.is_tag() {
if let Ok(tag) = ref_.peel_to_tag() {
built_refs.tag.push(Tag {
name: ref_.shorthand().unwrap().to_string(),
tagger: tag.tagger().map(Into::into),
name: String::from_utf8_lossy(ref_.shorthand_bytes()).into_owned(),
tagger: tag.tagger().map(TryInto::try_into).transpose()?,
});
}
}
@@ -225,28 +244,34 @@
two_tagger.cmp(&one_tagger)
});
Arc::new(built_refs)
Ok(Arc::new(built_refs))
})
.await
.unwrap()
.context("Failed to join Tokio task")?
})
.await
}
#[instrument(skip(self))]
pub async fn readme(self: Arc<Self>) -> Option<(ReadmeFormat, Arc<str>)> {
pub async fn readme(
self: Arc<Self>,
) -> Result<Option<(ReadmeFormat, Arc<str>)>, Arc<anyhow::Error>> {
const README_FILES: &[&str] = &["README.md", "README", "README.txt"];
let git = self.git.clone();
git.readme_cache
.get_with(self.cache_key.clone(), async move {
.try_get_with(self.cache_key.clone(), async move {
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let head = repo.head().unwrap();
let commit = head.peel_to_commit().unwrap();
let tree = commit.tree().unwrap();
let head = repo.head().context("Couldn't find HEAD of repository")?;
let commit = head.peel_to_commit().context(
"Couldn't find the commit that the HEAD of the repository refers to",
)?;
let tree = commit
.tree()
.context("Couldn't get the tree that the HEAD refers to")?;
for name in README_FILES {
let tree_entry = if let Some(file) = tree.get_name(name) {
@@ -273,64 +298,68 @@
if Path::new(name).extension().and_then(OsStr::to_str) == Some("md") {
let value = parse_and_transform_markdown(content, &self.git.syntax_set);
return Some((ReadmeFormat::Markdown, Arc::from(value)));
return Ok(Some((ReadmeFormat::Markdown, Arc::from(value))));
}
return Some((ReadmeFormat::Plaintext, Arc::from(content)));
return Ok(Some((ReadmeFormat::Plaintext, Arc::from(content))));
}
None
Ok(None)
})
.await
.unwrap()
.context("Failed to join Tokio task")?
})
.await
}
#[instrument(skip(self))]
pub async fn latest_commit(self: Arc<Self>) -> Commit {
pub async fn latest_commit(self: Arc<Self>) -> Result<Commit> {
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let head = repo.head().unwrap();
let commit = head.peel_to_commit().unwrap();
let head = repo.head().context("Couldn't find HEAD of repository")?;
let commit = head
.peel_to_commit()
.context("Couldn't find commit HEAD of repository refers to")?;
let (diff_plain, diff_output, diff_stats) =
fetch_diff_and_stats(&repo, &commit, &self.git.syntax_set);
fetch_diff_and_stats(&repo, &commit, &self.git.syntax_set)?;
let mut commit = Commit::from(commit);
let mut commit = Commit::try_from(commit)?;
commit.diff_stats = diff_stats;
commit.diff = diff_output;
commit.diff_plain = diff_plain;
commit
Ok(commit)
})
.await
.unwrap()
.context("Failed to join Tokio task")?
}
#[instrument(skip(self))]
pub async fn commit(self: Arc<Self>, commit: &str) -> Arc<Commit> {
let commit = Oid::from_str(commit).unwrap();
pub async fn commit(self: Arc<Self>, commit: &str) -> Result<Arc<Commit>, Arc<anyhow::Error>> {
let commit = Oid::from_str(commit)
.map_err(anyhow::Error::from)
.map_err(Arc::new)?;
let git = self.git.clone();
git.commits
.get_with(commit, async move {
.try_get_with(commit, async move {
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let commit = repo.find_commit(commit).unwrap();
let commit = repo.find_commit(commit)?;
let (diff_plain, diff_output, diff_stats) =
fetch_diff_and_stats(&repo, &commit, &self.git.syntax_set);
fetch_diff_and_stats(&repo, &commit, &self.git.syntax_set)?;
let mut commit = Commit::from(commit);
let mut commit = Commit::try_from(commit)?;
commit.diff_stats = diff_stats;
commit.diff = diff_output;
commit.diff_plain = diff_plain;
Arc::new(commit)
Ok(Arc::new(commit))
})
.await
.unwrap()
.context("Failed to join Tokio task")?
})
.await
}
@@ -427,14 +456,16 @@
time: OffsetDateTime,
}
impl From<Signature<'_>> for CommitUser {
fn from(v: Signature<'_>) -> Self {
CommitUser {
name: v.name().unwrap().to_string(),
email: v.email().unwrap().to_string(),
impl TryFrom<Signature<'_>> for CommitUser {
type Error = anyhow::Error;
fn try_from(v: Signature<'_>) -> Result<Self> {
Ok(CommitUser {
name: String::from_utf8_lossy(v.name_bytes()).into_owned(),
email: String::from_utf8_lossy(v.email_bytes()).into_owned(),
email_md5: format!("{:x}", md5::compute(v.email_bytes())),
time: OffsetDateTime::from_unix_timestamp(v.when().seconds()).unwrap(),
}
time: OffsetDateTime::from_unix_timestamp(v.when().seconds())?,
})
}
}
@@ -469,21 +500,31 @@
pub diff: String,
pub diff_plain: Bytes,
}
impl TryFrom<git2::Commit<'_>> for Commit {
type Error = anyhow::Error;
impl From<git2::Commit<'_>> for Commit {
fn from(commit: git2::Commit<'_>) -> Self {
Commit {
author: commit.author().into(),
committer: commit.committer().into(),
fn try_from(commit: git2::Commit<'_>) -> Result<Self> {
Ok(Commit {
author: CommitUser::try_from(commit.author())?,
committer: CommitUser::try_from(commit.committer())?,
oid: commit.id().to_string(),
tree: commit.tree_id().to_string(),
parents: commit.parent_ids().map(|v| v.to_string()).collect(),
summary: commit.summary().unwrap().to_string(),
body: commit.body().map(ToString::to_string).unwrap_or_default(),
summary: commit
.summary_bytes()
.map(String::from_utf8_lossy)
.unwrap_or_else(|| Cow::Borrowed(""))
.into_owned(),
body: commit
.body_bytes()
.map(String::from_utf8_lossy)
.unwrap_or_else(|| Cow::Borrowed(""))
.into_owned(),
diff_stats: String::with_capacity(0),
diff: String::with_capacity(0),
diff_plain: Bytes::new(),
}
})
}
}
@@ -522,37 +563,35 @@
repo: &git2::Repository,
commit: &git2::Commit<'_>,
syntax_set: &SyntaxSet,
) -> (Bytes, String, String) {
let current_tree = commit.tree().unwrap();
) -> Result<(Bytes, String, String)> {
let current_tree = commit.tree().context("Couldn't get tree for the commit")?;
let parent_tree = commit.parents().next().and_then(|v| v.tree().ok());
let mut diff_opts = DiffOptions::new();
let mut diff = repo
.diff_tree_to_tree(
parent_tree.as_ref(),
Some(¤t_tree),
Some(&mut diff_opts),
)
.unwrap();
let mut diff = repo.diff_tree_to_tree(
parent_tree.as_ref(),
Some(¤t_tree),
Some(&mut diff_opts),
)?;
let mut diff_plain = BytesMut::new();
let email = diff.format_email(1, 1, commit, None).unwrap();
let email = diff
.format_email(1, 1, commit, None)
.context("Couldn't build diff for commit")?;
diff_plain.extend_from_slice(&*email);
let diff_stats = diff
.stats()
.unwrap()
.to_buf(DiffStatsFormat::FULL, 80)
.unwrap()
.stats()?
.to_buf(DiffStatsFormat::FULL, 80)?
.as_str()
.unwrap()
.unwrap_or("")
.to_string();
let diff_output = format_diff(&diff, syntax_set);
let diff_output = format_diff(&diff, syntax_set)?;
(diff_plain.freeze(), diff_output, diff_stats)
Ok((diff_plain.freeze(), diff_output, diff_stats))
}
fn format_file(content: &[u8], extension: &str, syntax_set: &SyntaxSet) -> String {
let content = std::str::from_utf8(content).unwrap();
fn format_file(content: &[u8], extension: &str, syntax_set: &SyntaxSet) -> Result<String> {
let content = String::from_utf8_lossy(content);
let syntax = syntax_set
.find_syntax_by_extension(extension)
@@ -560,20 +599,20 @@
let mut html_generator =
ClassedHTMLGenerator::new_with_class_style(syntax, syntax_set, ClassStyle::Spaced);
for line in LinesWithEndings::from(content) {
for line in LinesWithEndings::from(&content) {
html_generator
.parse_html_for_line_which_includes_newline(line)
.unwrap();
.context("Couldn't parse line of file")?;
}
format!(
Ok(format!(
"<code>{}</code>",
html_generator.finalize().replace('\n', "</code>\n<code>")
)
))
}
#[instrument(skip(diff, syntax_set))]
fn format_diff(diff: &git2::Diff<'_>, syntax_set: &SyntaxSet) -> String {
fn format_diff(diff: &git2::Diff<'_>, syntax_set: &SyntaxSet) -> Result<String> {
let mut diff_output = String::new();
diff.print(DiffFormat::Patch, |delta, _diff_hunk, diff_line| {
@@ -590,11 +629,14 @@
let line = String::from_utf8_lossy(diff_line.content());
let extension = if should_highlight_as_source {
let path = delta.new_file().path().unwrap();
path.extension()
.or_else(|| path.file_name())
.unwrap()
.to_string_lossy()
if let Some(path) = delta.new_file().path() {
path.extension()
.or_else(|| path.file_name())
.map(|v| v.to_string_lossy())
.unwrap_or_else(|| Cow::Borrowed(""))
} else {
Cow::Borrowed("")
}
} else {
Cow::Borrowed("patch")
};
@@ -603,11 +645,9 @@
.unwrap_or_else(|| syntax_set.find_syntax_plain_text());
let mut html_generator =
ClassedHTMLGenerator::new_with_class_style(syntax, syntax_set, ClassStyle::Spaced);
html_generator
.parse_html_for_line_which_includes_newline(&line)
.unwrap();
let _ = html_generator.parse_html_for_line_which_includes_newline(&line);
if let Some(class) = class {
write!(diff_output, r#"<span class="diff-{class}">"#).unwrap();
let _ = write!(diff_output, r#"<span class="diff-{class}">"#);
}
diff_output.push_str(&html_generator.finalize());
if class.is_some() {
@@ -616,7 +656,7 @@
true
})
.unwrap();
.context("Failed to prepare diff")?;
diff_output
Ok(diff_output)
}
@@ -55,11 +55,20 @@
let theme = bat_assets.get_theme("GitHub");
let css = syntect::html::css_for_theme_with_class_style(theme, ClassStyle::Spaced).unwrap();
let css = Box::leak(format!(r#"@media (prefers-color-scheme: light){{{}}}"#, css).into_boxed_str().into_boxed_bytes());
let css = Box::leak(
format!(r#"@media (prefers-color-scheme: light){{{}}}"#, css)
.into_boxed_str()
.into_boxed_bytes(),
);
let dark_theme = bat_assets.get_theme("TwoDark");
let dark_css = syntect::html::css_for_theme_with_class_style(dark_theme, ClassStyle::Spaced).unwrap();
let dark_css = Box::leak(format!(r#"@media (prefers-color-scheme: dark){{{}}}"#, dark_css).into_boxed_str().into_boxed_bytes());
let dark_css =
syntect::html::css_for_theme_with_class_style(dark_theme, ClassStyle::Spaced).unwrap();
let dark_css = Box::leak(
format!(r#"@media (prefers-color-scheme: dark){{{}}}"#, dark_css)
.into_boxed_str()
.into_boxed_bytes(),
);
let app = Router::new()
.route("/", get(methods::index::handle))
@@ -8,6 +8,7 @@
};
use askama::Template;
use axum::http::StatusCode;
use axum::{
body::HttpBody,
extract::Query,
@@ -53,6 +54,28 @@
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Error(anyhow::Error);
impl From<Arc<anyhow::Error>> for Error {
fn from(e: Arc<anyhow::Error>) -> Self {
Self(anyhow::Error::msg(format!("{:?}", e)))
}
}
impl From<anyhow::Error> for Error {
fn from(e: anyhow::Error) -> Self {
Self(e)
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
(StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self.0)).into_response()
}
}
pub async fn service<ReqBody: HttpBody + Send + Debug + 'static>(
mut request: Request<ReqBody>,
@@ -71,22 +94,28 @@
let mut child_path = None;
macro_rules! h {
($handler:ident) => {
BoxCloneService::new($handler.into_service())
};
}
let mut service = match uri_parts.pop() {
Some("about") => BoxCloneService::new(handle_about.into_service()),
Some("refs") if uri_parts.last() == Some(&"info") => {
uri_parts.pop();
BoxCloneService::new(handle_info_refs.into_service())
h!(handle_info_refs)
}
Some("git-upload-pack") => BoxCloneService::new(handle_git_upload_pack.into_service()),
Some("refs") => BoxCloneService::new(handle_refs.into_service()),
Some("log") => BoxCloneService::new(handle_log.into_service()),
Some("tree") => BoxCloneService::new(handle_tree.into_service()),
Some("commit") => BoxCloneService::new(handle_commit.into_service()),
Some("diff") => BoxCloneService::new(handle_diff.into_service()),
Some("patch") => BoxCloneService::new(handle_patch.into_service()),
Some("tag") => BoxCloneService::new(handle_tag.into_service()),
Some("git-upload-pack") => h!(handle_git_upload_pack),
Some("refs") => h!(handle_refs),
Some("log") => h!(handle_log),
Some("tree") => h!(handle_tree),
Some("commit") => h!(handle_commit),
Some("diff") => h!(handle_diff),
Some("patch") => h!(handle_patch),
Some("tag") => h!(handle_tag),
Some(v) => {
uri_parts.push(v);
@@ -107,9 +136,9 @@
child_path = Some(reconstructed_path.into_iter().collect::<PathBuf>().clean());
BoxCloneService::new(handle_tree.into_service())
h!(handle_tree)
} else {
BoxCloneService::new(handle_summary.into_service())
h!(handle_summary)
}
}
None => panic!("not found"),
@@ -142,20 +171,20 @@
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Arc<Git>>,
Extension(db): Extension<sled::Db>,
) -> Response {
let open_repo = git.repo(repository_path).await;
let refs = open_repo.refs().await;
) -> Result<Response> {
let open_repo = git.repo(repository_path).await?;
let refs = open_repo.refs().await?;
let repository = crate::database::schema::repository::Repository::open(&db, &*repo).unwrap();
let commit_tree = repository.get().commit_tree(&db, "refs/heads/master");
let commits = commit_tree.fetch_latest(11, 0).await;
let commit_list = commits.iter().map(Yoke::get).collect();
into_response(&SummaryView {
Ok(into_response(&SummaryView {
repo,
refs,
commit_list,
})
}))
}
#[derive(Deserialize)]
@@ -176,11 +205,11 @@
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Arc<Git>>,
Query(query): Query<TagQuery>,
) -> Response {
let open_repo = git.repo(repository_path).await;
let tag = open_repo.tag_info(&query.name).await;
) -> Result<Response> {
let open_repo = git.repo(repository_path).await?;
let tag = open_repo.tag_info(&query.name).await?;
into_response(&TagView { repo, tag })
Ok(into_response(&TagView { repo, tag }))
}
#[derive(Deserialize)]
@@ -204,7 +233,7 @@
Extension(repo): Extension<Repository>,
Extension(db): Extension<sled::Db>,
Query(query): Query<LogQuery>,
) -> Response {
) -> Result<Response> {
let offset = query.offset.unwrap_or(0);
let reference = format!("refs/heads/{}", query.branch.as_deref().unwrap_or("master"));
@@ -221,12 +250,12 @@
let commits = commits.iter().map(Yoke::get).collect();
into_response(&LogView {
Ok(into_response(&LogView {
repo,
commits,
next_offset,
branch: query.branch,
})
}))
}
#[derive(Deserialize)]
@@ -237,7 +266,7 @@
pub async fn handle_info_refs(
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Query(query): Query<SmartGitQuery>,
) -> Response {
) -> Result<Response> {
let out = std::process::Command::new("git")
.arg("http-backend")
@@ -248,13 +277,13 @@
.output()
.unwrap();
crate::git_cgi::cgi_to_response(&out.stdout)
Ok(crate::git_cgi::cgi_to_response(&out.stdout))
}
pub async fn handle_git_upload_pack(
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
body: Bytes,
) -> Response {
) -> Result<Response> {
let mut child = std::process::Command::new("git")
.arg("http-backend")
@@ -270,7 +299,7 @@
child.stdin.as_mut().unwrap().write_all(&body).unwrap();
let out = child.wait_with_output().unwrap();
crate::git_cgi::cgi_to_response(&out.stdout)
Ok(crate::git_cgi::cgi_to_response(&out.stdout))
}
#[derive(Template)]
@@ -284,11 +313,11 @@
Extension(repo): Extension<Repository>,
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Arc<Git>>,
) -> Response {
let open_repo = git.repo(repository_path).await;
let refs = open_repo.refs().await;
) -> Result<Response> {
let open_repo = git.repo(repository_path).await?;
let refs = open_repo.refs().await?;
into_response(&RefsView { repo, refs })
Ok(into_response(&RefsView { repo, refs }))
}
#[derive(Template)]
@@ -302,11 +331,11 @@
Extension(repo): Extension<Repository>,
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Arc<Git>>,
) -> Response {
let open_repo = git.clone().repo(repository_path).await;
let readme = open_repo.readme().await;
) -> Result<Response> {
let open_repo = git.clone().repo(repository_path).await?;
let readme = open_repo.readme().await?;
into_response(&AboutView { repo, readme })
Ok(into_response(&AboutView { repo, readme }))
}
#[derive(Template)]
@@ -326,15 +355,15 @@
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Arc<Git>>,
Query(query): Query<CommitQuery>,
) -> Response {
let open_repo = git.repo(repository_path).await;
) -> Result<Response> {
let open_repo = git.repo(repository_path).await?;
let commit = if let Some(commit) = query.id {
open_repo.commit(&commit).await
open_repo.commit(&commit).await?
} else {
Arc::new(open_repo.latest_commit().await)
Arc::new(open_repo.latest_commit().await?)
};
into_response(&CommitView { repo, commit })
Ok(into_response(&CommitView { repo, commit }))
}
#[derive(Deserialize)]
@@ -367,7 +396,7 @@
Extension(ChildPath(child_path)): Extension<ChildPath>,
Extension(git): Extension<Arc<Git>>,
Query(query): Query<TreeQuery>,
) -> Response {
) -> Result<Response> {
#[derive(Template)]
#[template(path = "repo/tree.html")]
pub struct TreeView {
@@ -383,15 +412,17 @@
pub file: FileWithContent,
}
let open_repo = git.repo(repository_path).await;
let open_repo = git.repo(repository_path).await?;
match open_repo
.path(child_path, query.id.as_deref(), query.branch.clone())
.await
{
PathDestination::Tree(items) => into_response(&TreeView { repo, items, query }),
PathDestination::File(file) => into_response(&FileView { repo, file }),
}
Ok(
match open_repo
.path(child_path, query.id.as_deref(), query.branch.clone())
.await?
{
PathDestination::Tree(items) => into_response(&TreeView { repo, items, query }),
PathDestination::File(file) => into_response(&FileView { repo, file }),
},
)
}
#[derive(Template)]
@@ -406,27 +437,27 @@
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Arc<Git>>,
Query(query): Query<CommitQuery>,
) -> Response {
let open_repo = git.repo(repository_path).await;
) -> Result<Response> {
let open_repo = git.repo(repository_path).await?;
let commit = if let Some(commit) = query.id {
open_repo.commit(&commit).await
open_repo.commit(&commit).await?
} else {
Arc::new(open_repo.latest_commit().await)
Arc::new(open_repo.latest_commit().await?)
};
into_response(&DiffView { repo, commit })
Ok(into_response(&DiffView { repo, commit }))
}
pub async fn handle_patch(
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Arc<Git>>,
Query(query): Query<CommitQuery>,
) -> Response {
let open_repo = git.repo(repository_path).await;
) -> Result<Response> {
let open_repo = git.repo(repository_path).await?;
let commit = if let Some(commit) = query.id {
open_repo.commit(&commit).await
open_repo.commit(&commit).await?
} else {
Arc::new(open_repo.latest_commit().await)
Arc::new(open_repo.latest_commit().await?)
};
let headers = [(
@@ -434,5 +465,5 @@
HeaderValue::from_static("text/plain"),
)];
(headers, commit.diff_plain.clone()).into_response()
Ok((headers, commit.diff_plain.clone()).into_response())
}