branching: merge stable into default
authorRaphaël Gomès <rgomes@octobus.net>
Mon, 11 Jul 2022 09:54:40 +0200
changeset 49381 259df3e3152c
parent 49365 79b2c98ab7b4 (current diff)
parent 49380 55adff810546 (diff)
child 49382 eec5e00e782d
branching: merge stable into default
--- a/.hgsigs	Thu Jun 16 15:20:48 2022 +0200
+++ b/.hgsigs	Mon Jul 11 09:54:40 2022 +0200
@@ -229,3 +229,5 @@
 0ddd5e1f5f67438af85d12e4ce6c39021dde9916 0 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
+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
--- a/.hgtags	Thu Jun 16 15:20:48 2022 +0200
+++ b/.hgtags	Mon Jul 11 09:54:40 2022 +0200
@@ -242,3 +242,5 @@
 0ddd5e1f5f67438af85d12e4ce6c39021dde9916 6.1.2
 6b10151b962108f65bfa12b3918b1021ca334f73 6.1.3
 0cc5f74ff7f0f4ac2427096bddbe102dbc2453ae 6.1.4
+288de6f5d724bba7bf1669e2838f196962bb7528 6.2rc0
+094a5fa3cf52f936e0de3f1e507c818bee5ece6b 6.2
--- a/contrib/import-checker.py	Thu Jun 16 15:20:48 2022 +0200
+++ b/contrib/import-checker.py	Mon Jul 11 09:54:40 2022 +0200
@@ -47,6 +47,7 @@
     'mercurial.thirdparty.zope',
     'mercurial.thirdparty.zope.interface',
     'typing',
+    'xml.etree.ElementTree',
 )
 
 # Allow list of symbols that can be directly imported.
--- a/hgext/convert/darcs.py	Thu Jun 16 15:20:48 2022 +0200
+++ b/hgext/convert/darcs.py	Mon Jul 11 09:54:40 2022 +0200
@@ -8,6 +8,10 @@
 import os
 import re
 import shutil
+from xml.etree.ElementTree import (
+    ElementTree,
+    XMLParser,
+)
 
 from mercurial.i18n import _
 from mercurial import (
@@ -20,26 +24,6 @@
 
 NoRepo = common.NoRepo
 
-# The naming drift of ElementTree is fun!
-
-try:
-    import xml.etree.cElementTree.ElementTree as ElementTree
-    import xml.etree.cElementTree.XMLParser as XMLParser
-except ImportError:
-    try:
-        import xml.etree.ElementTree.ElementTree as ElementTree
-        import xml.etree.ElementTree.XMLParser as XMLParser
-    except ImportError:
-        try:
-            import elementtree.cElementTree.ElementTree as ElementTree
-            import elementtree.cElementTree.XMLParser as XMLParser
-        except ImportError:
-            try:
-                import elementtree.ElementTree.ElementTree as ElementTree
-                import elementtree.ElementTree.XMLParser as XMLParser
-            except ImportError:
-                pass
-
 
 class darcs_source(common.converter_source, common.commandline):
     def __init__(self, ui, repotype, path, revs=None):
@@ -58,7 +42,7 @@
                 _(b'darcs version 2.1 or newer needed (found %r)') % version
             )
 
-        if b"ElementTree" not in globals():
+        if "ElementTree" not in globals():
             raise error.Abort(_(b"Python ElementTree module is not available"))
 
         self.path = os.path.realpath(path)
@@ -94,9 +78,9 @@
         )
         tagname = None
         child = None
-        for elt in tree.findall(b'patch'):
-            node = elt.get(b'hash')
-            name = elt.findtext(b'name', b'')
+        for elt in tree.findall('patch'):
+            node = self.recode(elt.get('hash'))
+            name = self.recode(elt.findtext('name', ''))
             if name.startswith(b'TAG '):
                 tagname = name[4:].strip()
             elif tagname is not None:
@@ -126,7 +110,7 @@
         # While we are decoding the XML as latin-1 to be as liberal as
         # possible, etree will still raise an exception if any
         # non-printable characters are in the XML changelog.
-        parser = XMLParser(encoding=b'latin-1')
+        parser = XMLParser(encoding='latin-1')
         p = self._run(cmd, **kwargs)
         etree.parse(p.stdout, parser=parser)
         p.wait()
@@ -136,7 +120,7 @@
     def format(self):
         output, status = self.run(b'show', b'repo', repodir=self.path)
         self.checkexit(status)
