raw
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 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 let n = r[i + 1..].parse::<usize>().ok().unwrap_or(1);
68 format!("{}~{}", &r[..i], n + config.log_per_page)
69 } else {
70 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 let next_page = if commits.len() < config.log_per_page + 1 {
83 None
84 } else {
85 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