Details
Details
- Reviewers
indygreg - Group Reviewers
hg-reviewers - Commits
- rHG6c906eaedd0d: nodemap: track the tip_node for validation
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Branch
- default
- Lint
No Linters Available - Unit
No Unit Test Coverage
indygreg |
hg-reviewers |
No Linters Available |
No Unit Test Coverage |
Path | Packages | |||
---|---|---|---|---|
M | mercurial/configitems.py (2 lines) | |||
M | mercurial/debugcommands.py (1 line) | |||
M | mercurial/revlog.py (7 lines) | |||
M | mercurial/revlogutils/nodemap.py (19 lines) | |||
M | tests/test-persistent-nodemap.t (56 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
d0dd19d0c19f | 44b036c3fd95 | Pierre-Yves David | Feb 27 2020, 10:32 AM |
Status | Author | Revision | |
---|---|---|---|
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | Alphare | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | Alphare | ||
Closed | marmoute | ||
Closed | marmoute |
) | ) | ||||
coreconfigitem( | coreconfigitem( | ||||
b'devel', b'legacy.exchange', default=list, | b'devel', b'legacy.exchange', default=list, | ||||
) | ) | ||||
# TODO before getting `persistent-nodemap` out of experimental | # TODO before getting `persistent-nodemap` out of experimental | ||||
# | # | ||||
# * code/tests around aborted transaction | # * code/tests around aborted transaction | ||||
# * code/tests around pending data for hooks | # * code/tests around pending data for hooks | ||||
# * code/tests around detection of invalid cache | |||||
# (eg: after strip from an incompatible client) | |||||
# * regenerate a new nodemap when the unused/total ration is to high | # * regenerate a new nodemap when the unused/total ration is to high | ||||
# * decide for a "status" of the persistent nodemap and associated location | # * decide for a "status" of the persistent nodemap and associated location | ||||
# - part of the store next the revlog itself (new requirements) | # - part of the store next the revlog itself (new requirements) | ||||
# - part of the cache directory | # - part of the cache directory | ||||
# - part of an `index` directory | # - part of an `index` directory | ||||
# (https://www.mercurial-scm.org/wiki/ComputedIndexPlan) | # (https://www.mercurial-scm.org/wiki/ComputedIndexPlan) | ||||
# * do we want to use this for more than just changelog? if so we need: | # * do we want to use this for more than just changelog? if so we need: | ||||
# - simpler "pending" logic for them | # - simpler "pending" logic for them |
elif opts['metadata']: | elif opts['metadata']: | ||||
unfi = repo.unfiltered() | unfi = repo.unfiltered() | ||||
cl = unfi.changelog | cl = unfi.changelog | ||||
nm_data = nodemap.persisted_data(cl) | nm_data = nodemap.persisted_data(cl) | ||||
if nm_data is not None: | if nm_data is not None: | ||||
docket, data = nm_data | docket, data = nm_data | ||||
ui.write((b"uid: %s\n") % docket.uid) | ui.write((b"uid: %s\n") % docket.uid) | ||||
ui.write((b"tip-rev: %d\n") % docket.tip_rev) | ui.write((b"tip-rev: %d\n") % docket.tip_rev) | ||||
ui.write((b"tip-node: %s\n") % hex(docket.tip_node)) | |||||
ui.write((b"data-length: %d\n") % docket.data_length) | ui.write((b"data-length: %d\n") % docket.data_length) | ||||
ui.write((b"data-unused: %d\n") % docket.data_unused) | ui.write((b"data-unused: %d\n") % docket.data_unused) | ||||
@command( | @command( | ||||
b'debugobsolete', | b'debugobsolete', | ||||
[ | [ | ||||
(b'', b'flags', 0, _(b'markers flag')), | (b'', b'flags', 0, _(b'markers flag')), |
use_nodemap = ( | use_nodemap = ( | ||||
not self._inline | not self._inline | ||||
and self.nodemap_file is not None | and self.nodemap_file is not None | ||||
and util.safehasattr(index, 'update_nodemap_data') | and util.safehasattr(index, 'update_nodemap_data') | ||||
) | ) | ||||
if use_nodemap: | if use_nodemap: | ||||
nodemap_data = nodemaputil.persisted_data(self) | nodemap_data = nodemaputil.persisted_data(self) | ||||
if nodemap_data is not None: | if nodemap_data is not None: | ||||
self._nodemap_docket = nodemap_data[0] | docket = nodemap_data[0] | ||||
if d[0][docket.tip_rev][7] == docket.tip_node: | |||||
# no changelog tampering | |||||
self._nodemap_docket = docket | |||||
index.update_nodemap_data(*nodemap_data) | index.update_nodemap_data(*nodemap_data) | ||||
except (ValueError, IndexError): | except (ValueError, IndexError): | ||||
raise error.RevlogError( | raise error.RevlogError( | ||||
_(b"index %s is corrupted") % self.indexfile | _(b"index %s is corrupted") % self.indexfile | ||||
) | ) | ||||
self.index, self._chunkcache = d | self.index, self._chunkcache = d | ||||
if not self._chunkcache: | if not self._chunkcache: | ||||
self._chunkclear() | self._chunkclear() | ||||
# revnum -> (chain-length, sum-delta-length) | # revnum -> (chain-length, sum-delta-length) |
if not pdata: | if not pdata: | ||||
return None | return None | ||||
offset = 0 | offset = 0 | ||||
(version,) = S_VERSION.unpack(pdata[offset : offset + S_VERSION.size]) | (version,) = S_VERSION.unpack(pdata[offset : offset + S_VERSION.size]) | ||||
if version != ONDISK_VERSION: | if version != ONDISK_VERSION: | ||||
return None | return None | ||||
offset += S_VERSION.size | offset += S_VERSION.size | ||||
headers = S_HEADER.unpack(pdata[offset : offset + S_HEADER.size]) | headers = S_HEADER.unpack(pdata[offset : offset + S_HEADER.size]) | ||||
uid_size, tip_rev, data_length, data_unused = headers | uid_size, tip_rev, data_length, data_unused, tip_node_size = headers | ||||
offset += S_HEADER.size | offset += S_HEADER.size | ||||
docket = NodeMapDocket(pdata[offset : offset + uid_size]) | docket = NodeMapDocket(pdata[offset : offset + uid_size]) | ||||
offset += uid_size | |||||
docket.tip_rev = tip_rev | docket.tip_rev = tip_rev | ||||
docket.tip_node = pdata[offset : offset + tip_node_size] | |||||
docket.data_length = data_length | docket.data_length = data_length | ||||
docket.data_unused = data_unused | docket.data_unused = data_unused | ||||
filename = _rawdata_filepath(revlog, docket) | filename = _rawdata_filepath(revlog, docket) | ||||
use_mmap = revlog.opener.options.get("exp-persistent-nodemap.mmap") | use_mmap = revlog.opener.options.get("exp-persistent-nodemap.mmap") | ||||
try: | try: | ||||
with revlog.opener(filename) as fd: | with revlog.opener(filename) as fd: | ||||
if use_mmap: | if use_mmap: | ||||
if feed_data: | if feed_data: | ||||
if use_mmap: | if use_mmap: | ||||
new_data = data | new_data = data | ||||
else: | else: | ||||
fd.flush() | fd.flush() | ||||
new_data = util.buffer(util.mmapread(fd, len(data))) | new_data = util.buffer(util.mmapread(fd, len(data))) | ||||
target_docket.data_length = len(data) | target_docket.data_length = len(data) | ||||
target_docket.tip_rev = revlog.tiprev() | target_docket.tip_rev = revlog.tiprev() | ||||
target_docket.tip_node = revlog.node(target_docket.tip_rev) | |||||
# EXP-TODO: if this is a cache, this should use a cache vfs, not a | # EXP-TODO: if this is a cache, this should use a cache vfs, not a | ||||
# store vfs | # store vfs | ||||
with revlog.opener(revlog.nodemap_file, b'w', atomictemp=True) as fp: | with revlog.opener(revlog.nodemap_file, b'w', atomictemp=True) as fp: | ||||
fp.write(target_docket.serialize()) | fp.write(target_docket.serialize()) | ||||
revlog._nodemap_docket = target_docket | revlog._nodemap_docket = target_docket | ||||
if feed_data: | if feed_data: | ||||
revlog.index.update_nodemap_data(target_docket, new_data) | revlog.index.update_nodemap_data(target_docket, new_data) | ||||
# | # | ||||
# The docket file contains information to find, qualify and validate the raw | # The docket file contains information to find, qualify and validate the raw | ||||
# data. Its content is currently very light, but it will expand as the on disk | # data. Its content is currently very light, but it will expand as the on disk | ||||
# nodemap gains the necessary features to be used in production. | # nodemap gains the necessary features to be used in production. | ||||
# version 0 is experimental, no BC garantee, do no use outside of tests. | # version 0 is experimental, no BC garantee, do no use outside of tests. | ||||
ONDISK_VERSION = 0 | ONDISK_VERSION = 0 | ||||
S_VERSION = struct.Struct(">B") | S_VERSION = struct.Struct(">B") | ||||
S_HEADER = struct.Struct(">BQQQ") | S_HEADER = struct.Struct(">BQQQQ") | ||||
ID_SIZE = 8 | ID_SIZE = 8 | ||||
def _make_uid(): | def _make_uid(): | ||||
"""return a new unique identifier. | """return a new unique identifier. | ||||
The identifier is random and composed of ascii characters.""" | The identifier is random and composed of ascii characters.""" | ||||
uid = _make_uid() | uid = _make_uid() | ||||
# a unique identifier for the data file: | # a unique identifier for the data file: | ||||
# - When new data are appended, it is preserved. | # - When new data are appended, it is preserved. | ||||
# - When a new data file is created, a new identifier is generated. | # - When a new data file is created, a new identifier is generated. | ||||
self.uid = uid | self.uid = uid | ||||
# the tipmost revision stored in the data file. This revision and all | # the tipmost revision stored in the data file. This revision and all | ||||
# revision before it are expected to be encoded in the data file. | # revision before it are expected to be encoded in the data file. | ||||
self.tip_rev = None | self.tip_rev = None | ||||
# the node of that tipmost revision, if it mismatch the current index | |||||
# data the docket is not valid for the current index and should be | |||||
# discarded. | |||||
# | |||||
# note: this method is not perfect as some destructive operation could | |||||
# preserve the same tip_rev + tip_node while altering lower revision. | |||||
# However this multiple other caches have the same vulnerability (eg: | |||||
# brancmap cache). | |||||
self.tip_node = None | |||||
# the size (in bytes) of the persisted data to encode the nodemap valid | # the size (in bytes) of the persisted data to encode the nodemap valid | ||||
# for `tip_rev`. | # for `tip_rev`. | ||||
# - data file shorter than this are corrupted, | # - data file shorter than this are corrupted, | ||||
# - any extra data should be ignored. | # - any extra data should be ignored. | ||||
self.data_length = None | self.data_length = None | ||||
# the amount (in bytes) of "dead" data, still in the data file but no | # the amount (in bytes) of "dead" data, still in the data file but no | ||||
# longer used for the nodemap. | # longer used for the nodemap. | ||||
self.data_unused = 0 | self.data_unused = 0 | ||||
def copy(self): | def copy(self): | ||||
new = NodeMapDocket(uid=self.uid) | new = NodeMapDocket(uid=self.uid) | ||||
new.tip_rev = self.tip_rev | new.tip_rev = self.tip_rev | ||||
new.tip_node = self.tip_node | |||||
new.data_length = self.data_length | new.data_length = self.data_length | ||||
new.data_unused = self.data_unused | new.data_unused = self.data_unused | ||||
return new | return new | ||||
def __cmp__(self, other): | def __cmp__(self, other): | ||||
if self.uid < other.uid: | if self.uid < other.uid: | ||||
return -1 | return -1 | ||||
if self.uid > other.uid: | if self.uid > other.uid: | ||||
"""return serialized bytes for a docket using the passed uid""" | """return serialized bytes for a docket using the passed uid""" | ||||
data = [] | data = [] | ||||
data.append(S_VERSION.pack(ONDISK_VERSION)) | data.append(S_VERSION.pack(ONDISK_VERSION)) | ||||
headers = ( | headers = ( | ||||
len(self.uid), | len(self.uid), | ||||
self.tip_rev, | self.tip_rev, | ||||
self.data_length, | self.data_length, | ||||
self.data_unused, | self.data_unused, | ||||
len(self.tip_node), | |||||
) | ) | ||||
data.append(S_HEADER.pack(*headers)) | data.append(S_HEADER.pack(*headers)) | ||||
data.append(self.uid) | data.append(self.uid) | ||||
data.append(self.tip_node) | |||||
return b''.join(data) | return b''.join(data) | ||||
def _rawdata_filepath(revlog, docket): | def _rawdata_filepath(revlog, docket): | ||||
"""The (vfs relative) nodemap's rawdata file for a given uid""" | """The (vfs relative) nodemap's rawdata file for a given uid""" | ||||
prefix = revlog.nodemap_file[:-2] | prefix = revlog.nodemap_file[:-2] | ||||
return b"%s-%s.nd" % (prefix, docket.uid) | return b"%s-%s.nd" % (prefix, docket.uid) | ||||
=================================== | =================================== | ||||
Test the persistent on-disk nodemap | Test the persistent on-disk nodemap | ||||
=================================== | =================================== | ||||
$ hg init test-repo | $ hg init test-repo | ||||
$ cd test-repo | $ cd test-repo | ||||
$ cat << EOF >> .hg/hgrc | $ cat << EOF >> .hg/hgrc | ||||
> [experimental] | > [experimental] | ||||
> exp-persistent-nodemap=yes | > exp-persistent-nodemap=yes | ||||
> [devel] | > [devel] | ||||
> persistent-nodemap=yes | > persistent-nodemap=yes | ||||
> EOF | > EOF | ||||
$ hg debugbuilddag .+5000 | $ hg debugbuilddag .+5000 | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5000 | tip-rev: 5000 | ||||
tip-node: 06ddac466af534d365326c13c3879f97caca3cb1 | |||||
data-length: 122880 | data-length: 122880 | ||||
data-unused: 0 | data-unused: 0 | ||||
$ f --size .hg/store/00changelog.n | $ f --size .hg/store/00changelog.n | ||||
.hg/store/00changelog.n: size=42 | .hg/store/00changelog.n: size=70 | ||||
Simple lookup works | Simple lookup works | ||||
$ ANYNODE=`hg log --template '{node|short}\n' --rev tip` | $ ANYNODE=`hg log --template '{node|short}\n' --rev tip` | ||||
$ hg log -r "$ANYNODE" --template '{rev}\n' | $ hg log -r "$ANYNODE" --template '{rev}\n' | ||||
5000 | 5000 | ||||
$ echo foo > foo | $ echo foo > foo | ||||
$ hg add foo | $ hg add foo | ||||
$ hg ci -m 'foo' | $ hg ci -m 'foo' | ||||
#if no-pure no-rust | #if no-pure no-rust | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5001 | tip-rev: 5001 | ||||
tip-node: 2dd9b5258caa46469ff07d4a3da1eb3529a51f49 | |||||
data-length: 122880 | data-length: 122880 | ||||
data-unused: 0 | data-unused: 0 | ||||
#else | #else | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5001 | tip-rev: 5001 | ||||
tip-node: 2dd9b5258caa46469ff07d4a3da1eb3529a51f49 | |||||
data-length: 123072 | data-length: 123072 | ||||
data-unused: 192 | data-unused: 192 | ||||
#endif | #endif | ||||
$ f --size .hg/store/00changelog.n | $ f --size .hg/store/00changelog.n | ||||
.hg/store/00changelog.n: size=42 | .hg/store/00changelog.n: size=70 | ||||
(The pure code use the debug code that perform incremental update, the C code reencode from scratch) | (The pure code use the debug code that perform incremental update, the C code reencode from scratch) | ||||
#if pure | #if pure | ||||
$ f --sha256 .hg/store/00changelog-*.nd --size | $ f --sha256 .hg/store/00changelog-*.nd --size | ||||
.hg/store/00changelog-????????????????.nd: size=123072, sha256=136472751566c8198ff09e306a7d2f9bd18bd32298d614752b73da4d6df23340 (glob) | .hg/store/00changelog-????????????????.nd: size=123072, sha256=136472751566c8198ff09e306a7d2f9bd18bd32298d614752b73da4d6df23340 (glob) | ||||
#endif | #endif | ||||
revision in index: 5003 | revision in index: 5003 | ||||
revision in nodemap: 5003 | revision in nodemap: 5003 | ||||
#if pure | #if pure | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5002 | tip-rev: 5002 | ||||
tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e | |||||
data-length: 123328 | data-length: 123328 | ||||
data-unused: 384 | data-unused: 384 | ||||
$ f --sha256 .hg/store/00changelog-*.nd --size | $ f --sha256 .hg/store/00changelog-*.nd --size | ||||
.hg/store/00changelog-????????????????.nd: size=123328, sha256=10d26e9776b6596af0f89143a54eba8cc581e929c38242a02a7b0760698c6c70 (glob) | .hg/store/00changelog-????????????????.nd: size=123328, sha256=10d26e9776b6596af0f89143a54eba8cc581e929c38242a02a7b0760698c6c70 (glob) | ||||
#endif | #endif | ||||
#if rust | #if rust | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5002 | tip-rev: 5002 | ||||
tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e | |||||
data-length: 123328 | data-length: 123328 | ||||
data-unused: 384 | data-unused: 384 | ||||
$ f --sha256 .hg/store/00changelog-*.nd --size | $ f --sha256 .hg/store/00changelog-*.nd --size | ||||
.hg/store/00changelog-????????????????.nd: size=123328, sha256=081eec9eb6708f2bf085d939b4c97bc0b6762bc8336bc4b93838f7fffa1516bf (glob) | .hg/store/00changelog-????????????????.nd: size=123328, sha256=081eec9eb6708f2bf085d939b4c97bc0b6762bc8336bc4b93838f7fffa1516bf (glob) | ||||
#endif | #endif | ||||
#if no-pure no-rust | #if no-pure no-rust | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5002 | tip-rev: 5002 | ||||
tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e | |||||
data-length: 122944 | data-length: 122944 | ||||
data-unused: 0 | data-unused: 0 | ||||
$ f --sha256 .hg/store/00changelog-*.nd --size | $ f --sha256 .hg/store/00changelog-*.nd --size | ||||
.hg/store/00changelog-????????????????.nd: size=122944, sha256=755976b22b64ab680401b45395953504e64e7fa8c31ac570f58dee21e15f9bc0 (glob) | .hg/store/00changelog-????????????????.nd: size=122944, sha256=755976b22b64ab680401b45395953504e64e7fa8c31ac570f58dee21e15f9bc0 (glob) | ||||
#endif | #endif | ||||
Test force warming the cache | Test force warming the cache | ||||
$ rm .hg/store/00changelog.n | $ rm .hg/store/00changelog.n | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
$ hg debugupdatecache | $ hg debugupdatecache | ||||
#if pure | #if pure | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5002 | tip-rev: 5002 | ||||
tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e | |||||
data-length: 122944 | data-length: 122944 | ||||
data-unused: 0 | data-unused: 0 | ||||
#else | #else | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5002 | tip-rev: 5002 | ||||
tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e | |||||
data-length: 122944 | data-length: 122944 | ||||
data-unused: 0 | data-unused: 0 | ||||
#endif | #endif | ||||
Check out of sync nodemap | Check out of sync nodemap | ||||
========================= | ========================= | ||||
First copy old data on the side. | First copy old data on the side. | ||||
$ hg log -r "$NODE" -T '{rev}\n' | $ hg log -r "$NODE" -T '{rev}\n' | ||||
5003 | 5003 | ||||
If the nodemap is lagging behind, it can catch up fine | If the nodemap is lagging behind, it can catch up fine | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5003 | tip-rev: 5003 | ||||
tip-node: 5c049e9c4a4af159bdcd65dce1b6bf303a0da6cf | |||||
data-length: 123200 (pure !) | data-length: 123200 (pure !) | ||||
data-length: 123200 (rust !) | data-length: 123200 (rust !) | ||||
data-length: 122944 (no-rust no-pure !) | data-length: 122944 (no-rust no-pure !) | ||||
data-unused: 256 (pure !) | data-unused: 256 (pure !) | ||||
data-unused: 256 (rust !) | data-unused: 256 (rust !) | ||||
data-unused: 0 (no-rust no-pure !) | data-unused: 0 (no-rust no-pure !) | ||||
$ cp -f ../tmp-copies/* .hg/store/ | $ cp -f ../tmp-copies/* .hg/store/ | ||||
$ hg debugnodemap --metadata | $ hg debugnodemap --metadata | ||||
uid: ???????????????? (glob) | uid: ???????????????? (glob) | ||||
tip-rev: 5002 | tip-rev: 5002 | ||||
tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e | |||||
data-length: 122944 | data-length: 122944 | ||||
data-unused: 0 | data-unused: 0 | ||||
$ hg log -r "$NODE" -T '{rev}\n' | $ hg log -r "$NODE" -T '{rev}\n' | ||||
5003 | 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, 2 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: 42bf3068c7ddfdfded53c4eb11d02266faeebfee | |||||
data-length: 123456 (pure !) | |||||
data-length: 246464 (rust !) | |||||
data-length: 123008 (no-pure no-rust !) | |||||
data-unused: 448 (pure !) | |||||
data-unused: 123904 (rust !) | |||||
data-unused: 0 (no-pure no-rust !) | |||||
$ cp -f ../tmp-copies/* .hg/store/ | |||||
$ hg debugnodemap --metadata | |||||
uid: ???????????????? (glob) | |||||
tip-rev: 5002 | |||||
tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e | |||||
data-length: 122944 | |||||
data-unused: 0 | |||||
$ hg log -r "$OTHERNODE" -T '{rev}\n' | |||||
5002 |