mirror of
https://github.com/facebook/sapling.git
synced 2024-10-12 17:58:27 +03:00
9dc21f8d0b
Summary: D13853115 adds `edenscm/` to `sys.path` and code still uses `import mercurial`. That has nasty problems if both `import mercurial` and `import edenscm.mercurial` are used, because Python would think `mercurial.foo` and `edenscm.mercurial.foo` are different modules so code like `try: ... except mercurial.error.Foo: ...`, or `isinstance(x, mercurial.foo.Bar)` would fail to handle the `edenscm.mercurial` version. There are also some module-level states (ex. `extensions._extensions`) that would cause trouble if they have multiple versions in a single process. Change imports to use the `edenscm` so ideally the `mercurial` is no longer imported at all. Add checks in extensions.py to catch unexpected extensions importing modules from the old (wrong) locations when running tests. Reviewed By: phillco Differential Revision: D13868981 fbshipit-source-id: f4e2513766957fd81d85407994f7521a08e4de48
302 lines
11 KiB
Python
302 lines
11 KiB
Python
# prune.py - mark changesets as obsolete
|
|
#
|
|
# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
|
|
# Logilab SA <contact@logilab.fr>
|
|
# Pierre-Yves David <pierre-yves.david@ens-lyon.org>
|
|
# Patrick Mezard <patrick@mezard.eu>
|
|
# Copyright 2017 Facebook, Inc.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
from __future__ import absolute_import
|
|
|
|
from edenscm.mercurial import (
|
|
bookmarks as bookmarksmod,
|
|
commands,
|
|
error,
|
|
extensions,
|
|
hintutil,
|
|
lock as lockmod,
|
|
obsolete,
|
|
registrar,
|
|
repair,
|
|
scmutil,
|
|
util,
|
|
)
|
|
from edenscm.mercurial.i18n import _
|
|
|
|
from . import common
|
|
|
|
|
|
cmdtable = {}
|
|
command = registrar.command(cmdtable)
|
|
|
|
|
|
def _getmetadata(**opts):
|
|
metadata = {}
|
|
date = opts.get("date")
|
|
user = opts.get("user")
|
|
if date:
|
|
metadata["date"] = "%i %i" % util.parsedate(date)
|
|
if user:
|
|
metadata["user"] = user
|
|
return metadata
|
|
|
|
|
|
def _reachablefrombookmark(repo, revs, bookmarks):
|
|
"""filter revisions and bookmarks reachable from the given bookmark
|
|
yoinked from mq.py
|
|
"""
|
|
repomarks = repo._bookmarks
|
|
if not bookmarks.issubset(repomarks):
|
|
raise error.Abort(
|
|
_("bookmark '%s' not found")
|
|
% ",".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 = {}
|
|
for mark, bnode in repomarks.iteritems():
|
|
nodetobookmarks.setdefault(bnode, []).append(mark)
|
|
for marks in nodetobookmarks.values():
|
|
if bookmarks.issuperset(marks):
|
|
rsrevs = repair.stripbmrevset(repo, marks[0])
|
|
revs = set(revs)
|
|
revs.update(set(rsrevs))
|
|
revs = sorted(revs)
|
|
return repomarks, revs
|
|
|
|
|
|
def _deletebookmark(repo, repomarks, bookmarks):
|
|
wlock = lock = tr = None
|
|
try:
|
|
wlock = repo.wlock()
|
|
lock = repo.lock()
|
|
tr = repo.transaction("prune")
|
|
changes = []
|
|
for bookmark in bookmarks:
|
|
changes.append((bookmark, None)) # delete the bookmark
|
|
repomarks.applychanges(repo, tr, changes)
|
|
tr.close()
|
|
for bookmark in sorted(bookmarks):
|
|
repo.ui.write(_("bookmark '%s' deleted\n") % bookmark)
|
|
finally:
|
|
lockmod.release(tr, lock, wlock)
|
|
|
|
|
|
@command(
|
|
"^prune",
|
|
[
|
|
("s", "succ", [], _("successor changeset")),
|
|
("r", "rev", [], _("revisions to prune")),
|
|
("k", "keep", None, _("does not modify working copy during prune")),
|
|
("", "biject", False, _("do a 1-1 map between rev and successor ranges")),
|
|
("", "fold", False, _("record a fold (multiple precursors, one successors)")),
|
|
("", "split", False, _("record a split (on precursor, multiple successors)")),
|
|
("B", "bookmark", [], _("remove revs only reachable from given" " bookmark")),
|
|
("d", "date", "", _("record the specified date in metadata"), _("DATE")),
|
|
("u", "user", "", _("record the specified user in metadata"), _("USER")),
|
|
],
|
|
_("[OPTION]... [[-r] REV]..."),
|
|
)
|
|
# XXX -U --noupdate option to prevent wc update and or bookmarks update ?
|
|
def prune(ui, repo, *revs, **opts):
|
|
"""hide changesets by marking them obsolete
|
|
|
|
Pruned changesets are obsolete with no successors. If they also have no
|
|
descendants, they are hidden (invisible to all commands).
|
|
|
|
Non-obsolete descendants of pruned changesets become "unstable". Use
|
|
:hg:`evolve` to handle this situation.
|
|
|
|
When you prune the parent of your working copy, Mercurial updates the
|
|
working copy to a non-obsolete parent.
|
|
|
|
You can use ``--succ`` to tell Mercurial that a newer version (successor)
|
|
of the pruned changeset exists. Mercurial records successor revisions in
|
|
obsolescence markers.
|
|
|
|
You can use the ``--biject`` option to specify a 1-1 mapping (bijection)
|
|
between revisions to pruned (precursor) and successor changesets. This
|
|
option may be removed in a future release (with the functionality provided
|
|
automatically).
|
|
|
|
If you specify multiple revisions in ``--succ``, you are recording a
|
|
"split" and must acknowledge it by passing ``--split``. Similarly, when you
|
|
prune multiple changesets with a single successor, you must pass the
|
|
``--fold`` option.
|
|
"""
|
|
if opts.get("keep", False):
|
|
hint = "strip-uncommit"
|
|
else:
|
|
hint = "strip-hide"
|
|
hintutil.trigger(hint)
|
|
|
|
revs = scmutil.revrange(repo, list(revs) + opts.get("rev", []))
|
|
succs = opts.get("succ", [])
|
|
bookmarks = set(opts.get("bookmark", ()))
|
|
metadata = _getmetadata(**opts)
|
|
biject = opts.get("biject")
|
|
fold = opts.get("fold")
|
|
split = opts.get("split")
|
|
|
|
options = [o for o in ("biject", "fold", "split") if opts.get(o)]
|
|
if 1 < len(options):
|
|
raise error.Abort(_("can only specify one of %s") % ", ".join(options))
|
|
|
|
if bookmarks:
|
|
repomarks, revs = _reachablefrombookmark(repo, revs, bookmarks)
|
|
if not revs:
|
|
# no revisions to prune - delete bookmark immediately
|
|
_deletebookmark(repo, repomarks, bookmarks)
|
|
|
|
if not revs:
|
|
raise error.Abort(_("nothing to prune"))
|
|
|
|
wlock = lock = tr = None
|
|
try:
|
|
wlock = repo.wlock()
|
|
lock = repo.lock()
|
|
tr = repo.transaction("prune")
|
|
# defines pruned changesets
|
|
precs = []
|
|
revs.sort()
|
|
for p in revs:
|
|
cp = repo[p]
|
|
if not cp.mutable():
|
|
# note: createmarkers() would have raised something anyway
|
|
raise error.Abort(
|
|
"cannot prune immutable changeset: %s" % cp,
|
|
hint="see 'hg help phases' for details",
|
|
)
|
|
precs.append(cp)
|
|
if not precs:
|
|
raise error.Abort("nothing to prune")
|
|
|
|
# defines successors changesets
|
|
sucs = scmutil.revrange(repo, succs)
|
|
sucs.sort()
|
|
sucs = tuple(repo[n] for n in sucs)
|
|
if not biject and len(sucs) > 1 and len(precs) > 1:
|
|
msg = "Can't use multiple successors for multiple precursors"
|
|
hint = _("use --biject to mark a series as a replacement" " for another")
|
|
raise error.Abort(msg, hint=hint)
|
|
elif biject and len(sucs) != len(precs):
|
|
msg = "Can't use %d successors for %d precursors" % (len(sucs), len(precs))
|
|
raise error.Abort(msg)
|
|
elif (len(precs) == 1 and len(sucs) > 1) and not split:
|
|
msg = "please add --split if you want to do a split"
|
|
raise error.Abort(msg)
|
|
elif len(sucs) == 1 and len(precs) > 1 and not fold:
|
|
msg = "please add --fold if you want to do a fold"
|
|
raise error.Abort(msg)
|
|
elif biject:
|
|
relations = [(p, (s,)) for p, s in zip(precs, sucs)]
|
|
else:
|
|
relations = [(p, sucs) for p in precs]
|
|
|
|
wdp = repo["."]
|
|
|
|
if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
|
|
# '.' killed, so update to the successor
|
|
newnode = sucs[0]
|
|
else:
|
|
# update to an unkilled parent
|
|
newnode = wdp
|
|
|
|
while newnode in precs or newnode.obsolete():
|
|
newnode = newnode.parents()[0]
|
|
|
|
if newnode.node() != wdp.node():
|
|
if opts.get("keep", False):
|
|
# This is largely the same as the implementation in
|
|
# strip.stripcmd(). We might want to refactor this somewhere
|
|
# common at some point.
|
|
|
|
# only reset the dirstate for files that would actually change
|
|
# between the working context and uctx
|
|
descendantrevs = repo.revs("%d::." % newnode.rev())
|
|
changedfiles = []
|
|
for rev in descendantrevs:
|
|
# blindly reset the files, regardless of what actually
|
|
# changed
|
|
changedfiles.extend(repo[rev].files())
|
|
|
|
# reset files that only changed in the dirstate too
|
|
dirstate = repo.dirstate
|
|
dirchanges = [f for f in dirstate if dirstate[f] != "n"]
|
|
changedfiles.extend(dirchanges)
|
|
repo.dirstate.rebuild(newnode.node(), newnode.manifest(), changedfiles)
|
|
dirstate.write(tr)
|
|
else:
|
|
bookactive = repo._activebookmark
|
|
# Active bookmark that we don't want to delete (with -B option)
|
|
# we deactivate and move it before the update and reactivate it
|
|
# after
|
|
movebookmark = bookactive and not bookmarks
|
|
if movebookmark:
|
|
bookmarksmod.deactivate(repo)
|
|
changes = [(bookactive, newnode.node())]
|
|
repo._bookmarks.applychanges(repo, tr, changes)
|
|
commands.update(ui, repo, newnode.rev())
|
|
ui.status(
|
|
_("working directory now at %s\n")
|
|
% ui.label(str(newnode), "evolve.node")
|
|
)
|
|
if movebookmark:
|
|
bookmarksmod.activate(repo, bookactive)
|
|
|
|
# update bookmarks
|
|
if bookmarks:
|
|
_deletebookmark(repo, repomarks, bookmarks)
|
|
|
|
# create markers
|
|
obsolete.createmarkers(repo, relations, metadata=metadata, operation="prune")
|
|
|
|
# informs that changeset have been pruned
|
|
ui.status(_("%i changesets pruned\n") % len(precs))
|
|
|
|
for ctx in repo.unfiltered().set("bookmark() and %ld", precs):
|
|
# used to be:
|
|
#
|
|
# ldest = list(repo.set('max((::%d) - obsolete())', ctx))
|
|
# if ldest:
|
|
# c = ldest[0]
|
|
#
|
|
# but then revset took a lazy arrow in the knee and became much
|
|
# slower. The new forms makes as much sense and a much faster.
|
|
for dest in ctx.ancestors():
|
|
if not dest.obsolete():
|
|
updatebookmarks = common.bookmarksupdater(repo, ctx.node(), tr)
|
|
updatebookmarks(dest.node())
|
|
break
|
|
|
|
tr.close()
|
|
finally:
|
|
lockmod.release(tr, lock, wlock)
|
|
|
|
|
|
def safestrip(orig, ui, repo, *revs, **kwargs):
|
|
revs = list(revs) + kwargs.pop("rev", [])
|
|
revs = set(scmutil.revrange(repo, revs))
|
|
revs = repo.revs("(%ld)::", revs)
|
|
return prune(ui, repo, *revs, **kwargs)
|
|
|
|
|
|
def wrapstrip(loaded):
|
|
try:
|
|
stripmod = extensions.find("strip")
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
extensions.wrapcommand(stripmod.cmdtable, "strip", safestrip)
|
|
|
|
|
|
def uisetup(ui):
|
|
# developer config: amend.safestrip
|
|
if ui.configbool("amend", "safestrip"):
|
|
extensions.afterloaded("strip", wrapstrip)
|