diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -70,15 +70,21 @@ data_path: Option<&Path>, ) -> Result { let index_path = index_path.as_ref(); - let index_mmap = repo.store_vfs().mmap_open(&index_path)?; + let index = { + match repo.store_vfs().mmap_open_opt(&index_path)? { + None => Index::new(Box::new(vec![])), + Some(index_mmap) => { + let version = get_version(&index_mmap)?; + if version != 1 { + // A proper new version should have had a repo/store requirement. + return Err(HgError::corrupted("corrupted revlog")); + } - let version = get_version(&index_mmap)?; - if version != 1 { - // A proper new version should have had a repo/store requirement. - return Err(HgError::corrupted("corrupted revlog")); - } - - let index = Index::new(Box::new(index_mmap))?; + let index = Index::new(Box::new(index_mmap))?; + Ok(index) + } + } + }?; let default_data_path = index_path.with_extension("d"); diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs --- a/rust/hg-core/src/vfs.rs +++ b/rust/hg-core/src/vfs.rs @@ -9,6 +9,8 @@ pub(crate) base: &'a Path, } +struct FileNotFound(std::io::Error, PathBuf); + impl Vfs<'_> { pub fn join(&self, relative_path: impl AsRef) -> PathBuf { self.base.join(relative_path) @@ -22,16 +24,41 @@ std::fs::read(&path).when_reading_file(&path) } + fn mmap_open_gen( + &self, + relative_path: impl AsRef, + ) -> Result, HgError> { + let path = self.join(relative_path); + let file = match std::fs::File::open(&path) { + Err(err) => { + if let ErrorKind::NotFound = err.kind() { + return Ok(Err(FileNotFound(err, path))); + }; + return (Err(err)).when_reading_file(&path); + } + Ok(file) => file, + }; + // TODO: what are the safety requirements here? + let mmap = unsafe { MmapOptions::new().map(&file) } + .when_reading_file(&path)?; + Ok(Ok(mmap)) + } + + pub fn mmap_open_opt( + &self, + relative_path: impl AsRef, + ) -> Result, HgError> { + self.mmap_open_gen(relative_path).map(|res| res.ok()) + } + pub fn mmap_open( &self, relative_path: impl AsRef, ) -> Result { - let path = self.base.join(relative_path); - let file = std::fs::File::open(&path).when_reading_file(&path)?; - // TODO: what are the safety requirements here? - let mmap = unsafe { MmapOptions::new().map(&file) } - .when_reading_file(&path)?; - Ok(mmap) + match self.mmap_open_gen(relative_path)? { + Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path), + Ok(res) => Ok(res), + } } pub fn rename( diff --git a/tests/test-empty-manifest-index.t b/tests/test-empty-manifest-index.t --- a/tests/test-empty-manifest-index.t +++ b/tests/test-empty-manifest-index.t @@ -1,23 +1,27 @@ -Create a repo such that the changelog entry refers to a null manifest node: +Test null revisions (node 0000000000000000000000000000000000000000, aka rev -1) +in various circumstances. + +Make an empty repo: $ hg init a $ cd a - $ hg log - $ touch x - $ hg add x - $ hg commit -m "init" - $ hg rm x - $ hg commit -q --amend - $ wc -c < .hg/store/00manifest.i - 0 - -Make sure that the manifest can be read (and is empty): - - $ hg --config rhg.on-unsupported=abort files -r . + $ hg files -r 0000000000000000000000000000000000000000 + [1] + $ hg files -r . [1] -Test a null changelog rev, too: +Add an empty commit (this makes the changelog refer to a null manifest node): + + + $ hg commit -m "init" --config ui.allowemptycommit=true - $ hg --config rhg.on-unsupported=abort files -r 0000000000000000000000000000000000000000 + $ hg files -r . [1] + +Strip that empty commit (this makes the changelog file empty, as opposed to missing): + + $ hg --config 'extensions.strip=' strip . > /dev/null + + $ hg files -r . + [1]