Patchwork D11651: rhg: do not fail when the repo is empty

login
register
mail settings
Submitter phabricator
Date Oct. 12, 2021, 8:07 p.m.
Message ID <differential-rev-PHID-DREV-mbjazw2wthynxj6lz3cf-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/49975/
State Superseded
Headers show

Comments

phabricator - Oct. 12, 2021, 8:07 p.m.
aalekseyev created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D11651

AFFECTED FILES
  rust/hg-core/src/revlog/revlog.rs
  rust/hg-core/src/vfs.rs
  tests/test-empty-manifest-index.t

CHANGE DETAILS




To: aalekseyev, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

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]
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<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(
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<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");