-        m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
+        m = re.search(br'^\s*Format:\s*(.*)$', output, re.MULTILINE)
         if not m:
             return None
         return b','.join(sorted(f.strip() for f in m.group(1).split(b',')))
@@ -159,13 +143,13 @@
     def getcommit(self, rev):
         elt = self.changes[rev]
         dateformat = b'%a %b %d %H:%M:%S %Z %Y'
-        date = dateutil.strdate(elt.get(b'local_date'), dateformat)
-        desc = elt.findtext(b'name') + b'\n' + elt.findtext(b'comment', b'')
+        date = dateutil.strdate(elt.get('local_date'), dateformat)
+        desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
         # etree can return unicode objects for name, comment, and author,
         # so recode() is used to ensure str objects are emitted.
         newdateformat = b'%Y-%m-%d %H:%M:%S %1%2'
         return common.commit(
-            author=self.recode(elt.get(b'author')),
+            author=self.recode(elt.get('author')),
             date=dateutil.datestr(date, newdateformat),
             desc=self.recode(desc).strip(),
             parents=self.parents[rev],
@@ -176,7 +160,7 @@
             b'pull',
             self.path,
             all=True,
-            match=b'hash %s' % rev,
+            match=b'hash %s' % self.recode(rev),
             no_test=True,
             no_posthook=True,
             external_merge=b'/bin/false',
@@ -194,13 +178,14 @@
         copies = {}
         changes = []
         man = None
-        for elt in self.changes[rev].find(b'summary').getchildren():
-            if elt.tag in (b'add_directory', b'remove_directory'):
+        for elt in self.changes[rev].find('summary'):
+            if elt.tag in ('add_directory', 'remove_directory'):
                 continue
-            if elt.tag == b'move':
+            if elt.tag == 'move':
                 if man is None:
                     man = self.manifest()
-                source, dest = elt.get(b'from'), elt.get(b'to')
+                source = self.recode(elt.get('from'))
+                dest = self.recode(elt.get('to'))
                 if source in man:
                     # File move
                     changes.append((source, rev))
@@ -217,7 +202,7 @@
                         changes.append((fdest, rev))
                         copies[fdest] = f
             else:
-                changes.append((elt.text.strip(), rev))
+                changes.append((self.recode(elt.text.strip()), rev))
         self.pull(rev)
         self.lastrev = rev
         return sorted(changes), copies, set()
--- a/mercurial/upgrade_utils/actions.py	Thu Jun 16 15:20:48 2022 +0200
+++ b/mercurial/upgrade_utils/actions.py	Mon Jul 11 09:54:40 2022 +0200
@@ -683,7 +683,11 @@
 
         newactions.append(d)
 
-    newactions.extend(o for o in sorted(optimizations) if o not in newactions)
+    newactions.extend(
+        o
+        for o in sorted(optimizations, key=(lambda x: x.name))
+        if o not in newactions
+    )
 
     # FUTURE consider adding some optimizations here for certain transitions.
     # e.g. adding generaldelta could schedule parent redeltas.
--- a/mercurial/utils/procutil.py	Thu Jun 16 15:20:48 2022 +0200
+++ b/mercurial/utils/procutil.py	Mon Jul 11 09:54:40 2022 +0200
@@ -80,12 +80,32 @@
 
 
 def make_line_buffered(stream):
-    if not isinstance(stream, io.BufferedIOBase):
-        # On Python 3, buffered streams can be expected to subclass
-        # BufferedIOBase. This is definitively the case for the streams
-        # initialized by the interpreter. For unbuffered streams, we don't need
-        # to emulate line buffering.
+    # First, check if we need to wrap the stream.
+    check_stream = stream
+    while True:
+        if isinstance(check_stream, WriteAllWrapper):
+            check_stream = check_stream.orig
+        elif pycompat.iswindows and isinstance(
+            check_stream,
+            # pytype: disable=module-attr
+            platform.winstdout
+            # pytype: enable=module-attr
+        ):
+            check_stream = check_stream.fp
+        else:
+            break
+    if isinstance(check_stream, io.RawIOBase):
+        # The stream is unbuffered, we don't need to emulate line buffering.
         return stream
+    elif isinstance(check_stream, io.BufferedIOBase):
+        # The stream supports some kind of buffering. We can't assume that
+        # lines are flushed. Fall back to wrapping the stream.
+        pass
+    else:
+        raise NotImplementedError(
+            "can't determine whether stream is buffered or not"
+        )
+
     if isinstance(stream, LineBufferedWrapper):
         return stream
     return LineBufferedWrapper(stream)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relnotes/6.2	Mon Jul 11 09:54:40 2022 +0200
@@ -0,0 +1,69 @@
+= Mercurial 6.2rc0 =
+
+'''This is the first release to support Python 3.6+ ''only'''''
+
+== New Features ==
+ * Introduce a way to auto-upgrade a repo for certain requirements (see `hg help config.format`)
+ * filemerge: add support for partial conflict resolution by external tool
+ * contrib: add a partial-merge tool for sorted lists (such as Python imports)
+ * revlog: reorder p1 and p2 when p1 is null and p2 is not while respecting issue6528
+ * rhg: add support for ignoring all extensions
+ * completion: install completers to conventional locations
+ * revert: ask user to confirm before tracking new file when interactive
+ * Rust implementation now uses the new dirstate API
+ * sslutil: be less strict about which ciphers are allowed when using --insecure
+ * sslutil: support TLSV1_ALERT_PROTOCOL_VERSION reason code
+ * absorb: make `--edit-lines` imply `--apply-changes`
+ * diff: add help text to highlight the ability to do merge diffs
+ * censor: make rhg fall back to python when encountering a censored node
+ * clone: use better names for temp files
+ * debuglock: make the command more useful in non-interactive mode
+ * debugdeltachain: distinct between snapshot and other diffs
+ * debugindex: rename to debugindex debug-revlog-index
+ * Make debug-revlog-index give out more information
+ * sparse: use the rust code even when sparse is present
+
+== Bug Fixes ==
+ * Python 3 bugfixes
+ * Windows bugfixes
+ * templates: make `firstline` filter not keep '\v', '\f' and similar
+ * rhg: sort unsupported extensions in error message
+ * Improve performance of all functions that extract the first line of a text
+ * crecord: avoid duplicating lines when reverting noeol->eol change
+ * Some config.path options are now discoverable via config
+ * mail: don't complain about a multi-word email.method
+ * bundlespec: do not overwrite bundlespec value with the config one
+ * bundlespec: do not check for `-` in the params portion of the bundlespec
+ * bundlespec: handle the presence of obsmarker part
+ * sparse: start moving away from the global variable for detection of usage
+ * rust-changelog: don't skip empty lines when iterating over changeset lines
+ * narrow: support debugupgraderepo
+ * bundle: quick fix to ludicrous performance penalty
+ * followlines: don't put Unicode directly into the .js file (issue6559)
+ * manifest: improve error message in case for tree manifest
+ * revlog: use %d to format int instead of %lu (issue6565)
+ * revlog: use appropriate format char for int ("i" instead of I")
+ * worker: stop relying on garbage collection to release memoryview
+ * worker: implement _blockingreader.readinto() (issue6444)
+ * worker: avoid potential partial write of pickled data
+
+== Backwards Compatibility Changes ==
+ * '''Removed Python 2 support''': this includes a lot of cleanup in our codebase, automation, testing, etc.
+ * debugindex: rename to debugindex debug-revlog-index
+
+== Miscellaneous ==
+
+ * Fix typos and add missing items from documentation
+ * dirstate-tree: optimize HashMap lookups with raw_entry_mut
+ * Rust dependencies have been upgraded
+ * revlog: rank computation is done by Rust when available
+ * Improve discovery test tooling
+ * Audit the number of queries done in discovery
+ * Improved .hgignore of the mercurial-devel repository itself
+ * Improve test coverage of dirstate-v2
+ * rust-requirements: allow loading repos with `bookmarksinstore` requirement
+ * Various Rust refactorings to help with revlog management
+ * Improve debugability of Rust structs
+ * Improve unit testing of the Rust dirstatemap
+ * Improve robustness of the Rust dirstatemap to corruption
+ * Improve changelog-v2 upgrade system
--- a/rust/hg-core/src/dirstate/entry.rs	Thu Jun 16 15:20:48 2022 +0200
+++ b/rust/hg-core/src/dirstate/entry.rs	Mon Jul 11 09:54:40 2022 +0200
@@ -83,7 +83,7 @@
                 second_ambiguous,
             })
         } else {
-            Err(DirstateV2ParseError)
+            Err(DirstateV2ParseError::new("when reading datetime"))
         }
     }
 
--- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs	Thu Jun 16 15:20:48 2022 +0200
+++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs	Mon Jul 11 09:54:40 2022 +0200
@@ -463,7 +463,7 @@
         if let Some(data) = on_disk.get(..data_size) {
             Ok(on_disk::read(data, metadata)?)
         } else {
-            Err(DirstateV2ParseError.into())
+            Err(DirstateV2ParseError::new("not enough bytes on disk").into())
         }
     }
 
--- a/rust/hg-core/src/dirstate_tree/on_disk.rs	Thu Jun 16 15:20:48 2022 +0200
+++ b/rust/hg-core/src/dirstate_tree/on_disk.rs	Mon Jul 11 09:54:40 2022 +0200
@@ -175,11 +175,21 @@
 ///
 /// This should only happen if Mercurial is buggy or a repository is corrupted.
 #[derive(Debug)]
-pub struct DirstateV2ParseError;
+pub struct DirstateV2ParseError {
+    message: String,
+}
+
+impl DirstateV2ParseError {
+    pub fn new<S: Into<String>>(message: S) -> Self {
+        Self {
+            message: message.into(),
+        }
+    }
+}
 
 impl From<DirstateV2ParseError> for HgError {
-    fn from(_: DirstateV2ParseError) -> Self {
-        HgError::corrupted("dirstate-v2 parse error")
+    fn from(e: DirstateV2ParseError) -> Self {
+        HgError::corrupted(format!("dirstate-v2 parse error: {}", e.message))
     }
 }
 
@@ -262,13 +272,16 @@
 pub fn read_docket(
     on_disk: &[u8],
 ) -> Result<Docket<'_>, DirstateV2ParseError> {
-    let (header, uuid) =
-        DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
+    let (header, uuid) = DocketHeader::from_bytes(on_disk).map_err(|e| {
+        DirstateV2ParseError::new(format!("when reading docket, {}", e))
+    })?;
     let uuid_size = header.uuid_size as usize;
     if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
         Ok(Docket { header, uuid })
     } else {
-        Err(DirstateV2ParseError)
+        Err(DirstateV2ParseError::new(
+            "invalid format marker or uuid size",
+        ))
     }
 }
 
