| @@ -20,6 +20,7 @@ use std::time::Duration; |
| |
| use chrono::{DateTime, Utc}; |
| use gix::bstr::ByteSlice; |
| +use globset::{Glob, GlobSet, GlobSetBuilder}; |
| use indicatif::{ProgressBar, ProgressStyle}; |
| use miette::{IntoDiagnostic, Result}; |
| |
| @@ -198,6 +199,8 @@ pub async fn build_git_repository_ui(config: &AbbayeConfig, git_cfg: &GitUiConfi |
| |
| let max_commits = git_cfg.max_commits; |
| let default_branch = git_cfg.default_branch.clone(); |
| + let include = build_globset(&git_cfg.include)?; |
| + let exclude = build_globset(&git_cfg.exclude)?; |
| let repo_path_clone = repo_path.clone(); |
| |
| // Compute clone URL before the blocking task so we can pass it into the |
| @@ -224,9 +227,9 @@ pub async fn build_git_repository_ui(config: &AbbayeConfig, git_cfg: &GitUiConfi |
| } |
| }; |
| |
| - let branches = collect_branch_entries(&repo, &default_branch)?; |
| - let (tags, ref_branches) = collect_refs(&repo)?; |
| - let ref_labels = build_ref_labels(&repo)?; |
| + let branches = collect_branch_entries(&repo, &default_branch, &include, &exclude)?; |
| + let (tags, ref_branches) = collect_refs(&repo, &include, &exclude)?; |
| + let ref_labels = build_ref_labels(&repo, &include, &exclude)?; |
| |
| // Walk commits per branch; collect unique commits for detail pages. |
| let mut unique_map: HashMap<String, CommitInfo> = HashMap::new(); |
| @@ -256,6 +259,10 @@ pub async fn build_git_repository_ui(config: &AbbayeConfig, git_cfg: &GitUiConfi |
| if !name.starts_with("refs/tags/") { |
| continue; |
| } |
| + let short_name = name.trim_start_matches("refs/tags/"); |
| + if !ref_is_included(short_name, &include, &exclude) { |
| + continue; |
| + } |
| if let Ok(id) = reference.peel_to_id() { |
| let hash = id.to_string(); |
| seen.entry(hash).or_insert_with(|| id.detach()); |
| @@ -450,6 +457,31 @@ pub fn generate_clone_command(config: &AbbayeConfig, git_cfg: &GitUiConfig) -> O |
| |
| // ── Git data collection ─────────────────────────────────────────────────────── |
| |
| +/// Compile a list of glob patterns (as written in `git_ui.exclude`/`include`) |
| +/// into a [`GlobSet`]. An empty pattern list compiles to an empty `GlobSet`, |
| +/// which matches nothing. |
| +fn build_globset(patterns: &[String]) -> Result<GlobSet> { |
| + let mut builder = GlobSetBuilder::new(); |
| + for pattern in patterns { |
| + let glob = Glob::new(pattern) |
| + .map_err(|e| miette::miette!("git_ui: invalid glob pattern '{pattern}': {e}"))?; |
| + builder.add(glob); |
| + } |
| + builder.build().into_diagnostic() |
| +} |
| + |
| +/// Decide whether a ref (by its short name, e.g. `"main"` or `"v1.0.0"`) |
| +/// should appear in the generated UI, per `git_ui.exclude`/`git_ui.include`. |
| +/// |
| +/// When `include` is non-empty, it acts as an allowlist: only refs matching |
| +/// at least one `include` pattern are kept. When `include` is empty, every |
| +/// ref is a candidate. From the resulting candidates, any ref matching an |
| +/// `exclude` pattern is then dropped. |
| +fn ref_is_included(short_name: &str, include: &GlobSet, exclude: &GlobSet) -> bool { |
| + let included = include.is_empty() || include.is_match(short_name); |
| + included && !exclude.is_match(short_name) |
| +} |
| + |
| /// Collect all local branches and assign output filenames. |
| /// |
| /// The branch whose short name matches `default_branch` gets `"index.html"`. |
| @@ -458,9 +490,14 @@ pub fn generate_clone_command(config: &AbbayeConfig, git_cfg: &GitUiConfig) -> O |
| /// |
| /// If no branch matches `default_branch`, the first branch (alphabetically) |
| /// receives `"index.html"` and a warning is emitted. |
| +/// |
| +/// Branches whose short name doesn't pass `git_ui.include`/`exclude` (see |
| +/// [`ref_is_included`]) are skipped entirely. |
| fn collect_branch_entries( |
| repo: &gix::Repository, |
| default_branch: &str, |
| + include: &GlobSet, |
| + exclude: &GlobSet, |
| ) -> Result<Vec<BranchEntry>> { |
| let mut entries: Vec<BranchEntry> = Vec::new(); |
| |
| @@ -474,6 +511,9 @@ fn collect_branch_entries( |
| } |
| |
| let short_name = name.trim_start_matches("refs/heads/").to_string(); |
| + if !ref_is_included(&short_name, include, exclude) { |
| + continue; |
| + } |
| let tip = match reference.peel_to_id() { |
| Ok(id) => id.detach(), |
| Err(_) => continue, |
| @@ -507,7 +547,14 @@ fn collect_branch_entries( |
| |
| /// Build a map from commit hash (hex string) to the ref badges pointing at it. |
| /// Tags come before branches within each entry; both are sorted alphabetically. |
| -fn build_ref_labels(repo: &gix::Repository) -> Result<HashMap<String, Vec<RefBadge>>> { |
| +/// |
| +/// Refs that don't pass `git_ui.include`/`exclude` (see [`ref_is_included`]) |
| +/// are omitted. |
| +fn build_ref_labels( |
| + repo: &gix::Repository, |
| + include: &GlobSet, |
| + exclude: &GlobSet, |
| +) -> Result<HashMap<String, Vec<RefBadge>>> { |
| let mut map: HashMap<String, Vec<RefBadge>> = HashMap::new(); |
| |
| let refs_platform = repo.references().into_diagnostic()?; |
| @@ -525,11 +572,17 @@ fn build_ref_labels(repo: &gix::Repository) -> Result<HashMap<String, Vec<RefBad |
| }; |
| |
| let badge = if let Some(label) = name.strip_prefix("refs/tags/") { |
| + if !ref_is_included(label, include, exclude) { |
| + continue; |
| + } |
| RefBadge { |
| label: label.to_string(), |
| kind: RefBadgeKind::Tag, |
| } |
| } else if let Some(label) = name.strip_prefix("refs/heads/") { |
| + if !ref_is_included(label, include, exclude) { |
| + continue; |
| + } |
| RefBadge { |
| label: label.to_string(), |
| kind: RefBadgeKind::Branch, |
| @@ -622,7 +675,14 @@ fn collect_commits( |
| } |
| |
| /// Collect tags and branches for the refs overview page. |
| -fn collect_refs(repo: &gix::Repository) -> Result<(Vec<RefInfo>, Vec<RefInfo>)> { |
| +/// |
| +/// Refs that don't pass `git_ui.include`/`exclude` (see [`ref_is_included`]) |
| +/// are omitted. |
| +fn collect_refs( |
| + repo: &gix::Repository, |
| + include: &GlobSet, |
| + exclude: &GlobSet, |
| +) -> Result<(Vec<RefInfo>, Vec<RefInfo>)> { |
| let mut tags: Vec<RefInfo> = Vec::new(); |
| let mut branches: Vec<RefInfo> = Vec::new(); |
| |
| @@ -643,6 +703,9 @@ fn collect_refs(repo: &gix::Repository) -> Result<(Vec<RefInfo>, Vec<RefInfo>)> |
| |
| if name.starts_with("refs/tags/") { |
| let short_name = name.trim_start_matches("refs/tags/").to_string(); |
| + if !ref_is_included(&short_name, include, exclude) { |
| + continue; |
| + } |
| tags.push(RefInfo { |
| name, |
| short_name, |
| @@ -651,6 +714,9 @@ fn collect_refs(repo: &gix::Repository) -> Result<(Vec<RefInfo>, Vec<RefInfo>)> |
| }); |
| } else if name.starts_with("refs/heads/") { |
| let short_name = name.trim_start_matches("refs/heads/").to_string(); |
| + if !ref_is_included(&short_name, include, exclude) { |
| + continue; |
| + } |
| branches.push(RefInfo { |
| name, |
| short_name, |