Mercurial > public > mercurial-scm > hg
diff hgext/strip.py @ 19826:4b1cbcfdabf7
mq: extract strip function as its standalone extension (issue3824)
Strip now lives in its own extension
reminder: The extension is surprisingly called `strip`. The `mq` extension
force the use of the strip extension when its enabled. This is both necessary
for backward compatibility (people expect `mq` to comes with strip) and become
some utility function used by `mq` are now in the strip extension.
author | Pierre-Yves David <pierre-yves.david@ens-lyon.org> |
---|---|
date | Thu, 26 Sep 2013 23:57:21 +0200 |
parents | 4b4997068143 |
children | 0a986233ecbf |
line wrap: on
line diff
--- a/hgext/strip.py Thu Sep 26 23:43:00 2013 +0200 +++ b/hgext/strip.py Thu Sep 26 23:57:21 2013 +0200 @@ -1,8 +1,13 @@ +"""This extension contains the strip commands. + +This extensions allows to strip changesets and all their descendants from the +repository. See the command help for details. +""" from mercurial.i18n import _ -from mercurial import cmdutil, hg, util from mercurial.node import nullid from mercurial.lock import release -from mercurial import repair +from mercurial import cmdutil, hg, scmutil, util +from mercurial import repair, bookmarks cmdtable = {} command = cmdutil.command(cmdtable) @@ -55,3 +60,158 @@ finally: release(lock, wlock) + +@command("strip", + [ + ('r', 'rev', [], _('strip specified revision (optional, ' + 'can specify revisions without this ' + 'option)'), _('REV')), + ('f', 'force', None, _('force removal of changesets, discard ' + 'uncommitted changes (no backup)')), + ('b', 'backup', None, _('bundle only changesets with local revision' + ' number greater than REV which are not' + ' descendants of REV (DEPRECATED)')), + ('', 'no-backup', None, _('no backups')), + ('', 'nobackup', None, _('no backups (DEPRECATED)')), + ('n', '', None, _('ignored (DEPRECATED)')), + ('k', 'keep', None, _("do not modify working copy during strip")), + ('B', 'bookmark', '', _("remove revs only reachable from given" + " bookmark"))], + _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...')) +def stripcmd(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. + """ + backup = 'all' + if opts.get('backup'): + backup = 'strip' + elif opts.get('no_backup') or opts.get('nobackup'): + backup = 'none' + + cl = repo.changelog + revs = list(revs) + opts.get('rev') + revs = set(scmutil.revrange(repo, revs)) + + if opts.get('bookmark'): + mark = opts.get('bookmark') + marks = repo._bookmarks + if mark not in marks: + raise util.Abort(_("bookmark '%s' not found") % mark) + + # 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. + uniquebm = True + for m, n in marks.iteritems(): + if m != mark and n == repo[mark].node(): + uniquebm = False + break + if uniquebm: + rsrevs = repo.revs("ancestors(bookmark(%s)) - " + "ancestors(head() and not bookmark(%s)) - " + "ancestors(bookmark() and not bookmark(%s))", + mark, mark, mark) + revs.update(set(rsrevs)) + if not revs: + del marks[mark] + marks.write() + ui.write(_("bookmark '%s' deleted\n") % mark) + + if not revs: + raise util.Abort(_('empty revision set')) + + descendants = set(cl.descendants(revs)) + strippedrevs = revs.union(descendants) + roots = revs.difference(descendants) + + update = False + # if one of the wdir parent is stripped we'll need + # to update away to an earlier revision + for p in repo.dirstate.parents(): + if p != nullid and cl.rev(p) in strippedrevs: + update = True + break + + rootnodes = set(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('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('keep'): + wlock = repo.wlock() + try: + urev, p2 = repo.changelog.parents(revs[0]) + if (util.safehasattr(repo, 'mq') and p2 != nullid + and p2 in [x.node for x in repo.mq.applied]): + urev = p2 + uctx = repo[urev] + + # only reset the dirstate for files that would actually change + # between the working context and uctx + descendantrevs = repo.revs("%s::." % uctx.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(urev, uctx.manifest(), changedfiles) + repo.dirstate.write() + update = False + finally: + wlock.release() + + if opts.get('bookmark'): + if mark == repo._bookmarkcurrent: + bookmarks.setcurrent(repo, None) + del marks[mark] + marks.write() + ui.write(_("bookmark '%s' deleted\n") % mark) + + strip(ui, repo, revs, backup=backup, update=update, force=opts.get('force')) + + return 0