Unified · Split
| @@ -10,12 +10,19 @@ pub enum Error { · 9 unmodified lines | |||
| 10 | PathConversion(Vec<u8>), | 10 | PathConversion(Vec<u8>), |
| 11 | #[error(transparent)] | 11 | #[error(transparent)] |
| 12 | Init(#[from] compound::init::Error), | 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 | pub fn resolve(objects_directory: impl Into<PathBuf>) -> Result<Option<compound::Db>, Error> { | 17 | pub fn resolve(objects_directory: impl Into<PathBuf>) -> Result<Option<compound::Db>, Error> { |
| 16 | let mut dir = objects_directory.into(); | 18 | let mut dir = objects_directory.into(); |
| 17 | let mut count = 0; | 19 | let mut count = 0; |
| 20 | let mut seen = Vec::new(); | ||
| 18 | loop { | 21 | loop { |
| 22 | if seen.contains(&dir) { | ||
| 23 | break Err(Error::Cycle(seen)); | ||
| 24 | } | ||
| 25 | seen.push(dir.clone()); | ||
| 19 | let content = match fs::read(dir.join("info").join("alternates")) { | 26 | let content = match fs::read(dir.join("info").join("alternates")) { |
| 20 | Ok(d) => d, | 27 | Ok(d) => d, |
| 21 | Err(err) if err.kind() == io::ErrorKind::NotFound => { | 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 | let to = objects_to.into(); | 6 | let to = objects_to.into(); |
| 7 | let at_info = at.join("info"); | 7 | let at_info = at.join("info"); |
| 8 | fs::create_dir_all(&at_info)?; | 8 | fs::create_dir_all(&at_info)?; |
| 9 | fs::create_dir(&to)?; | 9 | fs::create_dir_all(&to)?; |
| 10 | fs::write(at_info.join("alternates"), to.to_string_lossy().as_bytes())?; | 10 | fs::write(at_info.join("alternates"), to.to_string_lossy().as_bytes())?; |
| 11 | Ok((at, to)) | 11 | Ok((at, to)) |
| 12 | } | 12 | } |
| 13 | 13 | ||
| 14 | #[test] | 14 | #[test] |
| 15 | #[ignore] | ||
| 16 | fn circular_alternates_are_detected() -> crate::Result { | 15 | fn circular_alternates_are_detected() -> crate::Result { |
| 17 | let tmp = tempdir::TempDir::new("alternates")?; | 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 | alternate(tmp.path().join("b"), tmp.path().join("a"))?; | 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 | Ok(()) | 32 | Ok(()) |
| 22 | } | 33 | } |
| 23 | 34 | ||
| @@ -9,9 +9,10 @@ · 8 unmodified lines | |||
| 9 | * [x] Add #![deny(rust_2018_idioms)] everywhere | 9 | * [x] Add #![deny(rust_2018_idioms)] everywhere |
| 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. | 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 | * [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 | * [ ] circular dependencies test | 12 | * [x] circular dependencies test |
| 13 | * [ ] basic test | 13 | * [x] basic test |
| 14 | * [ ] loose upgrade: jwalk powered iteration behind a feature flag | 14 | * [ ] loose upgrade: jwalk powered iteration behind a feature flag |
| 15 | * [ ] full docs | ||
| 15 | * **git-config** | 16 | * **git-config** |
| 16 | * A complete implementation, writing a the git remote configuration is needed for finalizing the clone | 17 | * A complete implementation, writing a the git remote configuration is needed for finalizing the clone |
| 17 | * **git-ref** | 18 | * **git-ref** |