Skip to main content

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}