D10726: recover: only apply last journal record per file

2021-05-17 Thread joerg.sonnenberger (Joerg Sonnenberger)
joerg.sonnenberger created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  This got broken in 2019 when the size check was introduced. It is most
  noticable when dealing with transactions that involve an inline to
  non-inline revlog storage transaction. It wasn't seen as much at the
  time because the in-memory journal actually de-duplicated the entry
  implicity, but since 63edc384d3b7 
 
the on-disk journal is used for
  rollback as well as recover.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/transaction.py
  tests/test-transaction-rollback-on-revlog-split.t

CHANGE DETAILS

diff --git a/tests/test-transaction-rollback-on-revlog-split.t 
b/tests/test-transaction-rollback-on-revlog-split.t
--- a/tests/test-transaction-rollback-on-revlog-split.t
+++ b/tests/test-transaction-rollback-on-revlog-split.t
@@ -4,9 +4,9 @@
 Helper extension to intercept renames.
 
   $ cat > $TESTTMP/intercept_rename.py << EOF
-  > from mercurial.extensions import wrapfunction
-  > from mercurial.util import atomictempfile
-  > import os, sys
+  > import os
+  > import sys
+  > from mercurial import extensions, util
   > 
   > def extsetup(ui):
   > def close(orig, *args, **kwargs):
@@ -14,7 +14,7 @@
   > if path.endswith(b'/.hg/store/data/file.i'):
   > os._exit(80)
   > return orig(*args, **kwargs)
-  > wrapfunction(atomictempfile, 'close', close)
+  > extensions.wrapfunction(util.atomictempfile, 'close', close)
   > EOF
 
 
@@ -62,6 +62,28 @@
   data/file.d 0
   data/file.d 1046
   data/file.i 128
+  $ hg recover
+  rolling back interrupted transaction
+  (verify step skipped, run `hg verify` to check your repository content)
+  $ f -s .hg/store/data/file*
+  .hg/store/data/file.d: size=1046
+  .hg/store/data/file.i: size=128
+  $ hg tip
+  changeset:   1:3ce491143aec
+  tag: tip
+  user:test
+  date:Thu Jan 01 00:00:00 1970 +
+  summary: _
+  
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+   warning: revlog 'data/file.d' not in fncache!
+  checked 2 changesets with 2 changes to 1 files
+  1 warnings encountered!
+  hint: run "hg debugrebuildfncache" to recover from corrupt fncache
   $ cd ..
 
 Now retry the same but intercept the rename of the index and check that
@@ -83,4 +105,24 @@
   data/file.i 1174
   data/file.d 0
   data/file.d 1046
+
+  $ hg recover
+  rolling back interrupted transaction
+  (verify step skipped, run `hg verify` to check your repository content)
+  $ f -s .hg/store/data/file*
+  .hg/store/data/file.d: size=1046
+  .hg/store/data/file.i: size=1174
+  $ hg tip
+  changeset:   1:3ce491143aec
+  tag: tip
+  user:test
+  date:Thu Jan 01 00:00:00 1970 +
+  summary: _
+  
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  checked 2 changesets with 2 changes to 1 files
   $ cd ..
diff --git a/mercurial/transaction.py b/mercurial/transaction.py
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -56,7 +56,7 @@
 unlink=True,
 checkambigfiles=None,
 ):
-for f, o in entries:
+for f, o in sorted(dict(entries).items()):
 if o or not unlink:
 checkambig = checkambigfiles and (f, b'') in checkambigfiles
 try:



To: joerg.sonnenberger, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D10725: revlog: update data file record before index rename

2021-05-17 Thread joerg.sonnenberger (Joerg Sonnenberger)
joerg.sonnenberger created this revision.
Herald added a reviewer: indygreg.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  Whe migrating from inline to non-inline data storage, the data file is
  recorded initially as zero sized so that it is removed on failure. But
  the record has to be updated before the index is renamed since otherwise
  data is lost on rollback.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/revlog.py
  tests/test-transaction-rollback-on-revlog-split.t

CHANGE DETAILS

diff --git a/tests/test-transaction-rollback-on-revlog-split.t 
b/tests/test-transaction-rollback-on-revlog-split.t
--- a/tests/test-transaction-rollback-on-revlog-split.t
+++ b/tests/test-transaction-rollback-on-revlog-split.t
@@ -1,7 +1,27 @@
 Test correctness of revlog inline -> non-inline transition
 --
 
+Helper extension to intercept renames.
+
+  $ cat > $TESTTMP/intercept_rename.py << EOF
+  > from mercurial.extensions import wrapfunction
+  > from mercurial.util import atomictempfile
+  > import os, sys
+  > 
+  > def extsetup(ui):
+  > def close(orig, *args, **kwargs):
+  > path = args[0]._atomictempfile__name
+  > if path.endswith(b'/.hg/store/data/file.i'):
+  > os._exit(80)
+  > return orig(*args, **kwargs)
+  > wrapfunction(atomictempfile, 'close', close)
+  > EOF
+
+
 Test offset computation to correctly factor in the index entries themselve.
+Also test that the new data size has the correct size if the transaction is 
aborted
+after the index has been replaced.
+
 Test repo has one small, one moderate and one big change. The clone has
 the small and moderate change and will transition to non-inline storage when
 adding the big change.
@@ -18,6 +38,12 @@
 
   $ hg clone -r 1 troffset-computation troffset-computation-copy --config 
format.revlog-compression=none -q
   $ cd troffset-computation-copy
+
+Reference size:
+
+  $ f -s .hg/store/data/file*
+  .hg/store/data/file.i: size=1174
+
   $ cat > .hg/hgrc < [hooks]
   > pretxnchangegroup = python:$TESTDIR/helper-killhook.py:killme
