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().route("/{name}/commit/{sha}", get(commit_page))
13}
14
15async fn commit_page(
16 Path((name, sha)): Path<(String, String)>,
17 State(state): State<AppState>,
18) -> Response {
19 let repo_root = state.config.repo_root.clone();
20 let clone_base = state.config.clone_base.clone();
21 let name_for_blocking = name.clone();
22 let sha_for_blocking = sha.clone();
23 let result = tokio::task::spawn_blocking(move || -> anyhow::Result<_> {
24 let repo_path = crate::git::resolve_path(&repo_root, &name_for_blocking)?;
25 let repo = crate::git::open(&repo_root, &name_for_blocking)?;
26 let summary = crate::git::repo_summary(&repo_path, &clone_base)?;
27 let oid = crate::git::resolve_rev(&repo, &sha_for_blocking)?;
28 let commit = crate::git::commit_info(&repo, oid)?;
29 let files = crate::git::diff_commit(&repo_path, oid)?;
30 Ok((summary, commit, files))
31 })
32 .await;
33 match result {
34 Ok(Ok((summary, commit, files))) => render(
35 &state,
36 "commit.html",
37 &format!("/{name}/commit/{sha}"),
38 minijinja::context! {
39 repo => &summary,
40 commit => &commit,
41 files => &files,
42 },
43 ),
44 Ok(Err(e)) => not_found(&state, &name, e),
45 Err(e) => {
46 tracing::error!("commit_page join: {e}");
47 (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "render error").into_response()
48 }
49 }
50}