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::filters;
16use crate::git;
17use crate::routes::{AppState, RepoName};
18
19const PAGE_SIZE: usize = 100;
20
21#[derive(Deserialize)]
22pub(super) struct BranchPath {
23    branch: String,
24}
25
26#[derive(Deserialize)]
27pub(super) struct Pagination {
28    #[serde(default)]
29    after: Option<String>,
30}
31
32/// Template context for the branch list page.
33#[derive(Template, WebTemplate)]
34#[template(path = "branch_list.html")]
35struct BranchList {
36    repo: String,
37    branches: Vec<git::BranchInfo>,
38    site: std::sync::Arc<SiteConfig>,
39}
40
41/// Template context for the branch detail (log) page.
42#[derive(Template, WebTemplate)]
43#[template(path = "branch_detail.html")]
44struct BranchDetail {
45    repo: String,
46    branch: String,
47    tip_sha: String,
48    commits: Vec<git::CommitSummary>,
49    after: Option<String>,
50    next_cursor: Option<String>,
51    site: std::sync::Arc<SiteConfig>,
52}
53
54/// Renders the full list of branches for a repository.
55pub async fn list(
56    State(state): State<AppState>,
57    RepoName(repo): RepoName,
58) -> Result<impl IntoResponse> {
59    let git_repo = git::open_repo(&state.root, &repo)?;
60    let branches = git::load_branches(&git_repo);
61    Ok(BranchList {
62        repo,
63        branches,
64        site: state.site,
65    })
66}
67
68/// Renders a paginated log of commits reachable from the tip of the given branch.
69pub async fn detail(
70    State(state): State<AppState>,
71    RepoName(repo): RepoName,
72    Path(params): Path<BranchPath>,
73    Query(pagination): Query<Pagination>,
74) -> Result<impl IntoResponse> {
75    let after = pagination.after.filter(|s| !s.is_empty());
76    let git_repo = git::open_repo(&state.root, &repo)?;
77    let log = git::load_branch_log(&git_repo, &params.branch, after.clone(), PAGE_SIZE)?;
78
79    Ok(BranchDetail {
80        repo,
81        branch: params.branch,
82        tip_sha: log.tip_sha,
83        commits: log.commits,
84        after,
85        next_cursor: log.next_cursor,
86        site: state.site,
87    })
88}