rhg: Propogate manifest parse errors instead of panicking
authorSimon Sapin <simon.sapin@octobus.net>
Tue, 23 Nov 2021 18:27:42 +0100
changeset 48342 10c32e1b892a
parent 48341 51f26c8088b2
child 48343 eb428010aad2
rhg: Propogate manifest parse errors instead of panicking The Rust parser for the manifest file format is an iterator. Now every item from that iterator is a `Result`, which makes error handling required in multiple new places. This makes better recovery on errors possible, compare to a run time panic. Differential Revision: https://phab.mercurial-scm.org/D11771
rust/hg-core/src/operations/cat.rs
rust/hg-core/src/operations/list_tracked_files.rs
rust/hg-core/src/revlog/manifest.rs
rust/rhg/src/commands/files.rs
rust/rhg/src/commands/status.rs
rust/rhg/src/utils/path_utils.rs
--- a/rust/hg-core/src/operations/cat.rs	Fri Nov 19 17:34:48 2021 +0100
+++ b/rust/hg-core/src/operations/cat.rs	Tue Nov 23 18:27:42 2021 +0100
@@ -11,6 +11,7 @@
 
 use crate::utils::hg_path::HgPath;
 
+use crate::errors::HgError;
 use itertools::put_back;
 use itertools::PutBack;
 use std::cmp::Ordering;
@@ -28,21 +29,24 @@
 }
 
 // Find an item in an iterator over a sorted collection.
