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