wsb-github 0.251.6311

GitHub issues, pull requests, gists and Projects boards, mirrored once per machine for every project under $PRJ
Documentation
//! What the machine's mirror holds: everything every project under `$PRJ`
//! has on GitHub.
//!
//! Read from the projects rather than remembered, every time: a worktree
//! added this morning has commits CI reports on, a submodule removed should
//! stop being asked about, and a board a project stopped referencing should
//! stop costing requests.

use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;

use anyhow::Result;

use crate::boards::{self, BoardRef};
use crate::checkout;

/// Everything to mirror, and what could not be read while finding it out.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Targets {
    /// `owner/repo`, and every commit some worktree has it checked out at.
    pub repositories: BTreeMap<String, BTreeSet<String>>,
    /// Every board some project references.
    pub boards: BTreeSet<BoardRef>,
    /// What could not be read, by where it was: `project/worktree: why`.
    ///
    /// While there are any, what the mirror holds is never forgotten: a
    /// worktree that could not be read this minute is not a worktree that
    /// stopped having repositories, and treating it as one would delete what
    /// it holds on every transient failure.
    pub problems: Vec<String>,
}

impl Targets {
    /// What every project under `$PRJ` has on GitHub.
    pub fn of(prj: &wsb_prj::Prj) -> Result<Self> {
        let mut targets = Self::default();
        for project in prj.projects()? {
            match boards::read(&project) {
                Ok(declared) => targets.boards.extend(declared.boards),
                Err(error) => targets
                    .problems
                    .push(format!("{}: {error:#}", project.name())),
            }
            let worktrees = match project.worktrees() {
                Ok(worktrees) => worktrees,
                Err(error) => {
                    targets.problems.push(format!("{}: {error}", project.name()));
                    continue;
                }
            };
            for worktree in worktrees {
                let place = wsb_prj::Location {
                    project: project.clone(),
                    worktree: Some(worktree.name.clone()),
                };
                if let Err(error) = targets.add_checkout(&worktree.path) {
                    targets.problems.push(format!("{place}: {error:#}"));
                }
            }
        }
        Ok(targets)
    }

    /// Add what one checkout holds.
    pub fn add_checkout(&mut self, path: &Path) -> Result<()> {
        for repository in checkout::repositories(path)? {
            let commits = self.repositories.entry(repository.name).or_default();
            if let Some(head) = repository.head {
                commits.insert(head);
            }
        }
        Ok(())
    }

    /// The owners whose board lists are needed to find the referenced boards.
    pub fn board_owners(&self) -> BTreeSet<String> {
        self.boards.iter().map(|board| board.owner.clone()).collect()
    }
}