@@ -281,14 +294,17 @@
         map.dirstate_version = DirstateVersion::V2;
         return Ok(map);
     }
-    let (meta, _) = TreeMetadata::from_bytes(metadata)
-        .map_err(|_| DirstateV2ParseError)?;
+    let (meta, _) = TreeMetadata::from_bytes(metadata).map_err(|e| {
+        DirstateV2ParseError::new(format!("when parsing tree metadata, {}", e))
+    })?;
     let dirstate_map = DirstateMap {
         on_disk,
-        root: dirstate_map::ChildNodes::OnDisk(read_nodes(
-            on_disk,
-            meta.root_nodes,
-        )?),
+        root: dirstate_map::ChildNodes::OnDisk(
+            read_nodes(on_disk, meta.root_nodes).map_err(|mut e| {
+                e.message = format!("{}, when reading root notes", e.message);
+                e
+            })?,
+        ),
         nodes_with_entry_count: meta.nodes_with_entry_count.get(),
         nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
         ignore_patterns_hash: meta.ignore_patterns_hash,
@@ -317,7 +333,7 @@
                 .expect("dirstate-v2 base_name_start out of bounds");
             Ok(start)
         } else {
-            Err(DirstateV2ParseError)
+            Err(DirstateV2ParseError::new("not enough bytes for base name"))
         }
     }
 
@@ -571,11 +587,19 @@
     // `&[u8]` cannot occupy the entire addess space.
     let start = start.get().try_into().unwrap_or(std::usize::MAX);
     let len = len.try_into().unwrap_or(std::usize::MAX);
-    on_disk
-        .get(start..)
-        .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
+    let bytes = match on_disk.get(start..) {
+        Some(bytes) => bytes,
+        None => {
+            return Err(DirstateV2ParseError::new(
+                "not enough bytes from disk",
+            ))
+        }
+    };
+    T::slice_from_bytes(bytes, len)
+        .map_err(|e| {
+            DirstateV2ParseError::new(format!("when reading a slice, {}", e))
+        })
         .map(|(slice, _rest)| slice)
-        .ok_or_else(|| DirstateV2ParseError)
 }
 
 pub(crate) fn for_each_tracked_path<'on_disk>(
