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