wayver's git archive


a simple self-hosted git server
git clone https://git.wayver.dev/bile

src/handlers/repo_log.rs@1160e836ead3fbd29e297268d1009d648b68cae0

raw
Date Commit Message Author Files + -
2026-02-20 20:25 remove unneeded result type alias wayverd 13 27 31
...

1use axum::{
2    extract::State,
3    http::StatusCode,
4    response::{IntoResponse as _, Response},
5};
6
7use crate::{
8    BileState,
9    config::Config,
10    error::{Context as _, Result},
11    git::Repository,
12    http::{
13        extractor::{ObjectName, Ref, RepoName},
14        path::Path,
15        response::{ErrorPage, Html, Redirect},
16    },
17    utils::filters,
18};
19
20#[derive(askama::Template)]
21#[template(path = "log.html")]
22struct RepoLogTemplate<'a> {
23    config: &'a Config,
24    repo: &'a Repository,
25    commits: Vec<git2::Commit<'a>>,
26    branch: String,
27    // the spec the user should be linked to to see the next page of commits
28    next_page: Option<String>,
29}
30
31#[tracing::instrument(skip_all)]
32pub(crate) async fn get_1(state: State<BileState>, Path(repo_name): Path<RepoName>) -> Response {
33    state
34        .spawn(move |state| inner(&state, &repo_name, None, None))
35        .await
36}
37
38#[tracing::instrument(skip_all)]
39pub(crate) async fn get_2(
40    state: State<BileState>,
41    Path((repo_name, r#ref)): Path<(RepoName, Ref)>,
42) -> Response {
43    state
44        .spawn(move |state| inner(&state, &repo_name, Some(&r#ref), None))
45        .await
46}
47
48#[tracing::instrument(skip_all)]
49pub(crate) async fn get_3(
50    state: State<BileState>,
51    Path((repo_name, r#ref, object_name)): Path<(RepoName, Ref, ObjectName)>,
52) -> Response {
53    state
54        .spawn(move |state| inner(&state, &repo_name, Some(&r#ref), Some(&object_name)))
55        .await
56}
57
58fn inner(
59    state: &BileState,
60    repo_name: &RepoName,
61    r#ref: Option<&Ref>,
62    object_name: Option<&ObjectName>,
63) -> Result<Response> {
64    let Some(repo) = Repository::open(&state.config, repo_name).context("opening repository")?
65    else {
66        return Ok(ErrorPage::from(state)
67            .with_status(StatusCode::NOT_FOUND)
68            .into_response());
69    };
70
71    if repo.is_empty()? {
72        return Ok(Redirect::permanent(&format!("/{repo_name}"))
73            .unwrap_or(Redirect::PERMANENT_ROOT)
74            .into_response());
75    }
76
77    let r = r#ref.map_or("HEAD", |r| r.0.as_str());
78
79    let next_page_spec = if repo.is_shallow() {
80        String::new()
81    } else if let Some(i) = r.rfind('~') {
82        // there is a tilde, try to find a number too
83        let n = r[i + 1..].parse::<usize>().ok().unwrap_or(1);
84        format!("{}~{}", &r[..i], n + state.config.log_per_page)
85    } else {
86        // there was no tilde
87        format!("{}~{}", r, state.config.log_per_page)
88    };
89
90    let Some(mut commits) = repo
91        .commits_for_obj(r, state.config.log_per_page + 1, object_name)
92        .context("failed to get commits for object")?
93    else {
94        return Ok(ErrorPage::from(state)
95            .with_status(StatusCode::NOT_FOUND)
96            .into_response());
97    };
98
99    // check if there even is a next page
100    let next_page = if commits.len() < state.config.log_per_page + 1 {
101        None
102    } else {
103        // remove additional commit from next page check
104        commits.pop();
105        Some(next_page_spec)
106    };
107
108    let branch = repo.ref_or_head_shorthand(r#ref)?;
109
110    Ok(Html(RepoLogTemplate {
111        config: &state.config,
112        repo: &repo,
113        commits,
114        branch,
115        next_page,
116    })
117    .into_response())
118}
119