Files
galaxy/app/src/code_review/git_repo_models.rs
T

164 lines
6.4 KiB
Rust

use std::collections::HashMap;
#[cfg(feature = "local_fs")]
use repo_metadata::repositories::DetectedRepositories;
use warp_util::local_or_remote_path::LocalOrRemotePath;
use warpui::{Entity, ModelContext, ModelHandle, SingletonEntity, WeakModelHandle};
#[cfg(feature = "local_fs")]
use super::git_repo_model::new_local_git_repo_status_model;
use super::git_repo_model::{new_remote_git_repo_status_model, GitRepoStatusModel};
#[cfg(feature = "local_fs")]
use super::github_repo_model::LocalGitHubRepoModel;
use super::github_repo_model::{GitHubRepoModel, RemoteGitHubRepoModel};
// ── GitRepoModels (singleton cache) ─────────────────────────────────────────
/// Singleton model that acts as a cache / factory for per-repository
/// [`GitRepoStatusModel`] and [`GitHubRepoModel`] instances.
///
/// Multiple terminals in the same repo share a single sub-model. When the last
/// strong handle to a sub-model is dropped, the models are torn down automatically.
pub struct GitRepoModels {
// Per-repo status / GitHub-info models, keyed by `LocalOrRemotePath` so a
// single cache covers both local (watcher-backed) and remote (push
// receiver) repos. Each entry stores the unified-enum handle; callers in
// the same repo share it, and it is torn down when the last strong handle
// is dropped.
git_status_models: HashMap<LocalOrRemotePath, WeakModelHandle<GitRepoStatusModel>>,
github_repo_models: HashMap<LocalOrRemotePath, WeakModelHandle<GitHubRepoModel>>,
}
impl GitRepoModels {
pub fn new() -> Self {
Self {
git_status_models: HashMap::new(),
github_repo_models: HashMap::new(),
}
}
/// Get or create the per-repo status model for `repo`, returning a unified
/// [`GitRepoStatusModel`] handle that dispatches to a local watcher-backed
/// model or a remote push receiver based on the location.
///
/// Multiple callers in the same repo share one model (cached by
/// `LocalOrRemotePath`); it is torn down when the last strong handle is
/// dropped.
///
/// Callers hold the returned `ModelHandle` for as long as they need updates.
pub fn subscribe(
&mut self,
repo: &LocalOrRemotePath,
ctx: &mut ModelContext<Self>,
) -> anyhow::Result<ModelHandle<GitRepoStatusModel>> {
if let Some(handle) = self
.git_status_models
.get(repo)
.and_then(|weak| weak.upgrade(ctx))
{
return Ok(handle);
}
let handle = match repo {
LocalOrRemotePath::Local(repo_path) => {
#[cfg(feature = "local_fs")]
{
let Some(repository_model) = DetectedRepositories::as_ref(ctx)
.get_local_watched_repo_for_path(repo_path, ctx)
else {
anyhow::bail!(
"No watched repository found for path: {}",
repo_path.display()
);
};
new_local_git_repo_status_model(repo_path.clone(), repository_model, ctx)
}
#[cfg(not(feature = "local_fs"))]
{
anyhow::bail!(
"No watched repository found for path: {}",
repo_path.display()
);
}
}
LocalOrRemotePath::Remote(remote_path) => {
new_remote_git_repo_status_model(remote_path.clone(), ctx)
}
};
self.git_status_models
.insert(repo.clone(), handle.downgrade());
Ok(handle)
}
/// Get or create the per-repo GitHub-info model for `repo`, returning a
/// unified [`GitHubRepoModel`] handle that dispatches to a local
/// `gh`-driven model or a remote push receiver based on the location.
///
/// The local backend subscribes to the sibling git status model to track
/// the current branch and fetches PR / repository info on creation, on
/// branch change, and on a periodic timer. Multiple callers in the same
/// repo share one model (cached by `LocalOrRemotePath`).
///
/// Callers hold the returned `ModelHandle` for as long as they need updates.
pub fn subscribe_github_repo(
&mut self,
repo: &LocalOrRemotePath,
ctx: &mut ModelContext<Self>,
) -> anyhow::Result<ModelHandle<GitHubRepoModel>> {
if let Some(handle) = self
.github_repo_models
.get(repo)
.and_then(|weak| weak.upgrade(ctx))
{
return Ok(handle);
}
let handle = match repo {
LocalOrRemotePath::Local(repo_path) => {
#[cfg(feature = "local_fs")]
{
// LocalGitHubRepoModel needs a sibling GitRepoStatusModel for
// branch info.
let git_status = self.subscribe(repo, ctx)?;
let repo_path = repo_path.clone();
let inner =
ctx.add_model(|ctx| LocalGitHubRepoModel::new(repo_path, git_status, ctx));
ctx.add_model(|ctx| {
ctx.subscribe_to_model(&inner, |me, _, event, ctx| {
GitHubRepoModel::forward_event(me, event, ctx)
});
GitHubRepoModel::Local(inner)
})
}
#[cfg(not(feature = "local_fs"))]
{
anyhow::bail!(
"Local GitHub repo info is unavailable without local_fs: {}",
repo_path.display()
);
}
}
LocalOrRemotePath::Remote(remote_path) => {
let inner =
ctx.add_model(|ctx| RemoteGitHubRepoModel::new(remote_path.clone(), ctx));
ctx.add_model(|ctx| {
ctx.subscribe_to_model(&inner, |me, _, event, ctx| {
GitHubRepoModel::forward_event(me, event, ctx)
});
GitHubRepoModel::Remote(inner)
})
}
};
self.github_repo_models
.insert(repo.clone(), handle.downgrade());
Ok(handle)
}
}
impl Entity for GitRepoModels {
type Event = ();
}
impl SingletonEntity for GitRepoModels {}