@@ -25,5 +51,36 @@
   $ hg pull ../troffset-computation
   pulling from ../troffset-computation
   [80]
-  $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file | tail -1
+
+The first file.i entry should match the size above.
+The first file.d entry is the temporary record during the split,
+the second entry after the split happened. The sum of the second file.d
+and the second file.i entry should match the first file.i entry.
+
+  $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
+  data/file.i 1174
+  data/file.d 0
+  data/file.d 1046
   data/file.i 128
+  $ cd ..
+
+Now retry the same but intercept the rename of the index and check that
+the journal does not contain the new index size. This demonstrates the edge 
case
+where the data file is left as garbage.
+
+  $ hg clone -r 1 troffset-computation troffset-computation-copy2 --config 
format.revlog-compression=none -q
+  $ cd troffset-computation-copy2
+  $ cat > .hg/hgrc < [extensions]
+  > intercept_rename = $TESTTMP/intercept_rename.py
+  > [hooks]
+  > pretxnchangegroup = python:$TESTDIR/helper-killhook.py:killme
+  > EOF
+  $ hg pull ../troffset-computation
+  pulling from ../troffset-computation
+  [80]
+  $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
+  data/file.i 1174
+  data/file.d 0
+  data/file.d 1046
+  $ cd ..
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1999,6 +1999,12 @@
 e = header + e
 fp.write(e)
 
+# There is a small transactional race here. If the rename of
+# the index fails, we should remove the datafile. It is more
+# important to ensure that the data file is not truncated
+# when the index is replaced as otherwise data is lost.
+tr.replace(self._datafile, self.start(trindex))
+
 # the temp file replace the real index when we exit the context
 # manager
 



To: joerg.sonnenberger, indygreg, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D10724: revlog: fix index computation during inline->non-inline transition

2021-05-17 Thread joerg.sonnenberger (Joerg Sonnenberger)
joerg.sonnenberger created this revision.
Herald added a reviewer: indygreg.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  The computation 63edc384d3b7 
 
failed to factor in the index entries
  themselve as revlog.start() doesn't count them. Fonud by Valtenin
  Gatienbaron with a precise test case from me.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/revlog.py
  tests/helper-killhook.py
  tests/test-transaction-rollback-on-revlog-split.t

CHANGE DETAILS

diff --git a/tests/test-transaction-rollback-on-revlog-split.t 
b/tests/test-transaction-rollback-on-revlog-split.t
new file mode 100644
--- /dev/null
+++ b/tests/test-transaction-rollback-on-revlog-split.t
@@ -0,0 +1,29 @@
+Test correctness of revlog inline -> non-inline transition
+--
+
+Test offset computation to correctly factor in the index entries themselve.
+Test repo has one small, one moderate and one big change. The clone has
+the small and moderate change and will transition to non-inline storage when
+adding the big change.
+
+  $ hg init troffset-computation --config format.revlog-compression=none
+  $ cd troffset-computation
+  $ printf '% 20d' '1' > file
+  $ hg commit -Aqm_
+  $ printf '% 1024d' '1' > file
+  $ hg commit -Aqm_
+  $ dd if=/dev/zero of=file bs=1k count=128 > /dev/null 2>&1
+  $ hg commit -Aqm_
+  $ cd ..
+
+  $ hg clone -r 1 troffset-computation troffset-computation-copy --config 
format.revlog-compression=none -q
+  $ cd troffset-computation-copy
+  $ cat > .hg/hgrc < [hooks]
+  > pretxnchangegroup = python:$TESTDIR/helper-killhook.py:killme
+  > EOF
+  $ hg pull ../troffset-computation
+  pulling from ../troffset-computation
+  [80]
+  $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file | tail -1
+  data/file.i 128
diff --git a/tests/helper-killhook.py b/tests/helper-killhook.py
new file mode 100644
--- /dev/null
+++ b/tests/helper-killhook.py
@@ -0,0 +1,4 @@
+import os
+
+def killme(ui, repo, hooktype, **wkargs):
+os._exit(80)
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1985,7 +1985,7 @@
 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
 for r in self:
 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
-if troffset <= self.start(r):
+if troffset <= self.start(r) + r * self.index.entry_size:
 trindex = r
 
 with self._indexfp(b'w') as fp:



To: joerg.sonnenberger, indygreg, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D10723: rewriteutil: add pointer to help text when rewrite would cause divergence

2021-05-17 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  The evolve extension's version of the hint has this pointer. I missed
  it when I moved it to core.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/rewriteutil.py
  tests/test-amend.t
  tests/test-branch-change.t

CHANGE DETAILS

diff --git a/tests/test-branch-change.t b/tests/test-branch-change.t
--- a/tests/test-branch-change.t
+++ b/tests/test-branch-change.t
@@ -151,7 +151,7 @@
 
   $ hg branch -r 4 --hidden foobar
   abort: cannot change branch of 3938acfb5c0f, as that creates 
content-divergence with 7c1991464886
-  (add --verbose for details)
+  (add --verbose for details or see 'hg help evolution.instability')
   [10]
 
 Make sure bookmark movement is correct
diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -239,7 +239,7 @@
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg amend -m divergent
   abort: cannot amend 112478962961, as that creates content-divergence with 
16084da537dd
-  (add --verbose for details)
+  (add --verbose for details or see 'hg help evolution.instability')
   [10]
   $ hg amend -m divergent --config experimental.evolution.allowdivergence=true
   2 new content-divergent changesets
diff --git a/mercurial/rewriteutil.py b/mercurial/rewriteutil.py
--- a/mercurial/rewriteutil.py
+++ b/mercurial/rewriteutil.py
@@ -114,7 +114,11 @@
 raise error.InputError(msg)
 else:
 raise error.InputError(
-msg, hint=_(b"add --verbose for details")
+msg,
+hint=_(
+b"add --verbose for details or see "
+b"'hg help evolution.instability'"
+),
 )
 
 



