##// END OF EJS Templates
revlog: fix revset in reachableroots docstring...
revlog: fix revset in reachableroots docstring `reachableroots` will only return a subset of `roots` when `includepath` is False. For example, given the following linear DAG: 2 | 1 | 0 Using roots=0+2, heads=1, the definition in the docstring does not match what `reachableroots` actually does: ipdb> repo.changelog.reachableroots(0, roots=[0,2],heads=[1]) [0] ipdb> repo.revs('heads(::(0+2) & (0+2)::1)') <baseset+ [1]> The fix is to do `heads & ::roots` (or `heads & heads::roots`) first, then select their ancestors: ipdb> repo.revs('heads(::((0+2) & (0+2)::1))') <baseset+ [0]> The docstring was introduced by fd92bfbbe02d9 (2015-06-19 "revset: rename revsbetween to reachableroots and add an argument"), which introduced the `includepath=False` behavior for graphlog grandparents use-case. I believe the docstring instead of the code should be changed because changing the code to match the docstring can result in suboptimal graphlog like: o :\ : o : : :/ o As opposite to the current "linearized" graphlog: o | o : o Differential Revision: https://phab.mercurial-scm.org/D7518

File last commit:

r43623:bec73401 default
r44168:1a42f845 default
Show More
censor.py
119 lines | 3.9 KiB | text/x-python | PythonLexer
# Copyright (C) 2015 - Mike Edgar <adgar@google.com>
#
# This extension enables removal of file content at a given revision,
# rewriting the data/metadata of successive revisions to preserve revision log
# integrity.
"""erase file content at a given revision
The censor command instructs Mercurial to erase all content of a file at a given
revision *without updating the changeset hash.* This allows existing history to
remain valid while preventing future clones/pulls from receiving the erased
data.
Typical uses for censor are due to security or legal requirements, including::
* Passwords, private keys, cryptographic material
* Licensed data/code/libraries for which the license has expired
* Personally Identifiable Information or other private data
Censored nodes can interrupt mercurial's typical operation whenever the excised
data needs to be materialized. Some commands, like ``hg cat``/``hg revert``,
simply fail when asked to produce censored data. Others, like ``hg verify`` and
``hg update``, must be capable of tolerating censored data to continue to
function in a meaningful way. Such commands only tolerate censored file
revisions if they are allowed by the "censor.policy=ignore" config option.
A few informative commands such as ``hg grep`` will unconditionally
ignore censored data and merely report that it was encountered.
"""
from __future__ import absolute_import
from mercurial.i18n import _
from mercurial.node import short
from mercurial import (
error,
registrar,
scmutil,
)
cmdtable = {}
command = registrar.command(cmdtable)
# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
testedwith = b'ships-with-hg-core'
@command(
b'censor',
[
(
b'r',
b'rev',
b'',
_(b'censor file from specified revision'),
_(b'REV'),
),
(b't', b'tombstone', b'', _(b'replacement tombstone data'), _(b'TEXT')),
],
_(b'-r REV [-t TEXT] [FILE]'),
helpcategory=command.CATEGORY_MAINTENANCE,
)
def censor(ui, repo, path, rev=b'', tombstone=b'', **opts):
with repo.wlock(), repo.lock():
return _docensor(ui, repo, path, rev, tombstone, **opts)
def _docensor(ui, repo, path, rev=b'', tombstone=b'', **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'))
wctx = repo[None]
m = scmutil.match(wctx, (path,))
if m.anypats() or len(m.files()) != 1:
raise error.Abort(_(b'can only specify an explicit filename'))
path = m.files()[0]
flog = repo.file(path)
if not len(flog):
raise error.Abort(_(b'cannot censor file with no history'))
rev = scmutil.revsingle(repo, rev, rev).rev()
try:
ctx = repo[rev]
except KeyError:
raise error.Abort(_(b'invalid revision identifier %s') % rev)
try:
fctx = ctx.filectx(path)
except error.LookupError:
raise error.Abort(_(b'file does not exist at revision %s') % rev)
fnode = fctx.filenode()
heads = []
for headnode in repo.heads():
hc = repo[headnode]
if path in hc and hc.filenode(path) == fnode:
heads.append(hc)
if heads:
headlist = b', '.join([short(c.node()) for c in heads])
raise error.Abort(
_(b'cannot censor file in heads (%s)') % headlist,
hint=_(b'clean/delete and commit first'),
)
wp = wctx.parents()
if ctx.node() in [p.node() for p in wp]:
raise error.Abort(
_(b'cannot censor working directory'),
hint=_(b'clean/delete/update first'),
)
with repo.transaction(b'censor') as tr:
flog.censorrevision(tr, fnode, tombstone=tombstone)