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