##// END OF EJS Templates
strip: use a `changing_parents` context for --keep update...
marmoute -
r51007:62158471 default
parent child Browse files
Show More
@@ -1,281 +1,282 b''
1 from .i18n import _
1 from .i18n import _
2 from .pycompat import getattr
2 from .pycompat import getattr
3 from . import (
3 from . import (
4 bookmarks as bookmarksmod,
4 bookmarks as bookmarksmod,
5 cmdutil,
5 cmdutil,
6 error,
6 error,
7 hg,
7 hg,
8 lock as lockmod,
8 lock as lockmod,
9 logcmdutil,
9 logcmdutil,
10 mergestate as mergestatemod,
10 mergestate as mergestatemod,
11 pycompat,
11 pycompat,
12 registrar,
12 registrar,
13 repair,
13 repair,
14 scmutil,
14 scmutil,
15 util,
15 util,
16 )
16 )
17
17
18 release = lockmod.release
18 release = lockmod.release
19
19
20 cmdtable = {}
20 cmdtable = {}
21 command = registrar.command(cmdtable)
21 command = registrar.command(cmdtable)
22
22
23
23
24 def checklocalchanges(repo, force=False):
24 def checklocalchanges(repo, force=False):
25 s = repo.status()
25 s = repo.status()
26 if not force:
26 if not force:
27 cmdutil.checkunfinished(repo)
27 cmdutil.checkunfinished(repo)
28 cmdutil.bailifchanged(repo)
28 cmdutil.bailifchanged(repo)
29 else:
29 else:
30 cmdutil.checkunfinished(repo, skipmerge=True)
30 cmdutil.checkunfinished(repo, skipmerge=True)
31 return s
31 return s
32
32
33
33
34 def _findupdatetarget(repo, nodes):
34 def _findupdatetarget(repo, nodes):
35 unode, p2 = repo.changelog.parents(nodes[0])
35 unode, p2 = repo.changelog.parents(nodes[0])
36 currentbranch = repo[None].branch()
36 currentbranch = repo[None].branch()
37
37
38 if (
38 if (
39 util.safehasattr(repo, b'mq')
39 util.safehasattr(repo, b'mq')
40 and p2 != repo.nullid
40 and p2 != repo.nullid
41 and p2 in [x.node for x in repo.mq.applied]
41 and p2 in [x.node for x in repo.mq.applied]
42 ):
42 ):
43 unode = p2
43 unode = p2
44 elif currentbranch != repo[unode].branch():
44 elif currentbranch != repo[unode].branch():
45 pwdir = b'parents(wdir())'
45 pwdir = b'parents(wdir())'
46 revset = b'max(((parents(%ln::%r) + %r) - %ln::%r) and branch(%s))'
46 revset = b'max(((parents(%ln::%r) + %r) - %ln::%r) and branch(%s))'
47 branchtarget = repo.revs(
47 branchtarget = repo.revs(
48 revset, nodes, pwdir, pwdir, nodes, pwdir, currentbranch
48 revset, nodes, pwdir, pwdir, nodes, pwdir, currentbranch
49 )
49 )
50 if branchtarget:
50 if branchtarget:
51 cl = repo.changelog
51 cl = repo.changelog
52 unode = cl.node(branchtarget.first())
52 unode = cl.node(branchtarget.first())
53
53
54 return unode
54 return unode
55
55
56
56
57 def strip(
57 def strip(
58 ui,
58 ui,
59 repo,
59 repo,
60 revs,
60 revs,
61 update=True,
61 update=True,
62 backup=True,
62 backup=True,
63 force=None,
63 force=None,
64 bookmarks=None,
64 bookmarks=None,
65 soft=False,
65 soft=False,
66 ):
66 ):
67 with repo.wlock(), repo.lock():
67 with repo.wlock(), repo.lock():
68
68
69 if update:
69 if update:
70 checklocalchanges(repo, force=force)
70 checklocalchanges(repo, force=force)
71 urev = _findupdatetarget(repo, revs)
71 urev = _findupdatetarget(repo, revs)
72 hg.clean(repo, urev)
72 hg.clean(repo, urev)
73 repo.dirstate.write(repo.currenttransaction())
73 repo.dirstate.write(repo.currenttransaction())
74
74
75 if soft:
75 if soft:
76 repair.softstrip(ui, repo, revs, backup)
76 repair.softstrip(ui, repo, revs, backup)
77 else:
77 else:
78 repair.strip(ui, repo, revs, backup)
78 repair.strip(ui, repo, revs, backup)
79
79
80 repomarks = repo._bookmarks
80 repomarks = repo._bookmarks
81 if bookmarks:
81 if bookmarks:
82 with repo.transaction(b'strip') as tr:
82 with repo.transaction(b'strip') as tr:
83 if repo._activebookmark in bookmarks:
83 if repo._activebookmark in bookmarks:
84 bookmarksmod.deactivate(repo)
84 bookmarksmod.deactivate(repo)
85 repomarks.applychanges(repo, tr, [(b, None) for b in bookmarks])
85 repomarks.applychanges(repo, tr, [(b, None) for b in bookmarks])
86 for bookmark in sorted(bookmarks):
86 for bookmark in sorted(bookmarks):
87 ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
87 ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
88
88
89
89
90 @command(
90 @command(
91 b"debugstrip",
91 b"debugstrip",
92 [
92 [
93 (
93 (
94 b'r',
94 b'r',
95 b'rev',
95 b'rev',
96 [],
96 [],
97 _(
97 _(
98 b'strip specified revision (optional, '
98 b'strip specified revision (optional, '
99 b'can specify revisions without this '
99 b'can specify revisions without this '
100 b'option)'
100 b'option)'
101 ),
101 ),
102 _(b'REV'),
102 _(b'REV'),
103 ),
103 ),
104 (
104 (
105 b'f',
105 b'f',
106 b'force',
106 b'force',
107 None,
107 None,
108 _(
108 _(
109 b'force removal of changesets, discard '
109 b'force removal of changesets, discard '
110 b'uncommitted changes (no backup)'
110 b'uncommitted changes (no backup)'
111 ),
111 ),
112 ),
112 ),
113 (b'', b'no-backup', None, _(b'do not save backup bundle')),
113 (b'', b'no-backup', None, _(b'do not save backup bundle')),
114 (
114 (
115 b'',
115 b'',
116 b'nobackup',
116 b'nobackup',
117 None,
117 None,
118 _(b'do not save backup bundle (DEPRECATED)'),
118 _(b'do not save backup bundle (DEPRECATED)'),
119 ),
119 ),
120 (b'n', b'', None, _(b'ignored (DEPRECATED)')),
120 (b'n', b'', None, _(b'ignored (DEPRECATED)')),
121 (
121 (
122 b'k',
122 b'k',
123 b'keep',
123 b'keep',
124 None,
124 None,
125 _(b"do not modify working directory during strip"),
125 _(b"do not modify working directory during strip"),
126 ),
126 ),
127 (
127 (
128 b'B',
128 b'B',
129 b'bookmark',
129 b'bookmark',
130 [],
130 [],
131 _(b"remove revs only reachable from given bookmark"),
131 _(b"remove revs only reachable from given bookmark"),
132 _(b'BOOKMARK'),
132 _(b'BOOKMARK'),
133 ),
133 ),
134 (
134 (
135 b'',
135 b'',
136 b'soft',
136 b'soft',
137 None,
137 None,
138 _(b"simply drop changesets from visible history (EXPERIMENTAL)"),
138 _(b"simply drop changesets from visible history (EXPERIMENTAL)"),
139 ),
139 ),
140 ],
140 ],
141 _(b'hg debugstrip [-k] [-f] [-B bookmark] [-r] REV...'),
141 _(b'hg debugstrip [-k] [-f] [-B bookmark] [-r] REV...'),
142 helpcategory=command.CATEGORY_MAINTENANCE,
142 helpcategory=command.CATEGORY_MAINTENANCE,
143 )
143 )
144 def debugstrip(ui, repo, *revs, **opts):
144 def debugstrip(ui, repo, *revs, **opts):
145 """strip changesets and all their descendants from the repository
145 """strip changesets and all their descendants from the repository
146
146
147 The strip command removes the specified changesets and all their
147 The strip command removes the specified changesets and all their
148 descendants. If the working directory has uncommitted changes, the
148 descendants. If the working directory has uncommitted changes, the
149 operation is aborted unless the --force flag is supplied, in which
149 operation is aborted unless the --force flag is supplied, in which
150 case changes will be discarded.
150 case changes will be discarded.
151
151
152 If a parent of the working directory is stripped, then the working
152 If a parent of the working directory is stripped, then the working
153 directory will automatically be updated to the most recent
153 directory will automatically be updated to the most recent
154 available ancestor of the stripped parent after the operation
154 available ancestor of the stripped parent after the operation
155 completes.
155 completes.
156
156
157 Any stripped changesets are stored in ``.hg/strip-backup`` as a
157 Any stripped changesets are stored in ``.hg/strip-backup`` as a
158 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
158 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
159 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
159 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
160 where BUNDLE is the bundle file created by the strip. Note that
160 where BUNDLE is the bundle file created by the strip. Note that
161 the local revision numbers will in general be different after the
161 the local revision numbers will in general be different after the
162 restore.
162 restore.
163
163
164 Use the --no-backup option to discard the backup bundle once the
164 Use the --no-backup option to discard the backup bundle once the
165 operation completes.
165 operation completes.
166
166
167 Strip is not a history-rewriting operation and can be used on
167 Strip is not a history-rewriting operation and can be used on
168 changesets in the public phase. But if the stripped changesets have
168 changesets in the public phase. But if the stripped changesets have
169 been pushed to a remote repository you will likely pull them again.
169 been pushed to a remote repository you will likely pull them again.
170
170
171 Return 0 on success.
171 Return 0 on success.
172 """
172 """
173 opts = pycompat.byteskwargs(opts)
173 opts = pycompat.byteskwargs(opts)
174 backup = True
174 backup = True
175 if opts.get(b'no_backup') or opts.get(b'nobackup'):
175 if opts.get(b'no_backup') or opts.get(b'nobackup'):
176 backup = False
176 backup = False
177
177
178 cl = repo.changelog
178 cl = repo.changelog
179 revs = list(revs) + opts.get(b'rev')
179 revs = list(revs) + opts.get(b'rev')
180 revs = set(logcmdutil.revrange(repo, revs))
180 revs = set(logcmdutil.revrange(repo, revs))
181
181
182 with repo.wlock():
182 with repo.wlock():
183 bookmarks = set(opts.get(b'bookmark'))
183 bookmarks = set(opts.get(b'bookmark'))
184 if bookmarks:
184 if bookmarks:
185 repomarks = repo._bookmarks
185 repomarks = repo._bookmarks
186 if not bookmarks.issubset(repomarks):
186 if not bookmarks.issubset(repomarks):
187 raise error.Abort(
187 raise error.Abort(
188 _(b"bookmark '%s' not found")
188 _(b"bookmark '%s' not found")
189 % b','.join(sorted(bookmarks - set(repomarks.keys())))
189 % b','.join(sorted(bookmarks - set(repomarks.keys())))
190 )
190 )
191
191
192 # If the requested bookmark is not the only one pointing to a
192 # If the requested bookmark is not the only one pointing to a
193 # a revision we have to only delete the bookmark and not strip
193 # a revision we have to only delete the bookmark and not strip
194 # anything. revsets cannot detect that case.
194 # anything. revsets cannot detect that case.
195 nodetobookmarks = {}
195 nodetobookmarks = {}
196 for mark, node in repomarks.items():
196 for mark, node in repomarks.items():
197 nodetobookmarks.setdefault(node, []).append(mark)
197 nodetobookmarks.setdefault(node, []).append(mark)
198 for marks in nodetobookmarks.values():
198 for marks in nodetobookmarks.values():
199 if bookmarks.issuperset(marks):
199 if bookmarks.issuperset(marks):
200 rsrevs = scmutil.bookmarkrevs(repo, marks[0])
200 rsrevs = scmutil.bookmarkrevs(repo, marks[0])
201 revs.update(set(rsrevs))
201 revs.update(set(rsrevs))
202 if not revs:
202 if not revs:
203 with repo.lock(), repo.transaction(b'bookmark') as tr:
203 with repo.lock(), repo.transaction(b'bookmark') as tr:
204 bmchanges = [(b, None) for b in bookmarks]
204 bmchanges = [(b, None) for b in bookmarks]
205 repomarks.applychanges(repo, tr, bmchanges)
205 repomarks.applychanges(repo, tr, bmchanges)
206 for bookmark in sorted(bookmarks):
206 for bookmark in sorted(bookmarks):
207 ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
207 ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
208
208
209 if not revs:
209 if not revs:
210 raise error.Abort(_(b'empty revision set'))
210 raise error.Abort(_(b'empty revision set'))
211
211
212 descendants = set(cl.descendants(revs))
212 descendants = set(cl.descendants(revs))
213 strippedrevs = revs.union(descendants)
213 strippedrevs = revs.union(descendants)
214 roots = revs.difference(descendants)
214 roots = revs.difference(descendants)
215
215
216 # if one of the wdir parent is stripped we'll need
216 # if one of the wdir parent is stripped we'll need
217 # to update away to an earlier revision
217 # to update away to an earlier revision
218 update = any(
218 update = any(
219 p != repo.nullid and cl.rev(p) in strippedrevs
219 p != repo.nullid and cl.rev(p) in strippedrevs
220 for p in repo.dirstate.parents()
220 for p in repo.dirstate.parents()
221 )
221 )
222
222
223 rootnodes = {cl.node(r) for r in roots}
223 rootnodes = {cl.node(r) for r in roots}
224
224
225 q = getattr(repo, 'mq', None)
225 q = getattr(repo, 'mq', None)
226 if q is not None and q.applied:
226 if q is not None and q.applied:
227 # refresh queue state if we're about to strip
227 # refresh queue state if we're about to strip
228 # applied patches
228 # applied patches
229 if cl.rev(repo.lookup(b'qtip')) in strippedrevs:
229 if cl.rev(repo.lookup(b'qtip')) in strippedrevs:
230 q.applieddirty = True
230 q.applieddirty = True
231 start = 0
231 start = 0
232 end = len(q.applied)
232 end = len(q.applied)
233 for i, statusentry in enumerate(q.applied):
233 for i, statusentry in enumerate(q.applied):
234 if statusentry.node in rootnodes:
234 if statusentry.node in rootnodes:
235 # if one of the stripped roots is an applied
235 # if one of the stripped roots is an applied
236 # patch, only part of the queue is stripped
236 # patch, only part of the queue is stripped
237 start = i
237 start = i
238 break
238 break
239 del q.applied[start:end]
239 del q.applied[start:end]
240 q.savedirty()
240 q.savedirty()
241
241
242 revs = sorted(rootnodes)
242 revs = sorted(rootnodes)
243 if update and opts.get(b'keep'):
243 if update and opts.get(b'keep'):
244 with repo.dirstate.changing_parents(repo):
244 urev = _findupdatetarget(repo, revs)
245 urev = _findupdatetarget(repo, revs)
245 uctx = repo[urev]
246 uctx = repo[urev]
246
247
247 # only reset the dirstate for files that would actually change
248 # only reset the dirstate for files that would actually change
248 # between the working context and uctx
249 # between the working context and uctx
249 descendantrevs = repo.revs(b"only(., %d)", uctx.rev())
250 descendantrevs = repo.revs(b"only(., %d)", uctx.rev())
250 changedfiles = []
251 changedfiles = []
251 for rev in descendantrevs:
252 for rev in descendantrevs:
252 # blindly reset the files, regardless of what actually changed
253 # blindly reset the files, regardless of what actually changed
253 changedfiles.extend(repo[rev].files())
254 changedfiles.extend(repo[rev].files())
254
255
255 # reset files that only changed in the dirstate too
256 # reset files that only changed in the dirstate too
256 dirstate = repo.dirstate
257 dirstate = repo.dirstate
257 dirchanges = [
258 dirchanges = [
258 f for f in dirstate if not dirstate.get_entry(f).maybe_clean
259 f for f in dirstate if not dirstate.get_entry(f).maybe_clean
259 ]
260 ]
260 changedfiles.extend(dirchanges)
261 changedfiles.extend(dirchanges)
261
262
262 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
263 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
263 repo.dirstate.write(repo.currenttransaction())
264 repo.dirstate.write(repo.currenttransaction())
264
265
265 # clear resolve state
266 # clear resolve state
266 mergestatemod.mergestate.clean(repo)
267 mergestatemod.mergestate.clean(repo)
267
268
268 update = False
269 update = False
269
270
270 strip(
271 strip(
271 ui,
272 ui,
272 repo,
273 repo,
273 revs,
274 revs,
274 backup=backup,
275 backup=backup,
275 update=update,
276 update=update,
276 force=opts.get(b'force'),
277 force=opts.get(b'force'),
277 bookmarks=bookmarks,
278 bookmarks=bookmarks,
278 soft=opts[b'soft'],
279 soft=opts[b'soft'],
279 )
280 )
280
281
281 return 0
282 return 0
General Comments 0
You need to be logged in to leave comments. Login now