wsb_github/targets.rs
1//! What the machine's mirror holds: everything every project in the workshop
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 worktree's project settings name.
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 in the workshop has on GitHub: each worktree's
35 /// repositories and commits, and the boards its checkout's settings name.
36 pub fn of(workshop: &wsb_prj::Workshop) -> Result<Self> {
37 let mut targets = Self::default();
38 for workspace in workshop.workspaces()? {
39 let worktrees = match workspace.worktrees() {
40 Ok(worktrees) => worktrees,
41 Err(error) => {
42 targets.problems.push(format!("{}: {error}", workspace.name()));
43 continue;
44 }
45 };
46 for worktree in worktrees {
47 let place = wsb_prj::Location {
48 workspace: workspace.clone(),
49 worktree: Some(worktree.name.clone()),
50 };
51 match boards::read(&worktree.path) {
52 Ok(named) => targets.boards.extend(named),
53 Err(error) => targets.problems.push(format!("{place}: {error:#}")),
54 }
55 if let Err(error) = targets.add_checkout(&worktree.path) {
56 targets.problems.push(format!("{place}: {error:#}"));
57 }
58 }
59 }
60 Ok(targets)
61 }
62
63 /// Add what one checkout holds.
64 pub fn add_checkout(&mut self, path: &Path) -> Result<()> {
65 for repository in checkout::repositories(path)? {
66 let commits = self.repositories.entry(repository.name).or_default();
67 if let Some(head) = repository.head {
68 commits.insert(head);
69 }
70 }
71 Ok(())
72 }
73
74 /// The owners whose board lists are needed to find the referenced boards.
75 pub fn board_owners(&self) -> BTreeSet<String> {
76 self.boards.iter().map(|board| board.owner.clone()).collect()
77 }
78}