##// END OF EJS Templates
strip: fix last unprotected mq reference (issue4097)
Matt Mackall -
r20009:2802bedb stable
parent child Browse files
Show More
@@ -1,217 +1,219 b''
1 1 """strip changesets and their descendents from history
2 2
3 3 This extension allows you to strip changesets and all their descendants from the
4 4 repository. See the command help for details.
5 5 """
6 6 from mercurial.i18n import _
7 7 from mercurial.node import nullid
8 8 from mercurial.lock import release
9 9 from mercurial import cmdutil, hg, scmutil, util
10 10 from mercurial import repair, bookmarks
11 11
12 12 cmdtable = {}
13 13 command = cmdutil.command(cmdtable)
14 14 testedwith = 'internal'
15 15
16 16 def checksubstate(repo, baserev=None):
17 17 '''return list of subrepos at a different revision than substate.
18 18 Abort if any subrepos have uncommitted changes.'''
19 19 inclsubs = []
20 20 wctx = repo[None]
21 21 if baserev:
22 22 bctx = repo[baserev]
23 23 else:
24 24 bctx = wctx.parents()[0]
25 25 for s in sorted(wctx.substate):
26 26 if wctx.sub(s).dirty(True):
27 27 raise util.Abort(
28 28 _("uncommitted changes in subrepository %s") % s)
29 29 elif s not in bctx.substate or bctx.sub(s).dirty():
30 30 inclsubs.append(s)
31 31 return inclsubs
32 32
33 33 def checklocalchanges(repo, force=False, excsuffix=''):
34 34 cmdutil.checkunfinished(repo)
35 35 m, a, r, d = repo.status()[:4]
36 36 if not force:
37 37 if (m or a or r or d):
38 38 _("local changes found") # i18n tool detection
39 39 raise util.Abort(_("local changes found" + excsuffix))
40 40 if checksubstate(repo):
41 41 _("local changed subrepos found") # i18n tool detection
42 42 raise util.Abort(_("local changed subrepos found" + excsuffix))
43 43 return m, a, r, d
44 44
45 45 def strip(ui, repo, revs, update=True, backup="all", force=None):
46 46 wlock = lock = None
47 47 try:
48 48 wlock = repo.wlock()
49 49 lock = repo.lock()
50 50
51 51 if update:
52 52 checklocalchanges(repo, force=force)
53 53 urev, p2 = repo.changelog.parents(revs[0])
54 if p2 != nullid and p2 in [x.node for x in repo.mq.applied]:
54 if (util.safehasattr(repo, 'mq') and
55 p2 != nullid
56 and p2 in [x.node for x in repo.mq.applied]):
55 57 urev = p2
56 58 hg.clean(repo, urev)
57 59 repo.dirstate.write()
58 60
59 61 repair.strip(ui, repo, revs, backup)
60 62 finally:
61 63 release(lock, wlock)
62 64
63 65
64 66 @command("strip",
65 67 [
66 68 ('r', 'rev', [], _('strip specified revision (optional, '
67 69 'can specify revisions without this '
68 70 'option)'), _('REV')),
69 71 ('f', 'force', None, _('force removal of changesets, discard '
70 72 'uncommitted changes (no backup)')),
71 73 ('b', 'backup', None, _('bundle only changesets with local revision'
72 74 ' number greater than REV which are not'
73 75 ' descendants of REV (DEPRECATED)')),
74 76 ('', 'no-backup', None, _('no backups')),
75 77 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
76 78 ('n', '', None, _('ignored (DEPRECATED)')),
77 79 ('k', 'keep', None, _("do not modify working copy during strip")),
78 80 ('B', 'bookmark', '', _("remove revs only reachable from given"
79 81 " bookmark"))],
80 82 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
81 83 def stripcmd(ui, repo, *revs, **opts):
82 84 """strip changesets and all their descendants from the repository
83 85
84 86 The strip command removes the specified changesets and all their
85 87 descendants. If the working directory has uncommitted changes, the
86 88 operation is aborted unless the --force flag is supplied, in which
87 89 case changes will be discarded.
88 90
89 91 If a parent of the working directory is stripped, then the working
90 92 directory will automatically be updated to the most recent
91 93 available ancestor of the stripped parent after the operation
92 94 completes.
93 95
94 96 Any stripped changesets are stored in ``.hg/strip-backup`` as a
95 97 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
96 98 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
97 99 where BUNDLE is the bundle file created by the strip. Note that
98 100 the local revision numbers will in general be different after the
99 101 restore.
100 102
101 103 Use the --no-backup option to discard the backup bundle once the
102 104 operation completes.
103 105
104 106 Strip is not a history-rewriting operation and can be used on
105 107 changesets in the public phase. But if the stripped changesets have
106 108 been pushed to a remote repository you will likely pull them again.
107 109
108 110 Return 0 on success.
109 111 """
110 112 backup = 'all'
111 113 if opts.get('backup'):
112 114 backup = 'strip'
113 115 elif opts.get('no_backup') or opts.get('nobackup'):
114 116 backup = 'none'
115 117
116 118 cl = repo.changelog
117 119 revs = list(revs) + opts.get('rev')
118 120 revs = set(scmutil.revrange(repo, revs))
119 121
120 122 if opts.get('bookmark'):
121 123 mark = opts.get('bookmark')
122 124 marks = repo._bookmarks
123 125 if mark not in marks:
124 126 raise util.Abort(_("bookmark '%s' not found") % mark)
125 127
126 128 # If the requested bookmark is not the only one pointing to a
127 129 # a revision we have to only delete the bookmark and not strip
128 130 # anything. revsets cannot detect that case.
129 131 uniquebm = True
130 132 for m, n in marks.iteritems():
131 133 if m != mark and n == repo[mark].node():
132 134 uniquebm = False
133 135 break
134 136 if uniquebm:
135 137 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
136 138 "ancestors(head() and not bookmark(%s)) - "
137 139 "ancestors(bookmark() and not bookmark(%s))",
138 140 mark, mark, mark)
139 141 revs.update(set(rsrevs))
140 142 if not revs:
141 143 del marks[mark]
142 144 marks.write()
143 145 ui.write(_("bookmark '%s' deleted\n") % mark)
144 146
145 147 if not revs:
146 148 raise util.Abort(_('empty revision set'))
147 149
148 150 descendants = set(cl.descendants(revs))
149 151 strippedrevs = revs.union(descendants)
150 152 roots = revs.difference(descendants)
151 153
152 154 update = False
153 155 # if one of the wdir parent is stripped we'll need
154 156 # to update away to an earlier revision
155 157 for p in repo.dirstate.parents():
156 158 if p != nullid and cl.rev(p) in strippedrevs:
157 159 update = True
158 160 break
159 161
160 162 rootnodes = set(cl.node(r) for r in roots)
161 163
162 164 q = getattr(repo, 'mq', None)
163 165 if q is not None and q.applied:
164 166 # refresh queue state if we're about to strip
165 167 # applied patches
166 168 if cl.rev(repo.lookup('qtip')) in strippedrevs:
167 169 q.applieddirty = True
168 170 start = 0
169 171 end = len(q.applied)
170 172 for i, statusentry in enumerate(q.applied):
171 173 if statusentry.node in rootnodes:
172 174 # if one of the stripped roots is an applied
173 175 # patch, only part of the queue is stripped
174 176 start = i
175 177 break
176 178 del q.applied[start:end]
177 179 q.savedirty()
178 180
179 181 revs = sorted(rootnodes)
180 182 if update and opts.get('keep'):
181 183 wlock = repo.wlock()
182 184 try:
183 185 urev, p2 = repo.changelog.parents(revs[0])
184 186 if (util.safehasattr(repo, 'mq') and p2 != nullid
185 187 and p2 in [x.node for x in repo.mq.applied]):
186 188 urev = p2
187 189 uctx = repo[urev]
188 190
189 191 # only reset the dirstate for files that would actually change
190 192 # between the working context and uctx
191 193 descendantrevs = repo.revs("%s::." % uctx.rev())
192 194 changedfiles = []
193 195 for rev in descendantrevs:
194 196 # blindly reset the files, regardless of what actually changed
195 197 changedfiles.extend(repo[rev].files())
196 198
197 199 # reset files that only changed in the dirstate too
198 200 dirstate = repo.dirstate
199 201 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
200 202 changedfiles.extend(dirchanges)
201 203
202 204 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
203 205 repo.dirstate.write()
204 206 update = False
205 207 finally:
206 208 wlock.release()
207 209
208 210 if opts.get('bookmark'):
209 211 if mark == repo._bookmarkcurrent:
210 212 bookmarks.setcurrent(repo, None)
211 213 del marks[mark]
212 214 marks.write()
213 215 ui.write(_("bookmark '%s' deleted\n") % mark)
214 216
215 217 strip(ui, repo, revs, backup=backup, update=update, force=opts.get('force'))
216 218
217 219 return 0
General Comments 0
You need to be logged in to leave comments. Login now