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