wsb_github/targets.rs
1//! What the machine's mirror holds: everything every project under `$PRJ`
2//! has on GitHub.
3//!
4//! Read from the projects rather than remembered, every time: a worktree
5//! added this morning has commits CI reports on, a submodule removed should
6//! stop being asked about, and a board a project stopped referencing should
7//! stop costing requests.
8
9use std::collections::{BTreeMap, BTreeSet};
10use std::path::Path;
11
12use anyhow::Result;
13
14use crate::boards::{self, BoardRef};
15use crate::checkout;
16
17/// Everything to mirror, and what could not be read while finding it out.
18#[derive(Debug, Clone, Default, PartialEq, Eq)]
19pub struct Targets {
20 /// `owner/repo`, and every commit some worktree has it checked out at.
21 pub repositories: BTreeMap<String, BTreeSet<String>>,
22 /// Every board some project references.
23 pub boards: BTreeSet<BoardRef>,
24 /// What could not be read, by where it was: `project/worktree: why`.
25 ///
26 /// While there are any, what the mirror holds is never forgotten: a
27 /// worktree that could not be read this minute is not a worktree that
28 /// stopped having repositories, and treating it as one would delete what
29 /// it holds on every transient failure.
30 pub problems: Vec<String>,
31}
32
33impl Targets {
34 /// What every project under `$PRJ` has on GitHub.
35 pub fn of(prj: &wsb_prj::Prj) -> Result<Self> {
36 let mut targets = Self::default();
37 for project in prj.projects()? {
38 match boards::read(&project) {
39 Ok(declared) => targets.boards.extend(declared.boards),
40 Err(error) => targets
41 .problems
42 .push(format!("{}: {error:#}", project.name())),
43 }
44 let worktrees = match project.worktrees() {
45 Ok(worktrees) => worktrees,
46 Err(error) => {
47 targets.problems.push(format!("{}: {error}", project.name()));
48 continue;
49 }
50 };
51 for worktree in worktrees {
52 let place = wsb_prj::Location {
53 project: project.clone(),
54 worktree: Some(worktree.name.clone()),
55 };
56 if let Err(error) = targets.add_checkout(&worktree.path) {
57 targets.problems.push(format!("{place}: {error:#}"));
58 }
59 }
60 }
61 Ok(targets)
62 }
63
64 /// Add what one checkout holds.
65 pub fn add_checkout(&mut self, path: &Path) -> Result<()> {
66 for repository in checkout::repositories(path)? {
67 let commits = self.repositories.entry(repository.name).or_default();
68 if let Some(head) = repository.head {
69 commits.insert(head);
70 }
71 }
72 Ok(())
73 }
74
75 /// The owners whose board lists are needed to find the referenced boards.
76 pub fn board_owners(&self) -> BTreeSet<String> {
77 self.boards.iter().map(|board| board.owner.clone()).collect()
78 }
79}