1use crate::{
2 error::{Error, GResult},
3 file_system::{Directory, FileSystem, FileSystemError, search_for_files},
4 object::{Object, ObjectId},
5 object_store::{ObjectSize, ObjectType, cache::IndexCache},
6 reference::{Ref, RefName, read_packed_refs},
7};
8use alloc::collections::BTreeSet;
9use alloc::vec::Vec;
10
11pub struct RepoConfig {
13 pub(crate) index_offset_cache_max: usize,
14}
15impl RepoConfig {
16 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn index_offset_cache_max(&mut self, size: usize) -> &mut Self {
25 self.index_offset_cache_max = size;
26 self
27 }
28
29 pub async fn open<F: FileSystem>(&self, open_dir: F::Directory) -> GResult<Repo<F>> {
31 Repo::open_with_config(open_dir, self).await
32 }
33}
34
35impl Default for RepoConfig {
36 fn default() -> Self {
40 Self {
41 index_offset_cache_max: 64 * 1024 * 1024,
42 }
43 }
44}
45
46pub struct Repo<F: FileSystem> {
50 pub(crate) git_dir: F::Directory,
51 pub(crate) pack_dir: F::Directory,
52 pub(crate) index_cache: IndexCache,
53}
54
55impl<F: FileSystem> Repo<F> {
56 pub(crate) async fn open_with_config(
57 open_dir: F::Directory,
58 config: &RepoConfig,
59 ) -> GResult<Self> {
60 let git_dir = Self::resolve_git_dir(open_dir).await?;
61 let pack_dir = git_dir
62 .open_subdir(b"objects")
63 .await?
64 .open_subdir(b"pack")
65 .await?;
66 let index_cache = IndexCache::new(&pack_dir, config).await?;
67 Ok(Repo {
68 git_dir,
69 pack_dir,
70 index_cache,
71 })
72 }
73
74 pub(crate) async fn resolve_git_dir(open_dir: F::Directory) -> GResult<F::Directory> {
75 let head = open_dir.open_file(b"HEAD").await;
76 match head {
77 Ok(_) => Ok(open_dir),
78 Err(FileSystemError::NotFound(_)) => {
79 let git_dir = open_dir.open_subdir(b".git").await?;
80 let head = git_dir.open_file(b"HEAD").await;
81 match head {
82 Ok(_) => Ok(git_dir),
83 Err(FileSystemError::NotFound(_)) => Err(Error::NotAGitRepository),
84 Err(e) => Err(e.into()),
85 }
86 }
87 Err(e) => Err(e.into()),
88 }
89 }
90
91 pub async fn open(git_dir: F::Directory) -> GResult<Self> {
93 Self::open_with_config(git_dir, &RepoConfig::default()).await
94 }
95
96 pub async fn ref_names(&self) -> GResult<BTreeSet<RefName>> {
100 let mut out: BTreeSet<RefName> = BTreeSet::new();
101 out.insert(RefName::Head);
102 match self.git_dir.open_file(b"packed-refs").await {
103 Err(FileSystemError::NotFound(_)) => {}
104 Err(e) => return Err(e.into()),
105 Ok(mut packed_refs_file) => {
106 let packed_refs = read_packed_refs(&mut packed_refs_file).await?;
107 for (_, ref_name) in packed_refs {
108 out.insert(ref_name);
109 }
110 }
111 }
112 let refs_dir = self.git_dir.open_subdir(b"refs").await?;
113 let refs_paths = search_for_files(&refs_dir).await?;
114 for path in refs_paths {
115 let mut name: Vec<u8> = Vec::new();
116 for component in path {
117 if !name.is_empty() {
118 name.push(b'/');
119 }
120 name.extend_from_slice(&component);
121 }
122 out.insert(RefName::Ref(name));
123 }
124 Ok(out)
125 }
126
127 pub async fn head(&self) -> GResult<Ref> {
129 Ref::lookup(self, &RefName::Head).await
130 }
131
132 pub async fn lookup_ref(&self, name: &RefName) -> GResult<Ref> {
134 Ref::lookup(self, name).await
135 }
136
137 pub async fn lookup_object(&self, id: ObjectId) -> GResult<Object> {
140 Object::lookup(self, id).await
141 }
142
143 pub async fn lookup_object_size_type(&self, id: ObjectId) -> GResult<(ObjectSize, ObjectType)> {
146 Object::lookup_size_type(self, id).await
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::{
154 reference::RefTarget,
155 test::{helpers::make_basic_repo, impls::TestFileSystem, repo::TestRepo},
156 };
157 use futures::executor::block_on;
158
159 #[test]
160 fn read_head() {
161 let test_repo = TestRepo::new().unwrap();
162 let repo = test_repo.repo();
163 let head = block_on(repo.head()).unwrap();
164 assert_eq!(
165 head.target(),
166 &RefTarget::Symbolic(RefName::Ref(Vec::from(b"heads/main")))
167 );
168 }
169
170 #[test]
171 fn read_refs() {
172 let test_repo = make_basic_repo().unwrap();
173 test_repo.run_git(["branch", "a-branch"]).unwrap();
174 test_repo.run_git(["branch", "foo/a-branch"]).unwrap();
175 test_repo.run_git(["tag", "thin-tag"]).unwrap();
176 test_repo.run_git(["tag", "bar/thin-tag"]).unwrap();
177 test_repo
178 .run_git(["tag", "-a", "-m", "a tag message", "fat-tag"])
179 .unwrap();
180 test_repo
181 .run_git(["update-ref", "refs/remotes/origin/main", "HEAD"])
182 .unwrap();
183
184 let repo = test_repo.repo();
185 let refs = block_on(repo.ref_names()).unwrap();
186 let expected: BTreeSet<_> = vec![
187 RefName::Head,
188 RefName::Ref(b"stash".to_vec()),
189 RefName::Ref(b"heads/main".to_vec()),
190 RefName::Ref(b"heads/a-branch".to_vec()),
191 RefName::Ref(b"heads/foo/a-branch".to_vec()),
192 RefName::Ref(b"tags/thin-tag".to_vec()),
193 RefName::Ref(b"tags/bar/thin-tag".to_vec()),
194 RefName::Ref(b"tags/fat-tag".to_vec()),
195 RefName::Ref(b"tags/a-fat-tag".to_vec()),
196 RefName::Ref(b"remotes/origin/main".to_vec()),
197 ]
198 .into_iter()
199 .collect();
200 assert_eq!(&refs, &expected);
201 }
202
203 #[test]
204 fn open_non_bare_repo() {
205 let test_repo = make_basic_repo().unwrap();
206 let root_dir = test_repo.root_dir();
207 block_on(Repo::<TestFileSystem>::open(root_dir)).unwrap();
208 }
209}