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