use std::fs;
use std::path::{Path, PathBuf};
use crate::error::Result;
use crate::safety::atomic_write;
use super::{HeadStore, RefStore};
pub struct FileRefStore {
refs_dir: PathBuf,
}
impl FileRefStore {
pub fn new(agit_dir: &Path) -> Self {
Self {
refs_dir: agit_dir.join("refs"),
}
}
fn ref_path(&self, ref_name: &str) -> PathBuf {
self.refs_dir.join("heads").join(ref_name)
}
pub fn ensure_exists(&self) -> Result<()> {
fs::create_dir_all(self.refs_dir.join("heads"))?;
Ok(())
}
}
impl RefStore for FileRefStore {
fn get(&self, ref_name: &str) -> Result<Option<String>> {
let path = self.ref_path(ref_name);
if !path.exists() {
return Ok(None);
}
let content = fs::read_to_string(&path)?;
Ok(Some(content.trim().to_string()))
}
fn update(&self, ref_name: &str, hash: &str) -> Result<()> {
let path = self.ref_path(ref_name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
atomic_write(&path, hash.as_bytes())?;
Ok(())
}
fn delete(&self, ref_name: &str) -> Result<()> {
let path = self.ref_path(ref_name);
if path.exists() {
fs::remove_file(&path)?;
}
Ok(())
}
fn list(&self) -> Result<Vec<String>> {
let heads_dir = self.refs_dir.join("heads");
if !heads_dir.exists() {
return Ok(Vec::new());
}
let mut refs = Vec::new();
for entry in fs::read_dir(&heads_dir)? {
let entry = entry?;
if entry.file_type()?.is_file() {
if let Some(name) = entry.file_name().to_str() {
refs.push(name.to_string());
}
}
}
refs.sort();
Ok(refs)
}
}
pub struct FileHeadStore {
head_path: PathBuf,
}
impl FileHeadStore {
pub fn new(agit_dir: &Path) -> Self {
Self {
head_path: agit_dir.join("HEAD"),
}
}
pub fn ensure_exists(&self, default_branch: &str) -> Result<()> {
if !self.head_path.exists() {
fs::write(&self.head_path, default_branch)?;
}
Ok(())
}
}
impl HeadStore for FileHeadStore {
fn get(&self) -> Result<Option<String>> {
if !self.head_path.exists() {
return Ok(None);
}
let content = fs::read_to_string(&self.head_path)?;
Ok(Some(content.trim().to_string()))
}
fn set(&self, branch: &str) -> Result<()> {
atomic_write(&self.head_path, branch.as_bytes())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn setup() -> (TempDir, FileRefStore, FileHeadStore) {
let temp = TempDir::new().unwrap();
let agit_dir = temp.path().join(".agit");
fs::create_dir_all(&agit_dir).unwrap();
let refs = FileRefStore::new(&agit_dir);
let head = FileHeadStore::new(&agit_dir);
refs.ensure_exists().unwrap();
(temp, refs, head)
}
#[test]
fn test_ref_update_and_get() {
let (_temp, refs, _head) = setup();
refs.update("main", "abc123").unwrap();
let hash = refs.get("main").unwrap();
assert_eq!(hash, Some("abc123".to_string()));
}
#[test]
fn test_ref_not_found() {
let (_temp, refs, _head) = setup();
let hash = refs.get("nonexistent").unwrap();
assert_eq!(hash, None);
}
#[test]
fn test_ref_list() {
let (_temp, refs, _head) = setup();
refs.update("main", "hash1").unwrap();
refs.update("feature", "hash2").unwrap();
refs.update("develop", "hash3").unwrap();
let list = refs.list().unwrap();
assert_eq!(list, vec!["develop", "feature", "main"]);
}
#[test]
fn test_ref_delete() {
let (_temp, refs, _head) = setup();
refs.update("main", "abc123").unwrap();
assert!(refs.get("main").unwrap().is_some());
refs.delete("main").unwrap();
assert!(refs.get("main").unwrap().is_none());
}
#[test]
fn test_head_store() {
let (_temp, _refs, head) = setup();
head.ensure_exists("main").unwrap();
assert_eq!(head.get().unwrap(), Some("main".to_string()));
head.set("feature").unwrap();
assert_eq!(head.get().unwrap(), Some("feature".to_string()));
}
}