Parent [>]
1//! Route handlers for branch pages.
2//!
3//! Covers two routes: the branch list (`/{repo}/branches`) and the branch
4//! detail page (`/{repo}/branches/{*branch}`).  The detail handler resolves
5//! the branch ref to its tip commit and walks ancestors to produce a log view.
6
7use askama::Template;
8use askama_web::WebTemplate;
9use axum::extract::{Path, Query, State};
10use axum::response::IntoResponse;
11use serde::Deserialize;
12
13use crate::config::SiteConfig;
14use crate::error::Result;
15use crate::git;
16use crate::routes::{AppState, RepoName};
17
18const PAGE_SIZE: usize = 100;
19
20#[derive(Deserialize)]
21pub(super) struct BranchPath {
22    branch: String,
23}
24
25#[derive(Deserialize)]
26pub(super) struct Pagination {
27    #[serde(default)]
28    after: Option<String>,
29}
30
31/// Template context for the branch list page.
32#[derive(Template, WebTemplate)]
33#[template(path = "branch_list.html")]
34struct BranchList {
35    repo: String,
36    branches: Vec<git::BranchInfo>,
37    site: std::sync::Arc<SiteConfig>,
38}
39
40/// Template context for the branch detail (log) page.
41#[derive(Template, WebTemplate)]
42#[template(path = "branch_detail.html")]
43struct BranchDetail {
44    repo: String,
45    branch: String,
46    tip_sha: String,
47    commits: Vec<git::CommitSummary>,
48    after: Option<String>,
49    next_cursor: Option<String>,
50    site: std::sync::Arc<SiteConfig>,
51}
52
53/// Renders the full list of branches for a repository.
54pub async fn list(
55    State(state): State<AppState>,
56    RepoName(repo): RepoName,
57) -> Result<impl IntoResponse> {
58    let git_repo = git::open_repo(&state.root, &repo)?;
59    let branches = git::load_branches(&git_repo);
60    Ok(BranchList {
61        repo,
62        branches,
63        site: state.site,
64    })
65}
66
67/// Renders a paginated log of commits reachable from the tip of the given branch.
68pub async fn detail(
69    State(state): State<AppState>,
70    RepoName(repo): RepoName,
71    Path(params): Path<BranchPath>,
72    Query(pagination): Query<Pagination>,
73) -> Result<impl IntoResponse> {
74    let after = pagination.after.filter(|s| !s.is_empty());
75    let git_repo = git::open_repo(&state.root, &repo)?;
76    let log = git::load_branch_log(&git_repo, &params.branch, after.clone(), PAGE_SIZE)?;
77
78    Ok(BranchDetail {
79        repo,
80        branch: params.branch,
81        tip_sha: log.tip_sha,
82        commits: log.commits,
83        after,
84        next_cursor: log.next_cursor,
85        site: state.site,
86    })
87}