To: martinvonz, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D10718: dirstate-v2: Update the expected output of some tests for new requirement

2021-05-17 Thread SimonSapin
SimonSapin created this revision.
Herald added a reviewer: durin42.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  Fix most test failures (except in test-narrow-debugrebuilddirstate.t and
  test-upgrade-repo.t) caused by the new entry in config or in .hg/requires
  when running `run-tests.py --extra-config-opt format.exp-dirstate-v2=1`
  
  There is no CI so far for this configuration.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/localrepo.py
  tests/hghave.py
  tests/test-basic.t
  tests/test-clone-uncompressed.t
  tests/test-commandserver.t
  tests/test-config.t
  tests/test-init.t
  tests/test-lfconvert.t
  tests/test-lfs-largefiles.t
  tests/test-narrow-clone-no-ellipsis.t
  tests/test-narrow-clone-stream.t
  tests/test-narrow-clone.t
  tests/test-narrow-sparse.t
  tests/test-persistent-nodemap.t
  tests/test-phases.t
  tests/test-remotefilelog-clone-tree.t
  tests/test-remotefilelog-clone.t
  tests/test-remotefilelog-log.t
  tests/test-repo-compengines.t
  tests/test-requires.t
  tests/test-revlog-v2.t
  tests/test-share-safe.t
  tests/test-sparse-requirement.t
  tests/test-sqlitestore.t
  tests/test-stream-bundle-v2.t

CHANGE DETAILS

diff --git a/tests/test-stream-bundle-v2.t b/tests/test-stream-bundle-v2.t
--- a/tests/test-stream-bundle-v2.t
+++ b/tests/test-stream-bundle-v2.t
@@ -48,11 +48,13 @@
   Stream params: {}
   stream2 -- {bytecount: 1693, filecount: 11, requirements: 
dotencode%2Cfncache%2Cgeneraldelta%2Crevlogv1%2Csparserevlog%2Cstore} 
(mandatory: True) (no-zstd !)
   stream2 -- {bytecount: 1693, filecount: 11, requirements: 
dotencode%2Cfncache%2Cgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore}
 (mandatory: True) (zstd no-rust !)
-  stream2 -- {bytecount: 1693, filecount: 11, requirements: 
dotencode%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore}
 (mandatory: True) (rust !)
+  stream2 -- {bytecount: 1693, filecount: 11, requirements: 
dotencode%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore}
 (mandatory: True) (rust no-dirstate-v2 !)
+  stream2 -- {bytecount: 1693, filecount: 11, requirements: 
dotencode%2Cexp-dirstate-v2%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore}
 (mandatory: True) (dirstate-v2 !)
   $ hg debugbundle --spec bundle.hg
   
none-v2;stream=v2;requirements%3Ddotencode%2Cfncache%2Cgeneraldelta%2Crevlogv1%2Csparserevlog%2Cstore
 (no-zstd !)
   
none-v2;stream=v2;requirements%3Ddotencode%2Cfncache%2Cgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore
 (zstd no-rust !)
-  
none-v2;stream=v2;requirements%3Ddotencode%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore
 (rust !)
+  
none-v2;stream=v2;requirements%3Ddotencode%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore
 (rust no-dirstate-v2 !)
+  
none-v2;stream=v2;requirements%3Ddotencode%2Cexp-dirstate-v2%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore
 (dirstate-v2 !)
 
 Test that we can apply the bundle as a stream clone bundle
 
diff --git a/tests/test-sqlitestore.t b/tests/test-sqlitestore.t
--- a/tests/test-sqlitestore.t
+++ b/tests/test-sqlitestore.t
@@ -15,6 +15,7 @@
   $ hg init empty-no-sqlite
   $ cat empty-no-sqlite/.hg/requires
   dotencode
+  exp-dirstate-v2 (dirstate-v2 !)
   fncache
   generaldelta
   persistent-nodemap (rust !)
@@ -28,6 +29,7 @@
   $ hg --config storage.new-repo-backend=sqlite init empty-sqlite
   $ cat empty-sqlite/.hg/requires
   dotencode
+  exp-dirstate-v2 (dirstate-v2 !)
   exp-sqlite-001
   exp-sqlite-comp-001=zstd (zstd !)
   exp-sqlite-comp-001=$BUNDLE2_COMPRESSIONS$ (no-zstd !)
@@ -49,6 +51,7 @@
   $ hg --config storage.sqlite.compression=zlib init empty-zlib
   $ cat empty-zlib/.hg/requires
   dotencode
+  exp-dirstate-v2 (dirstate-v2 !)
   exp-sqlite-001
   exp-sqlite-comp-001=$BUNDLE2_COMPRESSIONS$
   fncache
@@ -64,6 +67,7 @@
   $ hg --config storage.sqlite.compression=none init empty-none
   $ cat empty-none/.hg/requires
   dotencode
+  exp-dirstate-v2 (dirstate-v2 !)
   exp-sqlite-001
   exp-sqlite-comp-001=none
   fncache
diff --git a/tests/test-sparse-requirement.t b/tests/test-sparse-requirement.t
--- a/tests/test-sparse-requirement.t
+++ b/tests/test-sparse-requirement.t
@@ -18,6 +18,7 @@
 
   $ cat .hg/requires
   dotencode
+  exp-dirstate-v2 (dirstate-v2 !)
   fncache
   generaldelta
   persistent-nodemap (rust !)
@@ -37,6 +38,7 @@
 
   $ cat .hg/requires
   dotencode
+  exp-dirstate-v2 (dirstate-v2 !)
   exp-sparse
   fncache
   generaldelta
