heroforge_core/repo/builders/
files.rs

1//! File operations builder.
2
3use crate::error::{FossilError, Result};
4use crate::repo::{FileInfo, Repository};
5
6/// Target for file queries.
7#[derive(Clone)]
8enum QueryTarget {
9    Trunk,
10    Branch(String),
11    Tag(String),
12    Commit(String),
13}
14
15/// Builder for file operations.
16pub struct FilesBuilder<'a> {
17    repo: &'a Repository,
18}
19
20impl<'a> FilesBuilder<'a> {
21    pub(crate) fn new(repo: &'a Repository) -> Self {
22        Self { repo }
23    }
24
25    /// Query files on trunk.
26    pub fn on_trunk(self) -> FileQuery<'a> {
27        FileQuery {
28            repo: self.repo,
29            target: QueryTarget::Trunk,
30            directory: None,
31        }
32    }
33
34    /// Query files on a specific branch.
35    pub fn on_branch(self, name: &str) -> FileQuery<'a> {
36        FileQuery {
37            repo: self.repo,
38            target: QueryTarget::Branch(name.to_string()),
39            directory: None,
40        }
41    }
42
43    /// Query files at a specific tag.
44    pub fn at_tag(self, name: &str) -> FileQuery<'a> {
45        FileQuery {
46            repo: self.repo,
47            target: QueryTarget::Tag(name.to_string()),
48            directory: None,
49        }
50    }
51
52    /// Query files at a specific commit.
53    pub fn at_commit(self, hash: &str) -> FileQuery<'a> {
54        FileQuery {
55            repo: self.repo,
56            target: QueryTarget::Commit(hash.to_string()),
57            directory: None,
58        }
59    }
60}
61
62/// Query for files at a specific point in the repository.
63pub struct FileQuery<'a> {
64    repo: &'a Repository,
65    target: QueryTarget,
66    directory: Option<String>,
67}
68
69impl<'a> FileQuery<'a> {
70    /// Resolve the target to a commit hash.
71    fn resolve_hash(&self) -> Result<String> {
72        match &self.target {
73            QueryTarget::Trunk => {
74                let tip = self.repo.database().get_trunk_tip()?;
75                self.repo.database().get_hash_by_rid(tip)
76            }
77            QueryTarget::Branch(name) => {
78                let tip = self.repo.database().get_branch_tip(name)?;
79                self.repo.database().get_hash_by_rid(tip)
80            }
81            QueryTarget::Tag(name) => self.repo.get_tag_checkin_internal(name),
82            QueryTarget::Commit(hash) => Ok(hash.clone()),
83        }
84    }
85
86    /// Scope to a specific directory.
87    pub fn in_dir(mut self, dir: &str) -> Self {
88        self.directory = Some(dir.to_string());
89        self
90    }
91
92    /// List all files.
93    pub fn list(&self) -> Result<Vec<FileInfo>> {
94        let hash = self.resolve_hash()?;
95        if let Some(dir) = &self.directory {
96            self.repo.list_directory_internal(&hash, dir)
97        } else {
98            self.repo.list_files_internal(&hash)
99        }
100    }
101
102    /// Find files matching a glob pattern.
103    pub fn find(&self, pattern: &str) -> Result<Vec<FileInfo>> {
104        let hash = self.resolve_hash()?;
105        self.repo.find_files_internal(&hash, pattern)
106    }
107
108    /// Read a file's content.
109    pub fn read(&self, path: &str) -> Result<Vec<u8>> {
110        let hash = self.resolve_hash()?;
111        self.repo.read_file_internal(&hash, path)
112    }
113
114    /// Read a file as a UTF-8 string.
115    pub fn read_string(&self, path: &str) -> Result<String> {
116        let content = self.read(path)?;
117        String::from_utf8(content).map_err(|e| FossilError::InvalidArtifact(e.to_string()))
118    }
119
120    /// List subdirectories in a directory.
121    pub fn subdirs(&self, dir: &str) -> Result<Vec<String>> {
122        let hash = self.resolve_hash()?;
123        self.repo.list_subdirs_internal(&hash, dir)
124    }
125}