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
--- 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(())