A minimal self-hosted git browser on Rust axum: bare repos rendered as a website with commits, diffs, syntax-highlighted blobs, atom feeds, and clone over HTTPS.
axumdockergitgit-browsergitoxidegixrustself-hosted
1use axum::{
2 extract::{Path, State},
3 http::header,
4 response::{IntoResponse, Response},
5 routing::get,
6 Router,
7};
8
9use crate::AppState;
10
11pub fn router() -> Router<AppState> {
12 Router::new().route("/{name}/atom.xml", get(feed))
13}
14
15async fn feed(Path(name): Path<String>, State(state): State<AppState>) -> Response {
16 let repo_root = state.config.repo_root.clone();
17 let clone_base = state.config.clone_base.clone();
18 let name_for_blocking = name.clone();
19 let result = tokio::task::spawn_blocking(move || -> anyhow::Result<_> {
20 let repo = crate::git::open(&repo_root, &name_for_blocking)?;
21 let summary = crate::git::repo_summary(
22 &crate::git::resolve_path(&repo_root, &name_for_blocking)?,
23 &clone_base,
24 )?;
25 let head = repo.head_commit()?;
26 let commits = crate::git::recent_commits(&repo, head.id, 25)?;
27 Ok((summary, commits))
28 })
29 .await;
30 let (summary, commits) = match result {
31 Ok(Ok(v)) => v,
32 Ok(Err(e)) => {
33 // Generic body: the error string can carry filesystem paths.
34 tracing::debug!("atom feed for unknown repo: {e:#}");
35 return (axum::http::StatusCode::NOT_FOUND, "not found").into_response();
36 }
37 Err(e) => {
38 tracing::error!("atom join: {e}");
39 return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "error").into_response();
40 }
41 };
42
43 let body = match state.env.get_template("atom.xml") {
44 Ok(t) => match t.render(minijinja::context! {
45 repo => &summary,
46 commits => &commits,
47 base_url => &state.config.base_url,
48 clone_base => &state.config.clone_base,
49 now => minijinja::context! { rfc3339 => chrono::Utc::now().to_rfc3339() },
50 }) {
51 Ok(b) => b,
52 Err(e) => {
53 tracing::error!("atom render: {e}");
54 return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "render error")
55 .into_response();
56 }
57 },
58 Err(e) => {
59 tracing::error!("atom template: {e}");
60 return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "render error")
61 .into_response();
62 }
63 };
64
65 let mut resp = body.into_response();
66 resp.headers_mut().insert(
67 header::CONTENT_TYPE,
68 "application/atom+xml; charset=utf-8".parse().unwrap(),
69 );
70 resp
71}