Mercurial > hg
comparison hgext/mq.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 | 28b1b7b9b4a9 |
comparison
equal
deleted
inserted
replaced
19825:4b4997068143 | 19826:4b1cbcfdabf7 |
---|---|
55 keepchanges = True | 55 keepchanges = True |
56 | 56 |
57 make them behave as if --keep-changes were passed, and non-conflicting | 57 make them behave as if --keep-changes were passed, and non-conflicting |
58 local changes will be tolerated and preserved. If incompatible options | 58 local changes will be tolerated and preserved. If incompatible options |
59 such as -f/--force or --exact are passed, this setting is ignored. | 59 such as -f/--force or --exact are passed, this setting is ignored. |
60 | |
61 This extension used to provide a strip command. This command now lives | |
62 in the strip extension. | |
60 ''' | 63 ''' |
61 | 64 |
62 from mercurial.i18n import _ | 65 from mercurial.i18n import _ |
63 from mercurial.node import bin, hex, short, nullid, nullrev | 66 from mercurial.node import bin, hex, short, nullid, nullrev |
64 from mercurial.lock import release | 67 from mercurial.lock import release |
65 from mercurial import commands, cmdutil, hg, scmutil, util, revset | 68 from mercurial import commands, cmdutil, hg, scmutil, util, revset |
66 from mercurial import extensions, error, phases, bookmarks | 69 from mercurial import extensions, error, phases |
67 from mercurial import patch as patchmod | 70 from mercurial import patch as patchmod |
68 from mercurial import localrepo | 71 from mercurial import localrepo |
69 from mercurial import subrepo | 72 from mercurial import subrepo |
70 import os, re, errno, shutil | 73 import os, re, errno, shutil |
71 | 74 |
2910 del q.applied[:] | 2913 del q.applied[:] |
2911 q.applieddirty = True | 2914 q.applieddirty = True |
2912 q.savedirty() | 2915 q.savedirty() |
2913 return 0 | 2916 return 0 |
2914 | 2917 |
2915 @command("strip", | |
2916 [ | |
2917 ('r', 'rev', [], _('strip specified revision (optional, ' | |
2918 'can specify revisions without this ' | |
2919 'option)'), _('REV')), | |
2920 ('f', 'force', None, _('force removal of changesets, discard ' | |
2921 'uncommitted changes (no backup)')), | |
2922 ('b', 'backup', None, _('bundle only changesets with local revision' | |
2923 ' number greater than REV which are not' | |
2924 ' descendants of REV (DEPRECATED)')), | |
2925 ('', 'no-backup', None, _('no backups')), | |
2926 ('', 'nobackup', None, _('no backups (DEPRECATED)')), | |
2927 ('n', '', None, _('ignored (DEPRECATED)')), | |
2928 ('k', 'keep', None, _("do not modify working copy during strip")), | |
2929 ('B', 'bookmark', '', _("remove revs only reachable from given" | |
2930 " bookmark"))], | |
2931 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...')) | |
2932 def stripcmd(ui, repo, *revs, **opts): | |
2933 """strip changesets and all their descendants from the repository | |
2934 | |
2935 The strip command removes the specified changesets and all their | |
2936 descendants. If the working directory has uncommitted changes, the | |
2937 operation is aborted unless the --force flag is supplied, in which | |
2938 case changes will be discarded. | |
2939 | |
2940 If a parent of the working directory is stripped, then the working | |
2941 directory will automatically be updated to the most recent | |
2942 available ancestor of the stripped parent after the operation | |
2943 completes. | |
2944 | |
2945 Any stripped changesets are stored in ``.hg/strip-backup`` as a | |
2946 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can | |
2947 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`, | |
2948 where BUNDLE is the bundle file created by the strip. Note that | |
2949 the local revision numbers will in general be different after the | |
2950 restore. | |
2951 | |
2952 Use the --no-backup option to discard the backup bundle once the | |
2953 operation completes. | |
2954 | |
2955 Strip is not a history-rewriting operation and can be used on | |
2956 changesets in the public phase. But if the stripped changesets have | |
2957 been pushed to a remote repository you will likely pull them again. | |
2958 | |
2959 Return 0 on success. | |
2960 """ | |
2961 backup = 'all' | |
2962 if opts.get('backup'): | |
2963 backup = 'strip' | |
2964 elif opts.get('no_backup') or opts.get('nobackup'): | |
2965 backup = 'none' | |
2966 | |
2967 cl = repo.changelog | |
2968 revs = list(revs) + opts.get('rev') | |
2969 revs = set(scmutil.revrange(repo, revs)) | |
2970 | |
2971 if opts.get('bookmark'): | |
2972 mark = opts.get('bookmark') | |
2973 marks = repo._bookmarks | |
2974 if mark not in marks: | |
2975 raise util.Abort(_("bookmark '%s' not found") % mark) | |
2976 | |
2977 # If the requested bookmark is not the only one pointing to a | |
2978 # a revision we have to only delete the bookmark and not strip | |
2979 # anything. revsets cannot detect that case. | |
2980 uniquebm = True | |
2981 for m, n in marks.iteritems(): | |
2982 if m != mark and n == repo[mark].node(): | |
2983 uniquebm = False | |
2984 break | |
2985 if uniquebm: | |
2986 rsrevs = repo.revs("ancestors(bookmark(%s)) - " | |
2987 "ancestors(head() and not bookmark(%s)) - " | |
2988 "ancestors(bookmark() and not bookmark(%s))", | |
2989 mark, mark, mark) | |
2990 revs.update(set(rsrevs)) | |
2991 if not revs: | |
2992 del marks[mark] | |
2993 marks.write() | |
2994 ui.write(_("bookmark '%s' deleted\n") % mark) | |
2995 | |
2996 if not revs: | |
2997 raise util.Abort(_('empty revision set')) | |
2998 | |
2999 descendants = set(cl.descendants(revs)) | |
3000 strippedrevs = revs.union(descendants) | |
3001 roots = revs.difference(descendants) | |
3002 | |
3003 update = False | |
3004 # if one of the wdir parent is stripped we'll need | |
3005 # to update away to an earlier revision | |
3006 for p in repo.dirstate.parents(): | |
3007 if p != nullid and cl.rev(p) in strippedrevs: | |
3008 update = True | |
3009 break | |
3010 | |
3011 rootnodes = set(cl.node(r) for r in roots) | |
3012 | |
3013 q = getattr(repo, 'mq', None) | |
3014 if q is not None and q.applied: | |
3015 # refresh queue state if we're about to strip | |
3016 # applied patches | |
3017 if cl.rev(repo.lookup('qtip')) in strippedrevs: | |
3018 q.applieddirty = True | |
3019 start = 0 | |
3020 end = len(q.applied) | |
3021 for i, statusentry in enumerate(q.applied): | |
3022 if statusentry.node in rootnodes: | |
3023 # if one of the stripped roots is an applied | |
3024 # patch, only part of the queue is stripped | |
3025 start = i | |
3026 break | |
3027 del q.applied[start:end] | |
3028 q.savedirty() | |
3029 | |
3030 revs = sorted(rootnodes) | |
3031 if update and opts.get('keep'): | |
3032 wlock = repo.wlock() | |
3033 try: | |
3034 urev, p2 = repo.changelog.parents(revs[0]) | |
3035 if (util.safehasattr(repo, 'mq') and p2 != nullid | |
3036 and p2 in [x.node for x in repo.mq.applied]): | |
3037 urev = p2 | |
3038 uctx = repo[urev] | |
3039 | |
3040 # only reset the dirstate for files that would actually change | |
3041 # between the working context and uctx | |
3042 descendantrevs = repo.revs("%s::." % uctx.rev()) | |
3043 changedfiles = [] | |
3044 for rev in descendantrevs: | |
3045 # blindly reset the files, regardless of what actually changed | |
3046 changedfiles.extend(repo[rev].files()) | |
3047 | |
3048 # reset files that only changed in the dirstate too | |
3049 dirstate = repo.dirstate | |
3050 dirchanges = [f for f in dirstate if dirstate[f] != 'n'] | |
3051 changedfiles.extend(dirchanges) | |
3052 | |
3053 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles) | |
3054 repo.dirstate.write() | |
3055 update = False | |
3056 finally: | |
3057 wlock.release() | |
3058 | |
3059 if opts.get('bookmark'): | |
3060 if mark == repo._bookmarkcurrent: | |
3061 bookmarks.setcurrent(repo, None) | |
3062 del marks[mark] | |
3063 marks.write() | |
3064 ui.write(_("bookmark '%s' deleted\n") % mark) | |
3065 | |
3066 strip(ui, repo, revs, backup=backup, update=update, force=opts.get('force')) | |
3067 | |
3068 return 0 | |
3069 | 2918 |
3070 @command("qselect", | 2919 @command("qselect", |
3071 [('n', 'none', None, _('disable all guards')), | 2920 [('n', 'none', None, _('disable all guards')), |
3072 ('s', 'series', None, _('list all guards in series file')), | 2921 ('s', 'series', None, _('list all guards in series file')), |
3073 ('', 'pop', None, _('pop to before first guarded applied patch')), | 2922 ('', 'pop', None, _('pop to before first guarded applied patch')), |