repos
/ repos-rust master

repos-rust

mirror archived upstream

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

2.4 KB · 72 lines · Rust Raw History
 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}