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 response::{IntoResponse, Response},
4 routing::get,
5 Router,
6};
7
8use crate::render::{not_found, render};
9use crate::AppState;
10
11pub fn router() -> Router<AppState> {
12 Router::new()
13 .route("/{name}/tree/{rev}", get(tree_root))
14 .route("/{name}/tree/{rev}/{*path}", get(tree_nested))
15}
16
17async fn tree_root(
18 Path((name, rev)): Path<(String, String)>,
19 State(state): State<AppState>,
20) -> Response {
21 tree_inner(state, name, rev, String::new()).await
22}
23
24async fn tree_nested(
25 Path((name, rev, path)): Path<(String, String, String)>,
26 State(state): State<AppState>,
27) -> Response {
28 tree_inner(state, name, rev, path).await
29}
30
31async fn tree_inner(state: AppState, name: String, rev: String, path: String) -> Response {
32 let repo_root = state.config.repo_root.clone();
33 let clone_base = state.config.clone_base.clone();
34 let name_for_blocking = name.clone();
35 let rev_for_blocking = rev.clone();
36 let path_for_blocking = path.clone();
37 let result = tokio::task::spawn_blocking(move || -> anyhow::Result<_> {
38 let repo = crate::git::open(&repo_root, &name_for_blocking)?;
39 let summary = crate::git::repo_summary(
40 &crate::git::resolve_path(&repo_root, &name_for_blocking)?,
41 &clone_base,
42 )?;
43 let oid = crate::git::resolve_rev(&repo, &rev_for_blocking)?;
44 let (entries, breadcrumb) = crate::git::list_tree(&repo, oid, &path_for_blocking)?;
45 Ok((summary, entries, breadcrumb))
46 })
47 .await;
48 match result {
49 Ok(Ok((summary, entries, breadcrumb))) => render(
50 &state,
51 "tree.html",
52 &format!("/{name}/tree/{rev}/{path}"),
53 minijinja::context! {
54 repo => &summary,
55 rev => &rev,
56 path => &path,
57 entries => &entries,
58 breadcrumb => &breadcrumb,
59 },
60 ),
61 Ok(Err(e)) => not_found(&state, &name, e),
62 Err(e) => {
63 tracing::error!("tree_page join: {e}");
64 (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "render error").into_response()
65 }
66 }
67}