# HG changeset patch # User Pierre-Yves David # Date 1380232641 -7200 # Node ID 4b1cbcfdabf7f48bfc22032d35185b028615dbd9 # Parent 4b499706814319430ff413986612919365176e6e 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. diff -r 4b4997068143 -r 4b1cbcfdabf7 hgext/mq.py --- a/hgext/mq.py Thu Sep 26 23:43:00 2013 +0200 +++ b/hgext/mq.py Thu Sep 26 23:57:21 2013 +0200 @@ -57,13 +57,16 @@ make them behave as if --keep-changes were passed, and non-conflicting local changes will be tolerated and preserved. If incompatible options such as -f/--force or --exact are passed, this setting is ignored. + +This extension used to provide a strip command. This command now lives +in the strip extension. ''' from mercurial.i18n import _ from mercurial.node import bin, hex, short, nullid, nullrev from mercurial.lock import release from mercurial import commands, cmdutil, hg, scmutil, util, revset -from mercurial import extensions, error, phases, bookmarks +from mercurial import extensions, error, phases from mercurial import patch as patchmod from mercurial import localrepo from mercurial import subrepo @@ -2912,160 +2915,6 @@ q.savedirty() return 0 -@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 @command("qselect", [('n', 'none', None, _('disable all guards')), diff -r 4b4997068143 -r 4b1cbcfdabf7 hgext/strip.py --- 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 diff -r 4b4997068143 -r 4b1cbcfdabf7 tests/test-mq.t --- a/tests/test-mq.t Thu Sep 26 23:43:00 2013 +0200 +++ b/tests/test-mq.t Thu Sep 26 23:57:21 2013 +0200 @@ -67,6 +67,9 @@ changes will be tolerated and preserved. If incompatible options such as -f/--force or --exact are passed, this setting is ignored. + This extension used to provide a strip command. This command now lives in the + strip extension. + list of commands: qapplied print the patches already applied @@ -91,7 +94,6 @@ qseries print the entire series file qtop print the name of the current patch qunapplied print the patches not yet applied - strip strip changesets and all their descendants from the repository use "hg -v help mq" to show builtin aliases and global options