@@ -583,8 +607,9 @@
     metadata: &[u8],
     mut f: impl FnMut(&'on_disk HgPath),
 ) -> Result<(), DirstateV2ParseError> {
-    let (meta, _) = TreeMetadata::from_bytes(metadata)
-        .map_err(|_| DirstateV2ParseError)?;
+    let (meta, _) = TreeMetadata::from_bytes(metadata).map_err(|e| {
+        DirstateV2ParseError::new(format!("when parsing tree metadata, {}", e))
+    })?;
     fn recur<'on_disk>(
         on_disk: &'on_disk [u8],
         nodes: ChildNodes,
--- a/rust/hg-core/src/revlog/revlog.rs	Thu Jun 16 15:20:48 2022 +0200
+++ b/rust/hg-core/src/revlog/revlog.rs	Mon Jul 11 09:54:40 2022 +0200
@@ -5,7 +5,6 @@
 use std::path::Path;
 
 use flate2::read::ZlibDecoder;
-use micro_timer::timed;
 use sha1::{Digest, Sha1};
 use zstd;
 
@@ -49,18 +48,20 @@
     fn from(error: NodeMapError) -> Self {
         match error {
             NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
-            NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(),
+            NodeMapError::RevisionNotInIndex(rev) => RevlogError::corrupted(
+                format!("nodemap point to revision {} not in index", rev),
+            ),
         }
     }
 }
 
-fn corrupted() -> HgError {
-    HgError::corrupted("corrupted revlog")
+fn corrupted<S: AsRef<str>>(context: S) -> HgError {
+    HgError::corrupted(format!("corrupted revlog, {}", context.as_ref()))
 }
 
 impl RevlogError {
-    fn corrupted() -> Self {
-        RevlogError::Other(corrupted())
+    fn corrupted<S: AsRef<str>>(context: S) -> Self {
+        RevlogError::Other(corrupted(context))
     }
 }
 
@@ -81,7 +82,6 @@
     ///
     /// It will also open the associated data file if index and data are not
     /// interleaved.
-    #[timed]
     pub fn open(
         store_vfs: &Vfs,
         index_path: impl AsRef<Path>,
@@ -155,7 +155,6 @@
 
     /// Return the revision number for the given node ID, if it exists in this
     /// revlog
-    #[timed]
     pub fn rev_from_node(
         &self,
         node: NodePrefix,
@@ -205,7 +204,6 @@
     /// All entries required to build the final data out of deltas will be
     /// retrieved as needed, and the deltas will be applied to the inital
     /// snapshot to rebuild the final data.
-    #[timed]
     pub fn get_rev_data(
         &self,
         rev: Revision,
@@ -240,7 +238,6 @@
 
     /// Build the full data of a revision out its snapshot
     /// and its deltas.
-    #[timed]
     fn build_data_from_deltas(
         snapshot: RevlogEntry,
         deltas: &[RevlogEntry],
@@ -329,7 +326,8 @@
         &self,
         rev: Revision,
     ) -> Result<RevlogEntry, HgError> {
-        return self.get_entry(rev).map_err(|_| corrupted());
+        self.get_entry(rev)
+            .map_err(|_| corrupted(format!("revision {} out of range", rev)))
     }
 }
 
@@ -449,7 +447,10 @@
         ) {
             Ok(data)
         } else {
-            Err(corrupted())
+            Err(corrupted(format!(
+                "hash check failed for revision {}",
+                self.rev
+            )))
         }
     }
 
@@ -478,7 +479,10 @@
             // zstd data.
             b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
             // A proper new format should have had a repo/store requirement.
-            _format_type => Err(corrupted()),
+            format_type => Err(corrupted(format!(
+                "unknown compression header '{}'",
+                format_type
+            ))),
         }
     }
 
@@ -486,12 +490,16 @@
         let mut decoder = ZlibDecoder::new(self.bytes);
         if self.is_delta() {
             let mut buf = Vec::with_capacity(self.compressed_len as usize);
-            decoder.read_to_end(&mut buf).map_err(|_| corrupted())?;
+            decoder
+                .read_to_end(&mut buf)
+                .map_err(|e| corrupted(e.to_string()))?;
             Ok(buf)
         } else {
             let cap = self.uncompressed_len.max(0) as usize;
             let mut buf = vec![0; cap];
-            decoder.read_exact(&mut buf).map_err(|_| corrupted())?;
+            decoder
+                .read_exact(&mut buf)
+                .map_err(|e| corrupted(e.to_string()))?;
             Ok(buf)
         }
     }
