Skip to main content

lit/storage/
index.rs

1use crate::crypto::encryption::{EncryptionConfig, EncryptionManager};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::Path;
6
7/// Index entry - represents a staged file
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct IndexEntry {
10    pub path: String,
11    pub hash: String,
12    pub mode: String,
13}
14
15/// The staging area (index)
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Index {
18    pub entries: HashMap<String, IndexEntry>,
19}
20
21impl Default for Index {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl Index {
28    /// Create a new empty index
29    pub fn new() -> Self {
30        Index {
31            entries: HashMap::new(),
32        }
33    }
34
35    /// Load index from disk
36    pub fn load(repo_path: &Path) -> Result<Self, String> {
37        Self::load_with_encryption(repo_path, None)
38    }
39
40    /// Load index from disk with encryption support
41    pub fn load_with_encryption(
42        repo_path: &Path,
43        passphrase: Option<&str>,
44    ) -> Result<Self, String> {
45        let index_path = repo_path.join(".lit").join("index");
46
47        if !index_path.exists() {
48            return Ok(Index::new());
49        }
50
51        let encrypted_data =
52            fs::read(&index_path).map_err(|e| format!("Failed to read index: {}", e))?;
53
54        // Decrypt if encryption is enabled
55        let data = {
56            let encryption_config = EncryptionConfig::load(repo_path)?;
57            let mut encryption_manager = EncryptionManager::new(encryption_config);
58
59            if let Some(pass) = passphrase {
60                encryption_manager.initialize(pass)?;
61            }
62
63            encryption_manager.decrypt(&encrypted_data)?
64        };
65
66        serde_json::from_slice(&data).map_err(|e| format!("Failed to parse index: {}", e))
67    }
68
69    /// Save index to disk
70    pub fn save(&self, repo_path: &Path) -> Result<(), String> {
71        self.save_with_encryption(repo_path, None)
72    }
73
74    /// Save index to disk with encryption support
75    pub fn save_with_encryption(
76        &self,
77        repo_path: &Path,
78        passphrase: Option<&str>,
79    ) -> Result<(), String> {
80        let index_path = repo_path.join(".lit").join("index");
81
82        let data = serde_json::to_vec_pretty(self)
83            .map_err(|e| format!("Failed to serialize index: {}", e))?;
84
85        // Encrypt if encryption is enabled
86        let final_data = {
87            let encryption_config = EncryptionConfig::load(repo_path)?;
88            let mut encryption_manager = EncryptionManager::new(encryption_config);
89
90            if let Some(pass) = passphrase {
91                encryption_manager.initialize(pass)?;
92            }
93
94            encryption_manager.encrypt(&data)?
95        };
96
97        fs::write(&index_path, final_data).map_err(|e| format!("Failed to write index: {}", e))
98    }
99
100    /// Add or update an entry in the index
101    pub fn add(&mut self, path: String, hash: String, mode: String) {
102        self.entries
103            .insert(path.clone(), IndexEntry { path, hash, mode });
104    }
105
106    /// Remove an entry from the index
107    pub fn remove(&mut self, path: &str) -> Option<IndexEntry> {
108        self.entries.remove(path)
109    }
110
111    /// Get all entries sorted by path
112    pub fn sorted_entries(&self) -> Vec<&IndexEntry> {
113        let mut entries: Vec<&IndexEntry> = self.entries.values().collect();
114        entries.sort_by(|a, b| a.path.cmp(&b.path));
115        entries
116    }
117
118    /// Clear the index
119    pub fn clear(&mut self) {
120        self.entries.clear();
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use tempfile::TempDir;
128
129    #[test]
130    fn test_index() {
131        let temp_dir = TempDir::new().unwrap();
132        let repo_path = temp_dir.path();
133
134        fs::create_dir_all(repo_path.join(".lit")).unwrap();
135
136        let mut index = Index::new();
137        index.add(
138            "file.txt".to_string(),
139            "abc123".to_string(),
140            "100644".to_string(),
141        );
142
143        index.save(repo_path).unwrap();
144
145        let loaded = Index::load(repo_path).unwrap();
146        assert_eq!(loaded.entries.len(), 1);
147        assert!(loaded.entries.contains_key("file.txt"));
148    }
149}