-fn find_item<'a, 'b, 'c, D, I: Iterator<Item = (&'a HgPath, D)>>(
+fn find_item<'a, D, I: Iterator<Item = Result<(&'a HgPath, D), HgError>>>(
     i: &mut PutBack<I>,
-    needle: &'b HgPath,
-) -> Option<D> {
+    needle: &HgPath,
+) -> Result<Option<D>, HgError> {
     loop {
         match i.next() {
-            None => return None,
-            Some(val) => match needle.as_bytes().cmp(val.0.as_bytes()) {
-                Ordering::Less => {
-                    i.put_back(val);
-                    return None;
+            None => return Ok(None),
+            Some(result) => {
+                let (path, value) = result?;
+                match needle.as_bytes().cmp(path.as_bytes()) {
+                    Ordering::Less => {
+                        i.put_back(Ok((path, value)));
+                        return Ok(None);
+                    }
+                    Ordering::Greater => continue,
+                    Ordering::Equal => return Ok(Some(value)),
                 }
-                Ordering::Greater => continue,
-                Ordering::Equal => return Some(val.1),
-            },
+            }
         }
     }
 }
@@ -51,23 +55,23 @@
     'manifest,
     'query,
     Data,
-    Manifest: Iterator<Item = (&'manifest HgPath, Data)>,
+    Manifest: Iterator<Item = Result<(&'manifest HgPath, Data), HgError>>,
     Query: Iterator<Item = &'query HgPath>,
 >(
     manifest: Manifest,
     query: Query,
-) -> (Vec<(&'query HgPath, Data)>, Vec<&'query HgPath>) {
+) -> Result<(Vec<(&'query HgPath, Data)>, Vec<&'query HgPath>), HgError> {
     let mut manifest = put_back(manifest);
     let mut res = vec![];
     let mut missing = vec![];
 
     for file in query {
-        match find_item(&mut manifest, file) {
+        match find_item(&mut manifest, file)? {
             None => missing.push(file),
             Some(item) => res.push((file, item)),
         }
     }
-    return (res, missing);
+    return Ok((res, missing));
 }
 
 /// Output the given revision of files
@@ -94,7 +98,7 @@
     let (found, missing) = find_files_in_manifest(
         manifest.files_with_nodes(),
         files.into_iter().map(|f| f.as_ref()),
-    );
+    )?;
 
     for (file_path, node_bytes) in found {
         found_any = true;
--- a/rust/hg-core/src/operations/list_tracked_files.rs	Fri Nov 19 17:34:48 2021 +0100
+++ b/rust/hg-core/src/operations/list_tracked_files.rs	Tue Nov 23 18:27:42 2021 +0100
@@ -76,7 +76,7 @@
 pub struct FilesForRev(Manifest);
 
 impl FilesForRev {
-    pub fn iter(&self) -> impl Iterator<Item = &HgPath> {
+    pub fn iter(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> {
         self.0.files()
     }
 }
--- a/rust/hg-core/src/revlog/manifest.rs	Fri Nov 19 17:34:48 2021 +0100
+++ b/rust/hg-core/src/revlog/manifest.rs	Tue Nov 23 18:27:42 2021 +0100
@@ -63,26 +63,28 @@
     }
 
     /// Return an iterator over the files of the entry.
-    pub fn files(&self) -> impl Iterator<Item = &HgPath> {
+    pub fn files(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> {
         self.lines().filter(|line| !line.is_empty()).map(|line| {
-            let pos = line
-                .iter()
-                .position(|x| x == &b'\0')
-                .expect("manifest line should contain \\0");
-            HgPath::new(&line[..pos])
+            let pos =
+                line.iter().position(|x| x == &b'\0').ok_or_else(|| {
+                    HgError::corrupted("manifest line should contain \\0")
+                })?;
+            Ok(HgPath::new(&line[..pos]))
         })
     }
 
     /// Return an iterator over the files of the entry.
-    pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> {
+    pub fn files_with_nodes(
+        &self,
+    ) -> impl Iterator<Item = Result<(&HgPath, &[u8]), HgError>> {
         self.lines().filter(|line| !line.is_empty()).map(|line| {
-            let pos = line
-                .iter()
-                .position(|x| x == &b'\0')
-                .expect("manifest line should contain \\0");
+            let pos =
+                line.iter().position(|x| x == &b'\0').ok_or_else(|| {
+                    HgError::corrupted("manifest line should contain \\0")
+                })?;
             let hash_start = pos + 1;
             let hash_end = hash_start + 40;
-            (HgPath::new(&line[..pos]), &line[hash_start..hash_end])
+            Ok((HgPath::new(&line[..pos]), &line[hash_start..hash_end]))
         })
     }
 
@@ -91,7 +93,8 @@
         // TODO: use binary search instead of linear scan. This may involve
         // building (and caching) an index of the byte indicex of each manifest
         // line.
-        for (manifest_path, node) in self.files_with_nodes() {
+        for entry in self.files_with_nodes() {
+            let (manifest_path, node) = entry?;
             if manifest_path == path {
                 return Ok(Some(Node::from_hex_for_repo(node)?));
             }
--- a/rust/rhg/src/commands/files.rs	Fri Nov 19 17:34:48 2021 +0100
+++ b/rust/rhg/src/commands/files.rs	Tue Nov 23 18:27:42 2021 +0100
@@ -3,6 +3,7 @@
 use crate::ui::UiError;
 use crate::utils::path_utils::relativize_paths;
 use clap::Arg;
+use hg::errors::HgError;
 use hg::operations::list_rev_tracked_files;
 use hg::operations::Dirstate;
 use hg::repo::Repo;
@@ -45,14 +46,14 @@
     } else {
         let distate = Dirstate::new(repo)?;
         let files = distate.tracked_files()?;
-        display_files(invocation.ui, repo, files)
+        display_files(invocation.ui, repo, files.into_iter().map(Ok))
     }
 }
 
 fn display_files<'a>(
     ui: &Ui,
     repo: &Repo,
-    files: impl IntoIterator<Item = &'a HgPath>,
+    files: impl IntoIterator<Item = Result<&'a HgPath, HgError>>,
 ) -> Result<(), CommandError> {
     let mut stdout = ui.stdout_buffer();
     let mut any = false;
--- a/rust/rhg/src/commands/status.rs	Fri Nov 19 17:34:48 2021 +0100
+++ b/rust/rhg/src/commands/status.rs	Tue Nov 23 18:27:42 2021 +0100
@@ -279,7 +279,7 @@
     if relative && !ui.plain() {
         relativize_paths(
             repo,
-            paths,
+            paths.iter().map(Ok),
             |path: Cow<[u8]>| -> Result<(), UiError> {
                 ui.write_stdout(
                     &[status_prefix, b" ", path.as_ref(), b"\n"].concat(),
--- a/rust/rhg/src/utils/path_utils.rs	Fri Nov 19 17:34:48 2021 +0100
+++ b/rust/rhg/src/utils/path_utils.rs	Tue Nov 23 18:27:42 2021 +0100
@@ -5,6 +5,7 @@
 
 use crate::error::CommandError;
 use crate::ui::UiError;
+use hg::errors::HgError;
 use hg::repo::Repo;
 use hg::utils::current_dir;
 use hg::utils::files::{get_bytes_from_path, relativize_path};
@@ -14,7 +15,7 @@
 
 pub fn relativize_paths(
     repo: &Repo,
-    paths: impl IntoIterator<Item = impl AsRef<HgPath>>,
+    paths: impl IntoIterator<Item = Result<impl AsRef<HgPath>, HgError>>,
     mut callback: impl FnMut(Cow<[u8]>) -> Result<(), UiError>,
 ) -> Result<(), CommandError> {
     let cwd = current_dir()?;
@@ -38,10 +39,10 @@
 
     for file in paths {
         if outside_repo {
-            let file = repo_root_hgpath.join(file.as_ref());
+            let file = repo_root_hgpath.join(file?.as_ref());
             callback(relativize_path(&file, &cwd_hgpath))?;
         } else {
-            callback(relativize_path(file.as_ref(), &cwd_hgpath))?;
+            callback(relativize_path(file?.as_ref(), &cwd_hgpath))?;
         }
     }
     Ok(())