@@ -59,6 +61,7 @@
 
   $ cat .hg/requires
   dotencode
+  exp-dirstate-v2 (dirstate-v2 !)
   

D10719: dirstate-v2: Change the on-disk format when the requirement is enabled

2021-05-17 Thread SimonSapin
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  For now, the format is the same except with an additional marker at the start.
  
  This marker is redundant: for existing repositories it is `.hg/requires` that
  determines which format to use. For new repositories, it is the new
  `format.exp-dirstate-v2` config. There is no upgrade or downgrade so far.
  
  Most of the changes are about plumbing a boolean through layers of APIs to
  indicate which format should be used.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  hgext/largefiles/lfutil.py
  mercurial/dirstate.py
  mercurial/interfaces/dirstate.py
  mercurial/localrepo.py
  rust/hg-core/src/dirstate_tree.rs
  rust/hg-core/src/dirstate_tree/dirstate_map.rs
  rust/hg-core/src/dirstate_tree/dispatch.rs
  rust/hg-core/src/dirstate_tree/on_disk.rs
  rust/hg-cpython/src/dirstate.rs
  rust/hg-cpython/src/dirstate/dirstate_map.rs
  rust/hg-cpython/src/dirstate/dispatch.rs
  rust/hg-cpython/src/dirstate/owning.rs

CHANGE DETAILS

diff --git a/rust/hg-cpython/src/dirstate/owning.rs 
b/rust/hg-cpython/src/dirstate/owning.rs
--- a/rust/hg-cpython/src/dirstate/owning.rs
+++ b/rust/hg-cpython/src/dirstate/owning.rs
@@ -31,9 +31,14 @@
 pub fn new(
 py: Python,
 on_disk: PyBytes,
+use_dirstate_v2: bool,
 ) -> Result<(Self, Option), DirstateError> {
 let bytes: &'_ [u8] = on_disk.data(py);
-let (map, parents) = DirstateMap::new(bytes)?;
+let (map, parents) = if use_dirstate_v2 {
+DirstateMap::new_v2(bytes)?
+} else {
+DirstateMap::new_v1(bytes)?
+};
 
 // Like in `bytes` above, this `'_` lifetime parameter borrows from
 // the bytes buffer owned by `on_disk`.
diff --git a/rust/hg-cpython/src/dirstate/dispatch.rs 
b/rust/hg-cpython/src/dirstate/dispatch.rs
--- a/rust/hg-cpython/src/dirstate/dispatch.rs
+++ b/rust/hg-cpython/src/dirstate/dispatch.rs
@@ -101,12 +101,20 @@
 self.get_mut().has_dir(directory)
 }
 
