Unified · Split
| @@ -10,12 +10,19 @@ pub enum Error { · 9 unmodified lines | ||
| 10 | 10 | PathConversion(Vec<u8>), |
| 11 | 11 | #[error(transparent)] |
| 12 | 12 | Init(#[from] compound::init::Error), |
| 13 | + #[error("Alternates form a cycle: {} -> {}", .0.iter().map(|p| format!("'{}'", p.display())).collect::<Vec<_>>().join(" -> "), .0.first().expect("more than one directories").display())] | |
| 14 | + Cycle(Vec<PathBuf>), | |
| 13 | 15 | } |
| 14 | 16 | |
| 15 | 17 | pub fn resolve(objects_directory: impl Into<PathBuf>) -> Result<Option<compound::Db>, Error> { |
| 16 | 18 | let mut dir = objects_directory.into(); |
| 17 | 19 | let mut count = 0; |
| 20 | + let mut seen = Vec::new(); | |
| 18 | 21 | loop { |
| 22 | + if seen.contains(&dir) { | |
| 23 | + break Err(Error::Cycle(seen)); | |
| 24 | + } | |
| 25 | + seen.push(dir.clone()); | |
| 19 | 26 | let content = match fs::read(dir.join("info").join("alternates")) { |
| 20 | 27 | Ok(d) => d, |
| 21 | 28 | Err(err) if err.kind() == io::ErrorKind::NotFound => { |
| @@ -6,18 +6,29 @@ fn alternate(objects_at: impl Into<PathBuf>, objects_to: impl Into<PathBuf>) -> · 5 unmodified lines | ||
| 6 | 6 | let to = objects_to.into(); |
| 7 | 7 | let at_info = at.join("info"); |
| 8 | 8 | fs::create_dir_all(&at_info)?; |
| 9 | − fs::create_dir(&to)?; | |
| 9 | + fs::create_dir_all(&to)?; | |
| 10 | 10 | fs::write(at_info.join("alternates"), to.to_string_lossy().as_bytes())?; |
| 11 | 11 | Ok((at, to)) |
| 12 | 12 | } |
| 13 | 13 | |
| 14 | 14 | #[test] |
| 15 | −#[ignore] | |
| 16 | 15 | fn circular_alternates_are_detected() -> crate::Result { |
| 17 | 16 | let tmp = tempdir::TempDir::new("alternates")?; |
| 18 | − alternate(tmp.path().join("a"), tmp.path().join("b"))?; | |
| 17 | + let (from, _) = alternate(tmp.path().join("a"), tmp.path().join("b"))?; | |
| 19 | 18 | alternate(tmp.path().join("b"), tmp.path().join("a"))?; |
| 20 | 19 | |
| 20 | + match alternate::resolve(&from) { | |
| 21 | + Err(alternate::Error::Cycle(chain)) => { | |
| 22 | + assert_eq!( | |
| 23 | + chain | |
| 24 | + .into_iter() | |
| 25 | + .map(|p| p.file_name().expect("non-root").to_str().expect("utf8").to_owned()) | |
| 26 | + .collect::<Vec<_>>(), | |
| 27 | + vec!["a", "b"] | |
| 28 | + ); | |
| 29 | + } | |
| 30 | + _ => unreachable!("should be a specific kind of error"), | |
| 31 | + } | |
| 21 | 32 | Ok(()) |
| 22 | 33 | } |
| 23 | 34 | |
| @@ -9,9 +9,10 @@ · 8 unmodified lines | ||
| 9 | 9 | * [x] Add #![deny(rust_2018_idioms)] everywhere |
| 10 | 10 | * [x] Where 'thiserror' is available, use it for all Errors. It is more powerful, and if we paid for it already, let's use it. |
| 11 | 11 | * [x] alternate DB (location - it's really must following the chain until a compound DB can be created) |
| 12 | − * [ ] circular dependencies test | |
| 13 | − * [ ] basic test | |
| 12 | + * [x] circular dependencies test | |
| 13 | + * [x] basic test | |
| 14 | 14 | * [ ] loose upgrade: jwalk powered iteration behind a feature flag |
| 15 | + * [ ] full docs | |
| 15 | 16 | * **git-config** |
| 16 | 17 | * A complete implementation, writing a the git remote configuration is needed for finalizing the clone |
| 17 | 18 | * **git-ref** |