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