Unified · Split
| @@ -91,9 +91,9 @@ become available. · 90 unmodified lines | |||
| 91 | * **sink** | 91 | * **sink** |
| 92 | * [x] write objects and obtain id | 92 | * [x] write objects and obtain id |
| 93 | * **alternates** | 93 | * **alternates** |
| 94 | * [ ] _database that act as link to other known ODB types on disk_ | 94 | * [ ] _database that act as link to other known git ODBs on disk_ |
| 95 | * [ ] handles cycles | 95 | * [ ] safe with cycles and recursive configurations |
| 96 | * [ ] handles recursive configurations | 96 | * [ ] multi-line with comments and quotes |
| 97 | * **multi-odb** | 97 | * **multi-odb** |
| 98 | * [ ] _an ODB for object lookup from multiple lower level ODB at once_ | 98 | * [ ] _an ODB for object lookup from multiple lower level ODB at once_ |
| 99 | * **promisor** | 99 | * **promisor** |
| @@ -14,31 +14,35 @@ pub enum Error { · 13 unmodified lines | |||
| 14 | Cycle(Vec<PathBuf>), | 14 | Cycle(Vec<PathBuf>), |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | pub fn resolve(objects_directory: impl Into<PathBuf>) -> Result<Option<compound::Db>, Error> { | 17 | pub fn resolve(objects_directory: impl Into<PathBuf>) -> Result<Vec<compound::Db>, Error> { |
| 18 | let mut dir = objects_directory.into(); | 18 | let mut dirs = vec![(0, objects_directory.into())]; |
| 19 | let mut count = 0; | 19 | let mut out = Vec::new(); |
| 20 | let mut seen = Vec::new(); | 20 | let mut seen = Vec::new(); |
| 21 | loop { | 21 | while let Some((depth, dir)) = dirs.pop() { |
| 22 | if seen.contains(&dir) { | 22 | if seen.contains(&dir) { |
| 23 | break Err(Error::Cycle(seen)); | 23 | return Err(Error::Cycle(seen)); |
| 24 | } | 24 | } |
| 25 | seen.push(dir.clone()); | 25 | seen.push(dir.clone()); |
| 26 | let content = match fs::read(dir.join("info").join("alternates")) { | 26 | match fs::read(dir.join("info").join("alternates")) { |
| 27 | Ok(d) => d, | 27 | Ok(content) => { |
| 28 | dirs.push(( | ||
| 29 | depth + 1, | ||
| 30 | content | ||
| 31 | .as_bstr() | ||
| 32 | .to_path() | ||
| 33 | .map(ToOwned::to_owned) | ||
| 34 | .map_err(|_| Error::PathConversion(content))?, | ||
| 35 | )); | ||
| 36 | } | ||
| 28 | Err(err) if err.kind() == io::ErrorKind::NotFound => { | 37 | Err(err) if err.kind() == io::ErrorKind::NotFound => { |
| 29 | break if count == 0 { | 38 | // Only resolve for repositories with at least one link, otherwise the line below isn't safe to call |
| 30 | Ok(None) | 39 | if depth != 0 { |
| 31 | } else { | 40 | // The tail of a chain doesn't have alternates, and thus is the real deal |
| 32 | Ok(Some(compound::Db::at(dir)?)) | 41 | out.push(compound::Db::at(dir)?); |
| 33 | } | 42 | } |
| 34 | } | 43 | } |
| 35 | Err(err) => break Err(err.into()), | 44 | Err(err) => return Err(err.into()), |
| 36 | }; | 45 | }; |
| 37 | dir = content | ||
| 38 | .as_bstr() | ||
| 39 | .to_path() | ||
| 40 | .map(ToOwned::to_owned) | ||
| 41 | .map_err(|_| Error::PathConversion(content))?; | ||
| 42 | count += 1; | ||
| 43 | } | 46 | } |
| 47 | Ok(out) | ||
| 44 | } | 48 | } |
| @@ -35,9 +35,7 @@ impl compound::Db { · 34 unmodified lines | |||
| 35 | Ok(compound::Db { | 35 | Ok(compound::Db { |
| 36 | loose: loose::Db::at(loose_objects.clone()), | 36 | loose: loose::Db::at(loose_objects.clone()), |
| 37 | packs, | 37 | packs, |
| 38 | alternate: crate::alternate::resolve(loose_objects) | 38 | alternates: crate::alternate::resolve(loose_objects).map_err(Box::new)?, |
| 39 | .map_err(Box::new)? | ||
| 40 | .map(Box::new), | ||
| 41 | }) | 39 | }) |
| 42 | } | 40 | } |
| 43 | } | 41 | } |
| @@ -3,7 +3,7 @@ use crate::{loose, pack}; · 2 unmodified lines | |||
| 3 | pub struct Db { | 3 | pub struct Db { |
| 4 | pub loose: loose::Db, | 4 | pub loose: loose::Db, |
| 5 | pub packs: Vec<pack::Bundle>, | 5 | pub packs: Vec<pack::Bundle>, |
| 6 | pub alternate: Option<Box<Db>>, | 6 | pub alternates: Vec<Db>, |
| 7 | } | 7 | } |
| 8 | 8 | ||
| 9 | pub mod object { | 9 | pub mod object { |
| @@ -2,12 +2,28 @@ use git_odb::alternate; · 1 unmodified line | |||
| 2 | use std::{fs, io, path::PathBuf}; | 2 | use std::{fs, io, path::PathBuf}; |
| 3 | 3 | ||
| 4 | fn alternate(objects_at: impl Into<PathBuf>, objects_to: impl Into<PathBuf>) -> Result<(PathBuf, PathBuf), io::Error> { | 4 | fn alternate(objects_at: impl Into<PathBuf>, objects_to: impl Into<PathBuf>) -> Result<(PathBuf, PathBuf), io::Error> { |
| 5 | alternate_with(objects_at, objects_to, None) | ||
| 6 | } | ||
| 7 | |||
| 8 | fn alternate_with( | ||
| 9 | objects_at: impl Into<PathBuf>, | ||
| 10 | objects_to: impl Into<PathBuf>, | ||
| 11 | content_before_to: Option<&str>, | ||
| 12 | ) -> Result<(PathBuf, PathBuf), io::Error> { | ||
| 5 | let at = objects_at.into(); | 13 | let at = objects_at.into(); |
| 6 | let to = objects_to.into(); | 14 | let to = objects_to.into(); |
| 7 | let at_info = at.join("info"); | 15 | let at_info = at.join("info"); |
| 8 | fs::create_dir_all(&at_info)?; | 16 | fs::create_dir_all(&at_info)?; |
| 9 | fs::create_dir_all(&to)?; | 17 | fs::create_dir_all(&to)?; |
| 10 | fs::write(at_info.join("alternates"), to.to_string_lossy().as_bytes())?; | 18 | let contents = if let Some(content) = content_before_to { |
| 19 | let mut c = vec![b'\n']; | ||
| 20 | c.extend(content.as_bytes()); | ||
| 21 | c.extend(to.to_string_lossy().as_bytes()); | ||
| 22 | c | ||
| 23 | } else { | ||
| 24 | to.to_string_lossy().as_bytes().to_owned() | ||
| 25 | }; | ||
| 26 | fs::write(at_info.join("alternates"), contents)?; | ||
| 11 | Ok((at, to)) | 27 | Ok((at, to)) |
| 12 | } | 28 | } |
| 13 | 29 | ||
| @@ -38,13 +54,15 @@ fn single_link() -> crate::Result { · 24 unmodified lines | |||
| 38 | let non_alternate = tmp.path().join("actual"); | 54 | let non_alternate = tmp.path().join("actual"); |
| 39 | 55 | ||
| 40 | let (from, to) = alternate(tmp.path().join("a"), non_alternate)?; | 56 | let (from, to) = alternate(tmp.path().join("a"), non_alternate)?; |
| 41 | assert_eq!(alternate::resolve(from).unwrap().unwrap().loose.path, to); | 57 | let alternates = alternate::resolve(from)?; |
| 58 | assert_eq!(alternates.len(), 1); | ||
| 59 | assert_eq!(alternates[0].loose.path, to); | ||
| 42 | Ok(()) | 60 | Ok(()) |
| 43 | } | 61 | } |
| 44 | 62 | ||
| 45 | #[test] | 63 | #[test] |
| 46 | fn no_alternate_in_first_objects_dir() -> crate::Result { | 64 | fn no_alternate_in_first_objects_dir() -> crate::Result { |
| 47 | let tmp = tempdir::TempDir::new("alternates")?; | 65 | let tmp = tempdir::TempDir::new("alternates")?; |
| 48 | assert!(alternate::resolve(tmp.path()).unwrap().is_none()); | 66 | assert!(alternate::resolve(tmp.path())?.is_empty()); |
| 49 | Ok(()) | 67 | Ok(()) |
| 50 | } | 68 | } |
| @@ -11,6 +11,11 @@ · 10 unmodified lines | |||
| 11 | * [x] alternate DB (location - it's really must following the chain until a compound DB can be created) | 11 | * [x] alternate DB (location - it's really must following the chain until a compound DB can be created) |
| 12 | * [x] circular dependencies test | 12 | * [x] circular dependencies test |
| 13 | * [x] basic test | 13 | * [x] basic test |
| 14 | * [ ] are multiple alternates supported, too? YES | ||
| 15 | * [ ] comments | ||
| 16 | * [ ] quotes | ||
| 17 | * [ ] support for relative directories | ||
| 18 | * [ ] alternates lookup | ||
| 14 | * [x] loose upgrade: jwalk powered iteration behind a feature flag | 19 | * [x] loose upgrade: jwalk powered iteration behind a feature flag |
| 15 | * [ ] full docs | 20 | * [ ] full docs |
| 16 | * **git-config** | 21 | * **git-config** |