wsb-github 0.275.7622

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 in the workshop
//! 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 worktree's project settings name.
    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 in the workshop has on GitHub: each worktree's
    /// repositories and commits, and the boards its checkout's settings name.
    pub fn of(workshop: &wsb_prj::Workshop) -> Result<Self> {
        let mut targets = Self::default();
        for workspace in workshop.workspaces()? {
            let worktrees = match workspace.worktrees() {
                Ok(worktrees) => worktrees,
                Err(error) => {
                    targets.problems.push(format!("{}: {error}", workspace.name()));
                    continue;
                }
            };
            for worktree in worktrees {
                let place = wsb_prj::Location {
                    workspace: workspace.clone(),
                    worktree: Some(worktree.name.clone()),
                };
                match boards::read(&worktree.path) {
                    Ok(named) => targets.boards.extend(named),
                    Err(error) => targets.problems.push(format!("{place}: {error:#}")),
                }
                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()
    }
}