use crate::github::{self, BranchLink, IssueStatus, PrStatus};
use git2::Repository;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GitHubFetchState<T> {
Idle,
Loading,
Loaded(T),
Error(String),
}
const IDLE_ISSUE: GitHubFetchState<IssueStatus> = GitHubFetchState::Idle;
const IDLE_PR: GitHubFetchState<PrStatus> = GitHubFetchState::Idle;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FetchKey {
Issue(u64),
Pr(u64),
}
pub struct GitHubFetch {
pub link: BranchLink,
pub link_slug: Option<String>,
pub forge: Option<std::sync::Arc<dyn crate::forge::Forge>>,
issue_cache: HashMap<u64, GitHubFetchState<IssueStatus>>,
pr_cache: HashMap<u64, GitHubFetchState<PrStatus>>,
}
impl Default for GitHubFetch {
fn default() -> Self {
Self::new()
}
}
impl GitHubFetch {
pub fn new() -> Self {
Self {
link: BranchLink::empty(),
link_slug: None,
forge: None,
issue_cache: HashMap::new(),
pr_cache: HashMap::new(),
}
}
pub fn refresh_link(&mut self, repo: &Repository, branch: Option<&str>, config: &crate::config::Config) {
let _ = self.reread_link(repo, branch, config);
self.invalidate();
}
#[must_use]
pub fn reread_link(&mut self, repo: &Repository, branch: Option<&str>, config: &crate::config::Config) -> bool {
let before = self.forge_identity();
self.forge = crate::forge::resolve(repo, config).ok();
self.link = branch
.and_then(|b| github::read_link(repo, b).ok())
.unwrap_or_else(BranchLink::empty);
self.link_slug = self.forge.as_ref().map(|f| f.slug().to_string());
if before != self.forge_identity() {
self.invalidate();
return true;
}
false
}
fn forge_identity(&self) -> Option<String> {
self
.forge
.as_ref()
.map(|f| format!("{} {}/{}", f.kind().as_str(), f.web_origin(), f.slug()))
}
pub fn invalidate(&mut self) {
self.issue_cache.clear();
self.pr_cache.clear();
}
pub fn apply_detected_pr(&mut self, detected: Option<u64>) {
github::apply_detected_pr(&mut self.link, detected);
}
pub fn clear_detected_pr(&mut self) {
if self.link.pr_source == github::LinkSource::Detected {
self.link.pr = None;
self.link.pr_source = github::LinkSource::None;
}
}
pub fn mark_loading(&mut self, key: FetchKey) {
match key {
FetchKey::Issue(n) => {
self.issue_cache.insert(n, GitHubFetchState::Loading);
}
FetchKey::Pr(n) => {
self.pr_cache.insert(n, GitHubFetchState::Loading);
}
}
}
pub fn is_cached(&self, key: FetchKey) -> bool {
self.has_terminal(key)
}
pub fn complete_issue(&mut self, number: u64, result: std::result::Result<IssueStatus, String>) {
self.issue_cache.insert(number, into_state(result));
}
pub fn complete_pr(&mut self, number: u64, result: std::result::Result<PrStatus, String>) {
self.pr_cache.insert(number, into_state(result));
}
pub fn apply_issue_result(&mut self, r: std::result::Result<IssueStatus, String>) {
let (number, state) = match r {
Ok(s) => (s.number, GitHubFetchState::Loaded(s)),
Err(e) => {
let Some(n) = self.link.issue else {
return;
};
(n, GitHubFetchState::Error(e))
}
};
self.issue_cache.insert(number, state);
}
pub fn apply_pr_result(&mut self, r: std::result::Result<PrStatus, String>) {
let (number, state) = match r {
Ok(s) => (s.number, GitHubFetchState::Loaded(s)),
Err(e) => {
let Some(n) = self.link.pr else {
return;
};
(n, GitHubFetchState::Error(e))
}
};
self.pr_cache.insert(number, state);
}
pub fn issue_fetch_state(&self, number: u64) -> &GitHubFetchState<IssueStatus> {
self.issue_cache.get(&number).unwrap_or(&IDLE_ISSUE)
}
pub fn pr_fetch_state(&self, number: u64) -> &GitHubFetchState<PrStatus> {
self.pr_cache.get(&number).unwrap_or(&IDLE_PR)
}
fn has_terminal(&self, key: FetchKey) -> bool {
match key {
FetchKey::Issue(n) => matches!(
self.issue_cache.get(&n),
Some(GitHubFetchState::Loaded(_)) | Some(GitHubFetchState::Error(_))
),
FetchKey::Pr(n) => matches!(
self.pr_cache.get(&n),
Some(GitHubFetchState::Loaded(_)) | Some(GitHubFetchState::Error(_))
),
}
}
}
fn into_state<T>(r: std::result::Result<T, String>) -> GitHubFetchState<T> {
match r {
Ok(s) => GitHubFetchState::Loaded(s),
Err(e) => GitHubFetchState::Error(e),
}
}