wayver's git archive


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

src/handlers/repo_log.rs@788f4460a8c003cb5018293ae250a8d8feaa2f1b

raw
Date Commit Message Author Files + -
2026-02-17 21:07 initial mvp wayverd 74 10800 0
...

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