rhg: do not fail when the repo is empty
authorArseniy Alekseyev <aalekseyev@janestreet.com>
Wed, 13 Oct 2021 10:17:27 -0700
changeset 48199 9d0e5629cfbf
parent 48198 61ce70fd420e
child 48200 b6fc7d188f68
rhg: do not fail when the repo is empty Differential Revision: https://phab.mercurial-scm.org/D11651
rust/hg-core/src/revlog/revlog.rs
rust/hg-core/src/vfs.rs
tests/test-empty-manifest-index.t
--- a/rust/hg-core/src/revlog/revlog.rs	Tue Oct 12 19:43:51 2021 +0100
+++ b/rust/hg-core/src/revlog/revlog.rs	Wed Oct 13 10:17:27 2021 -0700
@@ -70,15 +70,21 @@
         data_path: Option<&Path>,
     ) -> Result<Self, HgError> {
         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");
 
@@ -418,6 +424,6 @@
             .with_version(1)
             .build();
 
-        assert_eq!(get_version(&bytes), 1)
+        assert_eq!(get_version(&bytes).map_err(|_err|()), Ok(1))
     }
 }
--- a/rust/hg-core/src/vfs.rs	Tue Oct 12 19:43:51 2021 +0100
+++ b/rust/hg-core/src/vfs.rs	Wed Oct 13 10:17:27 2021 -0700
@@ -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<Path>) -> 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<Path>,
+    ) -> Result<Result<Mmap, FileNotFound>, 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<Path>,
+    ) -> Result<Option<Mmap>, HgError> {
+        self.mmap_open_gen(relative_path).map(|res| res.ok())
+    }
+
     pub fn mmap_open(
         &self,
         relative_path: impl AsRef<Path>,
     ) -> Result<Mmap, HgError> {
-        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(
--- a/tests/test-empty-manifest-index.t	Tue Oct 12 19:43:51 2021 +0100
+++ b/tests/test-empty-manifest-index.t	Wed Oct 13 10:17:27 2021 -0700
@@ -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]