##// END OF EJS Templates
repair: preserve phase also when not using generaldelta (issue5678)...
Martin von Zweigbergk -
r34179:91f0677d stable
parent child Browse files
Show More
@@ -1,1009 +1,1014 b''
1 1 # changegroup.py - Mercurial changegroup manipulation functions
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import os
11 11 import struct
12 12 import tempfile
13 13 import weakref
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullrev,
19 19 short,
20 20 )
21 21
22 22 from . import (
23 23 dagutil,
24 24 discovery,
25 25 error,
26 26 mdiff,
27 27 phases,
28 28 pycompat,
29 29 util,
30 30 )
31 31
32 32 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
33 33 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
34 34 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
35 35
36 36 def readexactly(stream, n):
37 37 '''read n bytes from stream.read and abort if less was available'''
38 38 s = stream.read(n)
39 39 if len(s) < n:
40 40 raise error.Abort(_("stream ended unexpectedly"
41 41 " (got %d bytes, expected %d)")
42 42 % (len(s), n))
43 43 return s
44 44
45 45 def getchunk(stream):
46 46 """return the next chunk from stream as a string"""
47 47 d = readexactly(stream, 4)
48 48 l = struct.unpack(">l", d)[0]
49 49 if l <= 4:
50 50 if l:
51 51 raise error.Abort(_("invalid chunk length %d") % l)
52 52 return ""
53 53 return readexactly(stream, l - 4)
54 54
55 55 def chunkheader(length):
56 56 """return a changegroup chunk header (string)"""
57 57 return struct.pack(">l", length + 4)
58 58
59 59 def closechunk():
60 60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 61 return struct.pack(">l", 0)
62 62
63 63 def writechunks(ui, chunks, filename, vfs=None):
64 64 """Write chunks to a file and return its filename.
65 65
66 66 The stream is assumed to be a bundle file.
67 67 Existing files will not be overwritten.
68 68 If no filename is specified, a temporary file is created.
69 69 """
70 70 fh = None
71 71 cleanup = None
72 72 try:
73 73 if filename:
74 74 if vfs:
75 75 fh = vfs.open(filename, "wb")
76 76 else:
77 77 # Increase default buffer size because default is usually
78 78 # small (4k is common on Linux).
79 79 fh = open(filename, "wb", 131072)
80 80 else:
81 81 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
82 82 fh = os.fdopen(fd, pycompat.sysstr("wb"))
83 83 cleanup = filename
84 84 for c in chunks:
85 85 fh.write(c)
86 86 cleanup = None
87 87 return filename
88 88 finally:
89 89 if fh is not None:
90 90 fh.close()
91 91 if cleanup is not None:
92 92 if filename and vfs:
93 93 vfs.unlink(cleanup)
94 94 else:
95 95 os.unlink(cleanup)
96 96
97 97 class cg1unpacker(object):
98 98 """Unpacker for cg1 changegroup streams.
99 99
100 100 A changegroup unpacker handles the framing of the revision data in
101 101 the wire format. Most consumers will want to use the apply()
102 102 method to add the changes from the changegroup to a repository.
103 103
104 104 If you're forwarding a changegroup unmodified to another consumer,
105 105 use getchunks(), which returns an iterator of changegroup
106 106 chunks. This is mostly useful for cases where you need to know the
107 107 data stream has ended by observing the end of the changegroup.
108 108
109 109 deltachunk() is useful only if you're applying delta data. Most
110 110 consumers should prefer apply() instead.
111 111
112 112 A few other public methods exist. Those are used only for
113 113 bundlerepo and some debug commands - their use is discouraged.
114 114 """
115 115 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
116 116 deltaheadersize = struct.calcsize(deltaheader)
117 117 version = '01'
118 118 _grouplistcount = 1 # One list of files after the manifests
119 119
120 120 def __init__(self, fh, alg, extras=None):
121 121 if alg is None:
122 122 alg = 'UN'
123 123 if alg not in util.compengines.supportedbundletypes:
124 124 raise error.Abort(_('unknown stream compression type: %s')
125 125 % alg)
126 126 if alg == 'BZ':
127 127 alg = '_truncatedBZ'
128 128
129 129 compengine = util.compengines.forbundletype(alg)
130 130 self._stream = compengine.decompressorreader(fh)
131 131 self._type = alg
132 132 self.extras = extras or {}
133 133 self.callback = None
134 134
135 135 # These methods (compressed, read, seek, tell) all appear to only
136 136 # be used by bundlerepo, but it's a little hard to tell.
137 137 def compressed(self):
138 138 return self._type is not None and self._type != 'UN'
139 139 def read(self, l):
140 140 return self._stream.read(l)
141 141 def seek(self, pos):
142 142 return self._stream.seek(pos)
143 143 def tell(self):
144 144 return self._stream.tell()
145 145 def close(self):
146 146 return self._stream.close()
147 147
148 148 def _chunklength(self):
149 149 d = readexactly(self._stream, 4)
150 150 l = struct.unpack(">l", d)[0]
151 151 if l <= 4:
152 152 if l:
153 153 raise error.Abort(_("invalid chunk length %d") % l)
154 154 return 0
155 155 if self.callback:
156 156 self.callback()
157 157 return l - 4
158 158
159 159 def changelogheader(self):
160 160 """v10 does not have a changelog header chunk"""
161 161 return {}
162 162
163 163 def manifestheader(self):
164 164 """v10 does not have a manifest header chunk"""
165 165 return {}
166 166
167 167 def filelogheader(self):
168 168 """return the header of the filelogs chunk, v10 only has the filename"""
169 169 l = self._chunklength()
170 170 if not l:
171 171 return {}
172 172 fname = readexactly(self._stream, l)
173 173 return {'filename': fname}
174 174
175 175 def _deltaheader(self, headertuple, prevnode):
176 176 node, p1, p2, cs = headertuple
177 177 if prevnode is None:
178 178 deltabase = p1
179 179 else:
180 180 deltabase = prevnode
181 181 flags = 0
182 182 return node, p1, p2, deltabase, cs, flags
183 183
184 184 def deltachunk(self, prevnode):
185 185 l = self._chunklength()
186 186 if not l:
187 187 return {}
188 188 headerdata = readexactly(self._stream, self.deltaheadersize)
189 189 header = struct.unpack(self.deltaheader, headerdata)
190 190 delta = readexactly(self._stream, l - self.deltaheadersize)
191 191 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
192 192 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
193 193 'deltabase': deltabase, 'delta': delta, 'flags': flags}
194 194
195 195 def getchunks(self):
196 196 """returns all the chunks contains in the bundle
197 197
198 198 Used when you need to forward the binary stream to a file or another
199 199 network API. To do so, it parse the changegroup data, otherwise it will
200 200 block in case of sshrepo because it don't know the end of the stream.
201 201 """
202 202 # an empty chunkgroup is the end of the changegroup
203 203 # a changegroup has at least 2 chunkgroups (changelog and manifest).
204 204 # after that, changegroup versions 1 and 2 have a series of groups
205 205 # with one group per file. changegroup 3 has a series of directory
206 206 # manifests before the files.
207 207 count = 0
208 208 emptycount = 0
209 209 while emptycount < self._grouplistcount:
210 210 empty = True
211 211 count += 1
212 212 while True:
213 213 chunk = getchunk(self)
214 214 if not chunk:
215 215 if empty and count > 2:
216 216 emptycount += 1
217 217 break
218 218 empty = False
219 219 yield chunkheader(len(chunk))
220 220 pos = 0
221 221 while pos < len(chunk):
222 222 next = pos + 2**20
223 223 yield chunk[pos:next]
224 224 pos = next
225 225 yield closechunk()
226 226
227 227 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
228 228 # We know that we'll never have more manifests than we had
229 229 # changesets.
230 230 self.callback = prog(_('manifests'), numchanges)
231 231 # no need to check for empty manifest group here:
232 232 # if the result of the merge of 1 and 2 is the same in 3 and 4,
233 233 # no new manifest will be created and the manifest group will
234 234 # be empty during the pull
235 235 self.manifestheader()
236 236 repo.manifestlog._revlog.addgroup(self, revmap, trp)
237 237 repo.ui.progress(_('manifests'), None)
238 238 self.callback = None
239 239
240 240 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
241 241 expectedtotal=None):
242 242 """Add the changegroup returned by source.read() to this repo.
243 243 srctype is a string like 'push', 'pull', or 'unbundle'. url is
244 244 the URL of the repo where this changegroup is coming from.
245 245
246 246 Return an integer summarizing the change to this repo:
247 247 - nothing changed or no source: 0
248 248 - more heads than before: 1+added heads (2..n)
249 249 - fewer heads than before: -1-removed heads (-2..-n)
250 250 - number of heads stays the same: 1
251 251 """
252 252 repo = repo.unfiltered()
253 253 def csmap(x):
254 254 repo.ui.debug("add changeset %s\n" % short(x))
255 255 return len(cl)
256 256
257 257 def revmap(x):
258 258 return cl.rev(x)
259 259
260 260 changesets = files = revisions = 0
261 261
262 262 try:
263 263 # The transaction may already carry source information. In this
264 264 # case we use the top level data. We overwrite the argument
265 265 # because we need to use the top level value (if they exist)
266 266 # in this function.
267 267 srctype = tr.hookargs.setdefault('source', srctype)
268 268 url = tr.hookargs.setdefault('url', url)
269 269 repo.hook('prechangegroup', throw=True, **tr.hookargs)
270 270
271 271 # write changelog data to temp files so concurrent readers
272 272 # will not see an inconsistent view
273 273 cl = repo.changelog
274 274 cl.delayupdate(tr)
275 275 oldheads = set(cl.heads())
276 276
277 277 trp = weakref.proxy(tr)
278 278 # pull off the changeset group
279 279 repo.ui.status(_("adding changesets\n"))
280 280 clstart = len(cl)
281 281 class prog(object):
282 282 def __init__(self, step, total):
283 283 self._step = step
284 284 self._total = total
285 285 self._count = 1
286 286 def __call__(self):
287 287 repo.ui.progress(self._step, self._count, unit=_('chunks'),
288 288 total=self._total)
289 289 self._count += 1
290 290 self.callback = prog(_('changesets'), expectedtotal)
291 291
292 292 efiles = set()
293 293 def onchangelog(cl, node):
294 294 efiles.update(cl.readfiles(node))
295 295
296 296 self.changelogheader()
297 297 cgnodes = cl.addgroup(self, csmap, trp, addrevisioncb=onchangelog)
298 298 efiles = len(efiles)
299 299
300 300 if not cgnodes:
301 301 repo.ui.develwarn('applied empty changegroup',
302 302 config='empty-changegroup')
303 303 clend = len(cl)
304 304 changesets = clend - clstart
305 305 repo.ui.progress(_('changesets'), None)
306 306 self.callback = None
307 307
308 308 # pull off the manifest group
309 309 repo.ui.status(_("adding manifests\n"))
310 310 self._unpackmanifests(repo, revmap, trp, prog, changesets)
311 311
312 312 needfiles = {}
313 313 if repo.ui.configbool('server', 'validate'):
314 314 cl = repo.changelog
315 315 ml = repo.manifestlog
316 316 # validate incoming csets have their manifests
317 317 for cset in xrange(clstart, clend):
318 318 mfnode = cl.changelogrevision(cset).manifest
319 319 mfest = ml[mfnode].readdelta()
320 320 # store file cgnodes we must see
321 321 for f, n in mfest.iteritems():
322 322 needfiles.setdefault(f, set()).add(n)
323 323
324 324 # process the files
325 325 repo.ui.status(_("adding file changes\n"))
326 326 newrevs, newfiles = _addchangegroupfiles(
327 327 repo, self, revmap, trp, efiles, needfiles)
328 328 revisions += newrevs
329 329 files += newfiles
330 330
331 331 deltaheads = 0
332 332 if oldheads:
333 333 heads = cl.heads()
334 334 deltaheads = len(heads) - len(oldheads)
335 335 for h in heads:
336 336 if h not in oldheads and repo[h].closesbranch():
337 337 deltaheads -= 1
338 338 htext = ""
339 339 if deltaheads:
340 340 htext = _(" (%+d heads)") % deltaheads
341 341
342 342 repo.ui.status(_("added %d changesets"
343 343 " with %d changes to %d files%s\n")
344 344 % (changesets, revisions, files, htext))
345 345 repo.invalidatevolatilesets()
346 346
347 347 if changesets > 0:
348 348 if 'node' not in tr.hookargs:
349 349 tr.hookargs['node'] = hex(cl.node(clstart))
350 350 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
351 351 hookargs = dict(tr.hookargs)
352 352 else:
353 353 hookargs = dict(tr.hookargs)
354 354 hookargs['node'] = hex(cl.node(clstart))
355 355 hookargs['node_last'] = hex(cl.node(clend - 1))
356 356 repo.hook('pretxnchangegroup', throw=True, **hookargs)
357 357
358 358 added = [cl.node(r) for r in xrange(clstart, clend)]
359 359 phaseall = None
360 360 if srctype in ('push', 'serve'):
361 361 # Old servers can not push the boundary themselves.
362 362 # New servers won't push the boundary if changeset already
363 363 # exists locally as secret
364 364 #
365 365 # We should not use added here but the list of all change in
366 366 # the bundle
367 367 if repo.publishing():
368 368 targetphase = phaseall = phases.public
369 369 else:
370 370 # closer target phase computation
371 371
372 372 # Those changesets have been pushed from the
373 373 # outside, their phases are going to be pushed
374 374 # alongside. Therefor `targetphase` is
375 375 # ignored.
376 376 targetphase = phaseall = phases.draft
377 377 if added:
378 378 phases.registernew(repo, tr, targetphase, added)
379 379 if phaseall is not None:
380 380 phases.advanceboundary(repo, tr, phaseall, cgnodes)
381 381
382 382 if changesets > 0:
383 383
384 384 def runhooks():
385 385 # These hooks run when the lock releases, not when the
386 386 # transaction closes. So it's possible for the changelog
387 387 # to have changed since we last saw it.
388 388 if clstart >= len(repo):
389 389 return
390 390
391 391 repo.hook("changegroup", **hookargs)
392 392
393 393 for n in added:
394 394 args = hookargs.copy()
395 395 args['node'] = hex(n)
396 396 del args['node_last']
397 397 repo.hook("incoming", **args)
398 398
399 399 newheads = [h for h in repo.heads()
400 400 if h not in oldheads]
401 401 repo.ui.log("incoming",
402 402 "%s incoming changes - new heads: %s\n",
403 403 len(added),
404 404 ', '.join([hex(c[:6]) for c in newheads]))
405 405
406 406 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
407 407 lambda tr: repo._afterlock(runhooks))
408 408 finally:
409 409 repo.ui.flush()
410 410 # never return 0 here:
411 411 if deltaheads < 0:
412 412 ret = deltaheads - 1
413 413 else:
414 414 ret = deltaheads + 1
415 415 return ret
416 416
417 417 class cg2unpacker(cg1unpacker):
418 418 """Unpacker for cg2 streams.
419 419
420 420 cg2 streams add support for generaldelta, so the delta header
421 421 format is slightly different. All other features about the data
422 422 remain the same.
423 423 """
424 424 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
425 425 deltaheadersize = struct.calcsize(deltaheader)
426 426 version = '02'
427 427
428 428 def _deltaheader(self, headertuple, prevnode):
429 429 node, p1, p2, deltabase, cs = headertuple
430 430 flags = 0
431 431 return node, p1, p2, deltabase, cs, flags
432 432
433 433 class cg3unpacker(cg2unpacker):
434 434 """Unpacker for cg3 streams.
435 435
436 436 cg3 streams add support for exchanging treemanifests and revlog
437 437 flags. It adds the revlog flags to the delta header and an empty chunk
438 438 separating manifests and files.
439 439 """
440 440 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
441 441 deltaheadersize = struct.calcsize(deltaheader)
442 442 version = '03'
443 443 _grouplistcount = 2 # One list of manifests and one list of files
444 444
445 445 def _deltaheader(self, headertuple, prevnode):
446 446 node, p1, p2, deltabase, cs, flags = headertuple
447 447 return node, p1, p2, deltabase, cs, flags
448 448
449 449 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
450 450 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
451 451 numchanges)
452 452 for chunkdata in iter(self.filelogheader, {}):
453 453 # If we get here, there are directory manifests in the changegroup
454 454 d = chunkdata["filename"]
455 455 repo.ui.debug("adding %s revisions\n" % d)
456 456 dirlog = repo.manifestlog._revlog.dirlog(d)
457 457 if not dirlog.addgroup(self, revmap, trp):
458 458 raise error.Abort(_("received dir revlog group is empty"))
459 459
460 460 class headerlessfixup(object):
461 461 def __init__(self, fh, h):
462 462 self._h = h
463 463 self._fh = fh
464 464 def read(self, n):
465 465 if self._h:
466 466 d, self._h = self._h[:n], self._h[n:]
467 467 if len(d) < n:
468 468 d += readexactly(self._fh, n - len(d))
469 469 return d
470 470 return readexactly(self._fh, n)
471 471
472 472 class cg1packer(object):
473 473 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
474 474 version = '01'
475 475 def __init__(self, repo, bundlecaps=None):
476 476 """Given a source repo, construct a bundler.
477 477
478 478 bundlecaps is optional and can be used to specify the set of
479 479 capabilities which can be used to build the bundle. While bundlecaps is
480 480 unused in core Mercurial, extensions rely on this feature to communicate
481 481 capabilities to customize the changegroup packer.
482 482 """
483 483 # Set of capabilities we can use to build the bundle.
484 484 if bundlecaps is None:
485 485 bundlecaps = set()
486 486 self._bundlecaps = bundlecaps
487 487 # experimental config: bundle.reorder
488 488 reorder = repo.ui.config('bundle', 'reorder')
489 489 if reorder == 'auto':
490 490 reorder = None
491 491 else:
492 492 reorder = util.parsebool(reorder)
493 493 self._repo = repo
494 494 self._reorder = reorder
495 495 self._progress = repo.ui.progress
496 496 if self._repo.ui.verbose and not self._repo.ui.debugflag:
497 497 self._verbosenote = self._repo.ui.note
498 498 else:
499 499 self._verbosenote = lambda s: None
500 500
501 501 def close(self):
502 502 return closechunk()
503 503
504 504 def fileheader(self, fname):
505 505 return chunkheader(len(fname)) + fname
506 506
507 507 # Extracted both for clarity and for overriding in extensions.
508 508 def _sortgroup(self, revlog, nodelist, lookup):
509 509 """Sort nodes for change group and turn them into revnums."""
510 510 # for generaldelta revlogs, we linearize the revs; this will both be
511 511 # much quicker and generate a much smaller bundle
512 512 if (revlog._generaldelta and self._reorder is None) or self._reorder:
513 513 dag = dagutil.revlogdag(revlog)
514 514 return dag.linearize(set(revlog.rev(n) for n in nodelist))
515 515 else:
516 516 return sorted([revlog.rev(n) for n in nodelist])
517 517
518 518 def group(self, nodelist, revlog, lookup, units=None):
519 519 """Calculate a delta group, yielding a sequence of changegroup chunks
520 520 (strings).
521 521
522 522 Given a list of changeset revs, return a set of deltas and
523 523 metadata corresponding to nodes. The first delta is
524 524 first parent(nodelist[0]) -> nodelist[0], the receiver is
525 525 guaranteed to have this parent as it has all history before
526 526 these changesets. In the case firstparent is nullrev the
527 527 changegroup starts with a full revision.
528 528
529 529 If units is not None, progress detail will be generated, units specifies
530 530 the type of revlog that is touched (changelog, manifest, etc.).
531 531 """
532 532 # if we don't have any revisions touched by these changesets, bail
533 533 if len(nodelist) == 0:
534 534 yield self.close()
535 535 return
536 536
537 537 revs = self._sortgroup(revlog, nodelist, lookup)
538 538
539 539 # add the parent of the first rev
540 540 p = revlog.parentrevs(revs[0])[0]
541 541 revs.insert(0, p)
542 542
543 543 # build deltas
544 544 total = len(revs) - 1
545 545 msgbundling = _('bundling')
546 546 for r in xrange(len(revs) - 1):
547 547 if units is not None:
548 548 self._progress(msgbundling, r + 1, unit=units, total=total)
549 549 prev, curr = revs[r], revs[r + 1]
550 550 linknode = lookup(revlog.node(curr))
551 551 for c in self.revchunk(revlog, curr, prev, linknode):
552 552 yield c
553 553
554 554 if units is not None:
555 555 self._progress(msgbundling, None)
556 556 yield self.close()
557 557
558 558 # filter any nodes that claim to be part of the known set
559 559 def prune(self, revlog, missing, commonrevs):
560 560 rr, rl = revlog.rev, revlog.linkrev
561 561 return [n for n in missing if rl(rr(n)) not in commonrevs]
562 562
563 563 def _packmanifests(self, dir, mfnodes, lookuplinknode):
564 564 """Pack flat manifests into a changegroup stream."""
565 565 assert not dir
566 566 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
567 567 lookuplinknode, units=_('manifests')):
568 568 yield chunk
569 569
570 570 def _manifestsdone(self):
571 571 return ''
572 572
573 573 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
574 574 '''yield a sequence of changegroup chunks (strings)'''
575 575 repo = self._repo
576 576 cl = repo.changelog
577 577
578 578 clrevorder = {}
579 579 mfs = {} # needed manifests
580 580 fnodes = {} # needed file nodes
581 581 changedfiles = set()
582 582
583 583 # Callback for the changelog, used to collect changed files and manifest
584 584 # nodes.
585 585 # Returns the linkrev node (identity in the changelog case).
586 586 def lookupcl(x):
587 587 c = cl.read(x)
588 588 clrevorder[x] = len(clrevorder)
589 589 n = c[0]
590 590 # record the first changeset introducing this manifest version
591 591 mfs.setdefault(n, x)
592 592 # Record a complete list of potentially-changed files in
593 593 # this manifest.
594 594 changedfiles.update(c[3])
595 595 return x
596 596
597 597 self._verbosenote(_('uncompressed size of bundle content:\n'))
598 598 size = 0
599 599 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
600 600 size += len(chunk)
601 601 yield chunk
602 602 self._verbosenote(_('%8.i (changelog)\n') % size)
603 603
604 604 # We need to make sure that the linkrev in the changegroup refers to
605 605 # the first changeset that introduced the manifest or file revision.
606 606 # The fastpath is usually safer than the slowpath, because the filelogs
607 607 # are walked in revlog order.
608 608 #
609 609 # When taking the slowpath with reorder=None and the manifest revlog
610 610 # uses generaldelta, the manifest may be walked in the "wrong" order.
611 611 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
612 612 # cc0ff93d0c0c).
613 613 #
614 614 # When taking the fastpath, we are only vulnerable to reordering
615 615 # of the changelog itself. The changelog never uses generaldelta, so
616 616 # it is only reordered when reorder=True. To handle this case, we
617 617 # simply take the slowpath, which already has the 'clrevorder' logic.
618 618 # This was also fixed in cc0ff93d0c0c.
619 619 fastpathlinkrev = fastpathlinkrev and not self._reorder
620 620 # Treemanifests don't work correctly with fastpathlinkrev
621 621 # either, because we don't discover which directory nodes to
622 622 # send along with files. This could probably be fixed.
623 623 fastpathlinkrev = fastpathlinkrev and (
624 624 'treemanifest' not in repo.requirements)
625 625
626 626 for chunk in self.generatemanifests(commonrevs, clrevorder,
627 627 fastpathlinkrev, mfs, fnodes):
628 628 yield chunk
629 629 mfs.clear()
630 630 clrevs = set(cl.rev(x) for x in clnodes)
631 631
632 632 if not fastpathlinkrev:
633 633 def linknodes(unused, fname):
634 634 return fnodes.get(fname, {})
635 635 else:
636 636 cln = cl.node
637 637 def linknodes(filerevlog, fname):
638 638 llr = filerevlog.linkrev
639 639 fln = filerevlog.node
640 640 revs = ((r, llr(r)) for r in filerevlog)
641 641 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
642 642
643 643 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
644 644 source):
645 645 yield chunk
646 646
647 647 yield self.close()
648 648
649 649 if clnodes:
650 650 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
651 651
652 652 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
653 653 fnodes):
654 654 repo = self._repo
655 655 mfl = repo.manifestlog
656 656 dirlog = mfl._revlog.dirlog
657 657 tmfnodes = {'': mfs}
658 658
659 659 # Callback for the manifest, used to collect linkrevs for filelog
660 660 # revisions.
661 661 # Returns the linkrev node (collected in lookupcl).
662 662 def makelookupmflinknode(dir):
663 663 if fastpathlinkrev:
664 664 assert not dir
665 665 return mfs.__getitem__
666 666
667 667 def lookupmflinknode(x):
668 668 """Callback for looking up the linknode for manifests.
669 669
670 670 Returns the linkrev node for the specified manifest.
671 671
672 672 SIDE EFFECT:
673 673
674 674 1) fclnodes gets populated with the list of relevant
675 675 file nodes if we're not using fastpathlinkrev
676 676 2) When treemanifests are in use, collects treemanifest nodes
677 677 to send
678 678
679 679 Note that this means manifests must be completely sent to
680 680 the client before you can trust the list of files and
681 681 treemanifests to send.
682 682 """
683 683 clnode = tmfnodes[dir][x]
684 684 mdata = mfl.get(dir, x).readfast(shallow=True)
685 685 for p, n, fl in mdata.iterentries():
686 686 if fl == 't': # subdirectory manifest
687 687 subdir = dir + p + '/'
688 688 tmfclnodes = tmfnodes.setdefault(subdir, {})
689 689 tmfclnode = tmfclnodes.setdefault(n, clnode)
690 690 if clrevorder[clnode] < clrevorder[tmfclnode]:
691 691 tmfclnodes[n] = clnode
692 692 else:
693 693 f = dir + p
694 694 fclnodes = fnodes.setdefault(f, {})
695 695 fclnode = fclnodes.setdefault(n, clnode)
696 696 if clrevorder[clnode] < clrevorder[fclnode]:
697 697 fclnodes[n] = clnode
698 698 return clnode
699 699 return lookupmflinknode
700 700
701 701 size = 0
702 702 while tmfnodes:
703 703 dir = min(tmfnodes)
704 704 nodes = tmfnodes[dir]
705 705 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
706 706 if not dir or prunednodes:
707 707 for x in self._packmanifests(dir, prunednodes,
708 708 makelookupmflinknode(dir)):
709 709 size += len(x)
710 710 yield x
711 711 del tmfnodes[dir]
712 712 self._verbosenote(_('%8.i (manifests)\n') % size)
713 713 yield self._manifestsdone()
714 714
715 715 # The 'source' parameter is useful for extensions
716 716 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
717 717 repo = self._repo
718 718 progress = self._progress
719 719 msgbundling = _('bundling')
720 720
721 721 total = len(changedfiles)
722 722 # for progress output
723 723 msgfiles = _('files')
724 724 for i, fname in enumerate(sorted(changedfiles)):
725 725 filerevlog = repo.file(fname)
726 726 if not filerevlog:
727 727 raise error.Abort(_("empty or missing revlog for %s") % fname)
728 728
729 729 linkrevnodes = linknodes(filerevlog, fname)
730 730 # Lookup for filenodes, we collected the linkrev nodes above in the
731 731 # fastpath case and with lookupmf in the slowpath case.
732 732 def lookupfilelog(x):
733 733 return linkrevnodes[x]
734 734
735 735 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
736 736 if filenodes:
737 737 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
738 738 total=total)
739 739 h = self.fileheader(fname)
740 740 size = len(h)
741 741 yield h
742 742 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
743 743 size += len(chunk)
744 744 yield chunk
745 745 self._verbosenote(_('%8.i %s\n') % (size, fname))
746 746 progress(msgbundling, None)
747 747
748 748 def deltaparent(self, revlog, rev, p1, p2, prev):
749 749 return prev
750 750
751 751 def revchunk(self, revlog, rev, prev, linknode):
752 752 node = revlog.node(rev)
753 753 p1, p2 = revlog.parentrevs(rev)
754 754 base = self.deltaparent(revlog, rev, p1, p2, prev)
755 755
756 756 prefix = ''
757 757 if revlog.iscensored(base) or revlog.iscensored(rev):
758 758 try:
759 759 delta = revlog.revision(node, raw=True)
760 760 except error.CensoredNodeError as e:
761 761 delta = e.tombstone
762 762 if base == nullrev:
763 763 prefix = mdiff.trivialdiffheader(len(delta))
764 764 else:
765 765 baselen = revlog.rawsize(base)
766 766 prefix = mdiff.replacediffheader(baselen, len(delta))
767 767 elif base == nullrev:
768 768 delta = revlog.revision(node, raw=True)
769 769 prefix = mdiff.trivialdiffheader(len(delta))
770 770 else:
771 771 delta = revlog.revdiff(base, rev)
772 772 p1n, p2n = revlog.parents(node)
773 773 basenode = revlog.node(base)
774 774 flags = revlog.flags(rev)
775 775 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
776 776 meta += prefix
777 777 l = len(meta) + len(delta)
778 778 yield chunkheader(l)
779 779 yield meta
780 780 yield delta
781 781 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
782 782 # do nothing with basenode, it is implicitly the previous one in HG10
783 783 # do nothing with flags, it is implicitly 0 for cg1 and cg2
784 784 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
785 785
786 786 class cg2packer(cg1packer):
787 787 version = '02'
788 788 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
789 789
790 790 def __init__(self, repo, bundlecaps=None):
791 791 super(cg2packer, self).__init__(repo, bundlecaps)
792 792 if self._reorder is None:
793 793 # Since generaldelta is directly supported by cg2, reordering
794 794 # generally doesn't help, so we disable it by default (treating
795 795 # bundle.reorder=auto just like bundle.reorder=False).
796 796 self._reorder = False
797 797
798 798 def deltaparent(self, revlog, rev, p1, p2, prev):
799 799 dp = revlog.deltaparent(rev)
800 800 if dp == nullrev and revlog.storedeltachains:
801 801 # Avoid sending full revisions when delta parent is null. Pick prev
802 802 # in that case. It's tempting to pick p1 in this case, as p1 will
803 803 # be smaller in the common case. However, computing a delta against
804 804 # p1 may require resolving the raw text of p1, which could be
805 805 # expensive. The revlog caches should have prev cached, meaning
806 806 # less CPU for changegroup generation. There is likely room to add
807 807 # a flag and/or config option to control this behavior.
808 808 return prev
809 809 elif dp == nullrev:
810 810 # revlog is configured to use full snapshot for a reason,
811 811 # stick to full snapshot.
812 812 return nullrev
813 813 elif dp not in (p1, p2, prev):
814 814 # Pick prev when we can't be sure remote has the base revision.
815 815 return prev
816 816 else:
817 817 return dp
818 818
819 819 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
820 820 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
821 821 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
822 822
823 823 class cg3packer(cg2packer):
824 824 version = '03'
825 825 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
826 826
827 827 def _packmanifests(self, dir, mfnodes, lookuplinknode):
828 828 if dir:
829 829 yield self.fileheader(dir)
830 830
831 831 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
832 832 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
833 833 units=_('manifests')):
834 834 yield chunk
835 835
836 836 def _manifestsdone(self):
837 837 return self.close()
838 838
839 839 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
840 840 return struct.pack(
841 841 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
842 842
843 843 _packermap = {'01': (cg1packer, cg1unpacker),
844 844 # cg2 adds support for exchanging generaldelta
845 845 '02': (cg2packer, cg2unpacker),
846 846 # cg3 adds support for exchanging revlog flags and treemanifests
847 847 '03': (cg3packer, cg3unpacker),
848 848 }
849 849
850 850 def allsupportedversions(repo):
851 851 versions = set(_packermap.keys())
852 852 if not (repo.ui.configbool('experimental', 'changegroup3') or
853 853 repo.ui.configbool('experimental', 'treemanifest') or
854 854 'treemanifest' in repo.requirements):
855 855 versions.discard('03')
856 856 return versions
857 857
858 858 # Changegroup versions that can be applied to the repo
859 859 def supportedincomingversions(repo):
860 860 return allsupportedversions(repo)
861 861
862 862 # Changegroup versions that can be created from the repo
863 863 def supportedoutgoingversions(repo):
864 864 versions = allsupportedversions(repo)
865 865 if 'treemanifest' in repo.requirements:
866 866 # Versions 01 and 02 support only flat manifests and it's just too
867 867 # expensive to convert between the flat manifest and tree manifest on
868 868 # the fly. Since tree manifests are hashed differently, all of history
869 869 # would have to be converted. Instead, we simply don't even pretend to
870 870 # support versions 01 and 02.
871 871 versions.discard('01')
872 872 versions.discard('02')
873 873 return versions
874 874
875 def localversion(repo):
876 # Finds the best version to use for bundles that are meant to be used
877 # locally, such as those from strip and shelve, and temporary bundles.
878 return max(supportedoutgoingversions(repo))
879
875 880 def safeversion(repo):
876 881 # Finds the smallest version that it's safe to assume clients of the repo
877 882 # will support. For example, all hg versions that support generaldelta also
878 883 # support changegroup 02.
879 884 versions = supportedoutgoingversions(repo)
880 885 if 'generaldelta' in repo.requirements:
881 886 versions.discard('01')
882 887 assert versions
883 888 return min(versions)
884 889
885 890 def getbundler(version, repo, bundlecaps=None):
886 891 assert version in supportedoutgoingversions(repo)
887 892 return _packermap[version][0](repo, bundlecaps)
888 893
889 894 def getunbundler(version, fh, alg, extras=None):
890 895 return _packermap[version][1](fh, alg, extras=extras)
891 896
892 897 def _changegroupinfo(repo, nodes, source):
893 898 if repo.ui.verbose or source == 'bundle':
894 899 repo.ui.status(_("%d changesets found\n") % len(nodes))
895 900 if repo.ui.debugflag:
896 901 repo.ui.debug("list of changesets:\n")
897 902 for node in nodes:
898 903 repo.ui.debug("%s\n" % hex(node))
899 904
900 905 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
901 906 repo = repo.unfiltered()
902 907 commonrevs = outgoing.common
903 908 csets = outgoing.missing
904 909 heads = outgoing.missingheads
905 910 # We go through the fast path if we get told to, or if all (unfiltered
906 911 # heads have been requested (since we then know there all linkrevs will
907 912 # be pulled by the client).
908 913 heads.sort()
909 914 fastpathlinkrev = fastpath or (
910 915 repo.filtername is None and heads == sorted(repo.heads()))
911 916
912 917 repo.hook('preoutgoing', throw=True, source=source)
913 918 _changegroupinfo(repo, csets, source)
914 919 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
915 920
916 921 def getsubset(repo, outgoing, bundler, source, fastpath=False):
917 922 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
918 923 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
919 924 {'clcount': len(outgoing.missing)})
920 925
921 926 def changegroupsubset(repo, roots, heads, source, version='01'):
922 927 """Compute a changegroup consisting of all the nodes that are
923 928 descendants of any of the roots and ancestors of any of the heads.
924 929 Return a chunkbuffer object whose read() method will return
925 930 successive changegroup chunks.
926 931
927 932 It is fairly complex as determining which filenodes and which
928 933 manifest nodes need to be included for the changeset to be complete
929 934 is non-trivial.
930 935
931 936 Another wrinkle is doing the reverse, figuring out which changeset in
932 937 the changegroup a particular filenode or manifestnode belongs to.
933 938 """
934 939 outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
935 940 bundler = getbundler(version, repo)
936 941 return getsubset(repo, outgoing, bundler, source)
937 942
938 943 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
939 944 version='01'):
940 945 """Like getbundle, but taking a discovery.outgoing as an argument.
941 946
942 947 This is only implemented for local repos and reuses potentially
943 948 precomputed sets in outgoing. Returns a raw changegroup generator."""
944 949 if not outgoing.missing:
945 950 return None
946 951 bundler = getbundler(version, repo, bundlecaps)
947 952 return getsubsetraw(repo, outgoing, bundler, source)
948 953
949 954 def getchangegroup(repo, source, outgoing, bundlecaps=None,
950 955 version='01'):
951 956 """Like getbundle, but taking a discovery.outgoing as an argument.
952 957
953 958 This is only implemented for local repos and reuses potentially
954 959 precomputed sets in outgoing."""
955 960 if not outgoing.missing:
956 961 return None
957 962 bundler = getbundler(version, repo, bundlecaps)
958 963 return getsubset(repo, outgoing, bundler, source)
959 964
960 965 def getlocalchangegroup(repo, *args, **kwargs):
961 966 repo.ui.deprecwarn('getlocalchangegroup is deprecated, use getchangegroup',
962 967 '4.3')
963 968 return getchangegroup(repo, *args, **kwargs)
964 969
965 970 def changegroup(repo, basenodes, source):
966 971 # to avoid a race we use changegroupsubset() (issue1320)
967 972 return changegroupsubset(repo, basenodes, repo.heads(), source)
968 973
969 974 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
970 975 revisions = 0
971 976 files = 0
972 977 for chunkdata in iter(source.filelogheader, {}):
973 978 files += 1
974 979 f = chunkdata["filename"]
975 980 repo.ui.debug("adding %s revisions\n" % f)
976 981 repo.ui.progress(_('files'), files, unit=_('files'),
977 982 total=expectedfiles)
978 983 fl = repo.file(f)
979 984 o = len(fl)
980 985 try:
981 986 if not fl.addgroup(source, revmap, trp):
982 987 raise error.Abort(_("received file revlog group is empty"))
983 988 except error.CensoredBaseError as e:
984 989 raise error.Abort(_("received delta base is censored: %s") % e)
985 990 revisions += len(fl) - o
986 991 if f in needfiles:
987 992 needs = needfiles[f]
988 993 for new in xrange(o, len(fl)):
989 994 n = fl.node(new)
990 995 if n in needs:
991 996 needs.remove(n)
992 997 else:
993 998 raise error.Abort(
994 999 _("received spurious file revlog entry"))
995 1000 if not needs:
996 1001 del needfiles[f]
997 1002 repo.ui.progress(_('files'), None)
998 1003
999 1004 for f, needs in needfiles.iteritems():
1000 1005 fl = repo.file(f)
1001 1006 for n in needs:
1002 1007 try:
1003 1008 fl.rev(n)
1004 1009 except error.LookupError:
1005 1010 raise error.Abort(
1006 1011 _('missing file data for %s:%s - run hg verify') %
1007 1012 (f, hex(n)))
1008 1013
1009 1014 return revisions, files
@@ -1,433 +1,433 b''
1 1 # repair.py - functions for repository repair for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 # Copyright 2007 Matt Mackall
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import hashlib
13 13
14 14 from .i18n import _
15 15 from .node import short
16 16 from . import (
17 17 bundle2,
18 18 changegroup,
19 19 discovery,
20 20 error,
21 21 exchange,
22 22 obsolete,
23 23 obsutil,
24 24 util,
25 25 )
26 26
27 27 def _bundle(repo, bases, heads, node, suffix, compress=True, obsolescence=True):
28 28 """create a bundle with the specified revisions as a backup"""
29 29
30 30 backupdir = "strip-backup"
31 31 vfs = repo.vfs
32 32 if not vfs.isdir(backupdir):
33 33 vfs.mkdir(backupdir)
34 34
35 35 # Include a hash of all the nodes in the filename for uniqueness
36 36 allcommits = repo.set('%ln::%ln', bases, heads)
37 37 allhashes = sorted(c.hex() for c in allcommits)
38 38 totalhash = hashlib.sha1(''.join(allhashes)).hexdigest()
39 39 name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
40 40
41 cgversion = changegroup.safeversion(repo)
41 cgversion = changegroup.localversion(repo)
42 42 comp = None
43 43 if cgversion != '01':
44 44 bundletype = "HG20"
45 45 if compress:
46 46 comp = 'BZ'
47 47 elif compress:
48 48 bundletype = "HG10BZ"
49 49 else:
50 50 bundletype = "HG10UN"
51 51
52 52 outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=heads)
53 53 contentopts = {
54 54 'cg.version': cgversion,
55 55 'obsolescence': obsolescence,
56 56 'phases': True,
57 57 }
58 58 return bundle2.writenewbundle(repo.ui, repo, 'strip', name, bundletype,
59 59 outgoing, contentopts, vfs, compression=comp)
60 60
61 61 def _collectfiles(repo, striprev):
62 62 """find out the filelogs affected by the strip"""
63 63 files = set()
64 64
65 65 for x in xrange(striprev, len(repo)):
66 66 files.update(repo[x].files())
67 67
68 68 return sorted(files)
69 69
70 70 def _collectbrokencsets(repo, files, striprev):
71 71 """return the changesets which will be broken by the truncation"""
72 72 s = set()
73 73 def collectone(revlog):
74 74 _, brokenset = revlog.getstrippoint(striprev)
75 75 s.update([revlog.linkrev(r) for r in brokenset])
76 76
77 77 collectone(repo.manifestlog._revlog)
78 78 for fname in files:
79 79 collectone(repo.file(fname))
80 80
81 81 return s
82 82
83 83 def strip(ui, repo, nodelist, backup=True, topic='backup'):
84 84 # This function requires the caller to lock the repo, but it operates
85 85 # within a transaction of its own, and thus requires there to be no current
86 86 # transaction when it is called.
87 87 if repo.currenttransaction() is not None:
88 88 raise error.ProgrammingError('cannot strip from inside a transaction')
89 89
90 90 # Simple way to maintain backwards compatibility for this
91 91 # argument.
92 92 if backup in ['none', 'strip']:
93 93 backup = False
94 94
95 95 repo = repo.unfiltered()
96 96 repo.destroying()
97 97
98 98 cl = repo.changelog
99 99 # TODO handle undo of merge sets
100 100 if isinstance(nodelist, str):
101 101 nodelist = [nodelist]
102 102 striplist = [cl.rev(node) for node in nodelist]
103 103 striprev = min(striplist)
104 104
105 105 files = _collectfiles(repo, striprev)
106 106 saverevs = _collectbrokencsets(repo, files, striprev)
107 107
108 108 # Some revisions with rev > striprev may not be descendants of striprev.
109 109 # We have to find these revisions and put them in a bundle, so that
110 110 # we can restore them after the truncations.
111 111 # To create the bundle we use repo.changegroupsubset which requires
112 112 # the list of heads and bases of the set of interesting revisions.
113 113 # (head = revision in the set that has no descendant in the set;
114 114 # base = revision in the set that has no ancestor in the set)
115 115 tostrip = set(striplist)
116 116 saveheads = set(saverevs)
117 117 for r in cl.revs(start=striprev + 1):
118 118 if any(p in tostrip for p in cl.parentrevs(r)):
119 119 tostrip.add(r)
120 120
121 121 if r not in tostrip:
122 122 saverevs.add(r)
123 123 saveheads.difference_update(cl.parentrevs(r))
124 124 saveheads.add(r)
125 125 saveheads = [cl.node(r) for r in saveheads]
126 126
127 127 # compute base nodes
128 128 if saverevs:
129 129 descendants = set(cl.descendants(saverevs))
130 130 saverevs.difference_update(descendants)
131 131 savebases = [cl.node(r) for r in saverevs]
132 132 stripbases = [cl.node(r) for r in tostrip]
133 133
134 134 stripobsidx = obsmarkers = ()
135 135 if repo.ui.configbool('devel', 'strip-obsmarkers'):
136 136 obsmarkers = obsutil.exclusivemarkers(repo, stripbases)
137 137 if obsmarkers:
138 138 stripobsidx = [i for i, m in enumerate(repo.obsstore)
139 139 if m in obsmarkers]
140 140
141 141 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
142 142 # is much faster
143 143 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
144 144 if newbmtarget:
145 145 newbmtarget = repo[newbmtarget.first()].node()
146 146 else:
147 147 newbmtarget = '.'
148 148
149 149 bm = repo._bookmarks
150 150 updatebm = []
151 151 for m in bm:
152 152 rev = repo[bm[m]].rev()
153 153 if rev in tostrip:
154 154 updatebm.append(m)
155 155
156 156 # create a changegroup for all the branches we need to keep
157 157 backupfile = None
158 158 vfs = repo.vfs
159 159 node = nodelist[-1]
160 160 if backup:
161 161 backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
162 162 repo.ui.status(_("saved backup bundle to %s\n") %
163 163 vfs.join(backupfile))
164 164 repo.ui.log("backupbundle", "saved backup bundle to %s\n",
165 165 vfs.join(backupfile))
166 166 tmpbundlefile = None
167 167 if saveheads:
168 168 # do not compress temporary bundle if we remove it from disk later
169 169 #
170 170 # We do not include obsolescence, it might re-introduce prune markers
171 171 # we are trying to strip. This is harmless since the stripped markers
172 172 # are already backed up and we did not touched the markers for the
173 173 # saved changesets.
174 174 tmpbundlefile = _bundle(repo, savebases, saveheads, node, 'temp',
175 175 compress=False, obsolescence=False)
176 176
177 177 mfst = repo.manifestlog._revlog
178 178
179 179 try:
180 180 with repo.transaction("strip") as tr:
181 181 offset = len(tr.entries)
182 182
183 183 tr.startgroup()
184 184 cl.strip(striprev, tr)
185 185 mfst.strip(striprev, tr)
186 186 striptrees(repo, tr, striprev, files)
187 187
188 188 for fn in files:
189 189 repo.file(fn).strip(striprev, tr)
190 190 tr.endgroup()
191 191
192 192 for i in xrange(offset, len(tr.entries)):
193 193 file, troffset, ignore = tr.entries[i]
194 194 with repo.svfs(file, 'a', checkambig=True) as fp:
195 195 fp.truncate(troffset)
196 196 if troffset == 0:
197 197 repo.store.markremoved(file)
198 198
199 199 deleteobsmarkers(repo.obsstore, stripobsidx)
200 200 del repo.obsstore
201 201
202 202 repo._phasecache.filterunknown(repo)
203 203 if tmpbundlefile:
204 204 ui.note(_("adding branch\n"))
205 205 f = vfs.open(tmpbundlefile, "rb")
206 206 gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
207 207 if not repo.ui.verbose:
208 208 # silence internal shuffling chatter
209 209 repo.ui.pushbuffer()
210 210 tmpbundleurl = 'bundle:' + vfs.join(tmpbundlefile)
211 211 txnname = 'strip'
212 212 if not isinstance(gen, bundle2.unbundle20):
213 213 txnname = "strip\n%s" % util.hidepassword(tmpbundleurl)
214 214 with repo.transaction(txnname) as tr:
215 215 bundle2.applybundle(repo, gen, tr, source='strip',
216 216 url=tmpbundleurl)
217 217 if not repo.ui.verbose:
218 218 repo.ui.popbuffer()
219 219 f.close()
220 220 repo._phasecache.invalidate()
221 221
222 222
223 223 with repo.transaction('repair') as tr:
224 224 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
225 225 bm.applychanges(repo, tr, bmchanges)
226 226
227 227 # remove undo files
228 228 for undovfs, undofile in repo.undofiles():
229 229 try:
230 230 undovfs.unlink(undofile)
231 231 except OSError as e:
232 232 if e.errno != errno.ENOENT:
233 233 ui.warn(_('error removing %s: %s\n') %
234 234 (undovfs.join(undofile), str(e)))
235 235
236 236 except: # re-raises
237 237 if backupfile:
238 238 ui.warn(_("strip failed, backup bundle stored in '%s'\n")
239 239 % vfs.join(backupfile))
240 240 if tmpbundlefile:
241 241 ui.warn(_("strip failed, unrecovered changes stored in '%s'\n")
242 242 % vfs.join(tmpbundlefile))
243 243 ui.warn(_("(fix the problem, then recover the changesets with "
244 244 "\"hg unbundle '%s'\")\n") % vfs.join(tmpbundlefile))
245 245 raise
246 246 else:
247 247 if tmpbundlefile:
248 248 # Remove temporary bundle only if there were no exceptions
249 249 vfs.unlink(tmpbundlefile)
250 250
251 251 repo.destroyed()
252 252 # return the backup file path (or None if 'backup' was False) so
253 253 # extensions can use it
254 254 return backupfile
255 255
256 256 def safestriproots(ui, repo, nodes):
257 257 """return list of roots of nodes where descendants are covered by nodes"""
258 258 torev = repo.unfiltered().changelog.rev
259 259 revs = set(torev(n) for n in nodes)
260 260 # tostrip = wanted - unsafe = wanted - ancestors(orphaned)
261 261 # orphaned = affected - wanted
262 262 # affected = descendants(roots(wanted))
263 263 # wanted = revs
264 264 tostrip = set(repo.revs('%ld-(::((roots(%ld)::)-%ld))', revs, revs, revs))
265 265 notstrip = revs - tostrip
266 266 if notstrip:
267 267 nodestr = ', '.join(sorted(short(repo[n].node()) for n in notstrip))
268 268 ui.warn(_('warning: orphaned descendants detected, '
269 269 'not stripping %s\n') % nodestr)
270 270 return [c.node() for c in repo.set('roots(%ld)', tostrip)]
271 271
272 272 class stripcallback(object):
273 273 """used as a transaction postclose callback"""
274 274
275 275 def __init__(self, ui, repo, backup, topic):
276 276 self.ui = ui
277 277 self.repo = repo
278 278 self.backup = backup
279 279 self.topic = topic or 'backup'
280 280 self.nodelist = []
281 281
282 282 def addnodes(self, nodes):
283 283 self.nodelist.extend(nodes)
284 284
285 285 def __call__(self, tr):
286 286 roots = safestriproots(self.ui, self.repo, self.nodelist)
287 287 if roots:
288 288 strip(self.ui, self.repo, roots, self.backup, self.topic)
289 289
290 290 def delayedstrip(ui, repo, nodelist, topic=None):
291 291 """like strip, but works inside transaction and won't strip irreverent revs
292 292
293 293 nodelist must explicitly contain all descendants. Otherwise a warning will
294 294 be printed that some nodes are not stripped.
295 295
296 296 Always do a backup. The last non-None "topic" will be used as the backup
297 297 topic name. The default backup topic name is "backup".
298 298 """
299 299 tr = repo.currenttransaction()
300 300 if not tr:
301 301 nodes = safestriproots(ui, repo, nodelist)
302 302 return strip(ui, repo, nodes, True, topic)
303 303 # transaction postclose callbacks are called in alphabet order.
304 304 # use '\xff' as prefix so we are likely to be called last.
305 305 callback = tr.getpostclose('\xffstrip')
306 306 if callback is None:
307 307 callback = stripcallback(ui, repo, True, topic)
308 308 tr.addpostclose('\xffstrip', callback)
309 309 if topic:
310 310 callback.topic = topic
311 311 callback.addnodes(nodelist)
312 312
313 313 def striptrees(repo, tr, striprev, files):
314 314 if 'treemanifest' in repo.requirements: # safe but unnecessary
315 315 # otherwise
316 316 for unencoded, encoded, size in repo.store.datafiles():
317 317 if (unencoded.startswith('meta/') and
318 318 unencoded.endswith('00manifest.i')):
319 319 dir = unencoded[5:-12]
320 320 repo.manifestlog._revlog.dirlog(dir).strip(striprev, tr)
321 321
322 322 def rebuildfncache(ui, repo):
323 323 """Rebuilds the fncache file from repo history.
324 324
325 325 Missing entries will be added. Extra entries will be removed.
326 326 """
327 327 repo = repo.unfiltered()
328 328
329 329 if 'fncache' not in repo.requirements:
330 330 ui.warn(_('(not rebuilding fncache because repository does not '
331 331 'support fncache)\n'))
332 332 return
333 333
334 334 with repo.lock():
335 335 fnc = repo.store.fncache
336 336 # Trigger load of fncache.
337 337 if 'irrelevant' in fnc:
338 338 pass
339 339
340 340 oldentries = set(fnc.entries)
341 341 newentries = set()
342 342 seenfiles = set()
343 343
344 344 repolen = len(repo)
345 345 for rev in repo:
346 346 ui.progress(_('rebuilding'), rev, total=repolen,
347 347 unit=_('changesets'))
348 348
349 349 ctx = repo[rev]
350 350 for f in ctx.files():
351 351 # This is to minimize I/O.
352 352 if f in seenfiles:
353 353 continue
354 354 seenfiles.add(f)
355 355
356 356 i = 'data/%s.i' % f
357 357 d = 'data/%s.d' % f
358 358
359 359 if repo.store._exists(i):
360 360 newentries.add(i)
361 361 if repo.store._exists(d):
362 362 newentries.add(d)
363 363
364 364 ui.progress(_('rebuilding'), None)
365 365
366 366 if 'treemanifest' in repo.requirements: # safe but unnecessary otherwise
367 367 for dir in util.dirs(seenfiles):
368 368 i = 'meta/%s/00manifest.i' % dir
369 369 d = 'meta/%s/00manifest.d' % dir
370 370
371 371 if repo.store._exists(i):
372 372 newentries.add(i)
373 373 if repo.store._exists(d):
374 374 newentries.add(d)
375 375
376 376 addcount = len(newentries - oldentries)
377 377 removecount = len(oldentries - newentries)
378 378 for p in sorted(oldentries - newentries):
379 379 ui.write(_('removing %s\n') % p)
380 380 for p in sorted(newentries - oldentries):
381 381 ui.write(_('adding %s\n') % p)
382 382
383 383 if addcount or removecount:
384 384 ui.write(_('%d items added, %d removed from fncache\n') %
385 385 (addcount, removecount))
386 386 fnc.entries = newentries
387 387 fnc._dirty = True
388 388
389 389 with repo.transaction('fncache') as tr:
390 390 fnc.write(tr)
391 391 else:
392 392 ui.write(_('fncache already up to date\n'))
393 393
394 394 def stripbmrevset(repo, mark):
395 395 """
396 396 The revset to strip when strip is called with -B mark
397 397
398 398 Needs to live here so extensions can use it and wrap it even when strip is
399 399 not enabled or not present on a box.
400 400 """
401 401 return repo.revs("ancestors(bookmark(%s)) - "
402 402 "ancestors(head() and not bookmark(%s)) - "
403 403 "ancestors(bookmark() and not bookmark(%s))",
404 404 mark, mark, mark)
405 405
406 406 def deleteobsmarkers(obsstore, indices):
407 407 """Delete some obsmarkers from obsstore and return how many were deleted
408 408
409 409 'indices' is a list of ints which are the indices
410 410 of the markers to be deleted.
411 411
412 412 Every invocation of this function completely rewrites the obsstore file,
413 413 skipping the markers we want to be removed. The new temporary file is
414 414 created, remaining markers are written there and on .close() this file
415 415 gets atomically renamed to obsstore, thus guaranteeing consistency."""
416 416 if not indices:
417 417 # we don't want to rewrite the obsstore with the same content
418 418 return
419 419
420 420 left = []
421 421 current = obsstore._all
422 422 n = 0
423 423 for i, m in enumerate(current):
424 424 if i in indices:
425 425 n += 1
426 426 continue
427 427 left.append(m)
428 428
429 429 newobsstorefile = obsstore.svfs('obsstore', 'w', atomictemp=True)
430 430 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
431 431 newobsstorefile.write(bytes)
432 432 newobsstorefile.close()
433 433 return n
@@ -1,983 +1,982 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > drawdag=$TESTDIR/drawdag.py
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
11 11 > EOF
12 12
13 13
14 14 $ hg init a
15 15 $ cd a
16 16 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
17 17 adding changesets
18 18 adding manifests
19 19 adding file changes
20 20 added 8 changesets with 7 changes to 7 files (+2 heads)
21 21 (run 'hg heads' to see heads, 'hg merge' to merge)
22 22 $ hg up tip
23 23 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 24 $ cd ..
25 25
26 26
27 27 Rebasing
28 28 D onto H - simple rebase:
29 29 (this also tests that editor is invoked if '--edit' is specified, and that we
30 30 can abort or warn for colliding untracked files)
31 31
32 32 $ hg clone -q -u . a a1
33 33 $ cd a1
34 34
35 35 $ hg tglog
36 36 @ 7: 'H'
37 37 |
38 38 | o 6: 'G'
39 39 |/|
40 40 o | 5: 'F'
41 41 | |
42 42 | o 4: 'E'
43 43 |/
44 44 | o 3: 'D'
45 45 | |
46 46 | o 2: 'C'
47 47 | |
48 48 | o 1: 'B'
49 49 |/
50 50 o 0: 'A'
51 51
52 52
53 53 $ hg status --rev "3^1" --rev 3
54 54 A D
55 55 $ echo collide > D
56 56 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit --config merge.checkunknown=warn
57 57 rebasing 3:32af7686d403 "D"
58 58 D: replacing untracked file
59 59 D
60 60
61 61
62 62 HG: Enter commit message. Lines beginning with 'HG:' are removed.
63 63 HG: Leave message empty to abort commit.
64 64 HG: --
65 65 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
66 66 HG: branch 'default'
67 67 HG: added D
68 68 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32af7686d403-6f7dface-rebase.hg (glob)
69 69 $ cat D.orig
70 70 collide
71 71 $ rm D.orig
72 72
73 73 $ hg tglog
74 74 o 7: 'D'
75 75 |
76 76 @ 6: 'H'
77 77 |
78 78 | o 5: 'G'
79 79 |/|
80 80 o | 4: 'F'
81 81 | |
82 82 | o 3: 'E'
83 83 |/
84 84 | o 2: 'C'
85 85 | |
86 86 | o 1: 'B'
87 87 |/
88 88 o 0: 'A'
89 89
90 90 $ cd ..
91 91
92 92
93 93 D onto F - intermediate point:
94 94 (this also tests that editor is not invoked if '--edit' is not specified, and
95 95 that we can ignore for colliding untracked files)
96 96
97 97 $ hg clone -q -u . a a2
98 98 $ cd a2
99 99 $ echo collide > D
100 100
101 101 $ HGEDITOR=cat hg rebase -s 3 -d 5 --config merge.checkunknown=ignore
102 102 rebasing 3:32af7686d403 "D"
103 103 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/32af7686d403-6f7dface-rebase.hg (glob)
104 104 $ cat D.orig
105 105 collide
106 106 $ rm D.orig
107 107
108 108 $ hg tglog
109 109 o 7: 'D'
110 110 |
111 111 | @ 6: 'H'
112 112 |/
113 113 | o 5: 'G'
114 114 |/|
115 115 o | 4: 'F'
116 116 | |
117 117 | o 3: 'E'
118 118 |/
119 119 | o 2: 'C'
120 120 | |
121 121 | o 1: 'B'
122 122 |/
123 123 o 0: 'A'
124 124
125 125 $ cd ..
126 126
127 127
128 128 E onto H - skip of G:
129 129 (this also tests that we can overwrite untracked files and don't create backups
130 130 if they have the same contents)
131 131
132 132 $ hg clone -q -u . a a3
133 133 $ cd a3
134 134 $ hg cat -r 4 E | tee E
135 135 E
136 136
137 137 $ hg rebase -s 4 -d 7
138 138 rebasing 4:9520eea781bc "E"
139 139 rebasing 6:eea13746799a "G"
140 140 note: rebase of 6:eea13746799a created no changes to commit
141 141 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/9520eea781bc-fcd8edd4-rebase.hg (glob)
142 142 $ f E.orig
143 143 E.orig: file not found
144 144
145 145 $ hg tglog
146 146 o 6: 'E'
147 147 |
148 148 @ 5: 'H'
149 149 |
150 150 o 4: 'F'
151 151 |
152 152 | o 3: 'D'
153 153 | |
154 154 | o 2: 'C'
155 155 | |
156 156 | o 1: 'B'
157 157 |/
158 158 o 0: 'A'
159 159
160 160 $ cd ..
161 161
162 162
163 163 F onto E - rebase of a branching point (skip G):
164 164
165 165 $ hg clone -q -u . a a4
166 166 $ cd a4
167 167
168 168 $ hg rebase -s 5 -d 4
169 169 rebasing 5:24b6387c8c8c "F"
170 170 rebasing 6:eea13746799a "G"
171 171 note: rebase of 6:eea13746799a created no changes to commit
172 172 rebasing 7:02de42196ebe "H" (tip)
173 173 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/24b6387c8c8c-c3fe765d-rebase.hg (glob)
174 174
175 175 $ hg tglog
176 176 @ 6: 'H'
177 177 |
178 178 o 5: 'F'
179 179 |
180 180 o 4: 'E'
181 181 |
182 182 | o 3: 'D'
183 183 | |
184 184 | o 2: 'C'
185 185 | |
186 186 | o 1: 'B'
187 187 |/
188 188 o 0: 'A'
189 189
190 190 $ cd ..
191 191
192 192
193 193 G onto H - merged revision having a parent in ancestors of target:
194 194
195 195 $ hg clone -q -u . a a5
196 196 $ cd a5
197 197
198 198 $ hg rebase -s 6 -d 7
199 199 rebasing 6:eea13746799a "G"
200 200 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/eea13746799a-883828ed-rebase.hg (glob)
201 201
202 202 $ hg tglog
203 203 o 7: 'G'
204 204 |\
205 205 | @ 6: 'H'
206 206 | |
207 207 | o 5: 'F'
208 208 | |
209 209 o | 4: 'E'
210 210 |/
211 211 | o 3: 'D'
212 212 | |
213 213 | o 2: 'C'
214 214 | |
215 215 | o 1: 'B'
216 216 |/
217 217 o 0: 'A'
218 218
219 219 $ cd ..
220 220
221 221
222 222 F onto B - G maintains E as parent:
223 223
224 224 $ hg clone -q -u . a a6
225 225 $ cd a6
226 226
227 227 $ hg rebase -s 5 -d 1
228 228 rebasing 5:24b6387c8c8c "F"
229 229 rebasing 6:eea13746799a "G"
230 230 rebasing 7:02de42196ebe "H" (tip)
231 231 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/24b6387c8c8c-c3fe765d-rebase.hg (glob)
232 232
233 233 $ hg tglog
234 234 @ 7: 'H'
235 235 |
236 236 | o 6: 'G'
237 237 |/|
238 238 o | 5: 'F'
239 239 | |
240 240 | o 4: 'E'
241 241 | |
242 242 | | o 3: 'D'
243 243 | | |
244 244 +---o 2: 'C'
245 245 | |
246 246 o | 1: 'B'
247 247 |/
248 248 o 0: 'A'
249 249
250 250 $ cd ..
251 251
252 252
253 253 These will fail (using --source):
254 254
255 255 G onto F - rebase onto an ancestor:
256 256
257 257 $ hg clone -q -u . a a7
258 258 $ cd a7
259 259
260 260 $ hg rebase -s 6 -d 5
261 261 nothing to rebase
262 262 [1]
263 263
264 264 F onto G - rebase onto a descendant:
265 265
266 266 $ hg rebase -s 5 -d 6
267 267 abort: source is ancestor of destination
268 268 [255]
269 269
270 270 G onto B - merge revision with both parents not in ancestors of target:
271 271
272 272 $ hg rebase -s 6 -d 1
273 273 rebasing 6:eea13746799a "G"
274 274 abort: cannot use revision 6 as base, result would have 3 parents
275 275 [255]
276 276 $ hg rebase --abort
277 277 rebase aborted
278 278
279 279 These will abort gracefully (using --base):
280 280
281 281 G onto G - rebase onto same changeset:
282 282
283 283 $ hg rebase -b 6 -d 6
284 284 nothing to rebase - eea13746799a is both "base" and destination
285 285 [1]
286 286
287 287 G onto F - rebase onto an ancestor:
288 288
289 289 $ hg rebase -b 6 -d 5
290 290 nothing to rebase
291 291 [1]
292 292
293 293 F onto G - rebase onto a descendant:
294 294
295 295 $ hg rebase -b 5 -d 6
296 296 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
297 297 [1]
298 298
299 299 C onto A - rebase onto an ancestor:
300 300
301 301 $ hg rebase -d 0 -s 2
302 302 rebasing 2:5fddd98957c8 "C"
303 303 rebasing 3:32af7686d403 "D"
304 304 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-f9244fa1-rebase.hg (glob)
305 305 $ hg tglog
306 306 o 7: 'D'
307 307 |
308 308 o 6: 'C'
309 309 |
310 310 | @ 5: 'H'
311 311 | |
312 312 | | o 4: 'G'
313 313 | |/|
314 314 | o | 3: 'F'
315 315 |/ /
316 316 | o 2: 'E'
317 317 |/
318 318 | o 1: 'B'
319 319 |/
320 320 o 0: 'A'
321 321
322 322
323 323 Check rebasing public changeset
324 324
325 325 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
326 326 $ hg rebase -d 0 -b 6
327 327 nothing to rebase
328 328 [1]
329 329 $ hg rebase -d 5 -b 6
330 330 abort: can't rebase public changeset e1c4361dd923
331 331 (see 'hg help phases' for details)
332 332 [255]
333 333 $ hg rebase -d 5 -r '1 + (6::)'
334 334 abort: can't rebase public changeset e1c4361dd923
335 335 (see 'hg help phases' for details)
336 336 [255]
337 337
338 338 $ hg rebase -d 5 -b 6 --keep
339 339 rebasing 6:e1c4361dd923 "C"
340 340 rebasing 7:c9659aac0000 "D" (tip)
341 341
342 342 Check rebasing mutable changeset
343 343 Source phase greater or equal to destination phase: new changeset get the phase of source:
344 344 $ hg id -n
345 345 5
346 346 $ hg rebase -s9 -d0
347 347 rebasing 9:2b23e52411f4 "D" (tip)
348 348 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-f942decf-rebase.hg (glob)
349 349 $ hg id -n # check we updated back to parent
350 350 5
351 351 $ hg log --template "{phase}\n" -r 9
352 352 draft
353 353 $ hg rebase -s9 -d1
354 354 rebasing 9:2cb10d0cfc6c "D" (tip)
355 355 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-ddb0f256-rebase.hg (glob)
356 356 $ hg log --template "{phase}\n" -r 9
357 357 draft
358 358 $ hg phase --force --secret 9
359 359 $ hg rebase -s9 -d0
360 360 rebasing 9:c5b12b67163a "D" (tip)
361 361 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-4e372053-rebase.hg (glob)
362 362 $ hg log --template "{phase}\n" -r 9
363 363 secret
364 364 $ hg rebase -s9 -d1
365 365 rebasing 9:2a0524f868ac "D" (tip)
366 366 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-cefd8574-rebase.hg (glob)
367 367 $ hg log --template "{phase}\n" -r 9
368 368 secret
369 369 Source phase lower than destination phase: new changeset get the phase of destination:
370 370 $ hg rebase -s8 -d9
371 371 rebasing 8:6d4f22462821 "C"
372 372 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-3441f70b-rebase.hg (glob)
373 373 $ hg log --template "{phase}\n" -r 'rev(9)'
374 374 secret
375 375
376 376 $ cd ..
377 377
378 378 Check that temporary bundle doesn't lose phase when not using generaldelta
379 379
380 380 $ hg --config format.usegeneraldelta=no init issue5678
381 381 $ cd issue5678
382 382 $ grep generaldelta .hg/requires
383 383 [1]
384 384 $ echo a > a
385 385 $ hg ci -Aqm a
386 386 $ echo b > b
387 387 $ hg ci -Aqm b
388 388 $ hg co -q '.^'
389 389 $ echo c > c
390 390 $ hg ci -Aqm c
391 391 $ hg phase --public
392 392 $ hg log -G -T '{rev}:{node|shortest} {phase} {desc}\n'
393 393 @ 2:d36c public c
394 394 |
395 395 | o 1:d2ae draft b
396 396 |/
397 397 o 0:cb9a public a
398 398
399 399 $ hg rebase -s 1 -d 2
400 400 rebasing 1:d2ae7f538514 "b"
401 401 saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/d2ae7f538514-2953539b-rebase.hg (glob)
402 BROKEN: d36c should remain public
403 402 $ hg log -G -T '{rev}:{node|shortest} {phase} {desc}\n'
404 403 o 2:c882 draft b
405 404 |
406 @ 1:d36c draft c
405 @ 1:d36c public c
407 406 |
408 407 o 0:cb9a public a
409 408
410 409 $ cd ..
411 410
412 411 Test for revset
413 412
414 413 We need a bit different graph
415 414 All destination are B
416 415
417 416 $ hg init ah
418 417 $ cd ah
419 418 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
420 419 adding changesets
421 420 adding manifests
422 421 adding file changes
423 422 added 9 changesets with 9 changes to 9 files (+2 heads)
424 423 (run 'hg heads' to see heads, 'hg merge' to merge)
425 424 $ hg tglog
426 425 o 8: 'I'
427 426 |
428 427 o 7: 'H'
429 428 |
430 429 o 6: 'G'
431 430 |
432 431 | o 5: 'F'
433 432 | |
434 433 | o 4: 'E'
435 434 |/
436 435 o 3: 'D'
437 436 |
438 437 o 2: 'C'
439 438 |
440 439 | o 1: 'B'
441 440 |/
442 441 o 0: 'A'
443 442
444 443 $ cd ..
445 444
446 445
447 446 Simple case with keep:
448 447
449 448 Source on have two descendant heads but ask for one
450 449
451 450 $ hg clone -q -u . ah ah1
452 451 $ cd ah1
453 452 $ hg rebase -r '2::8' -d 1
454 453 abort: can't remove original changesets with unrebased descendants
455 454 (use --keep to keep original changesets)
456 455 [255]
457 456 $ hg rebase -r '2::8' -d 1 -k
458 457 rebasing 2:c9e50f6cdc55 "C"
459 458 rebasing 3:ffd453c31098 "D"
460 459 rebasing 6:3d8a618087a7 "G"
461 460 rebasing 7:72434a4e60b0 "H"
462 461 rebasing 8:479ddb54a924 "I" (tip)
463 462 $ hg tglog
464 463 o 13: 'I'
465 464 |
466 465 o 12: 'H'
467 466 |
468 467 o 11: 'G'
469 468 |
470 469 o 10: 'D'
471 470 |
472 471 o 9: 'C'
473 472 |
474 473 | o 8: 'I'
475 474 | |
476 475 | o 7: 'H'
477 476 | |
478 477 | o 6: 'G'
479 478 | |
480 479 | | o 5: 'F'
481 480 | | |
482 481 | | o 4: 'E'
483 482 | |/
484 483 | o 3: 'D'
485 484 | |
486 485 | o 2: 'C'
487 486 | |
488 487 o | 1: 'B'
489 488 |/
490 489 o 0: 'A'
491 490
492 491
493 492 $ cd ..
494 493
495 494 Base on have one descendant heads we ask for but common ancestor have two
496 495
497 496 $ hg clone -q -u . ah ah2
498 497 $ cd ah2
499 498 $ hg rebase -r '3::8' -d 1
500 499 abort: can't remove original changesets with unrebased descendants
501 500 (use --keep to keep original changesets)
502 501 [255]
503 502 $ hg rebase -r '3::8' -d 1 --keep
504 503 rebasing 3:ffd453c31098 "D"
505 504 rebasing 6:3d8a618087a7 "G"
506 505 rebasing 7:72434a4e60b0 "H"
507 506 rebasing 8:479ddb54a924 "I" (tip)
508 507 $ hg tglog
509 508 o 12: 'I'
510 509 |
511 510 o 11: 'H'
512 511 |
513 512 o 10: 'G'
514 513 |
515 514 o 9: 'D'
516 515 |
517 516 | o 8: 'I'
518 517 | |
519 518 | o 7: 'H'
520 519 | |
521 520 | o 6: 'G'
522 521 | |
523 522 | | o 5: 'F'
524 523 | | |
525 524 | | o 4: 'E'
526 525 | |/
527 526 | o 3: 'D'
528 527 | |
529 528 | o 2: 'C'
530 529 | |
531 530 o | 1: 'B'
532 531 |/
533 532 o 0: 'A'
534 533
535 534
536 535 $ cd ..
537 536
538 537 rebase subset
539 538
540 539 $ hg clone -q -u . ah ah3
541 540 $ cd ah3
542 541 $ hg rebase -r '3::7' -d 1
543 542 abort: can't remove original changesets with unrebased descendants
544 543 (use --keep to keep original changesets)
545 544 [255]
546 545 $ hg rebase -r '3::7' -d 1 --keep
547 546 rebasing 3:ffd453c31098 "D"
548 547 rebasing 6:3d8a618087a7 "G"
549 548 rebasing 7:72434a4e60b0 "H"
550 549 $ hg tglog
551 550 o 11: 'H'
552 551 |
553 552 o 10: 'G'
554 553 |
555 554 o 9: 'D'
556 555 |
557 556 | o 8: 'I'
558 557 | |
559 558 | o 7: 'H'
560 559 | |
561 560 | o 6: 'G'
562 561 | |
563 562 | | o 5: 'F'
564 563 | | |
565 564 | | o 4: 'E'
566 565 | |/
567 566 | o 3: 'D'
568 567 | |
569 568 | o 2: 'C'
570 569 | |
571 570 o | 1: 'B'
572 571 |/
573 572 o 0: 'A'
574 573
575 574
576 575 $ cd ..
577 576
578 577 rebase subset with multiple head
579 578
580 579 $ hg clone -q -u . ah ah4
581 580 $ cd ah4
582 581 $ hg rebase -r '3::(7+5)' -d 1
583 582 abort: can't remove original changesets with unrebased descendants
584 583 (use --keep to keep original changesets)
585 584 [255]
586 585 $ hg rebase -r '3::(7+5)' -d 1 --keep
587 586 rebasing 3:ffd453c31098 "D"
588 587 rebasing 4:c01897464e7f "E"
589 588 rebasing 5:41bfcc75ed73 "F"
590 589 rebasing 6:3d8a618087a7 "G"
591 590 rebasing 7:72434a4e60b0 "H"
592 591 $ hg tglog
593 592 o 13: 'H'
594 593 |
595 594 o 12: 'G'
596 595 |
597 596 | o 11: 'F'
598 597 | |
599 598 | o 10: 'E'
600 599 |/
601 600 o 9: 'D'
602 601 |
603 602 | o 8: 'I'
604 603 | |
605 604 | o 7: 'H'
606 605 | |
607 606 | o 6: 'G'
608 607 | |
609 608 | | o 5: 'F'
610 609 | | |
611 610 | | o 4: 'E'
612 611 | |/
613 612 | o 3: 'D'
614 613 | |
615 614 | o 2: 'C'
616 615 | |
617 616 o | 1: 'B'
618 617 |/
619 618 o 0: 'A'
620 619
621 620
622 621 $ cd ..
623 622
624 623 More advanced tests
625 624
626 625 rebase on ancestor with revset
627 626
628 627 $ hg clone -q -u . ah ah5
629 628 $ cd ah5
630 629 $ hg rebase -r '6::' -d 2
631 630 rebasing 6:3d8a618087a7 "G"
632 631 rebasing 7:72434a4e60b0 "H"
633 632 rebasing 8:479ddb54a924 "I" (tip)
634 633 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-b4f73f31-rebase.hg (glob)
635 634 $ hg tglog
636 635 o 8: 'I'
637 636 |
638 637 o 7: 'H'
639 638 |
640 639 o 6: 'G'
641 640 |
642 641 | o 5: 'F'
643 642 | |
644 643 | o 4: 'E'
645 644 | |
646 645 | o 3: 'D'
647 646 |/
648 647 o 2: 'C'
649 648 |
650 649 | o 1: 'B'
651 650 |/
652 651 o 0: 'A'
653 652
654 653 $ cd ..
655 654
656 655
657 656 rebase with multiple root.
658 657 We rebase E and G on B
659 658 We would expect heads are I, F if it was supported
660 659
661 660 $ hg clone -q -u . ah ah6
662 661 $ cd ah6
663 662 $ hg rebase -r '(4+6)::' -d 1
664 663 rebasing 4:c01897464e7f "E"
665 664 rebasing 5:41bfcc75ed73 "F"
666 665 rebasing 6:3d8a618087a7 "G"
667 666 rebasing 7:72434a4e60b0 "H"
668 667 rebasing 8:479ddb54a924 "I" (tip)
669 668 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-aae93a24-rebase.hg (glob)
670 669 $ hg tglog
671 670 o 8: 'I'
672 671 |
673 672 o 7: 'H'
674 673 |
675 674 o 6: 'G'
676 675 |
677 676 | o 5: 'F'
678 677 | |
679 678 | o 4: 'E'
680 679 |/
681 680 | o 3: 'D'
682 681 | |
683 682 | o 2: 'C'
684 683 | |
685 684 o | 1: 'B'
686 685 |/
687 686 o 0: 'A'
688 687
689 688 $ cd ..
690 689
691 690 More complex rebase with multiple roots
692 691 each root have a different common ancestor with the destination and this is a detach
693 692
694 693 (setup)
695 694
696 695 $ hg clone -q -u . a a8
697 696 $ cd a8
698 697 $ echo I > I
699 698 $ hg add I
700 699 $ hg commit -m I
701 700 $ hg up 4
702 701 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
703 702 $ echo I > J
704 703 $ hg add J
705 704 $ hg commit -m J
706 705 created new head
707 706 $ echo I > K
708 707 $ hg add K
709 708 $ hg commit -m K
710 709 $ hg tglog
711 710 @ 10: 'K'
712 711 |
713 712 o 9: 'J'
714 713 |
715 714 | o 8: 'I'
716 715 | |
717 716 | o 7: 'H'
718 717 | |
719 718 +---o 6: 'G'
720 719 | |/
721 720 | o 5: 'F'
722 721 | |
723 722 o | 4: 'E'
724 723 |/
725 724 | o 3: 'D'
726 725 | |
727 726 | o 2: 'C'
728 727 | |
729 728 | o 1: 'B'
730 729 |/
731 730 o 0: 'A'
732 731
733 732 (actual test)
734 733
735 734 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
736 735 rebasing 8:e7ec4e813ba6 "I"
737 736 rebasing 10:23a4ace37988 "K" (tip)
738 737 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-b06984b3-rebase.hg (glob)
739 738 $ hg log --rev 'children(desc(G))'
740 739 changeset: 9:adb617877056
741 740 parent: 6:eea13746799a
742 741 user: test
743 742 date: Thu Jan 01 00:00:00 1970 +0000
744 743 summary: I
745 744
746 745 changeset: 10:882431a34a0e
747 746 tag: tip
748 747 parent: 6:eea13746799a
749 748 user: test
750 749 date: Thu Jan 01 00:00:00 1970 +0000
751 750 summary: K
752 751
753 752 $ hg tglog
754 753 @ 10: 'K'
755 754 |
756 755 | o 9: 'I'
757 756 |/
758 757 | o 8: 'J'
759 758 | |
760 759 | | o 7: 'H'
761 760 | | |
762 761 o---+ 6: 'G'
763 762 |/ /
764 763 | o 5: 'F'
765 764 | |
766 765 o | 4: 'E'
767 766 |/
768 767 | o 3: 'D'
769 768 | |
770 769 | o 2: 'C'
771 770 | |
772 771 | o 1: 'B'
773 772 |/
774 773 o 0: 'A'
775 774
776 775
777 776 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
778 777
779 778 $ cd ..
780 779 $ hg init cwd-vanish
781 780 $ cd cwd-vanish
782 781 $ touch initial-file
783 782 $ hg add initial-file
784 783 $ hg commit -m 'initial commit'
785 784 $ touch dest-file
786 785 $ hg add dest-file
787 786 $ hg commit -m 'dest commit'
788 787 $ hg up 0
789 788 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
790 789 $ touch other-file
791 790 $ hg add other-file
792 791 $ hg commit -m 'first source commit'
793 792 created new head
794 793 $ mkdir subdir
795 794 $ cd subdir
796 795 $ touch subfile
797 796 $ hg add subfile
798 797 $ hg commit -m 'second source with subdir'
799 798
800 799 $ hg rebase -b . -d 1 --traceback
801 800 rebasing 2:779a07b1b7a0 "first source commit"
802 801 current directory was removed (rmcwd !)
803 802 (consider changing to repo root: $TESTTMP/cwd-vanish) (rmcwd !)
804 803 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
805 804 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-rebase.hg (glob)
806 805
807 806 Get back to the root of cwd-vanish. Note that even though `cd ..`
808 807 works on most systems, it does not work on FreeBSD 10, so we use an
809 808 absolute path to get back to the repository.
810 809 $ cd $TESTTMP
811 810
812 811 Test that rebase is done in topo order (issue5370)
813 812
814 813 $ hg init order
815 814 $ cd order
816 815 $ touch a && hg add a && hg ci -m A
817 816 $ touch b && hg add b && hg ci -m B
818 817 $ touch c && hg add c && hg ci -m C
819 818 $ hg up 1
820 819 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
821 820 $ touch d && hg add d && hg ci -m D
822 821 created new head
823 822 $ hg up 2
824 823 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
825 824 $ touch e && hg add e && hg ci -m E
826 825 $ hg up 3
827 826 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
828 827 $ touch f && hg add f && hg ci -m F
829 828 $ hg up 0
830 829 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
831 830 $ touch g && hg add g && hg ci -m G
832 831 created new head
833 832
834 833 $ hg tglog
835 834 @ 6: 'G'
836 835 |
837 836 | o 5: 'F'
838 837 | |
839 838 | | o 4: 'E'
840 839 | | |
841 840 | o | 3: 'D'
842 841 | | |
843 842 | | o 2: 'C'
844 843 | |/
845 844 | o 1: 'B'
846 845 |/
847 846 o 0: 'A'
848 847
849 848
850 849 $ hg rebase -s 1 -d 6
851 850 rebasing 1:76035bbd54bd "B"
852 851 rebasing 2:d84f5cfaaf14 "C"
853 852 rebasing 4:82ae8dc7a9b7 "E"
854 853 rebasing 3:ab709c9f7171 "D"
855 854 rebasing 5:412b391de760 "F"
856 855 saved backup bundle to $TESTTMP/order/.hg/strip-backup/76035bbd54bd-e341bc99-rebase.hg (glob)
857 856
858 857 $ hg tglog
859 858 o 6: 'F'
860 859 |
861 860 o 5: 'D'
862 861 |
863 862 | o 4: 'E'
864 863 | |
865 864 | o 3: 'C'
866 865 |/
867 866 o 2: 'B'
868 867 |
869 868 @ 1: 'G'
870 869 |
871 870 o 0: 'A'
872 871
873 872
874 873 Test experimental revset
875 874 ========================
876 875
877 876 $ cd ../cwd-vanish
878 877
879 878 Make the repo a bit more interesting
880 879
881 880 $ hg up 1
882 881 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
883 882 $ echo aaa > aaa
884 883 $ hg add aaa
885 884 $ hg commit -m aaa
886 885 created new head
887 886 $ hg log -G
888 887 @ changeset: 4:5f7bc9025ed2
889 888 | tag: tip
890 889 | parent: 1:58d79cc1cf43
891 890 | user: test
892 891 | date: Thu Jan 01 00:00:00 1970 +0000
893 892 | summary: aaa
894 893 |
895 894 | o changeset: 3:1910d5ff34ea
896 895 | | user: test
897 896 | | date: Thu Jan 01 00:00:00 1970 +0000
898 897 | | summary: second source with subdir
899 898 | |
900 899 | o changeset: 2:82901330b6ef
901 900 |/ user: test
902 901 | date: Thu Jan 01 00:00:00 1970 +0000
903 902 | summary: first source commit
904 903 |
905 904 o changeset: 1:58d79cc1cf43
906 905 | user: test
907 906 | date: Thu Jan 01 00:00:00 1970 +0000
908 907 | summary: dest commit
909 908 |
910 909 o changeset: 0:e94b687f7da3
911 910 user: test
912 911 date: Thu Jan 01 00:00:00 1970 +0000
913 912 summary: initial commit
914 913
915 914
916 915 Testing from lower head
917 916
918 917 $ hg up 3
919 918 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
920 919 $ hg log -r '_destrebase()'
921 920 changeset: 4:5f7bc9025ed2
922 921 tag: tip
923 922 parent: 1:58d79cc1cf43
924 923 user: test
925 924 date: Thu Jan 01 00:00:00 1970 +0000
926 925 summary: aaa
927 926
928 927
929 928 Testing from upper head
930 929
931 930 $ hg log -r '_destrebase(4)'
932 931 changeset: 3:1910d5ff34ea
933 932 user: test
934 933 date: Thu Jan 01 00:00:00 1970 +0000
935 934 summary: second source with subdir
936 935
937 936 $ hg up 4
938 937 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
939 938 $ hg log -r '_destrebase()'
940 939 changeset: 3:1910d5ff34ea
941 940 user: test
942 941 date: Thu Jan 01 00:00:00 1970 +0000
943 942 summary: second source with subdir
944 943
945 944 Testing rebase being called inside another transaction
946 945
947 946 $ cd $TESTTMP
948 947 $ hg init tr-state
949 948 $ cd tr-state
950 949 $ cat > $TESTTMP/wraprebase.py <<EOF
951 950 > from __future__ import absolute_import
952 951 > from mercurial import extensions
953 952 > def _rebase(orig, ui, repo, *args, **kwargs):
954 953 > with repo.wlock():
955 954 > with repo.lock():
956 955 > with repo.transaction('wrappedrebase'):
957 956 > return orig(ui, repo, *args, **kwargs)
958 957 > def wraprebase(loaded):
959 958 > assert loaded
960 959 > rebasemod = extensions.find('rebase')
961 960 > extensions.wrapcommand(rebasemod.cmdtable, 'rebase', _rebase)
962 961 > def extsetup(ui):
963 962 > extensions.afterloaded('rebase', wraprebase)
964 963 > EOF
965 964
966 965 $ cat >> .hg/hgrc <<EOF
967 966 > [extensions]
968 967 > wraprebase=$TESTTMP/wraprebase.py
969 968 > [experimental]
970 969 > evolution=all
971 970 > EOF
972 971
973 972 $ hg debugdrawdag <<'EOS'
974 973 > B C
975 974 > |/
976 975 > A
977 976 > EOS
978 977
979 978 $ hg rebase -s C -d B
980 979 rebasing 2:dc0947a82db8 "C" (C tip)
981 980
982 981 $ [ -f .hg/rebasestate ] && echo 'WRONG: rebasestate should not exist'
983 982 [1]
@@ -1,1121 +1,1121 b''
1 1 $ echo "[format]" >> $HGRCPATH
2 2 $ echo "usegeneraldelta=yes" >> $HGRCPATH
3 3 $ echo "[extensions]" >> $HGRCPATH
4 4 $ echo "strip=" >> $HGRCPATH
5 5 $ echo "drawdag=$TESTDIR/drawdag.py" >> $HGRCPATH
6 6
7 7 $ restore() {
8 8 > hg unbundle -q .hg/strip-backup/*
9 9 > rm .hg/strip-backup/*
10 10 > }
11 11 $ teststrip() {
12 12 > hg up -C $1
13 13 > echo % before update $1, strip $2
14 14 > hg parents
15 15 > hg --traceback strip $2
16 16 > echo % after update $1, strip $2
17 17 > hg parents
18 18 > restore
19 19 > }
20 20
21 21 $ hg init test
22 22 $ cd test
23 23
24 24 $ echo foo > bar
25 25 $ hg ci -Ama
26 26 adding bar
27 27
28 28 $ echo more >> bar
29 29 $ hg ci -Amb
30 30
31 31 $ echo blah >> bar
32 32 $ hg ci -Amc
33 33
34 34 $ hg up 1
35 35 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36 $ echo blah >> bar
37 37 $ hg ci -Amd
38 38 created new head
39 39
40 40 $ echo final >> bar
41 41 $ hg ci -Ame
42 42
43 43 $ hg log
44 44 changeset: 4:443431ffac4f
45 45 tag: tip
46 46 user: test
47 47 date: Thu Jan 01 00:00:00 1970 +0000
48 48 summary: e
49 49
50 50 changeset: 3:65bd5f99a4a3
51 51 parent: 1:ef3a871183d7
52 52 user: test
53 53 date: Thu Jan 01 00:00:00 1970 +0000
54 54 summary: d
55 55
56 56 changeset: 2:264128213d29
57 57 user: test
58 58 date: Thu Jan 01 00:00:00 1970 +0000
59 59 summary: c
60 60
61 61 changeset: 1:ef3a871183d7
62 62 user: test
63 63 date: Thu Jan 01 00:00:00 1970 +0000
64 64 summary: b
65 65
66 66 changeset: 0:9ab35a2d17cb
67 67 user: test
68 68 date: Thu Jan 01 00:00:00 1970 +0000
69 69 summary: a
70 70
71 71
72 72 $ teststrip 4 4
73 73 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 74 % before update 4, strip 4
75 75 changeset: 4:443431ffac4f
76 76 tag: tip
77 77 user: test
78 78 date: Thu Jan 01 00:00:00 1970 +0000
79 79 summary: e
80 80
81 81 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 82 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
83 83 % after update 4, strip 4
84 84 changeset: 3:65bd5f99a4a3
85 85 tag: tip
86 86 parent: 1:ef3a871183d7
87 87 user: test
88 88 date: Thu Jan 01 00:00:00 1970 +0000
89 89 summary: d
90 90
91 91 $ teststrip 4 3
92 92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 93 % before update 4, strip 3
94 94 changeset: 4:443431ffac4f
95 95 tag: tip
96 96 user: test
97 97 date: Thu Jan 01 00:00:00 1970 +0000
98 98 summary: e
99 99
100 100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 101 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
102 102 % after update 4, strip 3
103 103 changeset: 1:ef3a871183d7
104 104 user: test
105 105 date: Thu Jan 01 00:00:00 1970 +0000
106 106 summary: b
107 107
108 108 $ teststrip 1 4
109 109 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 110 % before update 1, strip 4
111 111 changeset: 1:ef3a871183d7
112 112 user: test
113 113 date: Thu Jan 01 00:00:00 1970 +0000
114 114 summary: b
115 115
116 116 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
117 117 % after update 1, strip 4
118 118 changeset: 1:ef3a871183d7
119 119 user: test
120 120 date: Thu Jan 01 00:00:00 1970 +0000
121 121 summary: b
122 122
123 123 $ teststrip 4 2
124 124 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
125 125 % before update 4, strip 2
126 126 changeset: 4:443431ffac4f
127 127 tag: tip
128 128 user: test
129 129 date: Thu Jan 01 00:00:00 1970 +0000
130 130 summary: e
131 131
132 132 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
133 133 % after update 4, strip 2
134 134 changeset: 3:443431ffac4f
135 135 tag: tip
136 136 user: test
137 137 date: Thu Jan 01 00:00:00 1970 +0000
138 138 summary: e
139 139
140 140 $ teststrip 4 1
141 141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 142 % before update 4, strip 1
143 143 changeset: 4:264128213d29
144 144 tag: tip
145 145 parent: 1:ef3a871183d7
146 146 user: test
147 147 date: Thu Jan 01 00:00:00 1970 +0000
148 148 summary: c
149 149
150 150 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 151 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
152 152 % after update 4, strip 1
153 153 changeset: 0:9ab35a2d17cb
154 154 tag: tip
155 155 user: test
156 156 date: Thu Jan 01 00:00:00 1970 +0000
157 157 summary: a
158 158
159 159 $ teststrip null 4
160 160 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
161 161 % before update null, strip 4
162 162 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
163 163 % after update null, strip 4
164 164
165 165 $ hg log
166 166 changeset: 4:264128213d29
167 167 tag: tip
168 168 parent: 1:ef3a871183d7
169 169 user: test
170 170 date: Thu Jan 01 00:00:00 1970 +0000
171 171 summary: c
172 172
173 173 changeset: 3:443431ffac4f
174 174 user: test
175 175 date: Thu Jan 01 00:00:00 1970 +0000
176 176 summary: e
177 177
178 178 changeset: 2:65bd5f99a4a3
179 179 user: test
180 180 date: Thu Jan 01 00:00:00 1970 +0000
181 181 summary: d
182 182
183 183 changeset: 1:ef3a871183d7
184 184 user: test
185 185 date: Thu Jan 01 00:00:00 1970 +0000
186 186 summary: b
187 187
188 188 changeset: 0:9ab35a2d17cb
189 189 user: test
190 190 date: Thu Jan 01 00:00:00 1970 +0000
191 191 summary: a
192 192
193 193 $ hg up -C 4
194 194 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 195 $ hg parents
196 196 changeset: 4:264128213d29
197 197 tag: tip
198 198 parent: 1:ef3a871183d7
199 199 user: test
200 200 date: Thu Jan 01 00:00:00 1970 +0000
201 201 summary: c
202 202
203 203
204 204 $ hg --traceback strip 4
205 205 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 206 saved backup bundle to $TESTTMP/test/.hg/strip-backup/264128213d29-0b39d6bf-backup.hg (glob)
207 207 $ hg parents
208 208 changeset: 1:ef3a871183d7
209 209 user: test
210 210 date: Thu Jan 01 00:00:00 1970 +0000
211 211 summary: b
212 212
213 213 $ hg debugbundle .hg/strip-backup/*
214 214 Stream params: sortdict([('Compression', 'BZ')])
215 215 changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
216 216 264128213d290d868c54642d13aeaa3675551a78
217 217 phase-heads -- 'sortdict()'
218 218 264128213d290d868c54642d13aeaa3675551a78 draft
219 219 $ hg pull .hg/strip-backup/*
220 220 pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
221 221 searching for changes
222 222 adding changesets
223 223 adding manifests
224 224 adding file changes
225 225 added 1 changesets with 0 changes to 0 files (+1 heads)
226 226 (run 'hg heads' to see heads, 'hg merge' to merge)
227 227 $ rm .hg/strip-backup/*
228 228 $ hg log --graph
229 229 o changeset: 4:264128213d29
230 230 | tag: tip
231 231 | parent: 1:ef3a871183d7
232 232 | user: test
233 233 | date: Thu Jan 01 00:00:00 1970 +0000
234 234 | summary: c
235 235 |
236 236 | o changeset: 3:443431ffac4f
237 237 | | user: test
238 238 | | date: Thu Jan 01 00:00:00 1970 +0000
239 239 | | summary: e
240 240 | |
241 241 | o changeset: 2:65bd5f99a4a3
242 242 |/ user: test
243 243 | date: Thu Jan 01 00:00:00 1970 +0000
244 244 | summary: d
245 245 |
246 246 @ changeset: 1:ef3a871183d7
247 247 | user: test
248 248 | date: Thu Jan 01 00:00:00 1970 +0000
249 249 | summary: b
250 250 |
251 251 o changeset: 0:9ab35a2d17cb
252 252 user: test
253 253 date: Thu Jan 01 00:00:00 1970 +0000
254 254 summary: a
255 255
256 256 $ hg up -C 2
257 257 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
258 258 $ hg merge 4
259 259 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 260 (branch merge, don't forget to commit)
261 261
262 262 before strip of merge parent
263 263
264 264 $ hg parents
265 265 changeset: 2:65bd5f99a4a3
266 266 user: test
267 267 date: Thu Jan 01 00:00:00 1970 +0000
268 268 summary: d
269 269
270 270 changeset: 4:264128213d29
271 271 tag: tip
272 272 parent: 1:ef3a871183d7
273 273 user: test
274 274 date: Thu Jan 01 00:00:00 1970 +0000
275 275 summary: c
276 276
277 277 $ hg strip 4
278 278 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
279 279 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
280 280
281 281 after strip of merge parent
282 282
283 283 $ hg parents
284 284 changeset: 1:ef3a871183d7
285 285 user: test
286 286 date: Thu Jan 01 00:00:00 1970 +0000
287 287 summary: b
288 288
289 289 $ restore
290 290
291 291 $ hg up
292 292 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
293 293 updated to "264128213d29: c"
294 294 1 other heads for branch "default"
295 295 $ hg log -G
296 296 @ changeset: 4:264128213d29
297 297 | tag: tip
298 298 | parent: 1:ef3a871183d7
299 299 | user: test
300 300 | date: Thu Jan 01 00:00:00 1970 +0000
301 301 | summary: c
302 302 |
303 303 | o changeset: 3:443431ffac4f
304 304 | | user: test
305 305 | | date: Thu Jan 01 00:00:00 1970 +0000
306 306 | | summary: e
307 307 | |
308 308 | o changeset: 2:65bd5f99a4a3
309 309 |/ user: test
310 310 | date: Thu Jan 01 00:00:00 1970 +0000
311 311 | summary: d
312 312 |
313 313 o changeset: 1:ef3a871183d7
314 314 | user: test
315 315 | date: Thu Jan 01 00:00:00 1970 +0000
316 316 | summary: b
317 317 |
318 318 o changeset: 0:9ab35a2d17cb
319 319 user: test
320 320 date: Thu Jan 01 00:00:00 1970 +0000
321 321 summary: a
322 322
323 323
324 324 2 is parent of 3, only one strip should happen
325 325
326 326 $ hg strip "roots(2)" 3
327 327 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
328 328 $ hg log -G
329 329 @ changeset: 2:264128213d29
330 330 | tag: tip
331 331 | user: test
332 332 | date: Thu Jan 01 00:00:00 1970 +0000
333 333 | summary: c
334 334 |
335 335 o changeset: 1:ef3a871183d7
336 336 | user: test
337 337 | date: Thu Jan 01 00:00:00 1970 +0000
338 338 | summary: b
339 339 |
340 340 o changeset: 0:9ab35a2d17cb
341 341 user: test
342 342 date: Thu Jan 01 00:00:00 1970 +0000
343 343 summary: a
344 344
345 345 $ restore
346 346 $ hg log -G
347 347 o changeset: 4:443431ffac4f
348 348 | tag: tip
349 349 | user: test
350 350 | date: Thu Jan 01 00:00:00 1970 +0000
351 351 | summary: e
352 352 |
353 353 o changeset: 3:65bd5f99a4a3
354 354 | parent: 1:ef3a871183d7
355 355 | user: test
356 356 | date: Thu Jan 01 00:00:00 1970 +0000
357 357 | summary: d
358 358 |
359 359 | @ changeset: 2:264128213d29
360 360 |/ user: test
361 361 | date: Thu Jan 01 00:00:00 1970 +0000
362 362 | summary: c
363 363 |
364 364 o changeset: 1:ef3a871183d7
365 365 | user: test
366 366 | date: Thu Jan 01 00:00:00 1970 +0000
367 367 | summary: b
368 368 |
369 369 o changeset: 0:9ab35a2d17cb
370 370 user: test
371 371 date: Thu Jan 01 00:00:00 1970 +0000
372 372 summary: a
373 373
374 374 Failed hook while applying "saveheads" bundle.
375 375
376 376 $ hg strip 2 --config hooks.pretxnchangegroup.bad=false
377 377 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 378 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
379 379 transaction abort!
380 380 rollback completed
381 381 strip failed, backup bundle stored in '$TESTTMP/test/.hg/strip-backup/*-backup.hg' (glob)
382 382 strip failed, unrecovered changes stored in '$TESTTMP/test/.hg/strip-backup/*-temp.hg' (glob)
383 383 (fix the problem, then recover the changesets with "hg unbundle '$TESTTMP/test/.hg/strip-backup/*-temp.hg'") (glob)
384 384 abort: pretxnchangegroup.bad hook exited with status 1
385 385 [255]
386 386 $ restore
387 387 $ hg log -G
388 388 o changeset: 4:443431ffac4f
389 389 | tag: tip
390 390 | user: test
391 391 | date: Thu Jan 01 00:00:00 1970 +0000
392 392 | summary: e
393 393 |
394 394 o changeset: 3:65bd5f99a4a3
395 395 | parent: 1:ef3a871183d7
396 396 | user: test
397 397 | date: Thu Jan 01 00:00:00 1970 +0000
398 398 | summary: d
399 399 |
400 400 | o changeset: 2:264128213d29
401 401 |/ user: test
402 402 | date: Thu Jan 01 00:00:00 1970 +0000
403 403 | summary: c
404 404 |
405 405 @ changeset: 1:ef3a871183d7
406 406 | user: test
407 407 | date: Thu Jan 01 00:00:00 1970 +0000
408 408 | summary: b
409 409 |
410 410 o changeset: 0:9ab35a2d17cb
411 411 user: test
412 412 date: Thu Jan 01 00:00:00 1970 +0000
413 413 summary: a
414 414
415 415
416 416 2 different branches: 2 strips
417 417
418 418 $ hg strip 2 4
419 419 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
420 420 $ hg log -G
421 421 o changeset: 2:65bd5f99a4a3
422 422 | tag: tip
423 423 | user: test
424 424 | date: Thu Jan 01 00:00:00 1970 +0000
425 425 | summary: d
426 426 |
427 427 @ changeset: 1:ef3a871183d7
428 428 | user: test
429 429 | date: Thu Jan 01 00:00:00 1970 +0000
430 430 | summary: b
431 431 |
432 432 o changeset: 0:9ab35a2d17cb
433 433 user: test
434 434 date: Thu Jan 01 00:00:00 1970 +0000
435 435 summary: a
436 436
437 437 $ restore
438 438
439 439 2 different branches and a common ancestor: 1 strip
440 440
441 441 $ hg strip 1 "2|4"
442 442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 443 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
444 444 $ restore
445 445
446 446 verify fncache is kept up-to-date
447 447
448 448 $ touch a
449 449 $ hg ci -qAm a
450 450 $ cat .hg/store/fncache | sort
451 451 data/a.i
452 452 data/bar.i
453 453 $ hg strip tip
454 454 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
455 455 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
456 456 $ cat .hg/store/fncache
457 457 data/bar.i
458 458
459 459 stripping an empty revset
460 460
461 461 $ hg strip "1 and not 1"
462 462 abort: empty revision set
463 463 [255]
464 464
465 465 remove branchy history for qimport tests
466 466
467 467 $ hg strip 3
468 468 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
469 469
470 470
471 471 strip of applied mq should cleanup status file
472 472
473 473 $ echo "mq=" >> $HGRCPATH
474 474 $ hg up -C 3
475 475 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
476 476 $ echo fooagain >> bar
477 477 $ hg ci -mf
478 478 $ hg qimport -r tip:2
479 479
480 480 applied patches before strip
481 481
482 482 $ hg qapplied
483 483 d
484 484 e
485 485 f
486 486
487 487 stripping revision in queue
488 488
489 489 $ hg strip 3
490 490 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
491 491 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
492 492
493 493 applied patches after stripping rev in queue
494 494
495 495 $ hg qapplied
496 496 d
497 497
498 498 stripping ancestor of queue
499 499
500 500 $ hg strip 1
501 501 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
502 502 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
503 503
504 504 applied patches after stripping ancestor of queue
505 505
506 506 $ hg qapplied
507 507
508 508 Verify strip protects against stripping wc parent when there are uncommitted mods
509 509
510 510 $ echo b > b
511 511 $ echo bb > bar
512 512 $ hg add b
513 513 $ hg ci -m 'b'
514 514 $ hg log --graph
515 515 @ changeset: 1:76dcf9fab855
516 516 | tag: tip
517 517 | user: test
518 518 | date: Thu Jan 01 00:00:00 1970 +0000
519 519 | summary: b
520 520 |
521 521 o changeset: 0:9ab35a2d17cb
522 522 user: test
523 523 date: Thu Jan 01 00:00:00 1970 +0000
524 524 summary: a
525 525
526 526 $ hg up 0
527 527 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
528 528 $ echo c > bar
529 529 $ hg up -t false
530 530 merging bar
531 531 merging bar failed!
532 532 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
533 533 use 'hg resolve' to retry unresolved file merges
534 534 [1]
535 535 $ hg sum
536 536 parent: 1:76dcf9fab855 tip
537 537 b
538 538 branch: default
539 539 commit: 1 modified, 1 unknown, 1 unresolved
540 540 update: (current)
541 541 phases: 2 draft
542 542 mq: 3 unapplied
543 543
544 544 $ echo c > b
545 545 $ hg strip tip
546 546 abort: local changes found
547 547 [255]
548 548 $ hg strip tip --keep
549 549 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
550 550 $ hg log --graph
551 551 @ changeset: 0:9ab35a2d17cb
552 552 tag: tip
553 553 user: test
554 554 date: Thu Jan 01 00:00:00 1970 +0000
555 555 summary: a
556 556
557 557 $ hg status
558 558 M bar
559 559 ? b
560 560 ? bar.orig
561 561
562 562 $ rm bar.orig
563 563 $ hg sum
564 564 parent: 0:9ab35a2d17cb tip
565 565 a
566 566 branch: default
567 567 commit: 1 modified, 1 unknown
568 568 update: (current)
569 569 phases: 1 draft
570 570 mq: 3 unapplied
571 571
572 572 Strip adds, removes, modifies with --keep
573 573
574 574 $ touch b
575 575 $ hg add b
576 576 $ hg commit -mb
577 577 $ touch c
578 578
579 579 ... with a clean working dir
580 580
581 581 $ hg add c
582 582 $ hg rm bar
583 583 $ hg commit -mc
584 584 $ hg status
585 585 $ hg strip --keep tip
586 586 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
587 587 $ hg status
588 588 ! bar
589 589 ? c
590 590
591 591 ... with a dirty working dir
592 592
593 593 $ hg add c
594 594 $ hg rm bar
595 595 $ hg commit -mc
596 596 $ hg status
597 597 $ echo b > b
598 598 $ echo d > d
599 599 $ hg strip --keep tip
600 600 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
601 601 $ hg status
602 602 M b
603 603 ! bar
604 604 ? c
605 605 ? d
606 606
607 607 ... after updating the dirstate
608 608 $ hg add c
609 609 $ hg commit -mc
610 610 $ hg rm c
611 611 $ hg commit -mc
612 612 $ hg strip --keep '.^' -q
613 613 $ cd ..
614 614
615 615 stripping many nodes on a complex graph (issue3299)
616 616
617 617 $ hg init issue3299
618 618 $ cd issue3299
619 619 $ hg debugbuilddag '@a.:a@b.:b.:x<a@a.:a<b@b.:b<a@a.:a'
620 620 $ hg strip 'not ancestors(x)'
621 621 saved backup bundle to $TESTTMP/issue3299/.hg/strip-backup/*-backup.hg (glob)
622 622
623 623 test hg strip -B bookmark
624 624
625 625 $ cd ..
626 626 $ hg init bookmarks
627 627 $ cd bookmarks
628 628 $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b<m+2:d<2.:e<m+1:f'
629 629 $ hg bookmark -r 'a' 'todelete'
630 630 $ hg bookmark -r 'b' 'B'
631 631 $ hg bookmark -r 'b' 'nostrip'
632 632 $ hg bookmark -r 'c' 'delete'
633 633 $ hg bookmark -r 'd' 'multipledelete1'
634 634 $ hg bookmark -r 'e' 'multipledelete2'
635 635 $ hg bookmark -r 'f' 'singlenode1'
636 636 $ hg bookmark -r 'f' 'singlenode2'
637 637 $ hg up -C todelete
638 638 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
639 639 (activating bookmark todelete)
640 640 $ hg strip -B nostrip
641 641 bookmark 'nostrip' deleted
642 642 abort: empty revision set
643 643 [255]
644 644 $ hg strip -B todelete
645 645 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
646 646 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
647 647 bookmark 'todelete' deleted
648 648 $ hg id -ir dcbb326fdec2
649 649 abort: unknown revision 'dcbb326fdec2'!
650 650 [255]
651 651 $ hg id -ir d62d843c9a01
652 652 d62d843c9a01
653 653 $ hg bookmarks
654 654 B 9:ff43616e5d0f
655 655 delete 6:2702dd0c91e7
656 656 multipledelete1 11:e46a4836065c
657 657 multipledelete2 12:b4594d867745
658 658 singlenode1 13:43227190fef8
659 659 singlenode2 13:43227190fef8
660 660 $ hg strip -B multipledelete1 -B multipledelete2
661 661 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/e46a4836065c-89ec65c2-backup.hg (glob)
662 662 bookmark 'multipledelete1' deleted
663 663 bookmark 'multipledelete2' deleted
664 664 $ hg id -ir e46a4836065c
665 665 abort: unknown revision 'e46a4836065c'!
666 666 [255]
667 667 $ hg id -ir b4594d867745
668 668 abort: unknown revision 'b4594d867745'!
669 669 [255]
670 670 $ hg strip -B singlenode1 -B singlenode2
671 671 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/43227190fef8-8da858f2-backup.hg (glob)
672 672 bookmark 'singlenode1' deleted
673 673 bookmark 'singlenode2' deleted
674 674 $ hg id -ir 43227190fef8
675 675 abort: unknown revision '43227190fef8'!
676 676 [255]
677 677 $ hg strip -B unknownbookmark
678 678 abort: bookmark 'unknownbookmark' not found
679 679 [255]
680 680 $ hg strip -B unknownbookmark1 -B unknownbookmark2
681 681 abort: bookmark 'unknownbookmark1,unknownbookmark2' not found
682 682 [255]
683 683 $ hg strip -B delete -B unknownbookmark
684 684 abort: bookmark 'unknownbookmark' not found
685 685 [255]
686 686 $ hg strip -B delete
687 687 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
688 688 bookmark 'delete' deleted
689 689 $ hg id -ir 6:2702dd0c91e7
690 690 abort: unknown revision '2702dd0c91e7'!
691 691 [255]
692 692 $ hg update B
693 693 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
694 694 (activating bookmark B)
695 695 $ echo a > a
696 696 $ hg add a
697 697 $ hg strip -B B
698 698 abort: local changes found
699 699 [255]
700 700 $ hg bookmarks
701 701 * B 6:ff43616e5d0f
702 702
703 703 Make sure no one adds back a -b option:
704 704
705 705 $ hg strip -b tip
706 706 hg strip: option -b not recognized
707 707 hg strip [-k] [-f] [-B bookmark] [-r] REV...
708 708
709 709 strip changesets and all their descendants from the repository
710 710
711 711 (use 'hg help -e strip' to show help for the strip extension)
712 712
713 713 options ([+] can be repeated):
714 714
715 715 -r --rev REV [+] strip specified revision (optional, can specify
716 716 revisions without this option)
717 717 -f --force force removal of changesets, discard uncommitted
718 718 changes (no backup)
719 719 --no-backup no backups
720 720 -k --keep do not modify working directory during strip
721 721 -B --bookmark VALUE [+] remove revs only reachable from given bookmark
722 722 --mq operate on patch repository
723 723
724 724 (use 'hg strip -h' to show more help)
725 725 [255]
726 726
727 727 $ cd ..
728 728
729 729 Verify bundles don't get overwritten:
730 730
731 731 $ hg init doublebundle
732 732 $ cd doublebundle
733 733 $ touch a
734 734 $ hg commit -Aqm a
735 735 $ touch b
736 736 $ hg commit -Aqm b
737 737 $ hg strip -r 0
738 738 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
739 739 saved backup bundle to $TESTTMP/doublebundle/.hg/strip-backup/3903775176ed-e68910bd-backup.hg (glob)
740 740 $ ls .hg/strip-backup
741 741 3903775176ed-e68910bd-backup.hg
742 742 $ hg pull -q -r 3903775176ed .hg/strip-backup/3903775176ed-e68910bd-backup.hg
743 743 $ hg strip -r 0
744 744 saved backup bundle to $TESTTMP/doublebundle/.hg/strip-backup/3903775176ed-54390173-backup.hg (glob)
745 745 $ ls .hg/strip-backup
746 746 3903775176ed-54390173-backup.hg
747 747 3903775176ed-e68910bd-backup.hg
748 748 $ cd ..
749 749
750 750 Test that we only bundle the stripped changesets (issue4736)
751 751 ------------------------------------------------------------
752 752
753 753 initialization (previous repo is empty anyway)
754 754
755 755 $ hg init issue4736
756 756 $ cd issue4736
757 757 $ echo a > a
758 758 $ hg add a
759 759 $ hg commit -m commitA
760 760 $ echo b > b
761 761 $ hg add b
762 762 $ hg commit -m commitB
763 763 $ echo c > c
764 764 $ hg add c
765 765 $ hg commit -m commitC
766 766 $ hg up 'desc(commitB)'
767 767 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
768 768 $ echo d > d
769 769 $ hg add d
770 770 $ hg commit -m commitD
771 771 created new head
772 772 $ hg up 'desc(commitC)'
773 773 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
774 774 $ hg merge 'desc(commitD)'
775 775 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
776 776 (branch merge, don't forget to commit)
777 777 $ hg ci -m 'mergeCD'
778 778 $ hg log -G
779 779 @ changeset: 4:d8db9d137221
780 780 |\ tag: tip
781 781 | | parent: 2:5c51d8d6557d
782 782 | | parent: 3:6625a5168474
783 783 | | user: test
784 784 | | date: Thu Jan 01 00:00:00 1970 +0000
785 785 | | summary: mergeCD
786 786 | |
787 787 | o changeset: 3:6625a5168474
788 788 | | parent: 1:eca11cf91c71
789 789 | | user: test
790 790 | | date: Thu Jan 01 00:00:00 1970 +0000
791 791 | | summary: commitD
792 792 | |
793 793 o | changeset: 2:5c51d8d6557d
794 794 |/ user: test
795 795 | date: Thu Jan 01 00:00:00 1970 +0000
796 796 | summary: commitC
797 797 |
798 798 o changeset: 1:eca11cf91c71
799 799 | user: test
800 800 | date: Thu Jan 01 00:00:00 1970 +0000
801 801 | summary: commitB
802 802 |
803 803 o changeset: 0:105141ef12d0
804 804 user: test
805 805 date: Thu Jan 01 00:00:00 1970 +0000
806 806 summary: commitA
807 807
808 808
809 809 Check bundle behavior:
810 810
811 811 $ hg bundle -r 'desc(mergeCD)' --base 'desc(commitC)' ../issue4736.hg
812 812 2 changesets found
813 813 $ hg log -r 'bundle()' -R ../issue4736.hg
814 814 changeset: 3:6625a5168474
815 815 parent: 1:eca11cf91c71
816 816 user: test
817 817 date: Thu Jan 01 00:00:00 1970 +0000
818 818 summary: commitD
819 819
820 820 changeset: 4:d8db9d137221
821 821 tag: tip
822 822 parent: 2:5c51d8d6557d
823 823 parent: 3:6625a5168474
824 824 user: test
825 825 date: Thu Jan 01 00:00:00 1970 +0000
826 826 summary: mergeCD
827 827
828 828
829 829 check strip behavior
830 830
831 831 $ hg --config extensions.strip= strip 'desc(commitD)' --debug
832 832 resolving manifests
833 833 branchmerge: False, force: True, partial: False
834 834 ancestor: d8db9d137221+, local: d8db9d137221+, remote: eca11cf91c71
835 835 c: other deleted -> r
836 836 removing c
837 837 d: other deleted -> r
838 838 removing d
839 839 starting 4 threads for background file closing (?)
840 840 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
841 841 2 changesets found
842 842 list of changesets:
843 843 6625a516847449b6f0fa3737b9ba56e9f0f3032c
844 844 d8db9d1372214336d2b5570f20ee468d2c72fa8b
845 845 bundle2-output-bundle: "HG20", (1 params) 2 parts total
846 846 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
847 847 bundle2-output-part: "phase-heads" 24 bytes payload
848 848 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/6625a5168474-345bb43d-backup.hg (glob)
849 849 updating the branch cache
850 850 invalid branchheads cache (served): tip differs
851 851 truncating cache/rbc-revs-v1 to 24
852 852 $ hg log -G
853 853 o changeset: 2:5c51d8d6557d
854 854 | tag: tip
855 855 | user: test
856 856 | date: Thu Jan 01 00:00:00 1970 +0000
857 857 | summary: commitC
858 858 |
859 859 @ changeset: 1:eca11cf91c71
860 860 | user: test
861 861 | date: Thu Jan 01 00:00:00 1970 +0000
862 862 | summary: commitB
863 863 |
864 864 o changeset: 0:105141ef12d0
865 865 user: test
866 866 date: Thu Jan 01 00:00:00 1970 +0000
867 867 summary: commitA
868 868
869 869
870 870 strip backup content
871 871
872 872 $ hg log -r 'bundle()' -R .hg/strip-backup/6625a5168474-*-backup.hg
873 873 changeset: 3:6625a5168474
874 874 parent: 1:eca11cf91c71
875 875 user: test
876 876 date: Thu Jan 01 00:00:00 1970 +0000
877 877 summary: commitD
878 878
879 879 changeset: 4:d8db9d137221
880 880 tag: tip
881 881 parent: 2:5c51d8d6557d
882 882 parent: 3:6625a5168474
883 883 user: test
884 884 date: Thu Jan 01 00:00:00 1970 +0000
885 885 summary: mergeCD
886 886
887 887 Check that the phase cache is properly invalidated after a strip with bookmark.
888 888
889 889 $ cat > ../stripstalephasecache.py << EOF
890 890 > from mercurial import extensions, localrepo
891 891 > def transactioncallback(orig, repo, desc, *args, **kwargs):
892 892 > def test(transaction):
893 893 > # observe cache inconsistency
894 894 > try:
895 895 > [repo.changelog.node(r) for r in repo.revs("not public()")]
896 896 > except IndexError:
897 897 > repo.ui.status("Index error!\n")
898 898 > transaction = orig(repo, desc, *args, **kwargs)
899 899 > # warm up the phase cache
900 900 > list(repo.revs("not public()"))
901 901 > if desc != 'strip':
902 902 > transaction.addpostclose("phase invalidation test", test)
903 903 > return transaction
904 904 > def extsetup(ui):
905 905 > extensions.wrapfunction(localrepo.localrepository, "transaction",
906 906 > transactioncallback)
907 907 > EOF
908 908 $ hg up -C 2
909 909 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
910 910 $ echo k > k
911 911 $ hg add k
912 912 $ hg commit -m commitK
913 913 $ echo l > l
914 914 $ hg add l
915 915 $ hg commit -m commitL
916 916 $ hg book -r tip blah
917 917 $ hg strip ".^" --config extensions.crash=$TESTTMP/stripstalephasecache.py
918 918 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
919 919 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/8f0b4384875c-4fa10deb-backup.hg (glob)
920 920 $ hg up -C 1
921 921 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
922 922
923 923 Error during post-close callback of the strip transaction
924 924 (They should be gracefully handled and reported)
925 925
926 926 $ cat > ../crashstrip.py << EOF
927 927 > from mercurial import error
928 928 > def reposetup(ui, repo):
929 929 > class crashstriprepo(repo.__class__):
930 930 > def transaction(self, desc, *args, **kwargs):
931 931 > tr = super(crashstriprepo, self).transaction(desc, *args, **kwargs)
932 932 > if desc == 'strip':
933 933 > def crash(tra): raise error.Abort('boom')
934 934 > tr.addpostclose('crash', crash)
935 935 > return tr
936 936 > repo.__class__ = crashstriprepo
937 937 > EOF
938 938 $ hg strip tip --config extensions.crash=$TESTTMP/crashstrip.py
939 939 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/5c51d8d6557d-70daef06-backup.hg (glob)
940 940 strip failed, backup bundle stored in '$TESTTMP/issue4736/.hg/strip-backup/5c51d8d6557d-70daef06-backup.hg' (glob)
941 941 abort: boom
942 942 [255]
943 943
944 944 Use delayedstrip to strip inside a transaction
945 945
946 946 $ cd $TESTTMP
947 947 $ hg init delayedstrip
948 948 $ cd delayedstrip
949 949 $ hg debugdrawdag <<'EOS'
950 950 > D
951 951 > |
952 952 > C F H # Commit on top of "I",
953 953 > | |/| # Strip B+D+I+E+G+H+Z
954 954 > I B E G
955 955 > \|/
956 956 > A Z
957 957 > EOS
958 958 $ cp -R . ../scmutilcleanup
959 959
960 960 $ hg up -C I
961 961 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
962 962 $ echo 3 >> I
963 963 $ cat > $TESTTMP/delayedstrip.py <<EOF
964 964 > from mercurial import repair, commands
965 965 > def reposetup(ui, repo):
966 966 > def getnodes(expr):
967 967 > return [repo.changelog.node(r) for r in repo.revs(expr)]
968 968 > with repo.wlock():
969 969 > with repo.lock():
970 970 > with repo.transaction('delayedstrip'):
971 971 > repair.delayedstrip(ui, repo, getnodes('B+I+Z+D+E'), 'J')
972 972 > repair.delayedstrip(ui, repo, getnodes('G+H+Z'), 'I')
973 973 > commands.commit(ui, repo, message='J', date='0 0')
974 974 > EOF
975 975 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/delayedstrip.py
976 976 warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22
977 977 saved backup bundle to $TESTTMP/delayedstrip/.hg/strip-backup/f585351a92f8-17475721-I.hg (glob)
978 978
979 979 $ hg log -G -T '{rev}:{node|short} {desc}' -r 'sort(all(), topo)'
980 980 @ 6:2f2d51af6205 J
981 981 |
982 982 o 3:08ebfeb61bac I
983 983 |
984 984 | o 5:64a8289d2492 F
985 985 | |
986 986 | o 2:7fb047a69f22 E
987 987 |/
988 988 | o 4:26805aba1e60 C
989 989 | |
990 990 | o 1:112478962961 B
991 991 |/
992 992 o 0:426bada5c675 A
993 993
994 994 Test high-level scmutil.cleanupnodes API
995 995
996 996 $ cd $TESTTMP/scmutilcleanup
997 997 $ hg debugdrawdag <<'EOS'
998 998 > D2 F2 G2 # D2, F2, G2 are replacements for D, F, G
999 999 > | | |
1000 1000 > C H G
1001 1001 > EOS
1002 1002 $ for i in B C D F G I Z; do
1003 1003 > hg bookmark -i -r $i b-$i
1004 1004 > done
1005 1005 $ hg bookmark -i -r E 'b-F@divergent1'
1006 1006 $ hg bookmark -i -r H 'b-F@divergent2'
1007 1007 $ hg bookmark -i -r G 'b-F@divergent3'
1008 1008 $ cp -R . ../scmutilcleanup.obsstore
1009 1009
1010 1010 $ cat > $TESTTMP/scmutilcleanup.py <<EOF
1011 1011 > from mercurial import scmutil
1012 1012 > def reposetup(ui, repo):
1013 1013 > def nodes(expr):
1014 1014 > return [repo.changelog.node(r) for r in repo.revs(expr)]
1015 1015 > def node(expr):
1016 1016 > return nodes(expr)[0]
1017 1017 > with repo.wlock():
1018 1018 > with repo.lock():
1019 1019 > with repo.transaction('delayedstrip'):
1020 1020 > mapping = {node('F'): [node('F2')],
1021 1021 > node('D'): [node('D2')],
1022 1022 > node('G'): [node('G2')]}
1023 1023 > scmutil.cleanupnodes(repo, mapping, 'replace')
1024 1024 > scmutil.cleanupnodes(repo, nodes('((B::)+I+Z)-D2'), 'replace')
1025 1025 > EOF
1026 1026 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
1027 1027 warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60
1028 1028 saved backup bundle to $TESTTMP/scmutilcleanup/.hg/strip-backup/f585351a92f8-73fb7c03-replace.hg (glob)
1029 1029
1030 1030 $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
1031 1031 o 8:1473d4b996d1 G2 b-F@divergent3 b-G
1032 1032 |
1033 1033 | o 7:d11b3456a873 F2 b-F
1034 1034 | |
1035 1035 | o 5:5cb05ba470a7 H
1036 1036 |/|
1037 1037 | o 3:7fb047a69f22 E b-F@divergent1
1038 1038 | |
1039 1039 | | o 6:7c78f703e465 D2 b-D
1040 1040 | | |
1041 1041 | | o 4:26805aba1e60 C
1042 1042 | | |
1043 1043 | | o 2:112478962961 B
1044 1044 | |/
1045 1045 o | 1:1fc8102cda62 G
1046 1046 /
1047 1047 o 0:426bada5c675 A b-B b-C b-I
1048 1048
1049 1049 $ hg bookmark
1050 1050 b-B 0:426bada5c675
1051 1051 b-C 0:426bada5c675
1052 1052 b-D 6:7c78f703e465
1053 1053 b-F 7:d11b3456a873
1054 1054 b-F@divergent1 3:7fb047a69f22
1055 1055 b-F@divergent3 8:1473d4b996d1
1056 1056 b-G 8:1473d4b996d1
1057 1057 b-I 0:426bada5c675
1058 1058 b-Z -1:000000000000
1059 1059
1060 1060 Test the above using obsstore "by the way". Not directly related to strip, but
1061 1061 we have reusable code here
1062 1062
1063 1063 $ cd $TESTTMP/scmutilcleanup.obsstore
1064 1064 $ cat >> .hg/hgrc <<EOF
1065 1065 > [experimental]
1066 1066 > evolution=all
1067 1067 > evolution.track-operation=1
1068 1068 > EOF
1069 1069
1070 1070 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
1071 1071
1072 1072 $ rm .hg/localtags
1073 1073 $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
1074 1074 o 12:1473d4b996d1 G2 b-F@divergent3 b-G
1075 1075 |
1076 1076 | o 11:d11b3456a873 F2 b-F
1077 1077 | |
1078 1078 | o 8:5cb05ba470a7 H
1079 1079 |/|
1080 1080 | o 4:7fb047a69f22 E b-F@divergent1
1081 1081 | |
1082 1082 | | o 10:7c78f703e465 D2 b-D
1083 1083 | | |
1084 1084 | | x 6:26805aba1e60 C
1085 1085 | | |
1086 1086 | | x 3:112478962961 B
1087 1087 | |/
1088 1088 x | 1:1fc8102cda62 G
1089 1089 /
1090 1090 o 0:426bada5c675 A b-B b-C b-I
1091 1091
1092 1092 $ hg debugobsolete
1093 1093 1fc8102cda6204549f031015641606ccf5513ec3 1473d4b996d1d1b121de6b39fab6a04fbf9d873e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1094 1094 64a8289d249234b9886244d379f15e6b650b28e3 d11b3456a873daec7c7bc53e5622e8df6d741bd2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1095 1095 f585351a92f85104bff7c284233c338b10eb1df7 7c78f703e465d73102cc8780667ce269c5208a40 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1096 1096 48b9aae0607f43ff110d84e6883c151942add5ab 0 {0000000000000000000000000000000000000000} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1097 1097 112478962961147124edd43549aedd1a335e44bf 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1098 1098 08ebfeb61bac6e3f12079de774d285a0d6689eba 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1099 1099 26805aba1e600a82e93661149f2313866a221a7b 0 {112478962961147124edd43549aedd1a335e44bf} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1100 1100 $ cd ..
1101 1101
1102 1102 Test that obsmarkers are restored even when not using generaldelta
1103 1103
1104 1104 $ hg --config format.usegeneraldelta=no init issue5678
1105 1105 $ cd issue5678
1106 1106 $ cat >> .hg/hgrc <<EOF
1107 1107 > [experimental]
1108 1108 > evolution=all
1109 1109 > EOF
1110 1110 $ echo a > a
1111 1111 $ hg ci -Aqm a
1112 1112 $ hg ci --amend -m a2
1113 1113 $ hg debugobsolete
1114 1114 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1115 1115 $ hg strip .
1116 1116 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1117 1117 saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/489bac576828-bef27e14-backup.hg (glob)
1118 1118 $ hg unbundle -q .hg/strip-backup/*
1119 BROKEN: obsmarker got lost
1120 1119 $ hg debugobsolete
1120 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1121 1121 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now