##// END OF EJS Templates
scmutil: add a cleanupnodes method for developers...
Jun Wu -
r33088:65cadeea default
parent child Browse files
Show More
@@ -1,996 +1,1061 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 errno
11 11 import glob
12 12 import hashlib
13 13 import os
14 14 import re
15 15 import socket
16 16
17 17 from .i18n import _
18 18 from .node import (
19 hex,
20 nullid,
19 21 wdirid,
20 22 wdirrev,
21 23 )
22 24
23 25 from . import (
24 26 encoding,
25 27 error,
26 28 match as matchmod,
29 obsolete,
27 30 pathutil,
28 31 phases,
29 32 pycompat,
30 33 revsetlang,
31 34 similar,
32 35 util,
33 36 )
34 37
35 38 if pycompat.osname == 'nt':
36 39 from . import scmwindows as scmplatform
37 40 else:
38 41 from . import scmposix as scmplatform
39 42
40 43 termsize = scmplatform.termsize
41 44
42 45 class status(tuple):
43 46 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
44 47 and 'ignored' properties are only relevant to the working copy.
45 48 '''
46 49
47 50 __slots__ = ()
48 51
49 52 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
50 53 clean):
51 54 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
52 55 ignored, clean))
53 56
54 57 @property
55 58 def modified(self):
56 59 '''files that have been modified'''
57 60 return self[0]
58 61
59 62 @property
60 63 def added(self):
61 64 '''files that have been added'''
62 65 return self[1]
63 66
64 67 @property
65 68 def removed(self):
66 69 '''files that have been removed'''
67 70 return self[2]
68 71
69 72 @property
70 73 def deleted(self):
71 74 '''files that are in the dirstate, but have been deleted from the
72 75 working copy (aka "missing")
73 76 '''
74 77 return self[3]
75 78
76 79 @property
77 80 def unknown(self):
78 81 '''files not in the dirstate that are not ignored'''
79 82 return self[4]
80 83
81 84 @property
82 85 def ignored(self):
83 86 '''files not in the dirstate that are ignored (by _dirignore())'''
84 87 return self[5]
85 88
86 89 @property
87 90 def clean(self):
88 91 '''files that have not been modified'''
89 92 return self[6]
90 93
91 94 def __repr__(self, *args, **kwargs):
92 95 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
93 96 'unknown=%r, ignored=%r, clean=%r>') % self)
94 97
95 98 def itersubrepos(ctx1, ctx2):
96 99 """find subrepos in ctx1 or ctx2"""
97 100 # Create a (subpath, ctx) mapping where we prefer subpaths from
98 101 # ctx1. The subpaths from ctx2 are important when the .hgsub file
99 102 # has been modified (in ctx2) but not yet committed (in ctx1).
100 103 subpaths = dict.fromkeys(ctx2.substate, ctx2)
101 104 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
102 105
103 106 missing = set()
104 107
105 108 for subpath in ctx2.substate:
106 109 if subpath not in ctx1.substate:
107 110 del subpaths[subpath]
108 111 missing.add(subpath)
109 112
110 113 for subpath, ctx in sorted(subpaths.iteritems()):
111 114 yield subpath, ctx.sub(subpath)
112 115
113 116 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
114 117 # status and diff will have an accurate result when it does
115 118 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
116 119 # against itself.
117 120 for subpath in missing:
118 121 yield subpath, ctx2.nullsub(subpath, ctx1)
119 122
120 123 def nochangesfound(ui, repo, excluded=None):
121 124 '''Report no changes for push/pull, excluded is None or a list of
122 125 nodes excluded from the push/pull.
123 126 '''
124 127 secretlist = []
125 128 if excluded:
126 129 for n in excluded:
127 130 ctx = repo[n]
128 131 if ctx.phase() >= phases.secret and not ctx.extinct():
129 132 secretlist.append(n)
130 133
131 134 if secretlist:
132 135 ui.status(_("no changes found (ignored %d secret changesets)\n")
133 136 % len(secretlist))
134 137 else:
135 138 ui.status(_("no changes found\n"))
136 139
137 140 def callcatch(ui, func):
138 141 """call func() with global exception handling
139 142
140 143 return func() if no exception happens. otherwise do some error handling
141 144 and return an exit code accordingly. does not handle all exceptions.
142 145 """
143 146 try:
144 147 try:
145 148 return func()
146 149 except: # re-raises
147 150 ui.traceback()
148 151 raise
149 152 # Global exception handling, alphabetically
150 153 # Mercurial-specific first, followed by built-in and library exceptions
151 154 except error.LockHeld as inst:
152 155 if inst.errno == errno.ETIMEDOUT:
153 156 reason = _('timed out waiting for lock held by %r') % inst.locker
154 157 else:
155 158 reason = _('lock held by %r') % inst.locker
156 159 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
157 160 if not inst.locker:
158 161 ui.warn(_("(lock might be very busy)\n"))
159 162 except error.LockUnavailable as inst:
160 163 ui.warn(_("abort: could not lock %s: %s\n") %
161 164 (inst.desc or inst.filename, inst.strerror))
162 165 except error.OutOfBandError as inst:
163 166 if inst.args:
164 167 msg = _("abort: remote error:\n")
165 168 else:
166 169 msg = _("abort: remote error\n")
167 170 ui.warn(msg)
168 171 if inst.args:
169 172 ui.warn(''.join(inst.args))
170 173 if inst.hint:
171 174 ui.warn('(%s)\n' % inst.hint)
172 175 except error.RepoError as inst:
173 176 ui.warn(_("abort: %s!\n") % inst)
174 177 if inst.hint:
175 178 ui.warn(_("(%s)\n") % inst.hint)
176 179 except error.ResponseError as inst:
177 180 ui.warn(_("abort: %s") % inst.args[0])
178 181 if not isinstance(inst.args[1], basestring):
179 182 ui.warn(" %r\n" % (inst.args[1],))
180 183 elif not inst.args[1]:
181 184 ui.warn(_(" empty string\n"))
182 185 else:
183 186 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
184 187 except error.CensoredNodeError as inst:
185 188 ui.warn(_("abort: file censored %s!\n") % inst)
186 189 except error.RevlogError as inst:
187 190 ui.warn(_("abort: %s!\n") % inst)
188 191 except error.InterventionRequired as inst:
189 192 ui.warn("%s\n" % inst)
190 193 if inst.hint:
191 194 ui.warn(_("(%s)\n") % inst.hint)
192 195 return 1
193 196 except error.WdirUnsupported:
194 197 ui.warn(_("abort: working directory revision cannot be specified\n"))
195 198 except error.Abort as inst:
196 199 ui.warn(_("abort: %s\n") % inst)
197 200 if inst.hint:
198 201 ui.warn(_("(%s)\n") % inst.hint)
199 202 except ImportError as inst:
200 203 ui.warn(_("abort: %s!\n") % inst)
201 204 m = str(inst).split()[-1]
202 205 if m in "mpatch bdiff".split():
203 206 ui.warn(_("(did you forget to compile extensions?)\n"))
204 207 elif m in "zlib".split():
205 208 ui.warn(_("(is your Python install correct?)\n"))
206 209 except IOError as inst:
207 210 if util.safehasattr(inst, "code"):
208 211 ui.warn(_("abort: %s\n") % inst)
209 212 elif util.safehasattr(inst, "reason"):
210 213 try: # usually it is in the form (errno, strerror)
211 214 reason = inst.reason.args[1]
212 215 except (AttributeError, IndexError):
213 216 # it might be anything, for example a string
214 217 reason = inst.reason
215 218 if isinstance(reason, unicode):
216 219 # SSLError of Python 2.7.9 contains a unicode
217 220 reason = encoding.unitolocal(reason)
218 221 ui.warn(_("abort: error: %s\n") % reason)
219 222 elif (util.safehasattr(inst, "args")
220 223 and inst.args and inst.args[0] == errno.EPIPE):
221 224 pass
222 225 elif getattr(inst, "strerror", None):
223 226 if getattr(inst, "filename", None):
224 227 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
225 228 else:
226 229 ui.warn(_("abort: %s\n") % inst.strerror)
227 230 else:
228 231 raise
229 232 except OSError as inst:
230 233 if getattr(inst, "filename", None) is not None:
231 234 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
232 235 else:
233 236 ui.warn(_("abort: %s\n") % inst.strerror)
234 237 except MemoryError:
235 238 ui.warn(_("abort: out of memory\n"))
236 239 except SystemExit as inst:
237 240 # Commands shouldn't sys.exit directly, but give a return code.
238 241 # Just in case catch this and and pass exit code to caller.
239 242 return inst.code
240 243 except socket.error as inst:
241 244 ui.warn(_("abort: %s\n") % inst.args[-1])
242 245
243 246 return -1
244 247
245 248 def checknewlabel(repo, lbl, kind):
246 249 # Do not use the "kind" parameter in ui output.
247 250 # It makes strings difficult to translate.
248 251 if lbl in ['tip', '.', 'null']:
249 252 raise error.Abort(_("the name '%s' is reserved") % lbl)
250 253 for c in (':', '\0', '\n', '\r'):
251 254 if c in lbl:
252 255 raise error.Abort(_("%r cannot be used in a name") % c)
253 256 try:
254 257 int(lbl)
255 258 raise error.Abort(_("cannot use an integer as a name"))
256 259 except ValueError:
257 260 pass
258 261
259 262 def checkfilename(f):
260 263 '''Check that the filename f is an acceptable filename for a tracked file'''
261 264 if '\r' in f or '\n' in f:
262 265 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
263 266
264 267 def checkportable(ui, f):
265 268 '''Check if filename f is portable and warn or abort depending on config'''
266 269 checkfilename(f)
267 270 abort, warn = checkportabilityalert(ui)
268 271 if abort or warn:
269 272 msg = util.checkwinfilename(f)
270 273 if msg:
271 274 msg = "%s: %r" % (msg, f)
272 275 if abort:
273 276 raise error.Abort(msg)
274 277 ui.warn(_("warning: %s\n") % msg)
275 278
276 279 def checkportabilityalert(ui):
277 280 '''check if the user's config requests nothing, a warning, or abort for
278 281 non-portable filenames'''
279 282 val = ui.config('ui', 'portablefilenames', 'warn')
280 283 lval = val.lower()
281 284 bval = util.parsebool(val)
282 285 abort = pycompat.osname == 'nt' or lval == 'abort'
283 286 warn = bval or lval == 'warn'
284 287 if bval is None and not (warn or abort or lval == 'ignore'):
285 288 raise error.ConfigError(
286 289 _("ui.portablefilenames value is invalid ('%s')") % val)
287 290 return abort, warn
288 291
289 292 class casecollisionauditor(object):
290 293 def __init__(self, ui, abort, dirstate):
291 294 self._ui = ui
292 295 self._abort = abort
293 296 allfiles = '\0'.join(dirstate._map)
294 297 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
295 298 self._dirstate = dirstate
296 299 # The purpose of _newfiles is so that we don't complain about
297 300 # case collisions if someone were to call this object with the
298 301 # same filename twice.
299 302 self._newfiles = set()
300 303
301 304 def __call__(self, f):
302 305 if f in self._newfiles:
303 306 return
304 307 fl = encoding.lower(f)
305 308 if fl in self._loweredfiles and f not in self._dirstate:
306 309 msg = _('possible case-folding collision for %s') % f
307 310 if self._abort:
308 311 raise error.Abort(msg)
309 312 self._ui.warn(_("warning: %s\n") % msg)
310 313 self._loweredfiles.add(fl)
311 314 self._newfiles.add(f)
312 315
313 316 def filteredhash(repo, maxrev):
314 317 """build hash of filtered revisions in the current repoview.
315 318
316 319 Multiple caches perform up-to-date validation by checking that the
317 320 tiprev and tipnode stored in the cache file match the current repository.
318 321 However, this is not sufficient for validating repoviews because the set
319 322 of revisions in the view may change without the repository tiprev and
320 323 tipnode changing.
321 324
322 325 This function hashes all the revs filtered from the view and returns
323 326 that SHA-1 digest.
324 327 """
325 328 cl = repo.changelog
326 329 if not cl.filteredrevs:
327 330 return None
328 331 key = None
329 332 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
330 333 if revs:
331 334 s = hashlib.sha1()
332 335 for rev in revs:
333 336 s.update('%d;' % rev)
334 337 key = s.digest()
335 338 return key
336 339
337 340 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
338 341 '''yield every hg repository under path, always recursively.
339 342 The recurse flag will only control recursion into repo working dirs'''
340 343 def errhandler(err):
341 344 if err.filename == path:
342 345 raise err
343 346 samestat = getattr(os.path, 'samestat', None)
344 347 if followsym and samestat is not None:
345 348 def adddir(dirlst, dirname):
346 349 match = False
347 350 dirstat = os.stat(dirname)
348 351 for lstdirstat in dirlst:
349 352 if samestat(dirstat, lstdirstat):
350 353 match = True
351 354 break
352 355 if not match:
353 356 dirlst.append(dirstat)
354 357 return not match
355 358 else:
356 359 followsym = False
357 360
358 361 if (seen_dirs is None) and followsym:
359 362 seen_dirs = []
360 363 adddir(seen_dirs, path)
361 364 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
362 365 dirs.sort()
363 366 if '.hg' in dirs:
364 367 yield root # found a repository
365 368 qroot = os.path.join(root, '.hg', 'patches')
366 369 if os.path.isdir(os.path.join(qroot, '.hg')):
367 370 yield qroot # we have a patch queue repo here
368 371 if recurse:
369 372 # avoid recursing inside the .hg directory
370 373 dirs.remove('.hg')
371 374 else:
372 375 dirs[:] = [] # don't descend further
373 376 elif followsym:
374 377 newdirs = []
375 378 for d in dirs:
376 379 fname = os.path.join(root, d)
377 380 if adddir(seen_dirs, fname):
378 381 if os.path.islink(fname):
379 382 for hgname in walkrepos(fname, True, seen_dirs):
380 383 yield hgname
381 384 else:
382 385 newdirs.append(d)
383 386 dirs[:] = newdirs
384 387
385 388 def binnode(ctx):
386 389 """Return binary node id for a given basectx"""
387 390 node = ctx.node()
388 391 if node is None:
389 392 return wdirid
390 393 return node
391 394
392 395 def intrev(ctx):
393 396 """Return integer for a given basectx that can be used in comparison or
394 397 arithmetic operation"""
395 398 rev = ctx.rev()
396 399 if rev is None:
397 400 return wdirrev
398 401 return rev
399 402
400 403 def revsingle(repo, revspec, default='.'):
401 404 if not revspec and revspec != 0:
402 405 return repo[default]
403 406
404 407 l = revrange(repo, [revspec])
405 408 if not l:
406 409 raise error.Abort(_('empty revision set'))
407 410 return repo[l.last()]
408 411
409 412 def _pairspec(revspec):
410 413 tree = revsetlang.parse(revspec)
411 414 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
412 415
413 416 def revpair(repo, revs):
414 417 if not revs:
415 418 return repo.dirstate.p1(), None
416 419
417 420 l = revrange(repo, revs)
418 421
419 422 if not l:
420 423 first = second = None
421 424 elif l.isascending():
422 425 first = l.min()
423 426 second = l.max()
424 427 elif l.isdescending():
425 428 first = l.max()
426 429 second = l.min()
427 430 else:
428 431 first = l.first()
429 432 second = l.last()
430 433
431 434 if first is None:
432 435 raise error.Abort(_('empty revision range'))
433 436 if (first == second and len(revs) >= 2
434 437 and not all(revrange(repo, [r]) for r in revs)):
435 438 raise error.Abort(_('empty revision on one side of range'))
436 439
437 440 # if top-level is range expression, the result must always be a pair
438 441 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
439 442 return repo.lookup(first), None
440 443
441 444 return repo.lookup(first), repo.lookup(second)
442 445
443 446 def revrange(repo, specs):
444 447 """Execute 1 to many revsets and return the union.
445 448
446 449 This is the preferred mechanism for executing revsets using user-specified
447 450 config options, such as revset aliases.
448 451
449 452 The revsets specified by ``specs`` will be executed via a chained ``OR``
450 453 expression. If ``specs`` is empty, an empty result is returned.
451 454
452 455 ``specs`` can contain integers, in which case they are assumed to be
453 456 revision numbers.
454 457
455 458 It is assumed the revsets are already formatted. If you have arguments
456 459 that need to be expanded in the revset, call ``revsetlang.formatspec()``
457 460 and pass the result as an element of ``specs``.
458 461
459 462 Specifying a single revset is allowed.
460 463
461 464 Returns a ``revset.abstractsmartset`` which is a list-like interface over
462 465 integer revisions.
463 466 """
464 467 allspecs = []
465 468 for spec in specs:
466 469 if isinstance(spec, int):
467 470 spec = revsetlang.formatspec('rev(%d)', spec)
468 471 allspecs.append(spec)
469 472 return repo.anyrevs(allspecs, user=True)
470 473
471 474 def meaningfulparents(repo, ctx):
472 475 """Return list of meaningful (or all if debug) parentrevs for rev.
473 476
474 477 For merges (two non-nullrev revisions) both parents are meaningful.
475 478 Otherwise the first parent revision is considered meaningful if it
476 479 is not the preceding revision.
477 480 """
478 481 parents = ctx.parents()
479 482 if len(parents) > 1:
480 483 return parents
481 484 if repo.ui.debugflag:
482 485 return [parents[0], repo['null']]
483 486 if parents[0].rev() >= intrev(ctx) - 1:
484 487 return []
485 488 return parents
486 489
487 490 def expandpats(pats):
488 491 '''Expand bare globs when running on windows.
489 492 On posix we assume it already has already been done by sh.'''
490 493 if not util.expandglobs:
491 494 return list(pats)
492 495 ret = []
493 496 for kindpat in pats:
494 497 kind, pat = matchmod._patsplit(kindpat, None)
495 498 if kind is None:
496 499 try:
497 500 globbed = glob.glob(pat)
498 501 except re.error:
499 502 globbed = [pat]
500 503 if globbed:
501 504 ret.extend(globbed)
502 505 continue
503 506 ret.append(kindpat)
504 507 return ret
505 508
506 509 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
507 510 badfn=None):
508 511 '''Return a matcher and the patterns that were used.
509 512 The matcher will warn about bad matches, unless an alternate badfn callback
510 513 is provided.'''
511 514 if pats == ("",):
512 515 pats = []
513 516 if opts is None:
514 517 opts = {}
515 518 if not globbed and default == 'relpath':
516 519 pats = expandpats(pats or [])
517 520
518 521 def bad(f, msg):
519 522 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
520 523
521 524 if badfn is None:
522 525 badfn = bad
523 526
524 527 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
525 528 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
526 529
527 530 if m.always():
528 531 pats = []
529 532 return m, pats
530 533
531 534 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
532 535 badfn=None):
533 536 '''Return a matcher that will warn about bad matches.'''
534 537 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
535 538
536 539 def matchall(repo):
537 540 '''Return a matcher that will efficiently match everything.'''
538 541 return matchmod.always(repo.root, repo.getcwd())
539 542
540 543 def matchfiles(repo, files, badfn=None):
541 544 '''Return a matcher that will efficiently match exactly these files.'''
542 545 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
543 546
544 547 def origpath(ui, repo, filepath):
545 548 '''customize where .orig files are created
546 549
547 550 Fetch user defined path from config file: [ui] origbackuppath = <path>
548 551 Fall back to default (filepath) if not specified
549 552 '''
550 553 origbackuppath = ui.config('ui', 'origbackuppath', None)
551 554 if origbackuppath is None:
552 555 return filepath + ".orig"
553 556
554 557 filepathfromroot = os.path.relpath(filepath, start=repo.root)
555 558 fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
556 559
557 560 origbackupdir = repo.vfs.dirname(fullorigpath)
558 561 if not repo.vfs.exists(origbackupdir):
559 562 ui.note(_('creating directory: %s\n') % origbackupdir)
560 563 util.makedirs(origbackupdir)
561 564
562 565 return fullorigpath + ".orig"
563 566
567 def cleanupnodes(repo, mapping, operation):
568 """do common cleanups when old nodes are replaced by new nodes
569
570 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
571 (we might also want to move working directory parent in the future)
572
573 mapping is {oldnode: [newnode]} or a iterable of nodes if they do not have
574 replacements. operation is a string, like "rebase".
575 """
576 if not util.safehasattr(mapping, 'items'):
577 mapping = {n: () for n in mapping}
578
579 with repo.transaction('cleanup') as tr:
580 # Move bookmarks
581 bmarks = repo._bookmarks
582 bmarkchanged = False
583 for oldnode, newnodes in mapping.items():
584 oldbmarks = repo.nodebookmarks(oldnode)
585 if not oldbmarks:
586 continue
587 bmarkchanged = True
588 if len(newnodes) > 1:
589 heads = list(repo.set('heads(%ln)', newnodes))
590 if len(heads) != 1:
591 raise error.ProgrammingError(
592 'cannot figure out bookmark movement')
593 newnode = heads[0].node()
594 elif len(newnodes) == 0:
595 # move bookmark backwards
596 roots = list(repo.set('max((::%n) - %ln)', oldnode,
597 list(mapping)))
598 if roots:
599 newnode = roots[0].node()
600 else:
601 newnode = nullid
602 else:
603 newnode = newnodes[0]
604 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
605 (oldbmarks, hex(oldnode), hex(newnode)))
606 for name in oldbmarks:
607 bmarks[name] = newnode
608 if bmarkchanged:
609 bmarks.recordchange(tr)
610
611 # Obsolete or strip nodes
612 if obsolete.isenabled(repo, obsolete.createmarkersopt):
613 # If a node is already obsoleted, and we want to obsolete it
614 # without a successor, skip that obssolete request since it's
615 # unnecessary. That's the "if s or not isobs(n)" check below.
616 # Also sort the node in topology order, that might be useful for
617 # some obsstore logic.
618 # NOTE: the filtering and sorting might belong to createmarkers.
619 isobs = repo.obsstore.successors.__contains__
620 sortfunc = lambda ns: repo.changelog.rev(ns[0])
621 rels = [(repo[n], (repo[m] for m in s))
622 for n, s in sorted(mapping.items(), key=sortfunc)
623 if s or not isobs(n)]
624 obsolete.createmarkers(repo, rels, operation=operation)
625 else:
626 from . import repair # avoid import cycle
627 repair.delayedstrip(repo.ui, repo, list(mapping), operation)
628
564 629 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
565 630 if opts is None:
566 631 opts = {}
567 632 m = matcher
568 633 if dry_run is None:
569 634 dry_run = opts.get('dry_run')
570 635 if similarity is None:
571 636 similarity = float(opts.get('similarity') or 0)
572 637
573 638 ret = 0
574 639 join = lambda f: os.path.join(prefix, f)
575 640
576 641 wctx = repo[None]
577 642 for subpath in sorted(wctx.substate):
578 643 submatch = matchmod.subdirmatcher(subpath, m)
579 644 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
580 645 sub = wctx.sub(subpath)
581 646 try:
582 647 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
583 648 ret = 1
584 649 except error.LookupError:
585 650 repo.ui.status(_("skipping missing subrepository: %s\n")
586 651 % join(subpath))
587 652
588 653 rejected = []
589 654 def badfn(f, msg):
590 655 if f in m.files():
591 656 m.bad(f, msg)
592 657 rejected.append(f)
593 658
594 659 badmatch = matchmod.badmatch(m, badfn)
595 660 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
596 661 badmatch)
597 662
598 663 unknownset = set(unknown + forgotten)
599 664 toprint = unknownset.copy()
600 665 toprint.update(deleted)
601 666 for abs in sorted(toprint):
602 667 if repo.ui.verbose or not m.exact(abs):
603 668 if abs in unknownset:
604 669 status = _('adding %s\n') % m.uipath(abs)
605 670 else:
606 671 status = _('removing %s\n') % m.uipath(abs)
607 672 repo.ui.status(status)
608 673
609 674 renames = _findrenames(repo, m, added + unknown, removed + deleted,
610 675 similarity)
611 676
612 677 if not dry_run:
613 678 _markchanges(repo, unknown + forgotten, deleted, renames)
614 679
615 680 for f in rejected:
616 681 if f in m.files():
617 682 return 1
618 683 return ret
619 684
620 685 def marktouched(repo, files, similarity=0.0):
621 686 '''Assert that files have somehow been operated upon. files are relative to
622 687 the repo root.'''
623 688 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
624 689 rejected = []
625 690
626 691 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
627 692
628 693 if repo.ui.verbose:
629 694 unknownset = set(unknown + forgotten)
630 695 toprint = unknownset.copy()
631 696 toprint.update(deleted)
632 697 for abs in sorted(toprint):
633 698 if abs in unknownset:
634 699 status = _('adding %s\n') % abs
635 700 else:
636 701 status = _('removing %s\n') % abs
637 702 repo.ui.status(status)
638 703
639 704 renames = _findrenames(repo, m, added + unknown, removed + deleted,
640 705 similarity)
641 706
642 707 _markchanges(repo, unknown + forgotten, deleted, renames)
643 708
644 709 for f in rejected:
645 710 if f in m.files():
646 711 return 1
647 712 return 0
648 713
649 714 def _interestingfiles(repo, matcher):
650 715 '''Walk dirstate with matcher, looking for files that addremove would care
651 716 about.
652 717
653 718 This is different from dirstate.status because it doesn't care about
654 719 whether files are modified or clean.'''
655 720 added, unknown, deleted, removed, forgotten = [], [], [], [], []
656 721 audit_path = pathutil.pathauditor(repo.root)
657 722
658 723 ctx = repo[None]
659 724 dirstate = repo.dirstate
660 725 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
661 726 full=False)
662 727 for abs, st in walkresults.iteritems():
663 728 dstate = dirstate[abs]
664 729 if dstate == '?' and audit_path.check(abs):
665 730 unknown.append(abs)
666 731 elif dstate != 'r' and not st:
667 732 deleted.append(abs)
668 733 elif dstate == 'r' and st:
669 734 forgotten.append(abs)
670 735 # for finding renames
671 736 elif dstate == 'r' and not st:
672 737 removed.append(abs)
673 738 elif dstate == 'a':
674 739 added.append(abs)
675 740
676 741 return added, unknown, deleted, removed, forgotten
677 742
678 743 def _findrenames(repo, matcher, added, removed, similarity):
679 744 '''Find renames from removed files to added ones.'''
680 745 renames = {}
681 746 if similarity > 0:
682 747 for old, new, score in similar.findrenames(repo, added, removed,
683 748 similarity):
684 749 if (repo.ui.verbose or not matcher.exact(old)
685 750 or not matcher.exact(new)):
686 751 repo.ui.status(_('recording removal of %s as rename to %s '
687 752 '(%d%% similar)\n') %
688 753 (matcher.rel(old), matcher.rel(new),
689 754 score * 100))
690 755 renames[new] = old
691 756 return renames
692 757
693 758 def _markchanges(repo, unknown, deleted, renames):
694 759 '''Marks the files in unknown as added, the files in deleted as removed,
695 760 and the files in renames as copied.'''
696 761 wctx = repo[None]
697 762 with repo.wlock():
698 763 wctx.forget(deleted)
699 764 wctx.add(unknown)
700 765 for new, old in renames.iteritems():
701 766 wctx.copy(old, new)
702 767
703 768 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
704 769 """Update the dirstate to reflect the intent of copying src to dst. For
705 770 different reasons it might not end with dst being marked as copied from src.
706 771 """
707 772 origsrc = repo.dirstate.copied(src) or src
708 773 if dst == origsrc: # copying back a copy?
709 774 if repo.dirstate[dst] not in 'mn' and not dryrun:
710 775 repo.dirstate.normallookup(dst)
711 776 else:
712 777 if repo.dirstate[origsrc] == 'a' and origsrc == src:
713 778 if not ui.quiet:
714 779 ui.warn(_("%s has not been committed yet, so no copy "
715 780 "data will be stored for %s.\n")
716 781 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
717 782 if repo.dirstate[dst] in '?r' and not dryrun:
718 783 wctx.add([dst])
719 784 elif not dryrun:
720 785 wctx.copy(origsrc, dst)
721 786
722 787 def readrequires(opener, supported):
723 788 '''Reads and parses .hg/requires and checks if all entries found
724 789 are in the list of supported features.'''
725 790 requirements = set(opener.read("requires").splitlines())
726 791 missings = []
727 792 for r in requirements:
728 793 if r not in supported:
729 794 if not r or not r[0].isalnum():
730 795 raise error.RequirementError(_(".hg/requires file is corrupt"))
731 796 missings.append(r)
732 797 missings.sort()
733 798 if missings:
734 799 raise error.RequirementError(
735 800 _("repository requires features unknown to this Mercurial: %s")
736 801 % " ".join(missings),
737 802 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
738 803 " for more information"))
739 804 return requirements
740 805
741 806 def writerequires(opener, requirements):
742 807 with opener('requires', 'w') as fp:
743 808 for r in sorted(requirements):
744 809 fp.write("%s\n" % r)
745 810
746 811 class filecachesubentry(object):
747 812 def __init__(self, path, stat):
748 813 self.path = path
749 814 self.cachestat = None
750 815 self._cacheable = None
751 816
752 817 if stat:
753 818 self.cachestat = filecachesubentry.stat(self.path)
754 819
755 820 if self.cachestat:
756 821 self._cacheable = self.cachestat.cacheable()
757 822 else:
758 823 # None means we don't know yet
759 824 self._cacheable = None
760 825
761 826 def refresh(self):
762 827 if self.cacheable():
763 828 self.cachestat = filecachesubentry.stat(self.path)
764 829
765 830 def cacheable(self):
766 831 if self._cacheable is not None:
767 832 return self._cacheable
768 833
769 834 # we don't know yet, assume it is for now
770 835 return True
771 836
772 837 def changed(self):
773 838 # no point in going further if we can't cache it
774 839 if not self.cacheable():
775 840 return True
776 841
777 842 newstat = filecachesubentry.stat(self.path)
778 843
779 844 # we may not know if it's cacheable yet, check again now
780 845 if newstat and self._cacheable is None:
781 846 self._cacheable = newstat.cacheable()
782 847
783 848 # check again
784 849 if not self._cacheable:
785 850 return True
786 851
787 852 if self.cachestat != newstat:
788 853 self.cachestat = newstat
789 854 return True
790 855 else:
791 856 return False
792 857
793 858 @staticmethod
794 859 def stat(path):
795 860 try:
796 861 return util.cachestat(path)
797 862 except OSError as e:
798 863 if e.errno != errno.ENOENT:
799 864 raise
800 865
801 866 class filecacheentry(object):
802 867 def __init__(self, paths, stat=True):
803 868 self._entries = []
804 869 for path in paths:
805 870 self._entries.append(filecachesubentry(path, stat))
806 871
807 872 def changed(self):
808 873 '''true if any entry has changed'''
809 874 for entry in self._entries:
810 875 if entry.changed():
811 876 return True
812 877 return False
813 878
814 879 def refresh(self):
815 880 for entry in self._entries:
816 881 entry.refresh()
817 882
818 883 class filecache(object):
819 884 '''A property like decorator that tracks files under .hg/ for updates.
820 885
821 886 Records stat info when called in _filecache.
822 887
823 888 On subsequent calls, compares old stat info with new info, and recreates the
824 889 object when any of the files changes, updating the new stat info in
825 890 _filecache.
826 891
827 892 Mercurial either atomic renames or appends for files under .hg,
828 893 so to ensure the cache is reliable we need the filesystem to be able
829 894 to tell us if a file has been replaced. If it can't, we fallback to
830 895 recreating the object on every call (essentially the same behavior as
831 896 propertycache).
832 897
833 898 '''
834 899 def __init__(self, *paths):
835 900 self.paths = paths
836 901
837 902 def join(self, obj, fname):
838 903 """Used to compute the runtime path of a cached file.
839 904
840 905 Users should subclass filecache and provide their own version of this
841 906 function to call the appropriate join function on 'obj' (an instance
842 907 of the class that its member function was decorated).
843 908 """
844 909 raise NotImplementedError
845 910
846 911 def __call__(self, func):
847 912 self.func = func
848 913 self.name = func.__name__.encode('ascii')
849 914 return self
850 915
851 916 def __get__(self, obj, type=None):
852 917 # if accessed on the class, return the descriptor itself.
853 918 if obj is None:
854 919 return self
855 920 # do we need to check if the file changed?
856 921 if self.name in obj.__dict__:
857 922 assert self.name in obj._filecache, self.name
858 923 return obj.__dict__[self.name]
859 924
860 925 entry = obj._filecache.get(self.name)
861 926
862 927 if entry:
863 928 if entry.changed():
864 929 entry.obj = self.func(obj)
865 930 else:
866 931 paths = [self.join(obj, path) for path in self.paths]
867 932
868 933 # We stat -before- creating the object so our cache doesn't lie if
869 934 # a writer modified between the time we read and stat
870 935 entry = filecacheentry(paths, True)
871 936 entry.obj = self.func(obj)
872 937
873 938 obj._filecache[self.name] = entry
874 939
875 940 obj.__dict__[self.name] = entry.obj
876 941 return entry.obj
877 942
878 943 def __set__(self, obj, value):
879 944 if self.name not in obj._filecache:
880 945 # we add an entry for the missing value because X in __dict__
881 946 # implies X in _filecache
882 947 paths = [self.join(obj, path) for path in self.paths]
883 948 ce = filecacheentry(paths, False)
884 949 obj._filecache[self.name] = ce
885 950 else:
886 951 ce = obj._filecache[self.name]
887 952
888 953 ce.obj = value # update cached copy
889 954 obj.__dict__[self.name] = value # update copy returned by obj.x
890 955
891 956 def __delete__(self, obj):
892 957 try:
893 958 del obj.__dict__[self.name]
894 959 except KeyError:
895 960 raise AttributeError(self.name)
896 961
897 962 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
898 963 if lock is None:
899 964 raise error.LockInheritanceContractViolation(
900 965 'lock can only be inherited while held')
901 966 if environ is None:
902 967 environ = {}
903 968 with lock.inherit() as locker:
904 969 environ[envvar] = locker
905 970 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
906 971
907 972 def wlocksub(repo, cmd, *args, **kwargs):
908 973 """run cmd as a subprocess that allows inheriting repo's wlock
909 974
910 975 This can only be called while the wlock is held. This takes all the
911 976 arguments that ui.system does, and returns the exit code of the
912 977 subprocess."""
913 978 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
914 979 **kwargs)
915 980
916 981 def gdinitconfig(ui):
917 982 """helper function to know if a repo should be created as general delta
918 983 """
919 984 # experimental config: format.generaldelta
920 985 return (ui.configbool('format', 'generaldelta', False)
921 986 or ui.configbool('format', 'usegeneraldelta', True))
922 987
923 988 def gddeltaconfig(ui):
924 989 """helper function to know if incoming delta should be optimised
925 990 """
926 991 # experimental config: format.generaldelta
927 992 return ui.configbool('format', 'generaldelta', False)
928 993
929 994 class simplekeyvaluefile(object):
930 995 """A simple file with key=value lines
931 996
932 997 Keys must be alphanumerics and start with a letter, values must not
933 998 contain '\n' characters"""
934 999 firstlinekey = '__firstline'
935 1000
936 1001 def __init__(self, vfs, path, keys=None):
937 1002 self.vfs = vfs
938 1003 self.path = path
939 1004
940 1005 def read(self, firstlinenonkeyval=False):
941 1006 """Read the contents of a simple key-value file
942 1007
943 1008 'firstlinenonkeyval' indicates whether the first line of file should
944 1009 be treated as a key-value pair or reuturned fully under the
945 1010 __firstline key."""
946 1011 lines = self.vfs.readlines(self.path)
947 1012 d = {}
948 1013 if firstlinenonkeyval:
949 1014 if not lines:
950 1015 e = _("empty simplekeyvalue file")
951 1016 raise error.CorruptedState(e)
952 1017 # we don't want to include '\n' in the __firstline
953 1018 d[self.firstlinekey] = lines[0][:-1]
954 1019 del lines[0]
955 1020
956 1021 try:
957 1022 # the 'if line.strip()' part prevents us from failing on empty
958 1023 # lines which only contain '\n' therefore are not skipped
959 1024 # by 'if line'
960 1025 updatedict = dict(line[:-1].split('=', 1) for line in lines
961 1026 if line.strip())
962 1027 if self.firstlinekey in updatedict:
963 1028 e = _("%r can't be used as a key")
964 1029 raise error.CorruptedState(e % self.firstlinekey)
965 1030 d.update(updatedict)
966 1031 except ValueError as e:
967 1032 raise error.CorruptedState(str(e))
968 1033 return d
969 1034
970 1035 def write(self, data, firstline=None):
971 1036 """Write key=>value mapping to a file
972 1037 data is a dict. Keys must be alphanumerical and start with a letter.
973 1038 Values must not contain newline characters.
974 1039
975 1040 If 'firstline' is not None, it is written to file before
976 1041 everything else, as it is, not in a key=value form"""
977 1042 lines = []
978 1043 if firstline is not None:
979 1044 lines.append('%s\n' % firstline)
980 1045
981 1046 for k, v in data.items():
982 1047 if k == self.firstlinekey:
983 1048 e = "key name '%s' is reserved" % self.firstlinekey
984 1049 raise error.ProgrammingError(e)
985 1050 if not k[0].isalpha():
986 1051 e = "keys must start with a letter in a key-value file"
987 1052 raise error.ProgrammingError(e)
988 1053 if not k.isalnum():
989 1054 e = "invalid key name in a simple key-value file"
990 1055 raise error.ProgrammingError(e)
991 1056 if '\n' in v:
992 1057 e = "invalid value in a simple key-value file"
993 1058 raise error.ProgrammingError(e)
994 1059 lines.append("%s=%s\n" % (k, v))
995 1060 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
996 1061 fp.write(''.join(lines))
@@ -1,992 +1,1094 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(self, 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 $ cp -R . ../scmutilcleanup
958 959
959 960 $ hg up -C I
960 961 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
961 962 $ echo 3 >> I
962 963 $ cat > $TESTTMP/delayedstrip.py <<EOF
963 964 > from mercurial import repair, commands
964 965 > def reposetup(ui, repo):
965 966 > def getnodes(expr):
966 967 > return [repo.changelog.node(r) for r in repo.revs(expr)]
967 968 > with repo.wlock():
968 969 > with repo.lock():
969 970 > with repo.transaction('delayedstrip'):
970 971 > repair.delayedstrip(ui, repo, getnodes('B+I+Z+D+E'), 'J')
971 972 > repair.delayedstrip(ui, repo, getnodes('G+H+Z'), 'I')
972 973 > commands.commit(ui, repo, message='J', date='0 0')
973 974 > EOF
974 975 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/delayedstrip.py
975 976 warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22
976 977 saved backup bundle to $TESTTMP/delayedstrip/.hg/strip-backup/f585351a92f8-81fa23b0-I.hg (glob)
977 978
978 979 $ hg log -G -T '{rev}:{node|short} {desc}' -r 'sort(all(), topo)'
979 980 @ 6:2f2d51af6205 J
980 981 |
981 982 o 3:08ebfeb61bac I
982 983 |
983 984 | o 5:64a8289d2492 F
984 985 | |
985 986 | o 2:7fb047a69f22 E
986 987 |/
987 988 | o 4:26805aba1e60 C
988 989 | |
989 990 | o 1:112478962961 B
990 991 |/
991 992 o 0:426bada5c675 A
992 993
994 Test high-level scmutil.cleanupnodes API
995
996 $ cd $TESTTMP/scmutilcleanup
997 $ hg debugdrawdag <<'EOS'
998 > D2 F2 G2 # D2, F2, G2 are replacements for D, F, G
999 > | | |
1000 > C H G
1001 > EOS
1002 $ for i in B C D F G I Z; do
1003 > hg bookmark -i -r $i b-$i
1004 > done
1005 $ cp -R . ../scmutilcleanup.obsstore
1006
1007 $ cat > $TESTTMP/scmutilcleanup.py <<EOF
1008 > from mercurial import scmutil
1009 > def reposetup(ui, repo):
1010 > def nodes(expr):
1011 > return [repo.changelog.node(r) for r in repo.revs(expr)]
1012 > def node(expr):
1013 > return nodes(expr)[0]
1014 > with repo.wlock():
1015 > with repo.lock():
1016 > with repo.transaction('delayedstrip'):
1017 > mapping = {node('F'): [node('F2')],
1018 > node('D'): [node('D2')],
1019 > node('G'): [node('G2')]}
1020 > scmutil.cleanupnodes(repo, mapping, 'replace')
1021 > scmutil.cleanupnodes(repo, nodes('((B::)+I+Z)-D2'), 'replace')
1022 > EOF
1023 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
1024 warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60
1025 saved backup bundle to $TESTTMP/scmutilcleanup/.hg/strip-backup/f585351a92f8-73fb7c03-replace.hg (glob)
1026
1027 $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
1028 o 8:1473d4b996d1 G2 b-G
1029 |
1030 | o 7:d94e89b773b6 F2 b-F
1031 | |
1032 | o 5:7fe5bac4c918 H
1033 |/|
1034 | o 3:7fb047a69f22 E
1035 | |
1036 | | o 6:7c78f703e465 D2 b-D
1037 | | |
1038 | | o 4:26805aba1e60 C
1039 | | |
1040 | | o 2:112478962961 B
1041 | |/
1042 o | 1:1fc8102cda62 G
1043 /
1044 o 0:426bada5c675 A b-B b-C b-I
1045
1046 $ hg bookmark
1047 b-B 0:426bada5c675
1048 b-C 0:426bada5c675
1049 b-D 6:7c78f703e465
1050 b-F 7:d94e89b773b6
1051 b-G 8:1473d4b996d1
1052 b-I 0:426bada5c675
1053 b-Z -1:000000000000
1054
1055 Test the above using obsstore "by the way". Not directly related to strip, but
1056 we have reusable code here
1057
1058 $ cd $TESTTMP/scmutilcleanup.obsstore
1059 $ cat >> .hg/hgrc <<EOF
1060 > [experimental]
1061 > evolution=all
1062 > evolution.track-operation=1
1063 > EOF
1064
1065 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
1066
1067 $ rm .hg/localtags
1068 $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
1069 o 12:1473d4b996d1 G2 b-G
1070 |
1071 | o 11:d94e89b773b6 F2 b-F
1072 | |
1073 | o 8:7fe5bac4c918 H
1074 |/|
1075 | o 4:7fb047a69f22 E
1076 | |
1077 | | o 10:7c78f703e465 D2 b-D
1078 | | |
1079 | | x 6:26805aba1e60 C
1080 | | |
1081 | | x 3:112478962961 B
1082 | |/
1083 x | 1:1fc8102cda62 G
1084 /
1085 o 0:426bada5c675 A b-B b-C b-I
1086
1087 $ hg debugobsolete
1088 1fc8102cda6204549f031015641606ccf5513ec3 1473d4b996d1d1b121de6b39fab6a04fbf9d873e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1089 64a8289d249234b9886244d379f15e6b650b28e3 d94e89b773b67e72642a931159ada8d1a9246998 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1090 f585351a92f85104bff7c284233c338b10eb1df7 7c78f703e465d73102cc8780667ce269c5208a40 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1091 48b9aae0607f43ff110d84e6883c151942add5ab 0 {0000000000000000000000000000000000000000} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1092 112478962961147124edd43549aedd1a335e44bf 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1093 08ebfeb61bac6e3f12079de774d285a0d6689eba 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1094 26805aba1e600a82e93661149f2313866a221a7b 0 {112478962961147124edd43549aedd1a335e44bf} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
General Comments 0
You need to be logged in to leave comments. Login now