feat(cache): split cache module into submodules and wire invalidation hooks
Two-part change:
1) Submodule split of src/cache/ (was a single ~150-line mod.rs)
- src/cache/config.rs: CacheConfig, defaults, default_cache_dir(),
DEFAULT_NAME / DEFAULT_MEMORY_CAPACITY_BYTES /
DEFAULT_DISK_CAPACITY_BYTES constants
- src/cache/key.rs: CacheKey / CacheValue type aliases and key
derivation helpers (rev -> oid, blob-exists negatives)
- src/cache/store.rs: CacheStore — the foyer HybridCache wrapper
(open / close / get / insert / remove / contains), all errors
mapped to BabyError::Cache with op-tagged source
- src/cache/pools.rs: CachePools — multi-store aggregator used by
the global singleton; replaces the old monolithic CacheStore
- src/cache/stats.rs: CacheStats + counter accessors
- src/cache/invalidation.rs: hook_ref_updated / hook_blob_written
(single-process invalidation hooks, fired from usecase impls)
- src/cache/mod.rs: now just submodule re-exports of
(config, global, invalidation, key, pools, stats, store)
- src/cache/global.rs: GITBABY_CACHE singleton + init / try /
shutdown now operate on Arc<CachePools> instead of Arc<CacheStore>
2) Cache integration in usecase modules
- src/blob/usecase.rs: read paths go through cache.get / insert;
invalidation hooks fire on write
- src/commit/usecase.rs: commit-list results cached, invalidated on
commit write
- src/branch/usecase.rs: branch-list results cached, invalidated on
ref update
- src/refs/usecase.rs: lookup-cache + ref-update hook
- src/tags/usecase.rs: tag-info cache + write hook
3) AGENTS.md
- Document that cached serde payloads (CommitInfo, Vec<TreeEntry>,
etc.) follow additive-only compatibility: never rename/remove a
field, every new field gets #[serde(default)] so stale payloads
still deserialize.
- Note that the foyer cache is single-process: fixed on-disk dir
per config + in-process invalidation hooks. Sharing across
processes on the same repo can leave stale metadata; do not do
that.
CI: cargo build + cargo test (61 passed) green.
BREAKING: the public cache surface moved. Anyone reaching into
cache::CacheStore directly should switch to cache::CachePools (or
cache::global::try_global_cache). The high-level helpers in
cache::global keep the same name; only the inner type changed.
This commit is contained in:
parent
4d5e5ba5f9
commit
5d32290cf1
@ -35,6 +35,8 @@
|
|||||||
## Gotchas
|
## Gotchas
|
||||||
- No README. No docs beyond the code. Treat `Cargo.toml` + source as the only source of truth.
|
- No README. No docs beyond the code. Treat `Cargo.toml` + source as the only source of truth.
|
||||||
- `gix = "0.86"`, `tokio = 1.53` (full features), `time = 0.3` — all recent; do not bump them speculatively.
|
- `gix = "0.86"`, `tokio = 1.53` (full features), `time = 0.3` — all recent; do not bump them speculatively.
|
||||||
|
- Cache values that are serde-serialized (e.g. `CommitInfo`, `Vec<TreeEntry>`) follow a compatibility convention: fields are additive only — never rename/remove a field, and every new field must carry `#[serde(default)]` so stale cached payloads still deserialize.
|
||||||
|
- The foyer-backed cache (`src/cache/`) is single-process: it uses a fixed on-disk directory per config and relies on in-process invalidation hooks (`hook_ref_updated`, `hook_blob_written`). It must not be shared across processes on the same repo, or metadata (rev→oid, blob-exists negatives) can go stale.
|
||||||
- Public API surfaces async (`async_trait`); sync callers must use a runtime (tests use `#[tokio::test]`).
|
- Public API surfaces async (`async_trait`); sync callers must use a runtime (tests use `#[tokio::test]`).
|
||||||
|
|
||||||
## Planned but unimplemented (streaming-first server)
|
## Planned but unimplemented (streaming-first server)
|
||||||
|
|||||||
@ -13,6 +13,8 @@ use crate::blob::types::{
|
|||||||
};
|
};
|
||||||
use crate::error::BabyError;
|
use crate::error::BabyError;
|
||||||
|
|
||||||
|
const NEGATIVE_TTL_SECS: u64 = 60;
|
||||||
|
|
||||||
pub struct ChildBlobReader<R: AsyncBufRead + Unpin + Send> {
|
pub struct ChildBlobReader<R: AsyncBufRead + Unpin + Send> {
|
||||||
reader: R,
|
reader: R,
|
||||||
child: Option<Child>,
|
child: Option<Child>,
|
||||||
@ -87,6 +89,19 @@ impl GitBaby {
|
|||||||
oid: ObjectId,
|
oid: ObjectId,
|
||||||
opts: GetBlobOptions,
|
opts: GetBlobOptions,
|
||||||
) -> Result<BlobContent, BabyError> {
|
) -> Result<BlobContent, BabyError> {
|
||||||
|
if let Some(p) = crate::cache::try_global_cache() {
|
||||||
|
let key = crate::cache::object_key(crate::cache::NS_BLOB, &oid);
|
||||||
|
if let Ok(Some(mut data)) = p.store().get(&key).await {
|
||||||
|
if let Some(limit) = opts.limit
|
||||||
|
&& limit >= 0
|
||||||
|
&& (data.len() as i64) > limit
|
||||||
|
{
|
||||||
|
data.truncate(limit as usize);
|
||||||
|
}
|
||||||
|
let info = helper::parse_cat_file_blob_output(&data, &oid)?;
|
||||||
|
return Ok(BlobContent { info, data });
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut cmd = self
|
let mut cmd = self
|
||||||
.spawn_blob_env_cmd(|dir, env| helper::build_get_blob_cmd(dir, env, &oid))
|
.spawn_blob_env_cmd(|dir, env| helper::build_get_blob_cmd(dir, env, &oid))
|
||||||
.await?;
|
.await?;
|
||||||
@ -129,6 +144,23 @@ impl GitBaby {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn blob_exists(&self, oid: ObjectId) -> bool {
|
pub async fn blob_exists(&self, oid: ObjectId) -> bool {
|
||||||
|
if let Some(p) = crate::cache::try_global_cache() {
|
||||||
|
let key = crate::cache::object_key(crate::cache::NS_BLOB_EXISTS, &oid);
|
||||||
|
match p.store().get(&key).await {
|
||||||
|
Ok(Some(v)) if v.len() == 9 => {
|
||||||
|
let exists = v[0] != 0;
|
||||||
|
if exists {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let ts = u64::from_le_bytes(v[1..9].try_into().unwrap_or([0u8; 8]));
|
||||||
|
if now_unix_secs().saturating_sub(ts) < NEGATIVE_TTL_SECS {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
p.store().remove(&key);
|
||||||
|
}
|
||||||
|
Ok(_) | Err(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut cmd = match self
|
let mut cmd = match self
|
||||||
.spawn_blob_env_cmd(|dir, env| helper::build_blob_exists_cmd(dir, env, &oid))
|
.spawn_blob_env_cmd(|dir, env| helper::build_blob_exists_cmd(dir, env, &oid))
|
||||||
.await
|
.await
|
||||||
@ -136,10 +168,18 @@ impl GitBaby {
|
|||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
};
|
};
|
||||||
match cmd.run().await {
|
let ok = match cmd.run().await {
|
||||||
Ok(o) => o.status.success(),
|
Ok(o) => o.status.success(),
|
||||||
Err(_) => false,
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
if let Some(p) = crate::cache::try_global_cache() {
|
||||||
|
let key = crate::cache::object_key(crate::cache::NS_BLOB_EXISTS, &oid);
|
||||||
|
let mut v = Vec::with_capacity(9);
|
||||||
|
v.push(u8::from(ok));
|
||||||
|
v.extend_from_slice(&now_unix_secs().to_le_bytes());
|
||||||
|
p.store().insert(key, v);
|
||||||
}
|
}
|
||||||
|
ok
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn stream_blob(
|
pub async fn stream_blob(
|
||||||
@ -149,6 +189,14 @@ impl GitBaby {
|
|||||||
ChildBlobReader<BufReader<Box<dyn tokio::io::AsyncRead + Unpin + Send + Sync>>>,
|
ChildBlobReader<BufReader<Box<dyn tokio::io::AsyncRead + Unpin + Send + Sync>>>,
|
||||||
BabyError,
|
BabyError,
|
||||||
> {
|
> {
|
||||||
|
if let Some(p) = crate::cache::try_global_cache() {
|
||||||
|
let key = crate::cache::object_key(crate::cache::NS_BLOB, &oid);
|
||||||
|
if let Ok(Some(v)) = p.store().get(&key).await {
|
||||||
|
let boxed = Box::new(std::io::Cursor::new(v))
|
||||||
|
as Box<dyn tokio::io::AsyncRead + Unpin + Send + Sync>;
|
||||||
|
return Ok(ChildBlobReader::new(BufReader::new(boxed), None));
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut cmd = self
|
let mut cmd = self
|
||||||
.spawn_blob_env_cmd(|dir, env| helper::build_stream_blob_cmd(dir, env, &oid))
|
.spawn_blob_env_cmd(|dir, env| helper::build_stream_blob_cmd(dir, env, &oid))
|
||||||
.await?;
|
.await?;
|
||||||
@ -250,7 +298,9 @@ impl GitBaby {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let hex = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
let hex = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
crate::share::parse_object_id(&hex)
|
let oid = crate::share::parse_object_id(&hex)?;
|
||||||
|
let _ = crate::cache::invalidation::hook_blob_written(self, &oid).await;
|
||||||
|
Ok(oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_blob_env_cmd<F>(&self, builder: F) -> Result<crate::command::cmd::Cmd, BabyError>
|
async fn spawn_blob_env_cmd<F>(&self, builder: F) -> Result<crate::command::cmd::Cmd, BabyError>
|
||||||
@ -271,3 +321,10 @@ impl GitBaby {
|
|||||||
Ok(builder(&dir, env))
|
Ok(builder(&dir, env))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn now_unix_secs() -> u64 {
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|||||||
@ -95,6 +95,7 @@ impl GitBaby {
|
|||||||
name,
|
name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +123,7 @@ impl GitBaby {
|
|||||||
name,
|
name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +144,7 @@ impl GitBaby {
|
|||||||
if let Err(e) = cmd.run().await {
|
if let Err(e) = cmd.run().await {
|
||||||
return Err(helper::classify_cmd_error(e, name));
|
return Err(helper::classify_cmd_error(e, name));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +165,7 @@ impl GitBaby {
|
|||||||
name,
|
name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +185,8 @@ impl GitBaby {
|
|||||||
from,
|
from,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, from).await;
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, to).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,6 +220,10 @@ impl GitBaby {
|
|||||||
&target_name,
|
&target_name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if let HeadTarget::Branch(name) = &target {
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, name).await;
|
||||||
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, "HEAD").await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
91
src/cache/config.rs
vendored
Normal file
91
src/cache/config.rs
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub const DEFAULT_NAME: &str = "gitbaby-blob-cache";
|
||||||
|
pub const DEFAULT_MEMORY_CAPACITY_BYTES: usize = 64 * 1024 * 1024;
|
||||||
|
pub const DEFAULT_DISK_CAPACITY_BYTES: usize = 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
pub const DEFAULT_LFS_NAME: &str = "gitbaby-lfs-cache";
|
||||||
|
pub const DEFAULT_LFS_DISK_CAPACITY_BYTES: usize = 5 * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CacheConfig {
|
||||||
|
pub memory_capacity_bytes: usize,
|
||||||
|
pub disk_capacity_bytes: usize,
|
||||||
|
pub disk_dir: PathBuf,
|
||||||
|
pub name: Cow<'static, str>,
|
||||||
|
pub lfs: Option<LfsCacheConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LfsCacheConfig {
|
||||||
|
pub memory_capacity_bytes: usize,
|
||||||
|
pub disk_capacity_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CacheConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
memory_capacity_bytes: DEFAULT_MEMORY_CAPACITY_BYTES,
|
||||||
|
disk_capacity_bytes: DEFAULT_DISK_CAPACITY_BYTES,
|
||||||
|
disk_dir: default_cache_dir(),
|
||||||
|
name: Cow::Borrowed(DEFAULT_NAME),
|
||||||
|
lfs: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LfsCacheConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
memory_capacity_bytes: DEFAULT_MEMORY_CAPACITY_BYTES,
|
||||||
|
disk_capacity_bytes: DEFAULT_LFS_DISK_CAPACITY_BYTES,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CacheConfig {
|
||||||
|
pub fn from_env() -> Self {
|
||||||
|
Self {
|
||||||
|
memory_capacity_bytes: env_usize(
|
||||||
|
"GITBABY_CACHE_MEM_BYTES",
|
||||||
|
DEFAULT_MEMORY_CAPACITY_BYTES,
|
||||||
|
),
|
||||||
|
disk_capacity_bytes: env_usize("GITBABY_CACHE_DISK_BYTES", DEFAULT_DISK_CAPACITY_BYTES),
|
||||||
|
disk_dir: std::env::var_os("GITBABY_CACHE_DIR")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(default_cache_dir),
|
||||||
|
name: Cow::Borrowed(DEFAULT_NAME),
|
||||||
|
lfs: match env_usize(
|
||||||
|
"GITBABY_CACHE_LFS_DISK_BYTES",
|
||||||
|
DEFAULT_LFS_DISK_CAPACITY_BYTES,
|
||||||
|
) {
|
||||||
|
0 => None,
|
||||||
|
disk_capacity_bytes => Some(LfsCacheConfig {
|
||||||
|
memory_capacity_bytes: DEFAULT_MEMORY_CAPACITY_BYTES,
|
||||||
|
disk_capacity_bytes,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn env_usize(key: &str, default: usize) -> usize {
|
||||||
|
std::env::var(key)
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.trim().parse::<usize>().ok())
|
||||||
|
.unwrap_or(default)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_cache_dir() -> PathBuf {
|
||||||
|
if let Some(xdg) = std::env::var_os("XDG_CACHE_HOME") {
|
||||||
|
return PathBuf::from(xdg).join("gitbaby");
|
||||||
|
}
|
||||||
|
if let Some(local) = std::env::var_os("LOCALAPPDATA") {
|
||||||
|
return PathBuf::from(local).join("gitbaby");
|
||||||
|
}
|
||||||
|
if let Some(home) = std::env::var_os("HOME") {
|
||||||
|
return PathBuf::from(home).join(".cache").join("gitbaby");
|
||||||
|
}
|
||||||
|
std::env::temp_dir().join("gitbaby")
|
||||||
|
}
|
||||||
32
src/cache/global.rs
vendored
32
src/cache/global.rs
vendored
@ -1,17 +1,17 @@
|
|||||||
use std::sync::{Arc, LazyLock, Mutex};
|
use std::sync::{Arc, LazyLock, Mutex};
|
||||||
|
|
||||||
use crate::cache::{CacheConfig, CacheStore};
|
use crate::cache::{CacheConfig, CachePools};
|
||||||
use crate::error::BabyError;
|
use crate::error::BabyError;
|
||||||
|
|
||||||
pub static GITBABY_CACHE: LazyLock<Mutex<Option<Arc<CacheStore>>>> =
|
pub static GITBABY_CACHE: LazyLock<Mutex<Option<Arc<CachePools>>>> =
|
||||||
LazyLock::new(|| Mutex::new(None));
|
LazyLock::new(|| Mutex::new(None));
|
||||||
|
|
||||||
pub fn try_global_cache() -> Option<Arc<CacheStore>> {
|
pub fn try_global_cache() -> Option<Arc<CachePools>> {
|
||||||
let guard = GITBABY_CACHE.lock().ok()?;
|
let guard = GITBABY_CACHE.lock().ok()?;
|
||||||
guard.clone()
|
guard.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_global_cache(cfg: CacheConfig) -> Result<Arc<CacheStore>, BabyError> {
|
pub fn init_global_cache(cfg: CacheConfig) -> Result<Arc<CachePools>, BabyError> {
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
@ -21,34 +21,34 @@ pub fn init_global_cache(cfg: CacheConfig) -> Result<Arc<CacheStore>, BabyError>
|
|||||||
source
|
source
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let store = rt.block_on(CacheStore::open(cfg))?;
|
let pools = rt.block_on(CachePools::open(cfg))?;
|
||||||
install(store)
|
install(pools)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_global_cache_async(cfg: CacheConfig) -> Result<Arc<CacheStore>, BabyError> {
|
pub async fn init_global_cache_async(cfg: CacheConfig) -> Result<Arc<CachePools>, BabyError> {
|
||||||
let store = CacheStore::open(cfg).await?;
|
let pools = CachePools::open(cfg).await?;
|
||||||
install(store)
|
install(pools)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install(store: Arc<CacheStore>) -> Result<Arc<CacheStore>, BabyError> {
|
fn install(pools: Arc<CachePools>) -> Result<Arc<CachePools>, BabyError> {
|
||||||
let mut guard = GITBABY_CACHE
|
let mut guard = GITBABY_CACHE
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|source| BabyError::Custom(format!("global cache mutex poisoned: {}", source)))?;
|
.map_err(|source| BabyError::Custom(format!("global cache mutex poisoned: {}", source)))?;
|
||||||
*guard = Some(store.clone());
|
*guard = Some(pools.clone());
|
||||||
Ok(store)
|
Ok(pools)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn shutdown_global_cache() -> Result<(), BabyError> {
|
pub async fn shutdown_global_cache() -> Result<(), BabyError> {
|
||||||
let store = {
|
let pools = {
|
||||||
let mut guard = GITBABY_CACHE.lock().map_err(|source| {
|
let mut guard = GITBABY_CACHE.lock().map_err(|source| {
|
||||||
BabyError::Custom(format!("global cache mutex poisoned: {}", source))
|
BabyError::Custom(format!("global cache mutex poisoned: {}", source))
|
||||||
})?;
|
})?;
|
||||||
guard.take()
|
guard.take()
|
||||||
};
|
};
|
||||||
if let Some(store) = store
|
if let Some(pools) = pools
|
||||||
&& Arc::strong_count(&store) == 1
|
&& Arc::strong_count(&pools) == 1
|
||||||
{
|
{
|
||||||
store.close().await?;
|
pools.close().await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/cache/invalidation.rs
vendored
Normal file
25
src/cache/invalidation.rs
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use crate::GitBaby;
|
||||||
|
use crate::cache::key::{NS_BLOB_EXISTS, object_key, rev_key};
|
||||||
|
use crate::cache::try_global_cache;
|
||||||
|
use crate::error::BabyError;
|
||||||
|
|
||||||
|
pub async fn hook_ref_updated(git: &GitBaby, name: &str) -> Result<(), BabyError> {
|
||||||
|
let Some(pools) = try_global_cache() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let repo_dir = git.facade().git_repo_dir().await?;
|
||||||
|
let repo_dir = std::fs::canonicalize(&repo_dir).map_err(|source| BabyError::Io {
|
||||||
|
path: repo_dir,
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
pools.store().remove(&rev_key(&repo_dir, name));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn hook_blob_written(_git: &GitBaby, oid: &gix::ObjectId) -> Result<(), BabyError> {
|
||||||
|
let Some(pools) = try_global_cache() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
pools.store().remove(&object_key(NS_BLOB_EXISTS, oid));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
33
src/cache/key.rs
vendored
Normal file
33
src/cache/key.rs
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::cache::CacheKey;
|
||||||
|
|
||||||
|
pub const NS_BLOB: u8 = b'B';
|
||||||
|
pub const NS_BLOB_SIZE: u8 = b'S';
|
||||||
|
pub const NS_BLOB_EXISTS: u8 = b'E';
|
||||||
|
pub const NS_COMMIT: u8 = b'C';
|
||||||
|
pub const NS_REV: u8 = b'r';
|
||||||
|
pub const NS_TREE_ENTRIES: u8 = b't';
|
||||||
|
pub const NS_LFS: u8 = b'l';
|
||||||
|
|
||||||
|
pub fn encode(ns: u8, suffix: &[u8]) -> CacheKey {
|
||||||
|
let mut key = Vec::with_capacity(1 + suffix.len());
|
||||||
|
key.push(ns);
|
||||||
|
key.extend_from_slice(suffix);
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_key(ns: u8, oid: &gix::ObjectId) -> CacheKey {
|
||||||
|
encode(ns, oid.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rev_key(repo_dir: &Path, revision: &str) -> CacheKey {
|
||||||
|
let path = repo_dir.as_os_str().as_encoded_bytes();
|
||||||
|
let mut key = Vec::with_capacity(1 + 4 + path.len() + 1 + revision.len());
|
||||||
|
key.push(NS_REV);
|
||||||
|
key.extend_from_slice(&(path.len() as u32).to_le_bytes());
|
||||||
|
key.extend_from_slice(path);
|
||||||
|
key.push(0);
|
||||||
|
key.extend_from_slice(revision.as_bytes());
|
||||||
|
key
|
||||||
|
}
|
||||||
156
src/cache/mod.rs
vendored
156
src/cache/mod.rs
vendored
@ -1,146 +1,22 @@
|
|||||||
use std::borrow::Cow;
|
pub mod config;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use foyer::{BlockEngineConfig, DeviceBuilder, FsDeviceBuilder, HybridCache, HybridCacheBuilder};
|
|
||||||
|
|
||||||
use crate::error::BabyError;
|
|
||||||
|
|
||||||
pub mod global;
|
pub mod global;
|
||||||
|
pub mod invalidation;
|
||||||
|
pub mod key;
|
||||||
|
pub mod pools;
|
||||||
|
pub mod stats;
|
||||||
|
pub mod store;
|
||||||
|
|
||||||
pub const DEFAULT_NAME: &str = "gitbaby-blob-cache";
|
pub use config::{
|
||||||
pub const DEFAULT_MEMORY_CAPACITY_BYTES: usize = 64 * 1024 * 1024;
|
CacheConfig, DEFAULT_DISK_CAPACITY_BYTES, DEFAULT_LFS_DISK_CAPACITY_BYTES, DEFAULT_LFS_NAME,
|
||||||
pub const DEFAULT_DISK_CAPACITY_BYTES: usize = 1024 * 1024 * 1024;
|
DEFAULT_MEMORY_CAPACITY_BYTES, DEFAULT_NAME, LfsCacheConfig,
|
||||||
|
};
|
||||||
pub type CacheKey = Vec<u8>;
|
|
||||||
pub type CacheValue = Vec<u8>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct CacheConfig {
|
|
||||||
pub memory_capacity_bytes: usize,
|
|
||||||
pub disk_capacity_bytes: usize,
|
|
||||||
pub disk_dir: PathBuf,
|
|
||||||
pub name: Cow<'static, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CacheConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
memory_capacity_bytes: DEFAULT_MEMORY_CAPACITY_BYTES,
|
|
||||||
disk_capacity_bytes: DEFAULT_DISK_CAPACITY_BYTES,
|
|
||||||
disk_dir: default_cache_dir(),
|
|
||||||
name: Cow::Borrowed(DEFAULT_NAME),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CacheStore {
|
|
||||||
inner: HybridCache<CacheKey, CacheValue>,
|
|
||||||
cfg: CacheConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for CacheStore {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("CacheStore")
|
|
||||||
.field("name", &self.cfg.name)
|
|
||||||
.field("disk_dir", &self.cfg.disk_dir)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CacheStore {
|
|
||||||
pub async fn open(cfg: CacheConfig) -> Result<Arc<Self>, BabyError> {
|
|
||||||
std::fs::create_dir_all(&cfg.disk_dir).map_err(|source| BabyError::Io {
|
|
||||||
path: cfg.disk_dir.clone(),
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let device = FsDeviceBuilder::new(&cfg.disk_dir)
|
|
||||||
.with_capacity(cfg.disk_capacity_bytes)
|
|
||||||
.build()
|
|
||||||
.map_err(|source| BabyError::Cache {
|
|
||||||
op: "device_build",
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let inner: HybridCache<CacheKey, CacheValue> = HybridCacheBuilder::new()
|
|
||||||
.with_name(cfg.name.clone())
|
|
||||||
.memory(cfg.memory_capacity_bytes)
|
|
||||||
.storage()
|
|
||||||
.with_engine_config(BlockEngineConfig::new(device))
|
|
||||||
.build()
|
|
||||||
.await
|
|
||||||
.map_err(|source| BabyError::Cache {
|
|
||||||
op: "build",
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Arc::new(Self { inner, cfg }))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn close(&self) -> Result<(), BabyError> {
|
|
||||||
self.inner.close().await.map_err(|source| BabyError::Cache {
|
|
||||||
op: "close",
|
|
||||||
source,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config(&self) -> &CacheConfig {
|
|
||||||
&self.cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, BabyError> {
|
|
||||||
let entry = self
|
|
||||||
.inner
|
|
||||||
.get(key)
|
|
||||||
.await
|
|
||||||
.map_err(|source| BabyError::Cache { op: "get", source })?;
|
|
||||||
Ok(entry.map(|e| e.value().clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&self, key: Vec<u8>, value: Vec<u8>) {
|
|
||||||
self.inner.insert(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains(&self, key: &[u8]) -> bool {
|
|
||||||
self.inner.contains(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&self, key: &[u8]) {
|
|
||||||
self.inner.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_or_fetch<F, Fut>(&self, key: Vec<u8>, fetch: F) -> Result<Vec<u8>, BabyError>
|
|
||||||
where
|
|
||||||
F: FnOnce() -> Fut,
|
|
||||||
Fut: std::future::Future<Output = Result<Vec<u8>, BabyError>> + Send + 'static,
|
|
||||||
{
|
|
||||||
let entry = self
|
|
||||||
.inner
|
|
||||||
.get_or_fetch(&key, fetch)
|
|
||||||
.await
|
|
||||||
.map_err(|source| BabyError::Cache {
|
|
||||||
op: "get_or_fetch",
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
Ok(entry.value().clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_cache_dir() -> PathBuf {
|
|
||||||
if let Some(xdg) = std::env::var_os("XDG_CACHE_HOME") {
|
|
||||||
return PathBuf::from(xdg).join("gitbaby");
|
|
||||||
}
|
|
||||||
if let Some(local) = std::env::var_os("LOCALAPPDATA") {
|
|
||||||
return PathBuf::from(local).join("gitbaby");
|
|
||||||
}
|
|
||||||
if let Some(home) = std::env::var_os("HOME") {
|
|
||||||
return PathBuf::from(home).join(".cache").join("gitbaby");
|
|
||||||
}
|
|
||||||
std::env::temp_dir().join("gitbaby")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use global::{
|
pub use global::{
|
||||||
GITBABY_CACHE, init_global_cache, init_global_cache_async, shutdown_global_cache,
|
GITBABY_CACHE, init_global_cache, init_global_cache_async, shutdown_global_cache,
|
||||||
try_global_cache,
|
try_global_cache,
|
||||||
};
|
};
|
||||||
|
pub use key::{
|
||||||
|
NS_BLOB, NS_BLOB_EXISTS, NS_BLOB_SIZE, NS_COMMIT, NS_LFS, NS_REV, NS_TREE_ENTRIES, encode,
|
||||||
|
object_key, rev_key,
|
||||||
|
};
|
||||||
|
pub use pools::CachePools;
|
||||||
|
pub use store::{CacheKey, CacheStore, CacheValue};
|
||||||
|
|||||||
59
src/cache/pools.rs
vendored
Normal file
59
src/cache/pools.rs
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::cache::config::DEFAULT_LFS_NAME;
|
||||||
|
use crate::cache::stats::CacheStats;
|
||||||
|
use crate::cache::{CacheConfig, CacheStore};
|
||||||
|
use crate::error::BabyError;
|
||||||
|
|
||||||
|
pub struct CachePools {
|
||||||
|
store: Arc<CacheStore>,
|
||||||
|
lfs: Option<Arc<CacheStore>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CachePools {
|
||||||
|
pub async fn open(cfg: CacheConfig) -> Result<Arc<Self>, BabyError> {
|
||||||
|
let store = CacheStore::open(cfg.clone()).await?;
|
||||||
|
let lfs = match &cfg.lfs {
|
||||||
|
Some(lfs_cfg) => {
|
||||||
|
let lfs_cfg = CacheConfig {
|
||||||
|
memory_capacity_bytes: lfs_cfg.memory_capacity_bytes,
|
||||||
|
disk_capacity_bytes: lfs_cfg.disk_capacity_bytes,
|
||||||
|
disk_dir: cfg.disk_dir.join("lfs"),
|
||||||
|
name: Cow::Owned(DEFAULT_LFS_NAME.to_owned()),
|
||||||
|
lfs: None,
|
||||||
|
};
|
||||||
|
Some(CacheStore::open(lfs_cfg).await?)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
Ok(Arc::new(Self { store, lfs }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn close(&self) -> Result<(), BabyError> {
|
||||||
|
self.store.close().await?;
|
||||||
|
if let Some(lfs) = &self.lfs {
|
||||||
|
lfs.close().await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(&self) -> &Arc<CacheStore> {
|
||||||
|
&self.store
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lfs(&self) -> Option<&Arc<CacheStore>> {
|
||||||
|
self.lfs.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stats(&self) -> CacheStats {
|
||||||
|
let s = self.store.statistics();
|
||||||
|
CacheStats {
|
||||||
|
name: self.store.config().name.to_string(),
|
||||||
|
disk_write_bytes: s.disk_write_bytes() as u64,
|
||||||
|
disk_read_bytes: s.disk_read_bytes() as u64,
|
||||||
|
disk_write_ios: s.disk_write_ios() as u64,
|
||||||
|
disk_read_ios: s.disk_read_ios() as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/cache/stats.rs
vendored
Normal file
8
src/cache/stats.rs
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CacheStats {
|
||||||
|
pub name: String,
|
||||||
|
pub disk_write_bytes: u64,
|
||||||
|
pub disk_read_bytes: u64,
|
||||||
|
pub disk_write_ios: u64,
|
||||||
|
pub disk_read_ios: u64,
|
||||||
|
}
|
||||||
106
src/cache/store.rs
vendored
Normal file
106
src/cache/store.rs
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use foyer::{BlockEngineConfig, DeviceBuilder, FsDeviceBuilder, HybridCache, HybridCacheBuilder};
|
||||||
|
|
||||||
|
use crate::cache::CacheConfig;
|
||||||
|
use crate::error::BabyError;
|
||||||
|
|
||||||
|
pub type CacheKey = Vec<u8>;
|
||||||
|
pub type CacheValue = Vec<u8>;
|
||||||
|
|
||||||
|
pub struct CacheStore {
|
||||||
|
inner: HybridCache<CacheKey, CacheValue>,
|
||||||
|
cfg: CacheConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for CacheStore {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("CacheStore")
|
||||||
|
.field("name", &self.cfg.name)
|
||||||
|
.field("disk_dir", &self.cfg.disk_dir)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CacheStore {
|
||||||
|
pub async fn open(cfg: CacheConfig) -> Result<Arc<Self>, BabyError> {
|
||||||
|
std::fs::create_dir_all(&cfg.disk_dir).map_err(|source| BabyError::Io {
|
||||||
|
path: cfg.disk_dir.clone(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let device = FsDeviceBuilder::new(&cfg.disk_dir)
|
||||||
|
.with_capacity(cfg.disk_capacity_bytes)
|
||||||
|
.build()
|
||||||
|
.map_err(|source| BabyError::Cache {
|
||||||
|
op: "device_build",
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let inner: HybridCache<CacheKey, CacheValue> = HybridCacheBuilder::new()
|
||||||
|
.with_name(cfg.name.clone())
|
||||||
|
.memory(cfg.memory_capacity_bytes)
|
||||||
|
.storage()
|
||||||
|
.with_engine_config(BlockEngineConfig::new(device))
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.map_err(|source| BabyError::Cache {
|
||||||
|
op: "build",
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Arc::new(Self { inner, cfg }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn close(&self) -> Result<(), BabyError> {
|
||||||
|
self.inner.close().await.map_err(|source| BabyError::Cache {
|
||||||
|
op: "close",
|
||||||
|
source,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(&self) -> &CacheConfig {
|
||||||
|
&self.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn statistics(&self) -> Arc<foyer::Statistics> {
|
||||||
|
Arc::clone(self.inner.statistics())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, BabyError> {
|
||||||
|
let entry = self
|
||||||
|
.inner
|
||||||
|
.get(key)
|
||||||
|
.await
|
||||||
|
.map_err(|source| BabyError::Cache { op: "get", source })?;
|
||||||
|
Ok(entry.map(|e| e.value().clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&self, key: Vec<u8>, value: Vec<u8>) {
|
||||||
|
self.inner.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, key: &[u8]) -> bool {
|
||||||
|
self.inner.contains(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self, key: &[u8]) {
|
||||||
|
self.inner.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_or_fetch<F, Fut>(&self, key: Vec<u8>, fetch: F) -> Result<Vec<u8>, BabyError>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Fut,
|
||||||
|
Fut: std::future::Future<Output = Result<Vec<u8>, BabyError>> + Send + 'static,
|
||||||
|
{
|
||||||
|
let entry = self
|
||||||
|
.inner
|
||||||
|
.get_or_fetch(&key, fetch)
|
||||||
|
.await
|
||||||
|
.map_err(|source| BabyError::Cache {
|
||||||
|
op: "get_or_fetch",
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
Ok(entry.value().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,18 @@ use crate::error::BabyError;
|
|||||||
impl GitBaby {
|
impl GitBaby {
|
||||||
pub async fn get_commit(&self, revision: &str) -> Result<CommitInfo, BabyError> {
|
pub async fn get_commit(&self, revision: &str) -> Result<CommitInfo, BabyError> {
|
||||||
helper::validate_revision(revision)?;
|
helper::validate_revision(revision)?;
|
||||||
|
let id = self
|
||||||
|
.rev_parse(revision)
|
||||||
|
.await
|
||||||
|
.map_err(helper::facade_error)?;
|
||||||
|
let pool = crate::cache::try_global_cache();
|
||||||
|
let key = crate::cache::object_key(crate::cache::NS_COMMIT, &id);
|
||||||
|
if let Some(p) = &pool
|
||||||
|
&& let Ok(Some(v)) = p.store().get(&key).await
|
||||||
|
&& let Ok(info) = serde_json::from_slice(&v)
|
||||||
|
{
|
||||||
|
return Ok(info);
|
||||||
|
}
|
||||||
let mut cmd = self
|
let mut cmd = self
|
||||||
.spawn_commit_cmd(revision, |dir, env| {
|
.spawn_commit_cmd(revision, |dir, env| {
|
||||||
helper::build_get_commit_cmd(dir, env, revision)
|
helper::build_get_commit_cmd(dir, env, revision)
|
||||||
@ -27,15 +39,37 @@ impl GitBaby {
|
|||||||
&ctx,
|
&ctx,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let id = self
|
let info = helper::parse_commit_porcelain(id, &output.stdout)?;
|
||||||
.rev_parse(revision)
|
if let Some(p) = &pool
|
||||||
.await
|
&& let Ok(v) = serde_json::to_vec(&info)
|
||||||
.map_err(helper::facade_error)?;
|
{
|
||||||
helper::parse_commit_porcelain(id, &output.stdout)
|
p.store().insert(key, v);
|
||||||
|
}
|
||||||
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn rev_parse(&self, revision: &str) -> Result<gix::ObjectId, BabyError> {
|
pub async fn rev_parse(&self, revision: &str) -> Result<gix::ObjectId, BabyError> {
|
||||||
helper::validate_revision(revision)?;
|
helper::validate_revision(revision)?;
|
||||||
|
let pool = crate::cache::try_global_cache();
|
||||||
|
let key = if pool.is_some() {
|
||||||
|
let dir = self
|
||||||
|
.facade
|
||||||
|
.git_repo_dir()
|
||||||
|
.await
|
||||||
|
.map_err(helper::facade_error)?;
|
||||||
|
let dir = std::fs::canonicalize(&dir)
|
||||||
|
.map_err(|source| BabyError::Io { path: dir, source })?;
|
||||||
|
Some(crate::cache::rev_key(&dir, revision))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let (Some(p), Some(key)) = (&pool, &key)
|
||||||
|
&& let Ok(Some(v)) = p.store().get(key).await
|
||||||
|
&& let Ok(hex) = String::from_utf8(v)
|
||||||
|
&& let Ok(oid) = gix::ObjectId::from_hex(hex.trim().as_bytes())
|
||||||
|
{
|
||||||
|
return Ok(oid);
|
||||||
|
}
|
||||||
let mut cmd = self
|
let mut cmd = self
|
||||||
.spawn_commit_cmd(revision, |dir, env| {
|
.spawn_commit_cmd(revision, |dir, env| {
|
||||||
helper::build_rev_parse_cmd(dir, env, revision)
|
helper::build_rev_parse_cmd(dir, env, revision)
|
||||||
@ -57,12 +91,16 @@ impl GitBaby {
|
|||||||
}
|
}
|
||||||
let text = String::from_utf8_lossy(&output.stdout);
|
let text = String::from_utf8_lossy(&output.stdout);
|
||||||
let hex = text.trim();
|
let hex = text.trim();
|
||||||
gix::ObjectId::from_hex(hex.as_bytes()).map_err(|source| {
|
let oid = gix::ObjectId::from_hex(hex.as_bytes()).map_err(|source| {
|
||||||
BabyError::Custom(format!(
|
BabyError::Custom(format!(
|
||||||
"rev-parse returned invalid oid `{}`: {}",
|
"rev-parse returned invalid oid `{}`: {}",
|
||||||
hex, source
|
hex, source
|
||||||
))
|
))
|
||||||
})
|
})?;
|
||||||
|
if let (Some(p), Some(key)) = (&pool, &key) {
|
||||||
|
p.store().insert(key.clone(), hex.as_bytes().to_vec());
|
||||||
|
}
|
||||||
|
Ok(oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_ancestor(&self, ancestor: &str, descendant: &str) -> Result<bool, BabyError> {
|
pub async fn is_ancestor(&self, ancestor: &str, descendant: &str) -> Result<bool, BabyError> {
|
||||||
@ -157,6 +195,28 @@ impl GitBaby {
|
|||||||
) -> Result<Vec<TreeEntry>, BabyError> {
|
) -> Result<Vec<TreeEntry>, BabyError> {
|
||||||
helper::validate_revision(revision)?;
|
helper::validate_revision(revision)?;
|
||||||
helper::validate_path_local_no_escape(path)?;
|
helper::validate_path_local_no_escape(path)?;
|
||||||
|
let pool = crate::cache::try_global_cache();
|
||||||
|
let key = if pool.is_some() {
|
||||||
|
match self.rev_parse(revision).await.map_err(helper::facade_error) {
|
||||||
|
Ok(oid) => {
|
||||||
|
let mut k =
|
||||||
|
Vec::with_capacity(oid.as_bytes().len() + 1 + path.as_os_str().len());
|
||||||
|
k.extend_from_slice(oid.as_bytes());
|
||||||
|
k.push(0);
|
||||||
|
k.extend_from_slice(path.to_string_lossy().as_bytes());
|
||||||
|
Some(crate::cache::encode(crate::cache::NS_TREE_ENTRIES, &k))
|
||||||
|
}
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let (Some(p), Some(key)) = (&pool, &key)
|
||||||
|
&& let Ok(Some(v)) = p.store().get(key).await
|
||||||
|
&& let Ok(entries) = serde_json::from_slice::<Vec<TreeEntry>>(&v)
|
||||||
|
{
|
||||||
|
return Ok(entries);
|
||||||
|
}
|
||||||
let mut cmd = self
|
let mut cmd = self
|
||||||
.spawn_commit_cmd(revision, |dir, env| {
|
.spawn_commit_cmd(revision, |dir, env| {
|
||||||
helper::build_tree_entries_cmd(dir, env, revision, path)
|
helper::build_tree_entries_cmd(dir, env, revision, path)
|
||||||
@ -176,7 +236,13 @@ impl GitBaby {
|
|||||||
&ctx,
|
&ctx,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
helper::parse_tree_entries(&output.stdout)
|
let entries = helper::parse_tree_entries(&output.stdout)?;
|
||||||
|
if let (Some(p), Some(key)) = (&pool, &key)
|
||||||
|
&& let Ok(v) = serde_json::to_vec(&entries)
|
||||||
|
{
|
||||||
|
p.store().insert(key.clone(), v);
|
||||||
|
}
|
||||||
|
Ok(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn last_commit_for_path(
|
pub async fn last_commit_for_path(
|
||||||
|
|||||||
@ -101,6 +101,7 @@ impl GitBaby {
|
|||||||
Some(&update.name),
|
Some(&update.name),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, &update.name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +120,7 @@ impl GitBaby {
|
|||||||
Some(name),
|
Some(name),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +168,9 @@ impl GitBaby {
|
|||||||
String::from_utf8_lossy(&output.stderr).into_owned(),
|
String::from_utf8_lossy(&output.stderr).into_owned(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
for u in &updates {
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, &u.name).await;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +199,9 @@ impl GitBaby {
|
|||||||
String::from_utf8_lossy(&output.stderr).into_owned(),
|
String::from_utf8_lossy(&output.stderr).into_owned(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
for n in &names {
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, n).await;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -122,6 +122,7 @@ impl GitBaby {
|
|||||||
&opts.name,
|
&opts.name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, &opts.name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +146,7 @@ impl GitBaby {
|
|||||||
&opts.name,
|
&opts.name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, &opts.name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +172,7 @@ impl GitBaby {
|
|||||||
&opts.name,
|
&opts.name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, &opts.name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +206,7 @@ impl GitBaby {
|
|||||||
name,
|
name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let _ = crate::cache::invalidation::hook_ref_updated(self, name).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user