use std::collections::HashMap;
use crate::error::VcsError;
use crate::hash::{self, ObjectId};
use crate::object::Object;
use crate::store::{HeadState, ReflogEntry, Store};
#[derive(Debug)]
pub struct MemStore {
objects: HashMap<ObjectId, Object>,
refs: HashMap<String, ObjectId>,
head: HeadState,
reflogs: HashMap<String, Vec<ReflogEntry>>,
}
impl MemStore {
#[must_use]
pub fn new() -> Self {
Self {
objects: HashMap::new(),
refs: HashMap::new(),
head: HeadState::Branch("main".into()),
reflogs: HashMap::new(),
}
}
}
impl Default for MemStore {
fn default() -> Self {
Self::new()
}
}
impl Store for MemStore {
fn has(&self, id: &ObjectId) -> bool {
self.objects.contains_key(id)
}
fn get(&self, id: &ObjectId) -> Result<Object, VcsError> {
self.objects
.get(id)
.cloned()
.ok_or(VcsError::ObjectNotFound { id: *id })
}
fn put(&mut self, object: &Object) -> Result<ObjectId, VcsError> {
let id = compute_object_id(object)?;
self.objects.entry(id).or_insert_with(|| object.clone());
Ok(id)
}
fn get_ref(&self, name: &str) -> Result<Option<ObjectId>, VcsError> {
Ok(self.refs.get(name).copied())
}
fn set_ref(&mut self, name: &str, id: ObjectId) -> Result<(), VcsError> {
self.refs.insert(name.to_owned(), id);
Ok(())
}
fn delete_ref(&mut self, name: &str) -> Result<(), VcsError> {
self.refs
.remove(name)
.ok_or_else(|| VcsError::RefNotFound {
name: name.to_owned(),
})?;
Ok(())
}
fn list_refs(&self, prefix: &str) -> Result<Vec<(String, ObjectId)>, VcsError> {
let mut result: Vec<(String, ObjectId)> = self
.refs
.iter()
.filter(|(k, _)| k.starts_with(prefix))
.map(|(k, v)| (k.clone(), *v))
.collect();
result.sort_by(|a, b| a.0.cmp(&b.0));
Ok(result)
}
fn get_head(&self) -> Result<HeadState, VcsError> {
Ok(self.head.clone())
}
fn set_head(&mut self, state: HeadState) -> Result<(), VcsError> {
self.head = state;
Ok(())
}
fn list_objects(&self) -> Result<Vec<ObjectId>, VcsError> {
Ok(self.objects.keys().copied().collect())
}
fn delete_object(&mut self, id: &ObjectId) -> Result<(), VcsError> {
self.objects
.remove(id)
.ok_or(VcsError::ObjectNotFound { id: *id })?;
Ok(())
}
fn append_reflog(&mut self, ref_name: &str, entry: ReflogEntry) -> Result<(), VcsError> {
self.reflogs
.entry(ref_name.to_owned())
.or_default()
.push(entry);
Ok(())
}
fn read_reflog(
&self,
ref_name: &str,
limit: Option<usize>,
) -> Result<Vec<ReflogEntry>, VcsError> {
let entries = self.reflogs.get(ref_name).cloned().unwrap_or_default();
let mut reversed: Vec<ReflogEntry> = entries.into_iter().rev().collect();
if let Some(n) = limit {
reversed.truncate(n);
}
Ok(reversed)
}
}
fn compute_object_id(object: &Object) -> Result<ObjectId, VcsError> {
match object {
Object::Migration { src, tgt, mapping } => hash::hash_migration(*src, *tgt, mapping),
Object::Commit(commit) => hash::hash_commit(commit),
Object::Tag(tag) => hash::hash_tag(tag),
Object::DataSet(dataset) => hash::hash_dataset(dataset),
Object::Complement(complement) => hash::hash_complement(complement),
Object::Protocol(protocol) => hash::hash_protocol(protocol),
Object::Expr(expr) => hash::hash_expr(expr),
Object::EditLog(edit_log) => hash::hash_edit_log(edit_log),
Object::Theory(theory) => hash::hash_theory(theory),
Object::TheoryMorphism(morphism) => hash::hash_theory_morphism(morphism),
Object::CstComplement(cst_comp) => hash::hash_cst_complement(cst_comp),
Object::FileSchema(file) => hash::hash_file_schema(file),
Object::SchemaTree(tree) => hash::hash_schema_tree(tree),
Object::FlatSchema(schema) => hash::hash_schema(schema),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::VcsError;
use panproto_schema::{Schema, Vertex};
use std::collections::HashMap;
fn test_schema() -> Schema {
use panproto_gat::Name;
let mut vertices = HashMap::new();
vertices.insert(
Name::from("root"),
Vertex {
id: Name::from("root"),
kind: Name::from("object"),
nsid: None,
},
);
Schema {
protocol: "test".into(),
vertices,
edges: HashMap::new(),
hyper_edges: HashMap::new(),
constraints: HashMap::new(),
required: HashMap::new(),
nsids: HashMap::new(),
entries: Vec::new(),
variants: HashMap::new(),
orderings: HashMap::new(),
recursion_points: HashMap::new(),
spans: HashMap::new(),
usage_modes: HashMap::new(),
nominal: HashMap::new(),
coercions: HashMap::new(),
mergers: HashMap::new(),
defaults: HashMap::new(),
policies: HashMap::new(),
outgoing: HashMap::new(),
incoming: HashMap::new(),
between: HashMap::new(),
}
}
#[test]
fn put_get_round_trip() -> Result<(), VcsError> {
let mut store = MemStore::new();
let schema = test_schema();
let id = crate::tree::store_schema_as_tree(&mut store, schema)?;
assert!(store.has(&id));
let retrieved = store.get(&id)?;
match retrieved {
Object::SchemaTree(tree) => match *tree {
crate::object::SchemaTreeObject::SingleLeaf { .. } => {}
crate::object::SchemaTreeObject::Directory { .. } => {
panic!("expected SingleLeaf wrapper, got Directory")
}
},
_ => panic!("expected SchemaTree object"),
}
Ok(())
}
#[test]
fn flat_schema_put_get_round_trip() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = store.put(&Object::FlatSchema(Box::new(test_schema())))?;
match store.get(&id)? {
Object::FlatSchema(s) => {
assert_eq!(s.vertices.len(), 1);
assert_eq!(s.protocol, "test");
}
other => panic!("expected FlatSchema, got {}", other.type_name()),
}
Ok(())
}
#[test]
fn multi_leaf_schema_tree_round_trip() -> Result<(), VcsError> {
use crate::object::FileSchemaObject;
use std::path::PathBuf;
let mut store = MemStore::new();
let mk = |path: &str| FileSchemaObject {
path: path.to_owned(),
protocol: "project".to_owned(),
schema: test_schema(),
cross_file_edges: Vec::new(),
};
let root = crate::tree::build_schema_tree(
&mut store,
vec![
(PathBuf::from("src/a.rs"), mk("src/a.rs")),
(PathBuf::from("src/b.rs"), mk("src/b.rs")),
(PathBuf::from("c.rs"), mk("c.rs")),
],
)?;
let mut count = 0usize;
crate::tree::walk_tree(&store, &root, |_, _| {
count += 1;
Ok(())
})?;
assert_eq!(count, 3);
Ok(())
}
#[test]
fn put_idempotent() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id1 = crate::tree::store_schema_as_tree(&mut store, test_schema())?;
let id2 = crate::tree::store_schema_as_tree(&mut store, test_schema())?;
assert_eq!(id1, id2);
Ok(())
}
#[test]
fn get_nonexistent_returns_error() {
let store = MemStore::new();
let result = store.get(&ObjectId::ZERO);
assert!(result.is_err());
}
#[test]
fn ref_operations() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([42; 32]);
store.set_ref("refs/heads/main", id)?;
assert_eq!(store.get_ref("refs/heads/main")?, Some(id));
let refs = store.list_refs("refs/heads/")?;
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].0, "refs/heads/main");
store.delete_ref("refs/heads/main")?;
assert_eq!(store.get_ref("refs/heads/main")?, None);
Ok(())
}
#[test]
fn head_state() -> Result<(), VcsError> {
let mut store = MemStore::new();
assert_eq!(store.get_head()?, HeadState::Branch("main".into()));
let id = ObjectId::from_bytes([1; 32]);
store.set_head(HeadState::Detached(id))?;
assert_eq!(store.get_head()?, HeadState::Detached(id));
Ok(())
}
#[test]
fn reflog_append_and_read() -> Result<(), VcsError> {
let mut store = MemStore::new();
let entry1 = ReflogEntry {
old_id: None,
new_id: ObjectId::from_bytes([1; 32]),
author: "test".into(),
timestamp: 100,
message: "first".into(),
};
let entry2 = ReflogEntry {
old_id: Some(ObjectId::from_bytes([1; 32])),
new_id: ObjectId::from_bytes([2; 32]),
author: "test".into(),
timestamp: 200,
message: "second".into(),
};
store.append_reflog("HEAD", entry1)?;
store.append_reflog("HEAD", entry2)?;
let log = store.read_reflog("HEAD", None)?;
assert_eq!(log.len(), 2);
assert_eq!(log[0].message, "second");
assert_eq!(log[1].message, "first");
let log = store.read_reflog("HEAD", Some(1))?;
assert_eq!(log.len(), 1);
assert_eq!(log[0].message, "second");
Ok(())
}
#[test]
fn resolve_head_empty_repo() -> Result<(), VcsError> {
let store = MemStore::new();
assert_eq!(crate::store::resolve_head(&store)?, None);
Ok(())
}
#[test]
fn resolve_head_with_branch() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([1; 32]);
store.set_ref("refs/heads/main", id)?;
assert_eq!(crate::store::resolve_head(&store)?, Some(id));
Ok(())
}
#[test]
fn resolve_head_detached() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([1; 32]);
store.set_head(HeadState::Detached(id))?;
assert_eq!(crate::store::resolve_head(&store)?, Some(id));
Ok(())
}
}