=================================== Test the persistent on-disk nodemap =================================== $ cat << EOF >> $HGRCPATH > [format] > use-share-safe=yes > [extensions] > share= > EOF #if no-rust $ cat << EOF >> $HGRCPATH > [format] > use-persistent-nodemap=yes > [devel] > persistent-nodemap=yes > EOF #endif $ hg init test-repo --config storage.revlog.persistent-nodemap.slow-path=allow $ cd test-repo Check handling of the default slow-path value #if no-pure no-rust $ hg id abort: accessing `persistent-nodemap` repository without associated fast implementation. (check `hg help config.format.use-persistent-nodemap` for details) [255] Unlock further check (we are here to test the feature) $ cat << EOF >> $HGRCPATH > [storage] > # to avoid spamming the test > revlog.persistent-nodemap.slow-path=allow > EOF #endif #if rust Regression test for a previous bug in Rust/C FFI for the `Revlog_CAPI` capsule: in places where `mercurial/cext/revlog.c` function signatures use `Py_ssize_t` (64 bits on Linux x86_64), corresponding declarations in `rust/hg-cpython/src/cindex.rs` incorrectly used `libc::c_int` (32 bits). As a result, -1 passed from Rust for the null revision became 4294967295 in C. $ hg log -r 00000000 changeset: -1:000000000000 tag: tip user: date: Thu Jan 01 00:00:00 1970 +0000 #endif $ hg debugformat format-variant repo fncache: yes dirstate-v2: no dotencode: yes generaldelta: yes share-safe: yes sparserevlog: yes persistent-nodemap: yes copies-sdc: no revlog-v2: no changelog-v2: no plain-cl-delta: yes compression: zlib (no-zstd !) compression: zstd (zstd !) compression-level: default $ hg debugbuilddag .+5000 --new-file $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5000 tip-node: 6b02b8c7b96654c25e86ba69eda198d7e6ad8b3c data-length: 121088 data-unused: 0 data-unused: 0.000% $ f --size .hg/store/00changelog.n .hg/store/00changelog.n: size=62 Simple lookup works $ ANYNODE=`hg log --template '{node|short}\n' --rev tip` $ hg log -r "$ANYNODE" --template '{rev}\n' 5000 #if rust $ f --sha256 .hg/store/00changelog-*.nd .hg/store/00changelog-????????.nd: sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd (glob) $ f --sha256 .hg/store/00manifest-*.nd .hg/store/00manifest-????????.nd: sha256=97117b1c064ea2f86664a124589e47db0e254e8d34739b5c5cc5bf31c9da2b51 (glob) $ hg debugnodemap --dump-new | f --sha256 --size size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd 0000: 00 00 00 91 00 00 00 20 00 00 00 bb 00 00 00 e7 |....... ........| 0010: 00 00 00 66 00 00 00 a1 00 00 01 13 00 00 01 22 |...f..........."| 0020: 00 00 00 23 00 00 00 fc 00 00 00 ba 00 00 00 5e |...#...........^| 0030: 00 00 00 df 00 00 01 4e 00 00 01 65 00 00 00 ab |.......N...e....| 0040: 00 00 00 a9 00 00 00 95 00 00 00 73 00 00 00 38 |...........s...8| 0050: 00 00 00 cc 00 00 00 92 00 00 00 90 00 00 00 69 |...............i| 0060: 00 00 00 ec 00 00 00 8d 00 00 01 4f 00 00 00 12 |...........O....| 0070: 00 00 02 0c 00 00 00 77 00 00 00 9c 00 00 00 8f |.......w........| 0080: 00 00 00 d5 00 00 00 6b 00 00 00 48 00 00 00 b3 |.......k...H....| 0090: 00 00 00 e5 00 00 00 b5 00 00 00 8e 00 00 00 ad |................| 00a0: 00 00 00 7b 00 00 00 7c 00 00 00 0b 00 00 00 2b |...{...|.......+| 00b0: 00 00 00 c6 00 00 00 1e 00 00 01 08 00 00 00 11 |................| 00c0: 00 00 01 30 00 00 00 26 00 00 01 9c 00 00 00 35 |...0...&.......5| 00d0: 00 00 00 b8 00 00 01 31 00 00 00 2c 00 00 00 55 |.......1...,...U| 00e0: 00 00 00 8a 00 00 00 9a 00 00 00 0c 00 00 01 1e |................| 00f0: 00 00 00 a4 00 00 00 83 00 00 00 c9 00 00 00 8c |................| #else $ f --sha256 .hg/store/00changelog-*.nd .hg/store/00changelog-????????.nd: sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 (glob) $ hg debugnodemap --dump-new | f --sha256 --size size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0020: ff ff ff ff ff ff f5 06 ff ff ff ff ff ff f3 e7 |................| 0030: ff ff ef ca ff ff ff ff ff ff ff ff ff ff ff ff |................| 0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ed 08 |................| 0060: ff ff ed 66 ff ff ff ff ff ff ff ff ff ff ff ff |...f............| 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f6 ed |................| 00a0: ff ff ff ff ff ff fe 61 ff ff ff ff ff ff ff ff |.......a........| 00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f1 02 |................| 00f0: ff ff ff ff ff ff ed 1b ff ff ff ff ff ff ff ff |................| #endif $ hg debugnodemap --check revision in index: 5001 revision in nodemap: 5001 add a new commit $ hg up 5001 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo foo > foo $ hg add foo Check slow-path config value handling ------------------------------------- #if no-pure no-rust $ hg id --config "storage.revlog.persistent-nodemap.slow-path=invalid-value" unknown value for config "storage.revlog.persistent-nodemap.slow-path": "invalid-value" falling back to default value: abort abort: accessing `persistent-nodemap` repository without associated fast implementation. (check `hg help config.format.use-persistent-nodemap` for details) [255] $ hg log -r . --config "storage.revlog.persistent-nodemap.slow-path=warn" warning: accessing `persistent-nodemap` repository without associated fast implementation. (check `hg help config.format.use-persistent-nodemap` for details) changeset: 5000:6b02b8c7b966 tag: tip user: debugbuilddag date: Thu Jan 01 01:23:20 1970 +0000 summary: r5000 $ hg ci -m 'foo' --config "storage.revlog.persistent-nodemap.slow-path=abort" abort: accessing `persistent-nodemap` repository without associated fast implementation. (check `hg help config.format.use-persistent-nodemap` for details) [255] #else $ hg id --config "storage.revlog.persistent-nodemap.slow-path=invalid-value" unknown value for config "storage.revlog.persistent-nodemap.slow-path": "invalid-value" falling back to default value: abort 6b02b8c7b966+ tip #endif $ hg ci -m 'foo' #if no-pure no-rust $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5001 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c data-length: 121088 data-unused: 0 data-unused: 0.000% #else $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5001 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c data-length: 121344 data-unused: 256 data-unused: 0.211% #endif $ f --size .hg/store/00changelog.n .hg/store/00changelog.n: size=62 (The pure code use the debug code that perform incremental update, the C code reencode from scratch) #if pure $ f --sha256 .hg/store/00changelog-*.nd --size .hg/store/00changelog-????????.nd: size=121344, sha256=cce54c5da5bde3ad72a4938673ed4064c86231b9c64376b082b163fdb20f8f66 (glob) #endif #if rust $ f --sha256 .hg/store/00changelog-*.nd --size .hg/store/00changelog-????????.nd: size=121344, sha256=952b042fcf614ceb37b542b1b723e04f18f83efe99bee4e0f5ccd232ef470e58 (glob) #endif #if no-pure no-rust $ f --sha256 .hg/store/00changelog-*.nd --size .hg/store/00changelog-????????.nd: size=121088, sha256=df7c06a035b96cb28c7287d349d603baef43240be7736fe34eea419a49702e17 (glob) #endif $ hg debugnodemap --check revision in index: 5002 revision in nodemap: 5002 Test code path without mmap --------------------------- $ echo bar > bar $ hg add bar $ hg ci -m 'bar' --config storage.revlog.persistent-nodemap.mmap=no $ hg debugnodemap --check --config storage.revlog.persistent-nodemap.mmap=yes revision in index: 5003 revision in nodemap: 5003 $ hg debugnodemap --check --config storage.revlog.persistent-nodemap.mmap=no revision in index: 5003 revision in nodemap: 5003 #if pure $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5002 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd data-length: 121600 data-unused: 512 data-unused: 0.421% $ f --sha256 .hg/store/00changelog-*.nd --size .hg/store/00changelog-????????.nd: size=121600, sha256=def52503d049ccb823974af313a98a935319ba61f40f3aa06a8be4d35c215054 (glob) #endif #if rust $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5002 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd data-length: 121600 data-unused: 512 data-unused: 0.421% $ f --sha256 .hg/store/00changelog-*.nd --size .hg/store/00changelog-????????.nd: size=121600, sha256=dacf5b5f1d4585fee7527d0e67cad5b1ba0930e6a0928f650f779aefb04ce3fb (glob) #endif #if no-pure no-rust $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5002 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd data-length: 121088 data-unused: 0 data-unused: 0.000% $ f --sha256 .hg/store/00changelog-*.nd --size .hg/store/00changelog-????????.nd: size=121088, sha256=59fcede3e3cc587755916ceed29e3c33748cd1aa7d2f91828ac83e7979d935e8 (glob) #endif Test force warming the cache $ rm .hg/store/00changelog.n $ hg debugnodemap --metadata $ hg debugupdatecache #if pure $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5002 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd data-length: 121088 data-unused: 0 data-unused: 0.000% #else $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5002 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd data-length: 121088 data-unused: 0 data-unused: 0.000% #endif Check out of sync nodemap ========================= First copy old data on the side. $ mkdir ../tmp-copies $ cp .hg/store/00changelog-????????.nd .hg/store/00changelog.n ../tmp-copies Nodemap lagging behind ---------------------- make a new commit $ echo bar2 > bar $ hg ci -m 'bar2' $ NODE=`hg log -r tip -T '{node}\n'` $ hg log -r "$NODE" -T '{rev}\n' 5003 If the nodemap is lagging behind, it can catch up fine $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5003 tip-node: c9329770f979ade2d16912267c38ba5f82fd37b3 data-length: 121344 (pure !) data-length: 121344 (rust !) data-length: 121152 (no-rust no-pure !) data-unused: 192 (pure !) data-unused: 192 (rust !) data-unused: 0 (no-rust no-pure !) data-unused: 0.158% (pure !) data-unused: 0.158% (rust !) data-unused: 0.000% (no-rust no-pure !) $ cp -f ../tmp-copies/* .hg/store/ $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5002 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd data-length: 121088 data-unused: 0 data-unused: 0.000% $ hg log -r "$NODE" -T '{rev}\n' 5003 changelog altered ----------------- If the nodemap is not gated behind a requirements, an unaware client can alter the repository so the revlog used to generate the nodemap is not longer compatible with the persistent nodemap. We need to detect that. $ hg up "$NODE~5" 0 files updated, 0 files merged, 4 files removed, 0 files unresolved $ echo bar > babar $ hg add babar $ hg ci -m 'babar' created new head $ OTHERNODE=`hg log -r tip -T '{node}\n'` $ hg log -r "$OTHERNODE" -T '{rev}\n' 5004 $ hg --config extensions.strip= strip --rev "$NODE~1" --no-backup the nodemap should detect the changelog have been tampered with and recover. $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5002 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944 data-length: 121536 (pure !) data-length: 121088 (rust !) data-length: 121088 (no-pure no-rust !) data-unused: 448 (pure !) data-unused: 0 (rust !) data-unused: 0 (no-pure no-rust !) data-unused: 0.000% (rust !) data-unused: 0.369% (pure !) data-unused: 0.000% (no-pure no-rust !) $ cp -f ../tmp-copies/* .hg/store/ $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5002 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd data-length: 121088 data-unused: 0 data-unused: 0.000% $ hg log -r "$OTHERNODE" -T '{rev}\n' 5002 missing data file ----------------- $ UUID=`hg debugnodemap --metadata| grep 'uid:' | \ > sed 's/uid: //'` $ FILE=.hg/store/00changelog-"${UUID}".nd $ mv $FILE ../tmp-data-file $ cp .hg/store/00changelog.n ../tmp-docket mercurial don't crash $ hg log -r . changeset: 5002:b355ef8adce0 tag: tip parent: 4998:d918ad6d18d3 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: babar $ hg debugnodemap --metadata $ hg debugupdatecache $ hg debugnodemap --metadata uid: * (glob) tip-rev: 5002 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944 data-length: 121088 data-unused: 0 data-unused: 0.000% Sub-case: fallback for corrupted data file ------------------------------------------ Sabotaging the data file so that nodemap resolutions fail, triggering fallback to (non-persistent) C implementation. $ UUID=`hg debugnodemap --metadata| grep 'uid:' | \ > sed 's/uid: //'` $ FILE=.hg/store/00changelog-"${UUID}".nd $ python -c "fobj = open('$FILE', 'r+b'); fobj.write(b'\xff' * 121088); fobj.close()" The nodemap data file is still considered in sync with the docket. This would fail without the fallback to the (non-persistent) C implementation: $ hg log -r b355ef8adce0949b8bdf6afc72ca853740d65944 -T '{rev}\n' --traceback 5002 The nodemap data file hasn't been fixed, more tests can be inserted: $ hg debugnodemap --dump-disk | f --bytes=256 --hexdump --size size=121088 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0060: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| $ mv ../tmp-data-file $FILE $ mv ../tmp-docket .hg/store/00changelog.n Check transaction related property ================================== An up to date nodemap should be available to shell hooks, $ echo dsljfl > a $ hg add a $ hg ci -m a $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5003 tip-node: a52c5079765b5865d97b993b303a18740113bbb2 data-length: 121088 data-unused: 0 data-unused: 0.000% $ echo babar2 > babar $ hg ci -m 'babar2' --config "hooks.pretxnclose.nodemap-test=hg debugnodemap --metadata" uid: ???????? (glob) tip-rev: 5004 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984 data-length: 121280 (pure !) data-length: 121280 (rust !) data-length: 121088 (no-pure no-rust !) data-unused: 192 (pure !) data-unused: 192 (rust !) data-unused: 0 (no-pure no-rust !) data-unused: 0.158% (pure !) data-unused: 0.158% (rust !) data-unused: 0.000% (no-pure no-rust !) $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5004 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984 data-length: 121280 (pure !) data-length: 121280 (rust !) data-length: 121088 (no-pure no-rust !) data-unused: 192 (pure !) data-unused: 192 (rust !) data-unused: 0 (no-pure no-rust !) data-unused: 0.158% (pure !) data-unused: 0.158% (rust !) data-unused: 0.000% (no-pure no-rust !) Another process does not see the pending nodemap content during run. $ echo qpoasp > a $ hg ci -m a2 \ > --config "hooks.pretxnclose=sh \"$RUNTESTDIR/testlib/wait-on-file\" 20 sync-repo-read sync-txn-pending" \ > --config "hooks.txnclose=touch sync-txn-close" > output.txt 2>&1 & (read the repository while the commit transaction is pending) $ sh "$RUNTESTDIR/testlib/wait-on-file" 20 sync-txn-pending && \ > hg debugnodemap --metadata && \ > sh "$RUNTESTDIR/testlib/wait-on-file" 20 sync-txn-close sync-repo-read uid: ???????? (glob) tip-rev: 5004 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984 data-length: 121280 (pure !) data-length: 121280 (rust !) data-length: 121088 (no-pure no-rust !) data-unused: 192 (pure !) data-unused: 192 (rust !) data-unused: 0 (no-pure no-rust !) data-unused: 0.158% (pure !) data-unused: 0.158% (rust !) data-unused: 0.000% (no-pure no-rust !) $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121536 (pure !) data-length: 121536 (rust !) data-length: 121088 (no-pure no-rust !) data-unused: 448 (pure !) data-unused: 448 (rust !) data-unused: 0 (no-pure no-rust !) data-unused: 0.369% (pure !) data-unused: 0.369% (rust !) data-unused: 0.000% (no-pure no-rust !) $ cat output.txt Check that a failing transaction will properly revert the data $ echo plakfe > a $ f --size --sha256 .hg/store/00changelog-*.nd .hg/store/00changelog-????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !) .hg/store/00changelog-????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !) .hg/store/00changelog-????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !) $ hg ci -m a3 --config "extensions.abort=$RUNTESTDIR/testlib/crash_transaction_late.py" transaction abort! rollback completed abort: This is a late abort [255] $ hg debugnodemap --metadata uid: ???????? (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121536 (pure !) data-length: 121536 (rust !) data-length: 121088 (no-pure no-rust !) data-unused: 448 (pure !) data-unused: 448 (rust !) data-unused: 0 (no-pure no-rust !) data-unused: 0.369% (pure !) data-unused: 0.369% (rust !) data-unused: 0.000% (no-pure no-rust !) $ f --size --sha256 .hg/store/00changelog-*.nd .hg/store/00changelog-????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !) .hg/store/00changelog-????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !) .hg/store/00changelog-????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !) Check that removing content does not confuse the nodemap -------------------------------------------------------- removing data with rollback $ echo aso > a $ hg ci -m a4 $ hg rollback repository tip rolled back to revision 5005 (undo commit) working directory now based on revision 5005 $ hg id -r . 90d5d3ba2fc4 tip removing data with strip $ echo aso > a $ hg ci -m a4 $ hg --config extensions.strip= strip -r . --no-backup 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg id -r . --traceback 90d5d3ba2fc4 tip (be a good citizen and regenerate the nodemap) $ hg debugupdatecaches $ hg debugnodemap --metadata uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% Check race condition when multiple process write new data to the repository --------------------------------------------------------------------------- In this test, we check that two writers touching the repositories will not overwrite each other data. This test is prompted by the existent of issue6554. Where a writer ended up using and outdated docket to update the repository. See the dedicated extension for details on the race windows and read/write schedule necessary to end up in this situation: testlib/persistent-nodemap-race-ext.py The issue was initially observed on a server with a high push trafic, but it can be reproduced using a share and two commiting process which seems simpler. The test is Rust only as the other implementation does not use the same read/write patterns. $ cd .. #if rust $ cp -R test-repo race-repo $ hg share race-repo ./other-wc --config format.use-share-safe=yes updating working directory 5001 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg debugformat -R ./race-repo | egrep 'share-safe|persistent-nodemap' share-safe: yes persistent-nodemap: yes $ hg debugformat -R ./other-wc/ | egrep 'share-safe|persistent-nodemap' share-safe: yes persistent-nodemap: yes $ hg -R ./other-wc update 'min(head())' 3 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg -R ./race-repo debugnodemap --metadata uid: 43c37dde tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% $ hg -R ./race-repo log -G -r 'head()' @ changeset: 5005:90d5d3ba2fc4 | tag: tip ~ user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: a2 o changeset: 5001:16395c3cf7e2 | user: test ~ date: Thu Jan 01 00:00:00 1970 +0000 summary: foo $ hg -R ./other-wc log -G -r 'head()' o changeset: 5005:90d5d3ba2fc4 | tag: tip ~ user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: a2 @ changeset: 5001:16395c3cf7e2 | user: test ~ date: Thu Jan 01 00:00:00 1970 +0000 summary: foo $ echo left-side-race > race-repo/left-side-race $ hg -R ./race-repo/ add race-repo/left-side-race $ echo right-side-race > ./other-wc/right-side-race $ hg -R ./other-wc/ add ./other-wc/right-side-race $ mkdir sync-files $ mkdir outputs $ ( > hg -R ./race-repo/ commit -m left-side-commit \ > --config "extensions.race=${RUNTESTDIR}/testlib/persistent-nodemap-race-ext.py" \ > --config 'devel.nodemap-race.role=left'; > touch sync-files/left-done > ) > outputs/left.txt 2>&1 & $ ( > hg -R ./other-wc/ commit -m right-side-commit \ > --config "extensions.race=${RUNTESTDIR}/testlib/persistent-nodemap-race-ext.py" \ > --config 'devel.nodemap-race.role=right'; > touch sync-files/right-done > ) > outputs/right.txt 2>&1 & $ ( > hg -R ./race-repo/ check-nodemap-race \ > --config "extensions.race=${RUNTESTDIR}/testlib/persistent-nodemap-race-ext.py" \ > --config 'devel.nodemap-race.role=reader'; > touch sync-files/reader-done > ) > outputs/reader.txt 2>&1 & $ sh "$RUNTESTDIR"/testlib/wait-on-file 10 sync-files/left-done $ cat outputs/left.txt docket-details: uid: 43c37dde actual-tip: 5005 tip-rev: 5005 data-length: 121088 nodemap-race: left side locked and ready to commit docket-details: uid: 43c37dde actual-tip: 5005 tip-rev: 5005 data-length: 121088 finalized changelog write persisting changelog nodemap new data start at 121088 persisted changelog nodemap docket-details: uid: 43c37dde actual-tip: 5006 tip-rev: 5006 data-length: 121280 $ sh "$RUNTESTDIR"/testlib/wait-on-file 10 sync-files/right-done $ cat outputs/right.txt nodemap-race: right side start of the locking sequence nodemap-race: right side reading changelog nodemap-race: right side reading of changelog is done docket-details: uid: 43c37dde actual-tip: 5006 tip-rev: 5005 data-length: 121088 nodemap-race: right side ready to wait for the lock nodemap-race: right side locked and ready to commit docket-details: uid: 43c37dde actual-tip: 5006 tip-rev: 5006 data-length: 121280 right ready to write, waiting for reader right proceeding with writing its changelog index and nodemap finalized changelog write persisting changelog nodemap new data start at 121280 persisted changelog nodemap docket-details: uid: 43c37dde actual-tip: 5007 tip-rev: 5007 data-length: 121536 $ sh "$RUNTESTDIR"/testlib/wait-on-file 10 sync-files/reader-done $ cat outputs/reader.txt reader: reading changelog reader ready to read the changelog, waiting for right reader: nodemap docket read record-data-length: 121280 actual-data-length: 121280 file-actual-length: 121536 reader: changelog read docket-details: uid: 43c37dde actual-tip: 5006 tip-rev: 5006 data-length: 121280 tip-rev: 5006 tip-node: 492901161367 node-rev: 5006 $ hg -R ./race-repo log -G -r 'head()' o changeset: 5007:ac4a2abde241 | tag: tip ~ parent: 5001:16395c3cf7e2 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: right-side-commit @ changeset: 5006:492901161367 | user: test ~ date: Thu Jan 01 00:00:00 1970 +0000 summary: left-side-commit $ hg -R ./other-wc log -G -r 'head()' @ changeset: 5007:ac4a2abde241 | tag: tip ~ parent: 5001:16395c3cf7e2 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: right-side-commit o changeset: 5006:492901161367 | user: test ~ date: Thu Jan 01 00:00:00 1970 +0000 summary: left-side-commit #endif Test upgrade / downgrade ======================== $ cd ./test-repo/ downgrading $ cat << EOF >> .hg/hgrc > [format] > use-persistent-nodemap=no > EOF $ hg debugformat -v format-variant repo config default fncache: yes yes yes dirstate-v2: no no no dotencode: yes yes yes generaldelta: yes yes yes share-safe: yes yes no sparserevlog: yes yes yes persistent-nodemap: yes no no copies-sdc: no no no revlog-v2: no no no changelog-v2: no no no plain-cl-delta: yes yes yes compression: zlib zlib zlib (no-zstd !) compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugupgraderepo --run --no-backup --quiet upgrade will perform the following actions: requirements preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-zstd no-dirstate-v2 !) preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd no-dirstate-v2 !) preserved: dotencode, exp-dirstate-v2, fncache, generaldelta, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd dirstate-v2 !) removed: persistent-nodemap processed revlogs: - all-filelogs - changelog - manifest $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' 00changelog-*.nd (glob) 00manifest-*.nd (glob) undo.backup.00changelog.n undo.backup.00manifest.n $ hg debugnodemap --metadata upgrading $ cat << EOF >> .hg/hgrc > [format] > use-persistent-nodemap=yes > EOF $ hg debugformat -v format-variant repo config default fncache: yes yes yes dirstate-v2: no no no dotencode: yes yes yes generaldelta: yes yes yes share-safe: yes yes no sparserevlog: yes yes yes persistent-nodemap: no yes no copies-sdc: no no no revlog-v2: no no no changelog-v2: no no no plain-cl-delta: yes yes yes compression: zlib zlib zlib (no-zstd !) compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugupgraderepo --run --no-backup --quiet upgrade will perform the following actions: requirements preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-zstd no-dirstate-v2 !) preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd no-dirstate-v2 !) preserved: dotencode, exp-dirstate-v2, fncache, generaldelta, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd dirstate-v2 !) added: persistent-nodemap processed revlogs: - all-filelogs - changelog - manifest $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' 00changelog-*.nd (glob) 00changelog.n 00manifest-*.nd (glob) 00manifest.n undo.backup.00changelog.n undo.backup.00manifest.n $ hg debugnodemap --metadata uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% Running unrelated upgrade $ hg debugupgraderepo --run --no-backup --quiet --optimize re-delta-all upgrade will perform the following actions: requirements preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (no-zstd no-dirstate-v2 !) preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd no-dirstate-v2 !) preserved: dotencode, exp-dirstate-v2, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd dirstate-v2 !) optimisations: re-delta-all processed revlogs: - all-filelogs - changelog - manifest $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' 00changelog-*.nd (glob) 00changelog.n 00manifest-*.nd (glob) 00manifest.n $ hg debugnodemap --metadata uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% Persistent nodemap and local/streaming clone ============================================ $ cd .. standard clone -------------- The persistent nodemap should exist after a streaming clone $ hg clone --pull --quiet -U test-repo standard-clone $ ls -1 standard-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' 00changelog-*.nd (glob) 00changelog.n 00manifest-*.nd (glob) 00manifest.n $ hg -R standard-clone debugnodemap --metadata uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% local clone ------------ The persistent nodemap should exist after a streaming clone $ hg clone -U test-repo local-clone $ ls -1 local-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' 00changelog-*.nd (glob) 00changelog.n 00manifest-*.nd (glob) 00manifest.n $ hg -R local-clone debugnodemap --metadata uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% Test various corruption case ============================ Missing datafile ---------------- Test behavior with a missing datafile $ hg clone --quiet --pull test-repo corruption-test-repo $ ls -1 corruption-test-repo/.hg/store/00changelog* corruption-test-repo/.hg/store/00changelog-*.nd (glob) corruption-test-repo/.hg/store/00changelog.d corruption-test-repo/.hg/store/00changelog.i corruption-test-repo/.hg/store/00changelog.n $ rm corruption-test-repo/.hg/store/00changelog*.nd $ hg log -R corruption-test-repo -r . changeset: 5005:90d5d3ba2fc4 tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: a2 $ ls -1 corruption-test-repo/.hg/store/00changelog* corruption-test-repo/.hg/store/00changelog.d corruption-test-repo/.hg/store/00changelog.i corruption-test-repo/.hg/store/00changelog.n Truncated data file ------------------- Test behavior with a too short datafile rebuild the missing data $ hg -R corruption-test-repo debugupdatecache $ ls -1 corruption-test-repo/.hg/store/00changelog* corruption-test-repo/.hg/store/00changelog-*.nd (glob) corruption-test-repo/.hg/store/00changelog.d corruption-test-repo/.hg/store/00changelog.i corruption-test-repo/.hg/store/00changelog.n truncate the file $ datafilepath=`ls corruption-test-repo/.hg/store/00changelog*.nd` $ f -s $datafilepath corruption-test-repo/.hg/store/00changelog-*.nd: size=121088 (glob) $ dd if=$datafilepath bs=1000 count=10 of=$datafilepath-tmp status=noxfer 10+0 records in 10+0 records out $ mv $datafilepath-tmp $datafilepath $ f -s $datafilepath corruption-test-repo/.hg/store/00changelog-*.nd: size=10000 (glob) Check that Mercurial reaction to this event $ hg -R corruption-test-repo log -r . --traceback changeset: 5005:90d5d3ba2fc4 tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: a2 stream clone ============ The persistent nodemap should exist after a streaming clone Simple case ----------- No race condition $ hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone --debug | egrep '00(changelog|manifest)' adding [s] 00manifest.n (62 bytes) adding [s] 00manifest-*.nd (118 KB) (glob) adding [s] 00changelog.n (62 bytes) adding [s] 00changelog-*.nd (118 KB) (glob) adding [s] 00manifest.d (452 KB) (no-zstd !) adding [s] 00manifest.d (491 KB) (zstd !) adding [s] 00changelog.d (360 KB) (no-zstd !) adding [s] 00changelog.d (368 KB) (zstd !) adding [s] 00manifest.i (313 KB) adding [s] 00changelog.i (313 KB) $ ls -1 stream-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' 00changelog-*.nd (glob) 00changelog.n 00manifest-*.nd (glob) 00manifest.n $ hg -R stream-clone debugnodemap --metadata uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% new data appened ----------------- Other commit happening on the server during the stream clone setup the step-by-step stream cloning $ HG_TEST_STREAM_WALKED_FILE_1="$TESTTMP/sync_file_walked_1" $ export HG_TEST_STREAM_WALKED_FILE_1 $ HG_TEST_STREAM_WALKED_FILE_2="$TESTTMP/sync_file_walked_2" $ export HG_TEST_STREAM_WALKED_FILE_2 $ HG_TEST_STREAM_WALKED_FILE_3="$TESTTMP/sync_file_walked_3" $ export HG_TEST_STREAM_WALKED_FILE_3 $ cat << EOF >> test-repo/.hg/hgrc > [extensions] > steps=$RUNTESTDIR/testlib/ext-stream-clone-steps.py > EOF Check and record file state beforehand $ f --size test-repo/.hg/store/00changelog* test-repo/.hg/store/00changelog-*.nd: size=121088 (glob) test-repo/.hg/store/00changelog.d: size=376891 (zstd !) test-repo/.hg/store/00changelog.d: size=368890 (no-zstd !) test-repo/.hg/store/00changelog.i: size=320384 test-repo/.hg/store/00changelog.n: size=62 $ hg -R test-repo debugnodemap --metadata | tee server-metadata.txt uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% Prepare a commit $ echo foo >> test-repo/foo $ hg -R test-repo/ add test-repo/foo Do a mix of clone and commit at the same time so that the file listed on disk differ at actual transfer time. $ (hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone-race-1 --debug 2>> clone-output | egrep '00(changelog|manifest)' >> clone-output; touch $HG_TEST_STREAM_WALKED_FILE_3) & $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1 $ hg -R test-repo/ commit -m foo $ touch $HG_TEST_STREAM_WALKED_FILE_2 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3 $ cat clone-output adding [s] 00manifest.n (62 bytes) adding [s] 00manifest-*.nd (118 KB) (glob) adding [s] 00changelog.n (62 bytes) adding [s] 00changelog-*.nd (118 KB) (glob) adding [s] 00manifest.d (452 KB) (no-zstd !) adding [s] 00manifest.d (491 KB) (zstd !) adding [s] 00changelog.d (360 KB) (no-zstd !) adding [s] 00changelog.d (368 KB) (zstd !) adding [s] 00manifest.i (313 KB) adding [s] 00changelog.i (313 KB) Check the result state $ f --size stream-clone-race-1/.hg/store/00changelog* stream-clone-race-1/.hg/store/00changelog-*.nd: size=121088 (glob) stream-clone-race-1/.hg/store/00changelog.d: size=368890 (no-zstd !) stream-clone-race-1/.hg/store/00changelog.d: size=376891 (zstd !) stream-clone-race-1/.hg/store/00changelog.i: size=320384 stream-clone-race-1/.hg/store/00changelog.n: size=62 $ hg -R stream-clone-race-1 debugnodemap --metadata | tee client-metadata.txt uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 data-unused: 0 data-unused: 0.000% We get a usable nodemap, so no rewrite would be needed and the metadata should be identical (ie: the following diff should be empty) This isn't the case for the `no-rust` `no-pure` implementation as it use a very minimal nodemap implementation that unconditionnaly rewrite the nodemap "all the time". #if no-rust no-pure $ diff -u server-metadata.txt client-metadata.txt --- server-metadata.txt * (glob) +++ client-metadata.txt * (glob) @@ -1,4 +1,4 @@ -uid: * (glob) +uid: * (glob) tip-rev: 5005 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe data-length: 121088 [1] #else $ diff -u server-metadata.txt client-metadata.txt #endif Clean up after the test. $ rm -f "$HG_TEST_STREAM_WALKED_FILE_1" $ rm -f "$HG_TEST_STREAM_WALKED_FILE_2" $ rm -f "$HG_TEST_STREAM_WALKED_FILE_3" full regeneration ----------------- A full nodemap is generated (ideally this test would append enough data to make sure the nodemap data file get changed, however to make thing simpler we will force the regeneration for this test. Check the initial state $ f --size test-repo/.hg/store/00changelog* test-repo/.hg/store/00changelog-*.nd: size=121344 (glob) (rust !) test-repo/.hg/store/00changelog-*.nd: size=121344 (glob) (pure !) test-repo/.hg/store/00changelog-*.nd: size=121152 (glob) (no-rust no-pure !) test-repo/.hg/store/00changelog.d: size=376950 (zstd !) test-repo/.hg/store/00changelog.d: size=368949 (no-zstd !) test-repo/.hg/store/00changelog.i: size=320448 test-repo/.hg/store/00changelog.n: size=62 $ hg -R test-repo debugnodemap --metadata | tee server-metadata-2.txt uid: * (glob) tip-rev: 5006 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b data-length: 121344 (rust !) data-length: 121344 (pure !) data-length: 121152 (no-rust no-pure !) data-unused: 192 (rust !) data-unused: 192 (pure !) data-unused: 0 (no-rust no-pure !) data-unused: 0.158% (rust !) data-unused: 0.158% (pure !) data-unused: 0.000% (no-rust no-pure !) Performe the mix of clone and full refresh of the nodemap, so that the files (and filenames) are different between listing time and actual transfer time. $ (hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone-race-2 --debug 2>> clone-output-2 | egrep '00(changelog|manifest)' >> clone-output-2; touch $HG_TEST_STREAM_WALKED_FILE_3) & $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1 $ rm test-repo/.hg/store/00changelog.n $ rm test-repo/.hg/store/00changelog-*.nd $ hg -R test-repo/ debugupdatecache $ touch $HG_TEST_STREAM_WALKED_FILE_2 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3 (note: the stream clone code wronly pick the `undo.` files) $ cat clone-output-2 adding [s] undo.backup.00manifest.n (62 bytes) (known-bad-output !) adding [s] undo.backup.00changelog.n (62 bytes) (known-bad-output !) adding [s] 00manifest.n (62 bytes) adding [s] 00manifest-*.nd (118 KB) (glob) adding [s] 00changelog.n (62 bytes) adding [s] 00changelog-*.nd (118 KB) (glob) adding [s] 00manifest.d (492 KB) (zstd !) adding [s] 00manifest.d (452 KB) (no-zstd !) adding [s] 00changelog.d (360 KB) (no-zstd !) adding [s] 00changelog.d (368 KB) (zstd !) adding [s] 00manifest.i (313 KB) adding [s] 00changelog.i (313 KB) Check the result. $ f --size stream-clone-race-2/.hg/store/00changelog* stream-clone-race-2/.hg/store/00changelog-*.nd: size=121344 (glob) (rust !) stream-clone-race-2/.hg/store/00changelog-*.nd: size=121344 (glob) (pure !) stream-clone-race-2/.hg/store/00changelog-*.nd: size=121152 (glob) (no-rust no-pure !) stream-clone-race-2/.hg/store/00changelog.d: size=376950 (zstd !) stream-clone-race-2/.hg/store/00changelog.d: size=368949 (no-zstd !) stream-clone-race-2/.hg/store/00changelog.i: size=320448 stream-clone-race-2/.hg/store/00changelog.n: size=62 $ hg -R stream-clone-race-2 debugnodemap --metadata | tee client-metadata-2.txt uid: * (glob) tip-rev: 5006 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b data-length: 121344 (rust !) data-unused: 192 (rust !) data-unused: 0.158% (rust !) data-length: 121152 (no-rust no-pure !) data-unused: 0 (no-rust no-pure !) data-unused: 0.000% (no-rust no-pure !) data-length: 121344 (pure !) data-unused: 192 (pure !) data-unused: 0.158% (pure !) We get a usable nodemap, so no rewrite would be needed and the metadata should be identical (ie: the following diff should be empty) This isn't the case for the `no-rust` `no-pure` implementation as it use a very minimal nodemap implementation that unconditionnaly rewrite the nodemap "all the time". #if no-rust no-pure $ diff -u server-metadata-2.txt client-metadata-2.txt --- server-metadata-2.txt * (glob) +++ client-metadata-2.txt * (glob) @@ -1,4 +1,4 @@ -uid: * (glob) +uid: * (glob) tip-rev: 5006 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b data-length: 121152 [1] #else $ diff -u server-metadata-2.txt client-metadata-2.txt #endif Clean up after the test $ rm -f $HG_TEST_STREAM_WALKED_FILE_1 $ rm -f $HG_TEST_STREAM_WALKED_FILE_2 $ rm -f $HG_TEST_STREAM_WALKED_FILE_3