##// END OF EJS Templates
strip: use repo._bookmarks.recordchange instead of repo._bookmarks.write...
Laurent Charignon -
r27052:b9d0b45d default
parent child Browse files
Show More
@@ -1,225 +1,238
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 if repo._activebookmark in bookmarks:
67 tr = None
68 bookmarksmod.deactivate(repo)
68 try:
69 for bookmark in bookmarks:
69 tr = repo.transaction('strip')
70 del repomarks[bookmark]
70 if repo._activebookmark in bookmarks:
71 repomarks.write()
71 bookmarksmod.deactivate(repo)
72 for bookmark in sorted(bookmarks):
72 for bookmark in bookmarks:
73 ui.write(_("bookmark '%s' deleted\n") % bookmark)
73 del repomarks[bookmark]
74 repomarks.recordchange(tr)
75 tr.close()
76 for bookmark in sorted(bookmarks):
77 ui.write(_("bookmark '%s' deleted\n") % bookmark)
78 finally:
79 release(tr)
74 finally:
80 finally:
75 release(lock, wlock)
81 release(lock, wlock)
76
82
77
83
78 @command("strip",
84 @command("strip",
79 [
85 [
80 ('r', 'rev', [], _('strip specified revision (optional, '
86 ('r', 'rev', [], _('strip specified revision (optional, '
81 'can specify revisions without this '
87 'can specify revisions without this '
82 'option)'), _('REV')),
88 'option)'), _('REV')),
83 ('f', 'force', None, _('force removal of changesets, discard '
89 ('f', 'force', None, _('force removal of changesets, discard '
84 'uncommitted changes (no backup)')),
90 'uncommitted changes (no backup)')),
85 ('', 'no-backup', None, _('no backups')),
91 ('', 'no-backup', None, _('no backups')),
86 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
92 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
87 ('n', '', None, _('ignored (DEPRECATED)')),
93 ('n', '', None, _('ignored (DEPRECATED)')),
88 ('k', 'keep', None, _("do not modify working directory during "
94 ('k', 'keep', None, _("do not modify working directory during "
89 "strip")),
95 "strip")),
90 ('B', 'bookmark', [], _("remove revs only reachable from given"
96 ('B', 'bookmark', [], _("remove revs only reachable from given"
91 " bookmark"))],
97 " bookmark"))],
92 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
98 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
93 def stripcmd(ui, repo, *revs, **opts):
99 def stripcmd(ui, repo, *revs, **opts):
94 """strip changesets and all their descendants from the repository
100 """strip changesets and all their descendants from the repository
95
101
96 The strip command removes the specified changesets and all their
102 The strip command removes the specified changesets and all their
97 descendants. If the working directory has uncommitted changes, the
103 descendants. If the working directory has uncommitted changes, the
98 operation is aborted unless the --force flag is supplied, in which
104 operation is aborted unless the --force flag is supplied, in which
99 case changes will be discarded.
105 case changes will be discarded.
100
106
101 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
102 directory will automatically be updated to the most recent
108 directory will automatically be updated to the most recent
103 available ancestor of the stripped parent after the operation
109 available ancestor of the stripped parent after the operation
104 completes.
110 completes.
105
111
106 Any stripped changesets are stored in ``.hg/strip-backup`` as a
112 Any stripped changesets are stored in ``.hg/strip-backup`` as a
107 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
113 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
108 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
114 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
109 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
110 the local revision numbers will in general be different after the
116 the local revision numbers will in general be different after the
111 restore.
117 restore.
112
118
113 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
114 operation completes.
120 operation completes.
115
121
116 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
117 changesets in the public phase. But if the stripped changesets have
123 changesets in the public phase. But if the stripped changesets have
118 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.
119
125
120 Return 0 on success.
126 Return 0 on success.
121 """
127 """
122 backup = True
128 backup = True
123 if opts.get('no_backup') or opts.get('nobackup'):
129 if opts.get('no_backup') or opts.get('nobackup'):
124 backup = False
130 backup = False
125
131
126 cl = repo.changelog
132 cl = repo.changelog
127 revs = list(revs) + opts.get('rev')
133 revs = list(revs) + opts.get('rev')
128 revs = set(scmutil.revrange(repo, revs))
134 revs = set(scmutil.revrange(repo, revs))
129
135
130 wlock = repo.wlock()
136 wlock = repo.wlock()
131 try:
137 try:
132 bookmarks = set(opts.get('bookmark'))
138 bookmarks = set(opts.get('bookmark'))
133 if bookmarks:
139 if bookmarks:
134 repomarks = repo._bookmarks
140 repomarks = repo._bookmarks
135 if not bookmarks.issubset(repomarks):
141 if not bookmarks.issubset(repomarks):
136 raise error.Abort(_("bookmark '%s' not found") %
142 raise error.Abort(_("bookmark '%s' not found") %
137 ','.join(sorted(bookmarks - set(repomarks.keys()))))
143 ','.join(sorted(bookmarks - set(repomarks.keys()))))
138
144
139 # If the requested bookmark is not the only one pointing to a
145 # If the requested bookmark is not the only one pointing to a
140 # a revision we have to only delete the bookmark and not strip
146 # a revision we have to only delete the bookmark and not strip
141 # anything. revsets cannot detect that case.
147 # anything. revsets cannot detect that case.
142 nodetobookmarks = {}
148 nodetobookmarks = {}
143 for mark, node in repomarks.iteritems():
149 for mark, node in repomarks.iteritems():
144 nodetobookmarks.setdefault(node, []).append(mark)
150 nodetobookmarks.setdefault(node, []).append(mark)
145 for marks in nodetobookmarks.values():
151 for marks in nodetobookmarks.values():
146 if bookmarks.issuperset(marks):
152 if bookmarks.issuperset(marks):
147 rsrevs = repair.stripbmrevset(repo, marks[0])
153 rsrevs = repair.stripbmrevset(repo, marks[0])
148 revs.update(set(rsrevs))
154 revs.update(set(rsrevs))
149 if not revs:
155 if not revs:
150 for bookmark in bookmarks:
156 lock = tr = None
151 del repomarks[bookmark]
157 try:
152 repomarks.write()
158 lock = repo.lock()
153 for bookmark in sorted(bookmarks):
159 tr = repo.transaction('bookmark')
154 ui.write(_("bookmark '%s' deleted\n") % bookmark)
160 for bookmark in bookmarks:
161 del repomarks[bookmark]
162 repomarks.recordchange(tr)
163 tr.close()
164 for bookmark in sorted(bookmarks):
165 ui.write(_("bookmark '%s' deleted\n") % bookmark)
166 finally:
167 release(lock, tr)
155
168
156 if not revs:
169 if not revs:
157 raise error.Abort(_('empty revision set'))
170 raise error.Abort(_('empty revision set'))
158
171
159 descendants = set(cl.descendants(revs))
172 descendants = set(cl.descendants(revs))
160 strippedrevs = revs.union(descendants)
173 strippedrevs = revs.union(descendants)
161 roots = revs.difference(descendants)
174 roots = revs.difference(descendants)
162
175
163 update = False
176 update = False
164 # if one of the wdir parent is stripped we'll need
177 # if one of the wdir parent is stripped we'll need
165 # to update away to an earlier revision
178 # to update away to an earlier revision
166 for p in repo.dirstate.parents():
179 for p in repo.dirstate.parents():
167 if p != nullid and cl.rev(p) in strippedrevs:
180 if p != nullid and cl.rev(p) in strippedrevs:
168 update = True
181 update = True
169 break
182 break
170
183
171 rootnodes = set(cl.node(r) for r in roots)
184 rootnodes = set(cl.node(r) for r in roots)
172
185
173 q = getattr(repo, 'mq', None)
186 q = getattr(repo, 'mq', None)
174 if q is not None and q.applied:
187 if q is not None and q.applied:
175 # refresh queue state if we're about to strip
188 # refresh queue state if we're about to strip
176 # applied patches
189 # applied patches
177 if cl.rev(repo.lookup('qtip')) in strippedrevs:
190 if cl.rev(repo.lookup('qtip')) in strippedrevs:
178 q.applieddirty = True
191 q.applieddirty = True
179 start = 0
192 start = 0
180 end = len(q.applied)
193 end = len(q.applied)
181 for i, statusentry in enumerate(q.applied):
194 for i, statusentry in enumerate(q.applied):
182 if statusentry.node in rootnodes:
195 if statusentry.node in rootnodes:
183 # if one of the stripped roots is an applied
196 # if one of the stripped roots is an applied
184 # patch, only part of the queue is stripped
197 # patch, only part of the queue is stripped
185 start = i
198 start = i
186 break
199 break
187 del q.applied[start:end]
200 del q.applied[start:end]
188 q.savedirty()
201 q.savedirty()
189
202
190 revs = sorted(rootnodes)
203 revs = sorted(rootnodes)
191 if update and opts.get('keep'):
204 if update and opts.get('keep'):
192 urev, p2 = repo.changelog.parents(revs[0])
205 urev, p2 = repo.changelog.parents(revs[0])
193 if (util.safehasattr(repo, 'mq') and p2 != nullid
206 if (util.safehasattr(repo, 'mq') and p2 != nullid
194 and p2 in [x.node for x in repo.mq.applied]):
207 and p2 in [x.node for x in repo.mq.applied]):
195 urev = p2
208 urev = p2
196 uctx = repo[urev]
209 uctx = repo[urev]
197
210
198 # only reset the dirstate for files that would actually change
211 # only reset the dirstate for files that would actually change
199 # between the working context and uctx
212 # between the working context and uctx
200 descendantrevs = repo.revs("%s::." % uctx.rev())
213 descendantrevs = repo.revs("%s::." % uctx.rev())
201 changedfiles = []
214 changedfiles = []
202 for rev in descendantrevs:
215 for rev in descendantrevs:
203 # blindly reset the files, regardless of what actually changed
216 # blindly reset the files, regardless of what actually changed
204 changedfiles.extend(repo[rev].files())
217 changedfiles.extend(repo[rev].files())
205
218
206 # reset files that only changed in the dirstate too
219 # reset files that only changed in the dirstate too
207 dirstate = repo.dirstate
220 dirstate = repo.dirstate
208 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
221 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
209 changedfiles.extend(dirchanges)
222 changedfiles.extend(dirchanges)
210
223
211 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
224 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
212 repo.dirstate.write(repo.currenttransaction())
225 repo.dirstate.write(repo.currenttransaction())
213
226
214 # clear resolve state
227 # clear resolve state
215 merge.mergestate.clean(repo, repo['.'].node())
228 merge.mergestate.clean(repo, repo['.'].node())
216
229
217 update = False
230 update = False
218
231
219
232
220 strip(ui, repo, revs, backup=backup, update=update,
233 strip(ui, repo, revs, backup=backup, update=update,
221 force=opts.get('force'), bookmarks=bookmarks)
234 force=opts.get('force'), bookmarks=bookmarks)
222 finally:
235 finally:
223 wlock.release()
236 wlock.release()
224
237
225 return 0
238 return 0
General Comments 0
You need to be logged in to leave comments. Login now