| 1 | //! `/{repo}/refs` — branches and tags. |
| 2 | |
| 3 | use askama::Template; |
| 4 | use axum::http::StatusCode; |
| 5 | use axum::response::Response; |
| 6 | |
| 7 | use super::repo::{Active, Crumb, Ctx, RepoNav, set_cache_headers}; |
| 8 | use super::{Layout, render}; |
| 9 | use crate::config::SnapshotFormat; |
| 10 | use crate::git::refs::RefEntry; |
| 11 | use crate::render::encode_path; |
| 12 | use crate::render::time; |
| 13 | |
| 14 | pub struct RefRow { |
| 15 | pub name: String, |
| 16 | pub url: String, |
| 17 | pub subject: String, |
| 18 | pub age: String, |
| 19 | pub iso: String, |
| 20 | pub is_default: bool, |
| 21 | /// Snapshot downloads (`tar.gz`, `zip`), on tags. |
| 22 | pub archives: Vec<(&'static str, String)>, |
| 23 | } |
| 24 | |
| 25 | #[derive(Template)] |
| 26 | #[template(path = "refs.html")] |
| 27 | pub struct RefsPage { |
| 28 | pub layout: Layout, |
| 29 | pub nav: RepoNav, |
| 30 | pub crumbs: Vec<Crumb>, |
| 31 | pub here: Option<String>, |
| 32 | pub branches: Vec<RefRow>, |
| 33 | pub tags: Vec<RefRow>, |
| 34 | } |
| 35 | |
| 36 | pub fn page(ctx: &Ctx) -> Response { |
| 37 | let etag = ctx.etag("refs"); |
| 38 | if let Some(res) = ctx.not_modified(&etag, false) { |
| 39 | return res; |
| 40 | } |
| 41 | let refs = match ctx.refs() { |
| 42 | Ok(refs) => refs, |
| 43 | Err(e) => return ctx.internal_error(e), |
| 44 | }; |
| 45 | let now = time::now(); |
| 46 | let default = ctx.repo.meta.default_branch.as_deref(); |
| 47 | let row = |r: &RefEntry| RefRow { |
| 48 | name: r.name.clone(), |
| 49 | url: ctx.url("tree", &r.name, b""), |
| 50 | subject: r |
| 51 | .tag_subject |
| 52 | .clone() |
| 53 | .unwrap_or_else(|| r.info.subject.clone()), |
| 54 | age: time::relative(r.info.committer_time, now), |
| 55 | iso: time::iso(r.info.committer_time), |
| 56 | is_default: false, |
| 57 | archives: Vec::new(), |
| 58 | }; |
| 59 | let mut branches: Vec<RefRow> = refs.branches.iter().map(row).collect(); |
| 60 | // The default branch leads the list. |
| 61 | if let Some(i) = branches |
| 62 | .iter() |
| 63 | .position(|b| Some(b.name.as_str()) == default) |
| 64 | { |
| 65 | let mut b = branches.remove(i); |
| 66 | b.is_default = true; |
| 67 | branches.insert(0, b); |
| 68 | } |
| 69 | let crumbs = vec![Crumb { |
| 70 | name: ctx.repo.name.clone(), |
| 71 | url: ctx.repo_url(), |
| 72 | }]; |
| 73 | let page = RefsPage { |
| 74 | layout: Layout::new( |
| 75 | ctx.state, |
| 76 | ctx.config, |
| 77 | Some(&format!("Refs · {}", ctx.repo.name)), |
| 78 | ), |
| 79 | nav: ctx.nav(Active::Refs, None, |_| String::new()), |
| 80 | crumbs, |
| 81 | here: Some("Refs".into()), |
| 82 | branches, |
| 83 | tags: refs |
| 84 | .tags |
| 85 | .iter() |
| 86 | .map(|r| { |
| 87 | let mut row = row(r); |
| 88 | row.archives = archives(ctx, &r.name); |
| 89 | row |
| 90 | }) |
| 91 | .collect(), |
| 92 | }; |
| 93 | let mut res = render(StatusCode::OK, &page); |
| 94 | set_cache_headers(&mut res, &etag, false); |
| 95 | res |
| 96 | } |
| 97 | |
| 98 | /// Links to the enabled snapshot formats of `rev`. |
| 99 | fn archives(ctx: &Ctx, rev: &str) -> Vec<(&'static str, String)> { |
| 100 | ctx.config |
| 101 | .repos |
| 102 | .snapshots |
| 103 | .iter() |
| 104 | .map(|format| { |
| 105 | let ext = match format { |
| 106 | SnapshotFormat::TarGz => "tar.gz", |
| 107 | SnapshotFormat::Zip => "zip", |
| 108 | }; |
| 109 | let url = format!("{}/archive/{}.{ext}", ctx.repo_url(), encode_path(rev)); |
| 110 | (ext, url) |
| 111 | }) |
| 112 | .collect() |
| 113 | } |