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