pocket_cli/vcs/
objects.rs

1//! Object storage for Pocket VCS
2//!
3//! Handles the content-addressable storage of file contents and trees.
4
5use std::path::{Path, PathBuf};
6use std::fs;
7use serde::{Serialize, Deserialize};
8use anyhow::{Result, anyhow};
9use sha2::{Sha256, Digest};
10
11/// A unique identifier for an object
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct ObjectId(String);
14
15impl ObjectId {
16    /// Create a new object ID from content
17    pub fn from_content(content: &[u8]) -> Self {
18        let mut hasher = Sha256::new();
19        hasher.update(content);
20        let hash = hasher.finalize();
21        Self(format!("{:x}", hash))
22    }
23    
24    /// Parse an object ID from a string
25    pub fn from_str(s: &str) -> Result<Self> {
26        Ok(Self(s.to_string()))
27    }
28    
29    /// Get the string representation
30    pub fn as_str(&self) -> &str {
31        &self.0
32    }
33}
34
35/// Type of a tree entry
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37pub enum EntryType {
38    File,
39    Tree,
40}
41
42/// An entry in a tree
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct TreeEntry {
45    pub name: String,
46    pub id: ObjectId,
47    pub entry_type: EntryType,
48    pub permissions: u32,
49}
50
51/// A tree object representing a directory
52#[derive(Debug, Serialize, Deserialize)]
53pub struct Tree {
54    pub entries: Vec<TreeEntry>,
55}
56
57/// Object storage for the repository
58pub struct ObjectStore {
59    base_path: PathBuf,
60}
61
62impl ObjectStore {
63    /// Create a new object store
64    pub fn new(base_path: PathBuf) -> Self {
65        Self { base_path }
66    }
67    
68    /// Store a file in the object store
69    pub fn store_file(&self, path: &Path) -> Result<ObjectId> {
70        let content = fs::read(path)?;
71        self.store_object(&content)
72    }
73    
74    /// Store raw content in the object store
75    pub fn store_object(&self, content: &[u8]) -> Result<ObjectId> {
76        let id = ObjectId::from_content(content);
77        let object_path = self.get_object_path(&id);
78        
79        // Create parent directories if they don't exist
80        if let Some(parent) = object_path.parent() {
81            fs::create_dir_all(parent)?;
82        }
83        
84        // Only write if the object doesn't already exist
85        if !object_path.exists() {
86            fs::write(object_path, content)?;
87        }
88        
89        Ok(id)
90    }
91    
92    /// Retrieve an object from the store
93    pub fn get_object(&self, id: &ObjectId) -> Result<Vec<u8>> {
94        let path = self.get_object_path(id);
95        if !path.exists() {
96            return Err(anyhow!("Object not found: {}", id.as_str()));
97        }
98        
99        Ok(fs::read(path)?)
100    }
101    
102    /// Check if an object exists in the store
103    pub fn has_object(&self, id: &ObjectId) -> bool {
104        self.get_object_path(id).exists()
105    }
106    
107    /// Get the path for an object
108    fn get_object_path(&self, id: &ObjectId) -> PathBuf {
109        let id_str = id.as_str();
110        // Use first 2 chars as directory to avoid too many files in one dir
111        let prefix = &id_str[0..2];
112        let suffix = &id_str[2..];
113        self.base_path.join(prefix).join(suffix)
114    }
115    
116    /// Store a tree object
117    pub fn store_tree(&self, tree: &Tree) -> Result<ObjectId> {
118        let content = toml::to_string(tree)?;
119        self.store_object(content.as_bytes())
120    }
121    
122    /// Retrieve a tree object
123    pub fn get_tree(&self, id: &ObjectId) -> Result<Tree> {
124        let content = self.get_object(id)?;
125        let content_str = String::from_utf8(content)?;
126        let tree: Tree = toml::from_str(&content_str)?;
127        Ok(tree)
128    }
129    
130    // Additional methods would be implemented here:
131    // - create_tree_from_directory: Create a tree from a directory
132    // - apply_tree_to_directory: Apply a tree to a directory
133    // - etc.
134}