use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use super::daemon::Broker;
use super::protocol::CommsResponse;
use crate::git_history::proto::{GitHistoryOp, GitHistoryReply};
use crate::git_history::{GitHistoryError, GitHistoryIndex};
pub(crate) struct HistoryEntry {
index: Arc<GitHistoryIndex>,
build_lock: Arc<Mutex<()>>,
last_used: std::sync::Mutex<Instant>,
}
impl HistoryEntry {
fn touch(&self) {
*self.last_used.lock().unwrap_or_else(std::sync::PoisonError::into_inner) = Instant::now();
}
pub(crate) fn idle_for(&self) -> Duration {
self.last_used
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.elapsed()
}
}
impl Broker {
pub(crate) async fn on_git_history(&self, root: std::path::PathBuf, op: GitHistoryOp) -> CommsResponse {
match self.run_git_history_inproc(root, op).await {
Ok(reply) => CommsResponse::GitHistory(reply),
Err(GitHistoryError::Disabled) => CommsResponse::Error {
code: "git_history_disabled".to_string(),
message: GitHistoryError::Disabled.to_string(),
},
Err(error) => CommsResponse::Error {
code: "git_history_failed".to_string(),
message: error.to_string(),
},
}
}
pub(crate) async fn run_git_history_inproc(
&self,
root: std::path::PathBuf,
op: GitHistoryOp,
) -> Result<GitHistoryReply, GitHistoryError> {
self.mark_active().await;
if !crate::git_history::index_enabled() {
return Err(GitHistoryError::Disabled);
}
let dir = crate::git_history::shared_history_basemind_dir(&root);
let entry = self.history_entry(&dir).await?;
entry.touch();
let _build_guard = match op {
GitHistoryOp::Sync => Some(entry.build_lock.clone().lock_owned().await),
_ => None,
};
let _working = matches!(op, GitHistoryOp::Sync).then(|| self.begin_work());
let index = Arc::clone(&entry.index);
let err_dir = dir.clone();
tokio::task::spawn_blocking(move || run_git_history_op(&index, &root, &dir, op))
.await
.map_err(|join| GitHistoryError::Io {
path: err_dir,
source: std::io::Error::other(join.to_string()),
})?
}
async fn history_entry(&self, dir: &std::path::Path) -> Result<Arc<HistoryEntry>, GitHistoryError> {
if let Some(entry) = self.history_lookup(dir) {
return Ok(entry);
}
let _opening = self.git_history_open_lock.lock().await;
if let Some(entry) = self.history_lookup(dir) {
return Ok(entry);
}
let open_dir = dir.to_path_buf();
let opened = tokio::task::spawn_blocking(move || GitHistoryIndex::open(&open_dir))
.await
.map_err(|join| GitHistoryError::Io {
path: dir.to_path_buf(),
source: std::io::Error::other(join.to_string()),
})??;
let entry = Arc::new(HistoryEntry {
index: Arc::new(opened),
build_lock: Arc::new(Mutex::new(())),
last_used: std::sync::Mutex::new(Instant::now()),
});
self.git_history
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(dir.to_path_buf(), Arc::clone(&entry));
Ok(entry)
}
fn history_lookup(&self, dir: &std::path::Path) -> Option<Arc<HistoryEntry>> {
self.git_history
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(dir)
.map(Arc::clone)
}
}
impl crate::git_history::remote::HistoryHost for Broker {
fn run_history(
&self,
root: std::path::PathBuf,
op: GitHistoryOp,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<GitHistoryReply, GitHistoryError>> + Send + '_>>
{
Box::pin(self.run_git_history_inproc(root, op))
}
}
fn run_git_history_op(
index: &GitHistoryIndex,
root: &std::path::Path,
dir: &std::path::Path,
op: GitHistoryOp,
) -> Result<GitHistoryReply, GitHistoryError> {
match op {
GitHistoryOp::Sync => {
let repo = crate::git::Repo::discover(root)?;
let outcome = crate::git_history::builder::sync(index, &repo, dir)?;
tracing::info!(root = %root.display(), ?outcome, "git-history index sync complete");
Ok(GitHistoryReply::Synced(outcome.into()))
}
GitHistoryOp::IndexedHead => Ok(GitHistoryReply::IndexedHead(index.last_indexed_head_hex())),
GitHistoryOp::RecentCommits {
skip,
take,
include_files,
} => Ok(GitHistoryReply::Commits(index.recent_commits(
skip,
take,
include_files,
))),
GitHistoryOp::CommitsTouching { path, skip, take } => {
Ok(GitHistoryReply::Commits(index.commits_touching(&path, skip, take)))
}
GitHistoryOp::WindowCommits { window } => Ok(GitHistoryReply::Commits(index.window_commits(window))),
GitHistoryOp::SearchCommits {
query,
scope,
skip,
take,
} => Ok(GitHistoryReply::Commits(
index.search_commits(&query, scope, skip, take),
)),
}
}