@@ -500,15 +508,15 @@
         if self.is_delta() {
             let mut buf = Vec::with_capacity(self.compressed_len as usize);
             zstd::stream::copy_decode(self.bytes, &mut buf)
-                .map_err(|_| corrupted())?;
+                .map_err(|e| corrupted(e.to_string()))?;
             Ok(buf)
         } else {
             let cap = self.uncompressed_len.max(0) as usize;
             let mut buf = vec![0; cap];
             let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
-                .map_err(|_| corrupted())?;
+                .map_err(|e| corrupted(e.to_string()))?;
             if len != self.uncompressed_len as usize {
-                Err(corrupted())
+                Err(corrupted("uncompressed length does not match"))
             } else {
                 Ok(buf)
             }
--- a/rust/hg-cpython/src/revlog.rs	Thu Jun 16 15:20:48 2022 +0200
+++ b/rust/hg-cpython/src/revlog.rs	Mon Jul 11 09:54:40 2022 +0200
@@ -107,7 +107,10 @@
             String::from_utf8_lossy(node.data(py)).to_string()
         };
 
-        let prefix = NodePrefix::from_hex(&node_as_string).map_err(|_| PyErr::new::<ValueError, _>(py, "Invalid node or prefix"))?;
+        let prefix = NodePrefix::from_hex(&node_as_string)
+            .map_err(|_| PyErr::new::<ValueError, _>(
+                py, format!("Invalid node or prefix '{}'", node_as_string))
+            )?;
 
         nt.find_bin(idx, prefix)
             // TODO make an inner API returning the node directly
--- a/rust/rhg/src/commands/cat.rs	Thu Jun 16 15:20:48 2022 +0200
+++ b/rust/rhg/src/commands/cat.rs	Mon Jul 11 09:54:40 2022 +0200
@@ -67,10 +67,19 @@
             let message = "`..` or `.` path segment";
             return Err(CommandError::unsupported(message));
         }
+        let relative_path = working_directory
+            .strip_prefix(&cwd)
+            .unwrap_or(&working_directory);
         let stripped = normalized
             .strip_prefix(&working_directory)
-            // TODO: error message for path arguments outside of the repo
-            .map_err(|_| CommandError::abort(""))?;
+            .map_err(|_| {
+                CommandError::abort(format!(
+                    "abort: {} not under root '{}'\n(consider using '--cwd {}')",
+                    file,
+                    working_directory.display(),
+                    relative_path.display(),
+                ))
+            })?;
         let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
             .map_err(|e| CommandError::abort(e.to_string()))?;
         files.push(hg_file);
--- a/rust/rhg/src/commands/status.rs	Thu Jun 16 15:20:48 2022 +0200
+++ b/rust/rhg/src/commands/status.rs	Mon Jul 11 09:54:40 2022 +0200
@@ -517,10 +517,13 @@
     }
     let filelog = repo.filelog(hg_path)?;
     let fs_len = fs_metadata.len();
-    let filelog_entry =
-        filelog.entry_for_node(entry.node_id()?).map_err(|_| {
-            HgError::corrupted("filelog missing node from manifest")
-        })?;
+    let file_node = entry.node_id()?;
+    let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
+        HgError::corrupted(format!(
+            "filelog missing node {:?} from manifest",
+            file_node
+        ))
+    })?;
     if filelog_entry.file_data_len_not_equal_to(fs_len) {
         // No need to read file contents:
         // it cannot be equal if it has a different length.
--- a/tests/test-upgrade-repo.t	Thu Jun 16 15:20:48 2022 +0200
+++ b/tests/test-upgrade-repo.t	Mon Jul 11 09:54:40 2022 +0200
@@ -467,6 +467,7 @@
   re-delta-fulladd
      every revision will be re-added as if it was new content. It will go through the full storage mechanism giving extensions a chance to process it (eg. lfs). This is similar to "re-delta-all" but even slower since more logic is involved.
   
+
   $ hg debugupgrade --optimize re-delta-parent --quiet
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !)
@@ -480,6 +481,20 @@
     - manifest
   
 
+passing multiple optimization:
+
+  $ hg debugupgrade --optimize re-delta-parent --optimize re-delta-multibase --quiet
+  requirements
+     preserved: * (glob)
+  
+  optimisations: re-delta-multibase, re-delta-parent
+  
+  processed revlogs:
+    - all-filelogs
+    - changelog
+    - manifest
+  
+
 unknown optimization:
 
   $ hg debugupgrade --optimize foobar