-fn pack(
+fn pack_v1(
  self,
 parents: DirstateParents,
 now: Timestamp,
 ) -> Result, DirstateError> {
-self.get_mut().pack(parents, now)
+self.get_mut().pack_v1(parents, now)
+}
+
+fn pack_v2(
+ self,
+parents: DirstateParents,
+now: Timestamp,
+) -> Result, DirstateError> {
+self.get_mut().pack_v2(parents, now)
 }
 
 fn set_all_dirs( self) -> Result<(), DirstateMapError> {
diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs 
b/rust/hg-cpython/src/dirstate/dirstate_map.rs
--- a/rust/hg-cpython/src/dirstate/dirstate_map.rs
+++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs
@@ -55,13 +55,17 @@
 
 /// Returns a `(dirstate_map, parents)` tuple
 @staticmethod
-def new(use_dirstate_tree: bool, on_disk: PyBytes) -> PyResult {
-let dirstate_error = |_: DirstateError| {
-PyErr::new::(py, "Dirstate error".to_string())
+def new(
+use_dirstate_tree: bool,
+use_dirstate_v2: bool,
+on_disk: PyBytes,
+) -> PyResult {
+let dirstate_error = |e: DirstateError| {
+PyErr::new::(py, format!("Dirstate error: {:?}", 
e))
 };
-let (inner, parents) = if use_dirstate_tree {
+let (inner, parents) = if use_dirstate_tree || use_dirstate_v2 {
 let (map, parents) =
-OwningDirstateMap::new(py, on_disk)
+OwningDirstateMap::new(py, on_disk, use_dirstate_v2)
 .map_err(dirstate_error)?;
 (Box::new(map) as _, parents)
 } else {
@@ -288,6 +292,7 @@
 
 def write(
 ,
+use_dirstate_v2: bool,
 p1: PyObject,
 p2: PyObject,
 now: PyObject
@@ -298,7 +303,13 @@
 p2: extract_node_id(py, )?,
 };
 
-match self.inner(py).borrow_mut().pack(parents, now) {
+let mut inner = self.inner(py).borrow_mut();
+let result = if use_dirstate_v2 {
+inner.pack_v2(parents, now)
+} else {
+inner.pack_v1(parents, now)
+};
+match result {
 Ok(packed) => Ok(PyBytes::new(py, )),
 Err(_) => Err(PyErr::new::(
 py,
diff --git a/rust/hg-cpython/src/dirstate.rs b/rust/hg-cpython/src/dirstate.rs
--- a/rust/hg-cpython/src/dirstate.rs
+++ b/rust/hg-cpython/src/dirstate.rs
@@ -26,6 +26,7 @@
 exc, PyBytes, PyDict, PyErr, PyList, PyModule, PyObject, PyResult,
 PySequence, Python,
 };
+use hg::dirstate_tree::on_disk::V2_FORMAT_MARKER;
 use hg::{utils::hg_path::HgPathBuf, DirstateEntry, EntryState, StateMap};
 use libc::{c_char, c_int};
 use std::convert::TryFrom;
@@ -117,6 +118,7 @@
 )?;
 m.add_class::(py)?;
 m.add_class::(py)?;
+m.add(py, "V2_FORMAT_MARKER", 

D10722: dirstate-v2: Change the on-disk format to be tree-shaped

2021-05-17 Thread SimonSapin
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  Nodes are stored not only for tracked files but also for their ancestor
  directories. A node has "pointers" (byte count from the start of the file)
  to its direct child nodes. Everything can be accessed with zero copy.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  rust/hg-core/src/dirstate_tree/dirstate_map.rs
  rust/hg-core/src/dirstate_tree/on_disk.rs
  rust/hg-core/src/dirstate_tree/path_with_basename.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/dirstate_tree/path_with_basename.rs 
b/rust/hg-core/src/dirstate_tree/path_with_basename.rs
--- a/rust/hg-core/src/dirstate_tree/path_with_basename.rs
+++ b/rust/hg-core/src/dirstate_tree/path_with_basename.rs
@@ -24,18 +24,29 @@
 }
 }
 
+fn find_base_name_start(full_path: ) -> usize {
+if let Some(last_slash_position) =
+full_path.as_bytes().iter().rposition(|| byte == b'/')
+{
+last_slash_position + 1
+} else {
+0
+}
+}
+
 impl> WithBasename {
 pub fn new(full_path: T) -> Self {
-let base_name_start = if let Some(last_slash_position) = full_path
-.as_ref()
-.as_bytes()
-.iter()
-.rposition(|| byte == b'/')
-{
-last_slash_position + 1
-} else {
-0
-};
+Self {
+base_name_start: find_base_name_start(full_path.as_ref()),
+full_path,
+}
+}
+
+pub fn from_raw_parts(full_path: T, base_name_start: usize) -> Self {
+debug_assert_eq!(
+base_name_start,
+find_base_name_start(full_path.as_ref())
+);
 Self {
 base_name_start,
 full_path,
@@ -47,6 +58,10 @@
 _path.as_ref().as_bytes()[self.base_name_start..],
 )
 }
+
+pub fn base_name_start() -> usize {
+self.base_name_start
+}
 }
 
 impl> Borrow for WithBasename {
diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs 
b/rust/hg-core/src/dirstate_tree/on_disk.rs
--- a/rust/hg-core/src/dirstate_tree/on_disk.rs
+++ b/rust/hg-core/src/dirstate_tree/on_disk.rs
@@ -1,4 +1,335 @@
+//! The "version 2" disk representation of the dirstate
+//!
+//! # File format
+//!
+//! The file starts with a fixed-sized header, whose layout is defined by the
+//! `Header` struct. Its `root` field contains the slice (offset and length) to
+//! the nodes representing the files and directories at the root of the
+//! repository. Each node is also fixed-size, defined by the `Node` struct.
+//! Nodes in turn contain slices to variable-size paths, and to their own child
+//! nodes (if any) for nested files and directories.
+
+use crate::dirstate::parsers::clear_ambiguous_mtime;
+use crate::dirstate::parsers::Timestamp;
+use crate::dirstate_tree::dirstate_map::{self, DirstateMap};
+use crate::dirstate_tree::path_with_basename::WithBasename;
+use crate::errors::HgError;
+use crate::utils::hg_path::HgPath;
+use crate::DirstateEntry;
+use crate::DirstateError;
+use crate::DirstateParents;
+use bytes_cast::unaligned::{I32Be, U32Be, U64Be};
+use bytes_cast::BytesCast;
+use std::borrow::Cow;
+use std::convert::{TryFrom, TryInto};
+
 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
-/// Acts like a "magic number". This is a sanity check, not strictly necessary
-/// since `.hg/requires` already governs which format should be used.
+/// This a redundant sanity check more than an actual "magic number" since
+/// `.hg/requires` already governs which format should be used.
 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
+
+#[derive(BytesCast)]
+#[repr(C)]
+struct Header {
+marker: [u8; V2_FORMAT_MARKER.len()],
+
+/// `dirstatemap.parents()` in `mercurial/dirstate.py` relies on this
+/// `parents` field being at this offset, immediately after `marker`.
+parents: DirstateParents,
+
+root: ChildNodes,
+nodes_with_entry_count: Size,
+nodes_with_copy_source_count: Size,
+}
+
+#[derive(BytesCast)]
+#[repr(C)]
+struct Node {
+full_path: PathSlice,
+
+/// In bytes from `self.full_path.start`
+base_name_start: Size,
+
+copy_source: OptPathSlice,
+entry: OptEntry,
+children: ChildNodes,
+tracked_descendants_count: Size,
+}
+
+/// Either nothing if `state == b'\0'`, or a dirstate entry like in the v1
+/// format
+#[derive(BytesCast)]
+#[repr(C)]
+struct OptEntry {
+state: u8,
+mode: I32Be,
+mtime: I32Be,
+size: I32Be,
+}
+
+/// Counted in bytes from the start of the file
+///
+/// NOTE: If we decide to never support `.hg/dirstate` files larger than 4 GiB
+/// we could save space by using `U32Be` instead.
+type Offset = U64Be;
+
+/// Counted in number of items
+///
+/// NOTE: not supporting directories with more than 4 billion direct children,
+/// 

D10721: dirstate-tree: Extract into a method sorting children of a given node

2021-05-17 Thread SimonSapin
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  A later changset will use this in another place.
  
  This is an associated function (that Python would call static method)
  instead of a free function so it doesn’t need to be imported separately.
  It’s on `Node` rather than `ChildNodes` because the latter is a type alias
  to an external type (`HashMap`) so that would require an extension trait
  which needs to be imported separately.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  rust/hg-core/src/dirstate_tree/dirstate_map.rs
  rust/hg-core/src/dirstate_tree/status.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/dirstate_tree/status.rs 
b/rust/hg-core/src/dirstate_tree/status.rs
--- a/rust/hg-core/src/dirstate_tree/status.rs
+++ b/rust/hg-core/src/dirstate_tree/status.rs
@@ -110,11 +110,9 @@
 
 // `merge_join_by` requires both its input iterators to be sorted:
 
-let mut dirstate_nodes: Vec<_> = dirstate_nodes.iter_mut().collect();
+let dirstate_nodes = Node::sorted(dirstate_nodes);
 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
 // https://github.com/rust-lang/rust/issues/34162
-dirstate_nodes
-.sort_unstable_by(|(path1, _), (path2, _)| path1.cmp(path2));
 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(_name));
 
 itertools::merge_join_by(
diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs 
b/rust/hg-core/src/dirstate_tree/dirstate_map.rs
--- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs
+++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs
@@ -45,10 +45,11 @@
 /// Using a plain `HgPathBuf` of the full path from the repository root as a
 /// map key would also work: all paths in a given map have the same parent
 /// path, so comparing full paths gives the same result as comparing base
-/// names. However `BTreeMap` would waste time always re-comparing the same
+/// names. However `HashMap` would waste time always re-hashing the same
 /// string prefix.
+pub(super) type NodeKey<'on_disk> = WithBasename>;
 pub(super) type ChildNodes<'on_disk> =
-FastHashMap>, Node<'on_disk>>;
+FastHashMap, Node<'on_disk>>;
 
 /// Represents a file or a directory
 #[derive(Default)]
@@ -64,10 +65,20 @@
 tracked_descendants_count: usize,
 }
 
-impl Node<'_> {
+impl<'on_disk> Node<'on_disk> {
 pub(super) fn state() -> Option {
 self.entry.as_ref().map(|entry| entry.state)
 }
+
+pub(super) fn sorted<'tree>(
+nodes: &'tree mut ChildNodes<'on_disk>,
+) -> Vec<(&'tree NodeKey<'on_disk>, &'tree mut Self)> {
+let mut vec: Vec<_> = nodes.iter_mut().collect();
+// `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
+// https://github.com/rust-lang/rust/issues/34162
+vec.sort_unstable_by(|(path1, _), (path2, _)| path1.cmp(path2));
+vec
+}
 }
 
 /// `(full_path, entry, copy_source)`



To: SimonSapin, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D10716: dirstate-v2: Add a new experimental `exp-dirstate-v2` repository requirement

2021-05-17 Thread SimonSapin
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  This requirement is added to `.hg/requires` when creating a new repository
  if Rust extensions are enabled and the `format.exp-dirstate-v2` config is set.
  Nothing yet changes based on this requirement, but its mere presence affects
  some tests (for example if they print `.hg/requires`). The next two changesets
  update tests’ expected outputs accordingly.
  
  There is no CI so far that enables this configuration.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/configitems.py
  mercurial/dirstate.py
  mercurial/localrepo.py
  mercurial/requirements.py
  mercurial/upgrade_utils/actions.py

CHANGE DETAILS

diff --git a/mercurial/upgrade_utils/actions.py 
b/mercurial/upgrade_utils/actions.py
--- a/mercurial/upgrade_utils/actions.py
+++ b/mercurial/upgrade_utils/actions.py
@@ -971,6 +971,7 @@
 requirements.NODEMAP_REQUIREMENT,
 requirements.SHARESAFE_REQUIREMENT,
 requirements.REVLOGV2_REQUIREMENT,
+requirements.DIRSTATE_V2_REQUIREMENT,
 }
 for name in compression.compengines:
 engine = compression.compengines[name]
diff --git a/mercurial/requirements.py b/mercurial/requirements.py
--- a/mercurial/requirements.py
+++ b/mercurial/requirements.py
@@ -12,6 +12,8 @@
 STORE_REQUIREMENT = b'store'
 FNCACHE_REQUIREMENT = b'fncache'
 
+DIRSTATE_V2_REQUIREMENT = b'exp-dirstate-v2'
+
 # When narrowing is finalized and no longer subject to format changes,
 # we should move this to just "narrow" or similar.
 NARROW_REQUIREMENT = b'narrowhg-experimental'
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -879,6 +879,9 @@
 # Start with all requirements supported by this file.
 supported = set(localrepository._basesupported)
 
+if dirstate.SUPPORTS_DIRSTATE_V2:
+supported.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
+
 # Execute ``featuresetupfuncs`` entries if they belong to an extension
 # relevant to this ui instance.
 modules = {m.__name__ for n, m in extensions.extensions(ui)}
@@ -3514,6 +3517,18 @@
 if ui.configbool(b'format', b'sparse-revlog'):
 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
 
+# experimental config: format.exp-dirstate-v2
+if ui.configbool(b'format', b'exp-dirstate-v2'):
+if dirstate.SUPPORTS_DIRSTATE_V2:
+requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
+else:
+raise error.Abort(
+_(
+b"dirstate v2 format requested by config "
+b"but not supported (requires Rust extensions)"
+)
+)
+
 # experimental config: format.exp-use-side-data
 if ui.configbool(b'format', b'exp-use-side-data'):
 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -39,6 +39,8 @@
 parsers = policy.importmod('parsers')
 rustmod = policy.importrust('dirstate')
 
+SUPPORTS_DIRSTATE_V2 = rustmod is not None
+
 propertycache = util.propertycache
 filecache = scmutil.filecache
 _rangemask = 0x7FFF
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -1282,6 +1282,14 @@
 experimental=True,
 )
 coreconfigitem(
+# Enable this dirstate format *when creating a new repository*.
+# Which format to use for existing repos is controlled by .hg/requires
+b'format',
+b'exp-dirstate-v2',
+default=False,
+experimental=True,
+)
+coreconfigitem(
 b'format',
 b'dotencode',
 default=True,



To: SimonSapin, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D10720: dirstate-v2: Add a variant of some tests, that uses the new format

2021-05-17 Thread SimonSapin
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  With this, the new format receives some testing every time someone runs tests
  with Rust extensions enabled, including on CI.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  tests/test-dirstate-race.t
  tests/test-dirstate-race2.t
  tests/test-dirstate.t
  tests/test-hgignore.t
  tests/test-permissions.t
  tests/test-purge.t
  tests/test-status.t
  tests/test-symlinks.t

CHANGE DETAILS

diff --git a/tests/test-symlinks.t b/tests/test-symlinks.t
--- a/tests/test-symlinks.t
+++ b/tests/test-symlinks.t
@@ -1,6 +1,6 @@
 #require symlink
 
-#testcases dirstate-v1 dirstate-v1-tree
+#testcases dirstate-v1 dirstate-v1-tree dirstate-v2
 
 #if dirstate-v1-tree
 #require rust
@@ -8,6 +8,12 @@
   $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
 #endif
 
+#if dirstate-v2
+#require rust
+  $ echo '[format]' >> $HGRCPATH
+  $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
+#endif
+
 == tests added in 0.7 ==
 
   $ hg init test-symlinks-0.7; cd test-symlinks-0.7;
diff --git a/tests/test-status.t b/tests/test-status.t
--- a/tests/test-status.t
+++ b/tests/test-status.t
@@ -1,4 +1,10 @@
-#testcases dirstate-v1 dirstate-v1-tree
+#testcases dirstate-v1 dirstate-v1-tree dirstate-v2
+
+#if no-rust
+  $ hg init repo0 --config format.exp-dirstate-v2=1
+  abort: dirstate v2 format requested by config but not supported (requires 
Rust extensions)
+  [255]
+#endif
 
 #if dirstate-v1-tree
 #require rust
@@ -6,6 +12,12 @@
   $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
 #endif
 
+#if dirstate-v2
+#require rust
+  $ echo '[format]' >> $HGRCPATH
+  $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
+#endif
+
   $ hg init repo1
   $ cd repo1
   $ mkdir a b a/1 b/1 b/2
diff --git a/tests/test-purge.t b/tests/test-purge.t
--- a/tests/test-purge.t
+++ b/tests/test-purge.t
@@ -1,4 +1,4 @@
-#testcases dirstate-v1 dirstate-v1-tree
+#testcases dirstate-v1 dirstate-v1-tree dirstate-v2
 
 #if dirstate-v1-tree
 #require rust
@@ -6,6 +6,12 @@
   $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
 #endif
 
+#if dirstate-v2
+#require rust
+  $ echo '[format]' >> $HGRCPATH
+  $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
+#endif
+
 init
 
   $ hg init t
diff --git a/tests/test-permissions.t b/tests/test-permissions.t
--- a/tests/test-permissions.t
+++ b/tests/test-permissions.t
@@ -1,6 +1,6 @@
 #require unix-permissions no-root reporevlogstore
 
-#testcases dirstate-v1 dirstate-v1-tree
+#testcases dirstate-v1 dirstate-v1-tree dirstate-v2
 
 #if dirstate-v1-tree
 #require rust
@@ -8,6 +8,12 @@
   $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
 #endif
 
+#if dirstate-v2
+#require rust
+  $ echo '[format]' >> $HGRCPATH
+  $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
+#endif
+
   $ hg init t
   $ cd t
 
diff --git a/tests/test-hgignore.t b/tests/test-hgignore.t
--- a/tests/test-hgignore.t
+++ b/tests/test-hgignore.t
@@ -1,4 +1,4 @@
-#testcases dirstate-v1 dirstate-v1-tree
+#testcases dirstate-v1 dirstate-v1-tree dirstate-v2
 
 #if dirstate-v1-tree
 #require rust
@@ -6,6 +6,12 @@
   $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
 #endif
 
+#if dirstate-v2
+#require rust
+  $ echo '[format]' >> $HGRCPATH
+  $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
+#endif
+
   $ hg init ignorerepo
   $ cd ignorerepo
 
diff --git a/tests/test-dirstate.t b/tests/test-dirstate.t
--- a/tests/test-dirstate.t
+++ b/tests/test-dirstate.t
@@ -1,4 +1,4 @@
-#testcases dirstate-v1 dirstate-v1-tree
+#testcases dirstate-v1 dirstate-v1-tree dirstate-v2
 
 #if dirstate-v1-tree
 #require rust
@@ -6,6 +6,12 @@
   $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
 #endif
 
+#if dirstate-v2
+#require rust
+  $ echo '[format]' >> $HGRCPATH
+  $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
+#endif
+
 -- Test dirstate._dirs refcounting
 
   $ hg init t
diff --git a/tests/test-dirstate-race2.t b/tests/test-dirstate-race2.t
--- a/tests/test-dirstate-race2.t
+++ b/tests/test-dirstate-race2.t
@@ -1,4 +1,4 @@
-#testcases dirstate-v1 dirstate-v1-tree
+#testcases dirstate-v1 dirstate-v1-tree dirstate-v2
 
 #if dirstate-v1-tree
 #require rust
@@ -6,6 +6,12 @@
   $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
 #endif
 
+#if dirstate-v2
+#require rust
+  $ echo '[format]' >> $HGRCPATH
+  $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
+#endif
+
 Checking the size/permissions/file-type of files stored in the
 dirstate after an update where the files are changed concurrently
 outside of hg's control.
diff --git a/tests/test-dirstate-race.t b/tests/test-dirstate-race.t
--- a/tests/test-dirstate-race.t
+++ b/tests/test-dirstate-race.t
@@ -1,4 +1,4 @@
-#testcases dirstate-v1 dirstate-v1-tree
+#testcases dirstate-v1 dirstate-v1-tree dirstate-v2
 
 #if dirstate-v1-tree
 #require rust
@@ -6,6 +6,12 @@
   $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
 #endif
 
+#if dirstate-v2
+#require rust
+  $ echo '[format]' >> $HGRCPATH
+  $ echo 

D10717: tests: More cleanly separate expected hexdump output

2021-05-17 Thread SimonSapin
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  There are more lines that differ than are in common, and dirstate-v2
  will complicate that further.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  tests/test-clone-uncompressed.t

CHANGE DETAILS

diff --git a/tests/test-clone-uncompressed.t b/tests/test-clone-uncompressed.t
--- a/tests/test-clone-uncompressed.t
+++ b/tests/test-clone-uncompressed.t
@@ -216,49 +216,66 @@
   content-type: application/mercurial-0.2
   
 
+#if no-zstd no-rust
   $ f --size --hex --bytes 256 body
-  body: size=112262 (no-zstd !)
-  body: size=109410 (zstd no-rust !)
-  body: size=109431 (rust !)
+  body: size=112262
   : 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20...|
-  0010: 7f 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2...| 
(no-zstd !)
-  0020: 05 09 04 0c 44 62 79 74 65 63 6f 75 6e 74 39 38 |Dbytecount98| 
(no-zstd !)
-  0030: 37 37 35 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |775filecount1030| 
(no-zstd !)
-  0010: 99 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2...| 
(zstd no-rust !)
-  0010: ae 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2...| 
(rust !)
-  0020: 05 09 04 0c 5e 62 79 74 65 63 6f 75 6e 74 39 35 |^bytecount95| 
(zstd no-rust !)
-  0020: 05 09 04 0c 73 62 79 74 65 63 6f 75 6e 74 39 35 |sbytecount95| 
(rust !)
-  0030: 38 39 37 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |897filecount1030| 
(zstd !)
+  0010: 7f 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2...|
+  0020: 05 09 04 0c 44 62 79 74 65 63 6f 75 6e 74 39 38 |Dbytecount98|
+  0030: 37 37 35 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |775filecount1030|
+  0040: 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 65 |requirementsdote|
+  0050: 6e 63 6f 64 65 25 32 43 66 6e 63 61 63 68 65 25 |ncode%2Cfncache%|
+  0060: 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 25 32 |2Cgeneraldelta%2|
+  0070: 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 |Crevlogv1%2Cspar|
+  0080: 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 65 |serevlog%2Cstore|
+  0090: 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 00 |s.Bdata/0.i.|
+  00a0: 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 00 ||
+  00b0: 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff 80 ||
+  00c0: 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 |)c.I.#Vg.g,i|
+  00d0: d1 ec 39 00 00 00 00 00 00 00 00 00 00 00 00 75 |..9u|
+  00e0: 30 73 08 42 64 61 74 61 2f 31 2e 69 00 03 00 01 |0s.Bdata/1.i|
+  00f0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 ||
+#endif
+#if zstd no-rust
+  $ f --size --hex --bytes 256 body
+  body: size=109410
+  : 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20...|
+  0010: 99 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2...|
+  0020: 05 09 04 0c 5e 62 79 74 65 63 6f 75 6e 74 39 35 |^bytecount95|
+  0030: 38 39 37 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |897filecount1030|
   0040: 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 65 |requirementsdote|
   0050: 6e 63 6f 64 65 25 32 43 66 6e 63 61 63 68 65 25 |ncode%2Cfncache%|
   0060: 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 25 32 |2Cgeneraldelta%2|
-  0070: 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 |Crevlogv1%2Cspar| 
(no-zstd !)
-  0080: 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 65 |serevlog%2Cstore| 
(no-zstd !)
-  0090: 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 00 |s.Bdata/0.i.| 
(no-zstd !)
-  00a0: 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 00 || 
(no-zstd !)
-  00b0: 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff 80 || 
(no-zstd !)
-  00c0: 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 |)c.I.#Vg.g,i| 
(no-zstd !)
-  00d0: d1 ec 39 00 00 00 00 00 00 00 00 00 00 00 00 75 |..9u| 
(no-zstd !)
-  00e0: 30 73 08 42 64 61 74 61 2f 31 2e 69 00 03 00 01 |0s.Bdata/1.i| 
(no-zstd !)
-  00f0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 || 
(no-zstd !)
-  0070: 43 72 65 76 6c 6f 67 2d 63 6f 6d 70 72 65 73 73 |Crevlog-compress| 
(zstd no-rust !)
-  0070: 43 70 65 72 73 69 73 74 65 6e 74 2d 6e 6f 64 65 |Cpersistent-node| 
(rust !)
-  0080: 69 6f 6e 2d 7a 73 74 64 25 32 43 72 65 76 6c 6f |ion-zstd%2Crevlo| 
(zstd no-rust !)
-  0080: 6d 61 70 25 32 43 72 65 76 6c 6f 67 2d 63 6f 6d |map%2Crevlog-com| 
(rust !)
-  0090: 67 76 31 25 32 43 73 70 61 72 73 65 72 65 76 6c |gv1%2Csparserevl| 
(zstd no-rust !)
-  0090: 70 72 65 73 73 69 6f 6e 2d 7a 73 74 64 25 32 43 |pression-zstd%2C| 
(rust !)
-  00a0: 6f 67 25 32 43 73 74 6f 72 65 00 00 80 00 73 08 |og%2Cstores.| 
(zstd no-rust !)
-  00a0: 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 73 |revlogv1%2Cspars| 
(rust !)
-  00b0: 42 64 61 74 61 2f 30 2e 69 00 03 00 01 00 00 00 |Bdata/0.i...| 
(zstd no-rust !)
-  00b0: 65 72 65 76