refactor(git): refresh crate root, error, and lfs/archive/tree usecases
zhenyi committed
Modified lib/git/archive/usecase.rs lib/git/archive/usecase.rs +2 −2 View lib/git/archive/usecase.rs
@@ -121,8 +121,8 @@
121121 .root_dir(dir.join("storage").to_path_buf())
122122 .build(),
123123 );
124 let storage: std::sync::Arc<Box<dyn storage::StorageRepository>> = std::sync::Arc::new(
125 Box::new(LocalStorage::from_config(&settings).expect("storage")),
124 let storage: std::sync::Arc<dyn storage::StorageRepository> = std::sync::Arc::new(
125 LocalStorage::from_config(&settings).expect("storage"),
126126 );
127127 GitRepository::new(dir.join(name), oc, storage)
128128 }
Modified lib/git/graphql/error.rs lib/git/graphql/error.rs +1 −0 View lib/git/graphql/error.rs
@@ -17,6 +17,7 @@
1717 Error::RevParse(_) => "REV_PARSE_ERROR",
1818 Error::Serde(_) => "SERDE_ERROR",
1919 Error::Io(_) => "IO_ERROR",
20 Error::Lfs(_) => "LFS_ERROR",
2021 Error::Other(_) => "INTERNAL_ERROR",
2122 }
2223}
Modified lib/git/lfs/types.rs lib/git/lfs/types.rs +1 −0 View lib/git/lfs/types.rs
@@ -12,6 +12,7 @@
1212
1313/// LFS Operation type.
1414#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
1516pub enum LfsOperation {
1617 Upload,
1718 Download,
Modified lib/git/lfs/usecase.rs lib/git/lfs/usecase.rs +185 −41 View lib/git/lfs/usecase.rs
@@ -1,46 +1,72 @@
1//! lfs usecase: LFS batch handling (storage layer is delegated to lib/storage).
1//! lfs usecase: LFS batch handling. Physical storage is fully delegated to
2//! `lib/gitlfs` via `GitRepository::lfs`, decoupled from the Git object store.
23
34use std::collections::HashMap;
45
6use gitlfs::LfsOid;
7
58use crate::GitRepository;
69use crate::lfs::impls::LfsOps;
710use crate::lfs::types::{
8 LfsBatchRequest, LfsBatchResponse, LfsLock, LfsLockList, LfsObject, LfsUploadResult,
11 LfsAction, LfsBatchObject, LfsBatchRequest, LfsBatchResponse, LfsError, LfsLock, LfsLockList,
12 LfsObject, LfsOperation, LfsUploadResult,
913};
1014use crate::validate;
1115
1216impl LfsOps for GitRepository {
1317 fn lfs_handle_batch(&self, req: &LfsBatchRequest) -> Result<LfsBatchResponse, crate::Error> {
14 let _ = self.repo()?;
1518 let transfer = req
1619 .transfers
1720 .first()
1821 .cloned()
1922 .unwrap_or_else(|| "basic".to_string());
20 let action_key = match req.operation {
21 crate::lfs::types::LfsOperation::Upload => "upload",
22 crate::lfs::types::LfsOperation::Download => "download",
23 crate::lfs::types::LfsOperation::Verify => "verify",
24 };
2523 let objects = req
2624 .objects
2725 .iter()
2826 .map(|o| {
2927 validate::validate_oid(&o.oid, 64)?;
30 let mut actions = HashMap::new();
31 actions.insert(
32 action_key.into(),
33 crate::lfs::types::LfsAction {
34 href: format!("/api/lfs/{}/{}", action_key, o.oid),
35 header: None,
36 expires_at: None,
37 },
38 );
39 Ok(crate::lfs::types::LfsBatchObject {
28 let oid = LfsOid::new(&o.oid).map_err(crate::Error::Lfs)?;
29 let present = block_on_safe(|| self.lfs.has_object(&oid))?;
30 let (actions, error) = match req.operation {
31 LfsOperation::Upload => {
32 if present {
33 (None, None)
34 } else {
35 (Some(action_map("upload", &o.oid)), None)
36 }
37 }
38 LfsOperation::Download => {
39 if present {
40 (Some(action_map("download", &o.oid)), None)
41 } else {
42 (
43 None,
44 Some(LfsError {
45 code: 404,
46 message: format!("object {} not found", o.oid),
47 }),
48 )
49 }
50 }
51 LfsOperation::Verify => {
52 if present {
53 (None, None)
54 } else {
55 (
56 None,
57 Some(LfsError {
58 code: 404,
59 message: format!("object {} not found", o.oid),
60 }),
61 )
62 }
63 }
64 };
65 Ok(LfsBatchObject {
4066 oid: o.oid.clone(),
4167 size: o.size,
42 actions: Some(actions),
43 error: None,
68 actions,
69 error,
4470 })
4571 })
4672 .collect::<Result<Vec<_>, crate::Error>>()?;
@@ -49,14 +75,8 @@
4975
5076 fn lfs_upload(&self, obj: &LfsObject) -> Result<LfsUploadResult, crate::Error> {
5177 validate::validate_oid(&obj.oid, 64)?;
52 let repo = self.repo()?;
53 let present = repo
54 .try_find_object(
55 gix::hash::ObjectId::from_hex(obj.oid.as_bytes())
56 .map_err(|e| crate::Error::InvalidRevSpec(e.to_string()))?,
57 )
58 .map_err(|e| crate::Error::Find(e.to_string()))?
59 .is_some();
78 let oid = LfsOid::new(&obj.oid).map_err(crate::Error::Lfs)?;
79 let present = block_on_safe(|| self.lfs.has_object(&oid))?;
6080 Ok(LfsUploadResult {
6181 present,
6282 oid: obj.oid.clone(),
@@ -65,27 +85,20 @@
6585
6686 fn lfs_download(&self, obj: &LfsObject) -> Result<String, crate::Error> {
6787 validate::validate_oid(&obj.oid, 64)?;
68 let _ = self.repo()?;
6988 Ok(format!("/api/lfs/download/{}", obj.oid))
7089 }
7190
7291 fn lfs_verify(&self, obj: &LfsObject) -> Result<(), crate::Error> {
7392 validate::validate_oid(&obj.oid, 64)?;
74 let repo = self.repo()?;
75 let id = gix::hash::ObjectId::from_hex(obj.oid.as_bytes())
76 .map_err(|e| crate::Error::InvalidRevSpec(e.to_string()))?;
77 if repo
78 .try_find_object(id)
79 .map_err(|e| crate::Error::Find(e.to_string()))?
80 .is_none()
81 {
93 let oid = LfsOid::new(&obj.oid).map_err(crate::Error::Lfs)?;
94 let present = block_on_safe(|| self.lfs.has_object(&oid))?;
95 if !present {
8296 return Err(crate::Error::NotExist(obj.oid.clone()));
8397 }
8498 Ok(())
8599 }
86100
87101 fn lfs_lock(&self, path: &str, owner: &str) -> Result<LfsLock, crate::Error> {
88 let _ = self.repo()?;
89102 Ok(LfsLock {
90103 path: path.into(),
91104 owner: Some(owner.into()),
@@ -94,7 +107,6 @@
94107 }
95108
96109 fn lfs_unlock(&self, path: &str, owner: &str) -> Result<LfsLock, crate::Error> {
97 let _ = self.repo()?;
98110 Ok(LfsLock {
99111 path: path.into(),
100112 owner: Some(owner.into()),
@@ -107,7 +119,6 @@
107119 _cursor: Option<&str>,
108120 _limit: u64,
109121 ) -> Result<LfsLockList, crate::Error> {
110 let _ = self.repo()?;
111122 Ok(LfsLockList {
112123 locks: Vec::new(),
113124 next_cursor: None,
@@ -115,7 +126,140 @@
115126 }
116127
117128 fn lfs_cleanup_unreferenced(&self) -> Result<u64, crate::Error> {
118 let _ = self.repo()?;
119129 Ok(0)
120130 }
121131}
132
133fn action_map(action: &str, oid: &str) -> HashMap<String, LfsAction> {
134 let mut actions = HashMap::new();
135 actions.insert(
136 action.into(),
137 LfsAction {
138 href: format!("/api/lfs/{}/{}", action, oid),
139 header: None,
140 expires_at: None,
141 },
142 );
143 actions
144}
145
146/// Bridge the sync `LfsOps` trait to the async `LfsObjectStore`.
147fn block_on_safe<F, T>(f: impl FnOnce() -> F) -> Result<T, crate::Error>
148where
149 F: std::future::Future<Output = Result<T, gitlfs::LfsError>>,
150{
151 let fut = f();
152 match tokio::runtime::Handle::try_current() {
153 Ok(handle) => handle.block_on(fut),
154 Err(_) => tokio::runtime::Builder::new_current_thread()
155 .enable_all()
156 .build()
157 .map_err(|e| crate::Error::Other(e.to_string()))?
158 .block_on(fut),
159 }
160 .map_err(crate::Error::Lfs)
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::object::cache::ObjectCache;
167 use cache::CacheBuilder;
168 use std::sync::Arc;
169 use storage::config::StorageSettings;
170 use storage::local::LocalStorage;
171
172 fn block_on<F: std::future::Future>(f: F) -> F::Output {
173 tokio::runtime::Builder::new_current_thread()
174 .enable_all()
175 .build()
176 .unwrap()
177 .block_on(f)
178 }
179
180 fn make_repo(dir: &std::path::Path) -> GitRepository {
181 let cache_cfg = cache::config::cache::AppCacheSettings::default();
182 let cache = Arc::new(
183 block_on(CacheBuilder::from_config(cache_cfg).build())
184 .expect("cache build")
185 .expect("cache enabled"),
186 );
187 let oc = ObjectCache {
188 prefix: "test".to_string(),
189 cache,
190 expire_ttl: 300,
191 };
192 let mut settings = StorageSettings::default();
193 settings.set_local(
194 storage::config::LocalStorageSettings::builder()
195 .root_dir(dir.join("storage").to_path_buf())
196 .build(),
197 );
198 let storage: std::sync::Arc<dyn storage::StorageRepository> = std::sync::Arc::new(
199 LocalStorage::from_config(&settings).expect("storage"),
200 );
201 GitRepository::new(dir.join("repo"), oc, storage)
202 }
203
204 #[test]
205 fn lfs_roundtrip_through_store() {
206 let tmp = std::env::temp_dir().join(format!("git-lfs-test-{}", std::process::id()));
207 let _ = std::fs::remove_dir_all(&tmp);
208 let repo = make_repo(&tmp);
209
210 let data = b"hello lfs".to_vec();
211 let oid = LfsOid::compute(&data);
212 block_on(repo.lfs.put_object(&oid, bytes::Bytes::from(data.clone()))).unwrap();
213
214 let obj = LfsObject {
215 oid: oid.as_str().to_string(),
216 size: data.len() as i64,
217 content_type: None,
218 };
219
220 let up = repo.lfs_upload(&obj).unwrap();
221 assert!(up.present);
222
223 repo.lfs_verify(&obj).unwrap();
224
225 let req = LfsBatchRequest {
226 operation: LfsOperation::Upload,
227 transfers: vec!["basic".into()],
228 objects: vec![obj.clone()],
229 };
230 let resp = repo.lfs_handle_batch(&req).unwrap();
231 assert_eq!(resp.objects.len(), 1);
232 assert!(resp.objects[0].error.is_none());
233 assert!(resp.objects[0].actions.is_none());
234
235 let req = LfsBatchRequest {
236 operation: LfsOperation::Download,
237 transfers: vec!["basic".into()],
238 objects: vec![obj.clone()],
239 };
240 let resp = repo.lfs_handle_batch(&req).unwrap();
241 assert!(resp.objects[0].actions.is_some());
242 assert!(resp.objects[0].error.is_none());
243
244 let missing = LfsObject {
245 oid: "0".repeat(64),
246 size: 1,
247 content_type: None,
248 };
249 assert!(matches!(
250 repo.lfs_verify(&missing),
251 Err(crate::Error::NotExist(_))
252 ));
253
254 let req = LfsBatchRequest {
255 operation: LfsOperation::Download,
256 transfers: vec![],
257 objects: vec![missing.clone()],
258 };
259 let resp = repo.lfs_handle_batch(&req).unwrap();
260 assert!(resp.objects[0].actions.is_none());
261 assert!(resp.objects[0].error.is_some());
262
263 let _ = std::fs::remove_dir_all(&tmp);
264 }
265}
No newline at end of file
Modified lib/git/tree/usecase.rs lib/git/tree/usecase.rs +2 −2 View lib/git/tree/usecase.rs
@@ -344,8 +344,8 @@
344344 .root_dir(dir.join("storage").to_path_buf())
345345 .build(),
346346 );
347 let storage: std::sync::Arc<Box<dyn storage::StorageRepository>> = std::sync::Arc::new(
348 Box::new(LocalStorage::from_config(&settings).expect("storage")),
347 let storage: std::sync::Arc<dyn storage::StorageRepository> = std::sync::Arc::new(
348 LocalStorage::from_config(&settings).expect("storage"),
349349 );
350350 GitRepository::new(dir.join(name), oc, storage)
351351 }
Modified lib/git/error.rs lib/git/error.rs +2 −0 View lib/git/error.rs
@@ -31,4 +31,6 @@
3131 Io(String),
3232 #[error("other error: {0}")]
3333 Other(String),
34 #[error("lfs error: {0}")]
35 Lfs(#[from] gitlfs::LfsError),
3436}
Modified lib/git/lib.rs lib/git/lib.rs +11 −2 View lib/git/lib.rs
@@ -5,6 +5,7 @@
55
66pub mod error;
77pub use error::Error;
8use gitlfs::LfsObjectStore;
89use storage::StorageRepository;
910
1011pub mod gitcli;
@@ -40,7 +41,8 @@
4041 pub path: PathBuf,
4142 pub cache: ObjectCache,
4243 pub policy: Policy,
43 pub storage: Arc<Box<dyn StorageRepository>>,
44 pub storage: Arc<dyn StorageRepository>,
45 pub lfs: Arc<dyn LfsObjectStore>,
4446}
4547
4648impl GitRepository {
@@ -51,13 +53,15 @@
5153 pub fn new(
5254 path: impl Into<PathBuf>,
5355 cache: ObjectCache,
54 storage: Arc<Box<dyn StorageRepository>>,
56 storage: Arc<dyn StorageRepository>,
5557 ) -> Self {
58 let lfs = Arc::new(gitlfs::DefaultLfsStore::new(storage.clone(), "lfs"));
5659 Self {
5760 path: path.into(),
5861 cache,
5962 policy: Arc::new(DefaultPolicy),
6063 storage,
64 lfs,
6165 }
6266 }
6367
@@ -65,4 +69,9 @@
6569 self.policy = policy;
6670 self
6771 }
72
73 pub fn with_lfs_store(mut self, lfs: Arc<dyn LfsObjectStore>) -> Self {
74 self.lfs = lfs;
75 self
76 }
6877}