Mercurial > hg
changeset 51270:ceeb8fa23cc8
censor: accept multiple revision in a single call
This is useful when dealing with corruption, as all the corrupted revision can
be dealt with in one go.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Fri, 01 Dec 2023 22:56:08 +0100 |
parents | db121ddd171e |
children | eab5b061cd48 |
files | hgext/censor.py hgext/sqlitestore.py mercurial/revlog.py mercurial/revlogutils/rewrite.py mercurial/testing/storage.py tests/test-censor.t tests/test-censor2.t |
diffstat | 7 files changed, 55 insertions(+), 42 deletions(-) [+] |
line wrap: on
line diff
--- a/hgext/censor.py Fri Dec 01 22:46:46 2023 +0100 +++ b/hgext/censor.py Fri Dec 01 22:56:08 2023 +0100 @@ -36,7 +36,6 @@ from mercurial import ( error, - logcmdutil, registrar, scmutil, ) @@ -56,7 +55,7 @@ ( b'r', b'rev', - b'', + [], _(b'censor file from specified revision'), _(b'REV'), ), @@ -71,7 +70,7 @@ _(b'-r REV [-t TEXT] [FILE]'), helpcategory=command.CATEGORY_MAINTENANCE, ) -def censor(ui, repo, path, rev=b'', tombstone=b'', check_heads=True, **opts): +def censor(ui, repo, path, rev=(), tombstone=b'', check_heads=True, **opts): with repo.wlock(), repo.lock(): return _docensor( ui, @@ -84,11 +83,11 @@ ) -def _docensor(ui, repo, path, rev=b'', tombstone=b'', check_heads=True, **opts): +def _docensor(ui, repo, path, revs=(), tombstone=b'', check_heads=True, **opts): if not path: raise error.Abort(_(b'must specify file path to censor')) - if not rev: - raise error.Abort(_(b'must specify revision to censor')) + if not revs: + raise error.Abort(_(b'must specify revisions to censor')) wctx = repo[None] @@ -100,18 +99,17 @@ if not len(flog): raise error.Abort(_(b'cannot censor file with no history')) - rev = logcmdutil.revsingle(repo, rev, rev).rev() - try: - ctx = repo[rev] - except KeyError: - raise error.Abort(_(b'invalid revision identifier %s') % rev) + revs = scmutil.revrange(repo, revs) + if not revs: + raise error.Abort(_(b'no matching revisions')) + file_nodes = set() + for r in revs: + try: + ctx = repo[r] + file_nodes.add(ctx.filectx(path).filenode()) + except error.LookupError: + raise error.Abort(_(b'file does not exist at revision %s') % ctx) - try: - fctx = ctx.filectx(path) - except error.LookupError: - raise error.Abort(_(b'file does not exist at revision %s') % rev) - - fnode = fctx.filenode() if check_heads: heads = [] repo_heads = repo.heads() @@ -120,7 +118,7 @@ ui.status(msg) for headnode in repo_heads: hc = repo[headnode] - if path in hc and hc.filenode(path) == fnode: + if path in hc and hc.filenode(path) in file_nodes: heads.append(hc) if heads: headlist = b', '.join([short(c.node()) for c in heads]) @@ -138,7 +136,8 @@ hint=_(b'clean/delete/update first'), ) - msg = b'censoring 1 file revision\n' + msg = b'censoring %d file revisions\n' + msg %= len(file_nodes) ui.status(msg) with repo.transaction(b'censor') as tr: - flog.censorrevision(tr, fnode, tombstone=tombstone) + flog.censorrevision(tr, file_nodes, tombstone=tombstone)
--- a/hgext/sqlitestore.py Fri Dec 01 22:46:46 2023 +0100 +++ b/hgext/sqlitestore.py Fri Dec 01 22:56:08 2023 +0100 @@ -810,7 +810,11 @@ return not empty - def censorrevision(self, tr, censornode, tombstone=b''): + def censorrevision(self, tr, censor_nodes, tombstone=b''): + for node in censor_nodes: + self._censor_one_revision(tr, node, tombstone=tombstone) + + def _censor_one_revision(self, tr, censornode, tombstone): tombstone = storageutil.packmeta({b'censored': tombstone}, b'') # This restriction is cargo culted from revlogs and makes no sense for
--- a/mercurial/revlog.py Fri Dec 01 22:46:46 2023 +0100 +++ b/mercurial/revlog.py Fri Dec 01 22:56:08 2023 +0100 @@ -3835,16 +3835,16 @@ if addrevisioncb: addrevisioncb(self, rev, node) - def censorrevision(self, tr, censornode, tombstone=b''): + def censorrevision(self, tr, censor_nodes, tombstone=b''): if self._format_version == REVLOGV0: raise error.RevlogError( _(b'cannot censor with version %d revlogs') % self._format_version ) elif self._format_version == REVLOGV1: - rewrite.v1_censor(self, tr, censornode, tombstone) + rewrite.v1_censor(self, tr, censor_nodes, tombstone) else: - rewrite.v2_censor(self, tr, censornode, tombstone) + rewrite.v2_censor(self, tr, censor_nodes, tombstone) def verifyintegrity(self, state): """Verifies the integrity of the revlog.
--- a/mercurial/revlogutils/rewrite.py Fri Dec 01 22:46:46 2023 +0100 +++ b/mercurial/revlogutils/rewrite.py Fri Dec 01 22:56:08 2023 +0100 @@ -51,14 +51,14 @@ ) -def v1_censor(rl, tr, censornode, tombstone=b''): +def v1_censor(rl, tr, censor_nodes, tombstone=b''): """censors a revision in a "version 1" revlog""" assert rl._format_version == constants.REVLOGV1, rl._format_version # avoid cycle from .. import revlog - censorrev = rl.rev(censornode) + censor_revs = set(rl.rev(node) for node in censor_nodes) tombstone = storageutil.packmeta({b'censored': tombstone}, b'') # Rewriting the revlog in place is hard. Our strategy for censoring is @@ -87,14 +87,14 @@ node = rl.node(rev) p1, p2 = rl.parents(node) - if rev == censorrev: + if rev in censor_revs: newrl.addrawrevision( tombstone, tr, - rl.linkrev(censorrev), + rl.linkrev(rev), p1, p2, - censornode, + node, constants.REVIDX_ISCENSORED, ) @@ -138,12 +138,12 @@ rl._load_inner(chunk_cache) -def v2_censor(revlog, tr, censornode, tombstone=b''): +def v2_censor(revlog, tr, censor_nodes, tombstone=b''): """censors a revision in a "version 2" revlog""" assert revlog._format_version != REVLOGV0, revlog._format_version assert revlog._format_version != REVLOGV1, revlog._format_version - censor_revs = {revlog.rev(censornode)} + censor_revs = {revlog.rev(node) for node in censor_nodes} _rewrite_v2(revlog, tr, censor_revs, tombstone)
--- a/mercurial/testing/storage.py Fri Dec 01 22:46:46 2023 +0100 +++ b/mercurial/testing/storage.py Fri Dec 01 22:56:08 2023 +0100 @@ -1280,7 +1280,7 @@ node2 = f.add(b'foo\n' * 32, None, tr, 2, node1, f.nullid) with self._maketransactionfn() as tr: - f.censorrevision(tr, node1) + f.censorrevision(tr, [node1]) self.assertEqual(len(f), 3) self.assertEqual(list(f.revs()), [0, 1, 2])
--- a/tests/test-censor.t Fri Dec 01 22:46:46 2023 +0100 +++ b/tests/test-censor.t Fri Dec 01 22:56:08 2023 +0100 @@ -80,7 +80,7 @@ $ hg --config extensions.censor= --cwd foo/bar/baz censor -r $C2 -t "remove password" ../../../target checking for the censored content in 2 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions $ hg cat -r $H1 target | head -n 10 Tainted file is now sanitized $ hg cat -r $H2 target | head -n 10 @@ -101,7 +101,7 @@ $ hg --config extensions.censor= --cwd foo/bar/baz censor -r $C1 path:target checking for the censored content in 2 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions $ hg cat -r $H1 target | head -n 10 Tainted file is now sanitized $ hg cat -r $H2 target | head -n 10 @@ -243,7 +243,7 @@ $ hg --config extensions.censor= censor -r $C3 target checking for the censored content in 2 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions $ hg update -r $H2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge -r $C3 @@ -294,7 +294,7 @@ $ hg --config extensions.censor= censor -r $C4 target checking for the censored content in 2 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions $ hg cat -r $C4 target | head -n 10 $ hg cat -r "$H2^^" target | head -n 10 Tainted file now super sanitized @@ -329,7 +329,7 @@ $ hg --config extensions.censor= censor -r $C5 target checking for the censored content in 2 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions The important part is for the censor operation to not crash and the repository to not be corrupted. Right now this involve keeping the revlog split. @@ -410,7 +410,7 @@ $ hg --config extensions.censor= censor -r $REV target checking for the censored content in 3 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions $ hg cat -r $REV target | head -n 10 $ hg cat -r $CLEANREV target | head -n 10 Re-sanitized; nothing to see here @@ -513,7 +513,7 @@ $ hg --config extensions.censor= censor -r 0 target checking for the censored content in 3 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions $ hg bundle -r 0 --base null ../rinit/initbundle 1 changesets found $ cd ../rinit @@ -530,7 +530,17 @@ $ hg --config extensions.censor= censor -r 0 --no-check-heads target checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions + +Can censor multiple revision in one go. + + $ cd ../r + $ hg --config extensions.censor= censor -r 0+1 target + checking for the censored content in 3 heads + checking for the censored content in the working directory + censoring 2 file revisions + + #if revlogv2 @@ -572,7 +582,7 @@ $ hg --config extensions.censor= censor -r $B1 target checking for the censored content in 1 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions $ hg cat -r $B1 target | wc -l *0 (re)
--- a/tests/test-censor2.t Fri Dec 01 22:46:46 2023 +0100 +++ b/tests/test-censor2.t Fri Dec 01 22:56:08 2023 +0100 @@ -17,7 +17,7 @@ $ hg censor target --config extensions.censor= -r ".^^" checking for the censored content in 1 heads checking for the censored content in the working directory - censoring 1 file revision + censoring 1 file revisions $ hg update ".^" 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat target