use crate::cache::{CacheError, ObjectCache};
use crate::extract::Extractor;
use crate::git::{GitError, Repo};
use crate::store::StoreError;
use crate::{FactSet, Store};
#[derive(Debug, thiserror::Error)]
pub enum SyncError {
#[error(transparent)]
Store(#[from] StoreError),
#[error(transparent)]
Cache(#[from] CacheError),
#[error(transparent)]
Git(#[from] GitError),
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct SyncReport {
pub tree: String,
pub no_op: bool,
pub blobs_total: usize,
pub blobs_extracted: usize,
pub blobs_cached: usize,
pub nodes: u64,
pub edges: u64,
}
pub fn sync(
store: &mut Store,
repo: &Repo,
cache: &ObjectCache,
extractor: &dyn Extractor,
) -> Result<SyncReport, SyncError> {
let tree = repo.head_tree_id()?;
if store.sync_state()?.as_deref() == Some(tree.as_str()) {
return Ok(SyncReport {
no_op: true,
nodes: store.node_count()?,
edges: store.edge_count()?,
tree,
..SyncReport::default()
});
}
let blobs = repo.walk_blobs()?;
let mut assembled = FactSet::new();
let mut extracted = 0usize;
let mut cached = 0usize;
for blob in &blobs {
let key = cache_key(&blob.path, &blob.oid);
let facts = if let Some(facts) = cache.get(&key)? {
cached += 1;
facts
} else {
let bytes = repo.read_blob(&blob.oid)?;
let facts = extractor.extract(&blob.path, &blob.oid, &bytes);
cache.put(&key, &facts)?;
extracted += 1;
facts
};
let FactSet { nodes, edges } = facts;
assembled.nodes.extend(nodes);
assembled.edges.extend(edges);
}
store.rebuild(&assembled, &tree)?;
Ok(SyncReport {
no_op: false,
blobs_total: blobs.len(),
blobs_extracted: extracted,
blobs_cached: cached,
nodes: store.node_count()?,
edges: store.edge_count()?,
tree,
})
}
fn cache_key(path: &str, oid: &str) -> String {
format!("{oid}-{:016x}", fnv1a64(path.as_bytes()))
}
fn fnv1a64(bytes: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for &b in bytes {
hash ^= u64::from(b);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash
}
#[cfg(test)]
mod tests {
use super::cache_key;
#[test]
fn cache_key_separates_paths_but_is_stable() {
let oid = "abc123";
assert_eq!(cache_key("src/a.rs", oid), cache_key("src/a.rs", oid));
assert_ne!(cache_key("src/a.rs", oid), cache_key("src/b.rs", oid));
assert_ne!(cache_key("src/a.rs", "aaa"), cache_key("src/a.rs", "bbb"));
assert!(cache_key("src/a.rs", oid).starts_with("abc123-"));
}
}