##// END OF EJS Templates
interfaces: add a Protocol class for `scmutil.status`...
interfaces: add a Protocol class for `scmutil.status` I initially tried moving this to the `interfaces` package, both to have more cleanly defined interfaces (interfaces shouldn't have to reach into implementation files for their type info), and because importing `mercurial.ui` either directly or indirectly into `interfaces.repository` causes a situation where pytype stops inferring the type for `revlogutils.constants` that are imported by `revlog`. (Likely this is caused by a cycle. The `dirstate` interface already imports `scmutil`, which in turn imports `ui`, so the `repository` interface module importing the `dirstate` interface module as part of converting those classes to Protocol classes will trigger the issue.) I gave up on moving the class because `scmutil.status` depends on `stringutil`, which has a surprisingly long tail of dependencies. In any event, a standalone Protocol class might help with the Rust code.

File last commit:

r52755:607e94e0 default
r53347:a1c0f19e default
Show More
strip.py
281 lines | 8.8 KiB | text/x-python | PythonLexer
Matt Harbison
format: add many "missing" comma...
r52755 from __future__ import annotations
Valentin Gatien-Baron
strip: move into core...
r46477 from .i18n import _
from . import (
bookmarks as bookmarksmod,
cmdutil,
error,
hg,
lock as lockmod,
Martin von Zweigbergk
errors: raise InputError on bad revset to revrange() iff provided by the user...
r48928 logcmdutil,
Valentin Gatien-Baron
strip: move into core...
r46477 mergestate as mergestatemod,
pycompat,
registrar,
repair,
scmutil,
)
release = lockmod.release
cmdtable = {}
command = registrar.command(cmdtable)
def checklocalchanges(repo, force=False):
s = repo.status()
if not force:
cmdutil.checkunfinished(repo)
cmdutil.bailifchanged(repo)
else:
cmdutil.checkunfinished(repo, skipmerge=True)
return s
def _findupdatetarget(repo, nodes):
unode, p2 = repo.changelog.parents(nodes[0])
currentbranch = repo[None].branch()
if (
safehasattr: drop usage in favor of hasattr...
r51821 hasattr(repo, 'mq')
Joerg Sonnenberger
node: replace nullid and friends with nodeconstants class...
r47771 and p2 != repo.nullid
Valentin Gatien-Baron
strip: move into core...
r46477 and p2 in [x.node for x in repo.mq.applied]
):
unode = p2
elif currentbranch != repo[unode].branch():
pwdir = b'parents(wdir())'
revset = b'max(((parents(%ln::%r) + %r) - %ln::%r) and branch(%s))'
branchtarget = repo.revs(
revset, nodes, pwdir, pwdir, nodes, pwdir, currentbranch
)
if branchtarget:
cl = repo.changelog
unode = cl.node(branchtarget.first())
return unode
def strip(
ui,
repo,
revs,
update=True,
backup=True,
force=None,
bookmarks=None,
soft=False,
):
with repo.wlock(), repo.lock():
if update:
checklocalchanges(repo, force=force)
urev = _findupdatetarget(repo, revs)
hg.clean(repo, urev)
repo.dirstate.write(repo.currenttransaction())
if soft:
repair.softstrip(ui, repo, revs, backup)
else:
repair.strip(ui, repo, revs, backup)
repomarks = repo._bookmarks
if bookmarks:
with repo.transaction(b'strip') as tr:
if repo._activebookmark in bookmarks:
bookmarksmod.deactivate(repo)
repomarks.applychanges(repo, tr, [(b, None) for b in bookmarks])
for bookmark in sorted(bookmarks):
ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
@command(
b"debugstrip",
[
(
b'r',
b'rev',
[],
_(
b'strip specified revision (optional, '
b'can specify revisions without this '
b'option)'
),
_(b'REV'),
),
(
b'f',
b'force',
None,
_(
b'force removal of changesets, discard '
b'uncommitted changes (no backup)'
),
),
(b'', b'no-backup', None, _(b'do not save backup bundle')),
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 (
b'',
b'nobackup',
None,
_(b'do not save backup bundle (DEPRECATED)'),
),
Valentin Gatien-Baron
strip: move into core...
r46477 (b'n', b'', None, _(b'ignored (DEPRECATED)')),
(
b'k',
b'keep',
None,
_(b"do not modify working directory during strip"),
),
(
b'B',
b'bookmark',
[],
_(b"remove revs only reachable from given bookmark"),
_(b'BOOKMARK'),
),
(
b'',
b'soft',
None,
_(b"simply drop changesets from visible history (EXPERIMENTAL)"),
),
],
_(b'hg debugstrip [-k] [-f] [-B bookmark] [-r] REV...'),
helpcategory=command.CATEGORY_MAINTENANCE,
)
def debugstrip(ui, repo, *revs, **opts):
"""strip changesets and all their descendants from the repository
The strip command removes the specified changesets and all their
descendants. If the working directory has uncommitted changes, the
operation is aborted unless the --force flag is supplied, in which
case changes will be discarded.
If a parent of the working directory is stripped, then the working
directory will automatically be updated to the most recent
available ancestor of the stripped parent after the operation
completes.
Any stripped changesets are stored in ``.hg/strip-backup`` as a
bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
where BUNDLE is the bundle file created by the strip. Note that
the local revision numbers will in general be different after the
restore.
Use the --no-backup option to discard the backup bundle once the
operation completes.
Strip is not a history-rewriting operation and can be used on
changesets in the public phase. But if the stripped changesets have
been pushed to a remote repository you will likely pull them again.
Return 0 on success.
"""
opts = pycompat.byteskwargs(opts)
backup = True
if opts.get(b'no_backup') or opts.get(b'nobackup'):
backup = False
cl = repo.changelog
revs = list(revs) + opts.get(b'rev')
Martin von Zweigbergk
errors: raise InputError on bad revset to revrange() iff provided by the user...
r48928 revs = set(logcmdutil.revrange(repo, revs))
Valentin Gatien-Baron
strip: move into core...
r46477
with repo.wlock():
bookmarks = set(opts.get(b'bookmark'))
if bookmarks:
repomarks = repo._bookmarks
if not bookmarks.issubset(repomarks):
raise error.Abort(
_(b"bookmark '%s' not found")
% b','.join(sorted(bookmarks - set(repomarks.keys())))
)
# If the requested bookmark is not the only one pointing to a
# a revision we have to only delete the bookmark and not strip
# anything. revsets cannot detect that case.
nodetobookmarks = {}
Gregory Szorc
global: bulk replace simple pycompat.iteritems(x) with x.items()...
r49768 for mark, node in repomarks.items():
Valentin Gatien-Baron
strip: move into core...
r46477 nodetobookmarks.setdefault(node, []).append(mark)
for marks in nodetobookmarks.values():
if bookmarks.issuperset(marks):
rsrevs = scmutil.bookmarkrevs(repo, marks[0])
revs.update(set(rsrevs))
if not revs:
with repo.lock(), repo.transaction(b'bookmark') as tr:
bmchanges = [(b, None) for b in bookmarks]
repomarks.applychanges(repo, tr, bmchanges)
for bookmark in sorted(bookmarks):
ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
if not revs:
raise error.Abort(_(b'empty revision set'))
descendants = set(cl.descendants(revs))
strippedrevs = revs.union(descendants)
roots = revs.difference(descendants)
# if one of the wdir parent is stripped we'll need
# to update away to an earlier revision
update = any(
Joerg Sonnenberger
node: replace nullid and friends with nodeconstants class...
r47771 p != repo.nullid and cl.rev(p) in strippedrevs
Valentin Gatien-Baron
strip: move into core...
r46477 for p in repo.dirstate.parents()
)
rootnodes = {cl.node(r) for r in roots}
q = getattr(repo, 'mq', None)
if q is not None and q.applied:
# refresh queue state if we're about to strip
# applied patches
if cl.rev(repo.lookup(b'qtip')) in strippedrevs:
q.applieddirty = True
start = 0
end = len(q.applied)
for i, statusentry in enumerate(q.applied):
if statusentry.node in rootnodes:
# if one of the stripped roots is an applied
# patch, only part of the queue is stripped
start = i
break
del q.applied[start:end]
q.savedirty()
revs = sorted(rootnodes)
if update and opts.get(b'keep'):
strip: use a `changing_parents` context for --keep update...
r51007 with repo.dirstate.changing_parents(repo):
urev = _findupdatetarget(repo, revs)
uctx = repo[urev]
Valentin Gatien-Baron
strip: move into core...
r46477
strip: use a `changing_parents` context for --keep update...
r51007 # only reset the dirstate for files that would actually change
# between the working context and uctx
descendantrevs = repo.revs(b"only(., %d)", uctx.rev())
changedfiles = []
for rev in descendantrevs:
# blindly reset the files, regardless of what actually changed
changedfiles.extend(repo[rev].files())
Valentin Gatien-Baron
strip: move into core...
r46477
strip: use a `changing_parents` context for --keep update...
r51007 # reset files that only changed in the dirstate too
dirstate = repo.dirstate
dirchanges = [
f for f in dirstate if not dirstate.get_entry(f).maybe_clean
]
changedfiles.extend(dirchanges)
Valentin Gatien-Baron
strip: move into core...
r46477
strip: use a `changing_parents` context for --keep update...
r51007 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
repo.dirstate.write(repo.currenttransaction())
Valentin Gatien-Baron
strip: move into core...
r46477
strip: use a `changing_parents` context for --keep update...
r51007 # clear resolve state
mergestatemod.mergestate.clean(repo)
Valentin Gatien-Baron
strip: move into core...
r46477
strip: use a `changing_parents` context for --keep update...
r51007 update = False
Valentin Gatien-Baron
strip: move into core...
r46477
strip(
ui,
repo,
revs,
backup=backup,
update=update,
force=opts.get(b'force'),
bookmarks=bookmarks,
soft=opts[b'soft'],
)
return 0