##// END OF EJS Templates
histedit: replace various nodes lists with replacement graph (and issue3582)...
Pierre-Yves David -
r17758:5863f0e4 default
parent child Browse files
Show More
@@ -272,7 +272,7 b' def pick(ui, repo, ctx, ha, opts):'
272 oldctx = repo[ha]
272 oldctx = repo[ha]
273 if oldctx.parents()[0] == ctx:
273 if oldctx.parents()[0] == ctx:
274 ui.debug('node %s unchanged\n' % ha)
274 ui.debug('node %s unchanged\n' % ha)
275 return oldctx, [], [], []
275 return oldctx, []
276 hg.update(repo, ctx.node())
276 hg.update(repo, ctx.node())
277 stats = applychanges(ui, repo, oldctx, opts)
277 stats = applychanges(ui, repo, oldctx, opts)
278 if stats and stats[3] > 0:
278 if stats and stats[3] > 0:
@@ -284,8 +284,9 b' def pick(ui, repo, ctx, ha, opts):'
284 if n is None:
284 if n is None:
285 ui.warn(_('%s: empty changeset\n')
285 ui.warn(_('%s: empty changeset\n')
286 % node.hex(ha))
286 % node.hex(ha))
287 return ctx, [], [], []
287 return ctx, []
288 return repo[n], [n], [oldctx.node()], []
288 new = repo[n]
289 return new, [(oldctx.node(), (n,))]
289
290
290
291
291 def edit(ui, repo, ctx, ha, opts):
292 def edit(ui, repo, ctx, ha, opts):
@@ -308,7 +309,7 b' def fold(ui, repo, ctx, ha, opts):'
308 if n is None:
309 if n is None:
309 ui.warn(_('%s: empty changeset')
310 ui.warn(_('%s: empty changeset')
310 % node.hex(ha))
311 % node.hex(ha))
311 return ctx, [], [], []
312 return ctx, []
312 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
313 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
313
314
314 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
315 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
@@ -332,12 +333,18 b' def finishfold(ui, repo, ctx, oldctx, ne'
332 commitopts['date'] = max(ctx.date(), oldctx.date())
333 commitopts['date'] = max(ctx.date(), oldctx.date())
333 n = collapse(repo, ctx, repo[newnode], commitopts)
334 n = collapse(repo, ctx, repo[newnode], commitopts)
334 if n is None:
335 if n is None:
335 return ctx, [], [], []
336 return ctx, []
336 hg.update(repo, n)
337 hg.update(repo, n)
337 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
338 replacements = [(oldctx.node(), (newnode,)),
339 (ctx.node(), (n,)),
340 (newnode, (n,)),
341 ]
342 for ich in internalchanges:
343 replacements.append((ich, (n,)))
344 return repo[n], replacements
338
345
339 def drop(ui, repo, ctx, ha, opts):
346 def drop(ui, repo, ctx, ha, opts):
340 return ctx, [], [repo[ha].node()], []
347 return ctx, [(repo[ha].node(), ())]
341
348
342
349
343 def message(ui, repo, ctx, ha, opts):
350 def message(ui, repo, ctx, ha, opts):
@@ -353,9 +360,9 b' def message(ui, repo, ctx, ha, opts):'
353 extra=oldctx.extra())
360 extra=oldctx.extra())
354 newctx = repo[new]
361 newctx = repo[new]
355 if oldctx.node() != newctx.node():
362 if oldctx.node() != newctx.node():
356 return newctx, [new], [oldctx.node()], []
363 return newctx, [(oldctx.node(), (new,))]
357 # We didn't make an edit, so just indicate no replaced nodes
364 # We didn't make an edit, so just indicate no replaced nodes
358 return newctx, [new], [], []
365 return newctx, []
359
366
360 actiontable = {'p': pick,
367 actiontable = {'p': pick,
361 'pick': pick,
368 'pick': pick,
@@ -417,22 +424,20 b' def histedit(ui, repo, *parent, **opts):'
417 if opts.get('continue', False):
424 if opts.get('continue', False):
418 if len(parent) != 0:
425 if len(parent) != 0:
419 raise util.Abort(_('no arguments allowed with --continue'))
426 raise util.Abort(_('no arguments allowed with --continue'))
420 (parentctxnode, created, replaced, tmpnodes,
427 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
421 existing, rules, keep, topmost, replacemap) = readstate(repo)
428 currentparent, wantnull = repo.dirstate.parents()
422 parentctx = repo[parentctxnode]
429 parentctx = repo[parentctxnode]
423 existing = set(existing)
430 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
424 parentctx = bootstrapcontinue(ui, repo, parentctx, existing,
431 replacements.extend(repl)
425 replacemap, rules, tmpnodes, created,
426 replaced, opts)
427 elif opts.get('abort', False):
432 elif opts.get('abort', False):
428 if len(parent) != 0:
433 if len(parent) != 0:
429 raise util.Abort(_('no arguments allowed with --abort'))
434 raise util.Abort(_('no arguments allowed with --abort'))
430 (parentctxnode, created, replaced, tmpnodes,
435 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
431 existing, rules, keep, topmost, replacemap) = readstate(repo)
436 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
432 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
437 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
433 hg.clean(repo, topmost)
438 hg.clean(repo, topmost)
434 cleanupnode(ui, repo, 'created', created)
439 cleanupnode(ui, repo, 'created', tmpnodes)
435 cleanupnode(ui, repo, 'temp', tmpnodes)
440 cleanupnode(ui, repo, 'temp', leafs)
436 os.unlink(os.path.join(repo.path, 'histedit-state'))
441 os.unlink(os.path.join(repo.path, 'histedit-state'))
437 return
442 return
438 else:
443 else:
@@ -443,7 +448,6 b' def histedit(ui, repo, *parent, **opts):'
443
448
444 topmost, empty = repo.dirstate.parents()
449 topmost, empty = repo.dirstate.parents()
445
450
446
447 if len(parent) != 1:
451 if len(parent) != 1:
448 raise util.Abort(_('histedit requires exactly one parent revision'))
452 raise util.Abort(_('histedit requires exactly one parent revision'))
449 parent = scmutil.revsingle(repo, parent[0]).node()
453 parent = scmutil.revsingle(repo, parent[0]).node()
@@ -452,7 +456,6 b' def histedit(ui, repo, *parent, **opts):'
452 revs = between(repo, parent, topmost, keep)
456 revs = between(repo, parent, topmost, keep)
453
457
454 ctxs = [repo[r] for r in revs]
458 ctxs = [repo[r] for r in revs]
455 existing = [r.node() for r in ctxs]
456 rules = opts.get('commands', '')
459 rules = opts.get('commands', '')
457 if not rules:
460 if not rules:
458 rules = '\n'.join([makedesc(c) for c in ctxs])
461 rules = '\n'.join([makedesc(c) for c in ctxs])
@@ -475,72 +478,37 b' def histedit(ui, repo, *parent, **opts):'
475
478
476 parentctx = repo[parent].parents()[0]
479 parentctx = repo[parent].parents()[0]
477 keep = opts.get('keep', False)
480 keep = opts.get('keep', False)
478 replaced = []
481 replacements = []
479 replacemap = {}
480 tmpnodes = []
481 created = []
482
482
483
483
484 while rules:
484 while rules:
485 writestate(repo, parentctx.node(), created, replaced,
485 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
486 tmpnodes, existing, rules, keep, topmost, replacemap)
487 action, ha = rules.pop(0)
486 action, ha = rules.pop(0)
488 ui.debug('histedit: processing %s %s\n' % (action, ha))
487 ui.debug('histedit: processing %s %s\n' % (action, ha))
489 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
488 actfunc = actiontable[action]
490 ui, repo, parentctx, ha, opts)
489 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
491
490 replacements.extend(replacement_)
492 if replaced_:
493 clen, rlen = len(created_), len(replaced_)
494 if clen == rlen == 1:
495 ui.debug('histedit: exact replacement of %s with %s\n' % (
496 node.short(replaced_[0]), node.short(created_[0])))
497
498 replacemap[replaced_[0]] = created_[0]
499 elif clen > rlen:
500 assert rlen == 1, ('unexpected replacement of '
501 '%d changes with %d changes' % (rlen, clen))
502 # made more changesets than we're replacing
503 # TODO synthesize patch names for created patches
504 replacemap[replaced_[0]] = created_[-1]
505 ui.debug('histedit: created many, assuming %s replaced by %s' %
506 (node.short(replaced_[0]), node.short(created_[-1])))
507 elif rlen > clen:
508 if not created_:
509 # This must be a drop. Try and put our metadata on
510 # the parent change.
511 assert rlen == 1
512 r = replaced_[0]
513 ui.debug('histedit: %s seems replaced with nothing, '
514 'finding a parent\n' % (node.short(r)))
515 pctx = repo[r].parents()[0]
516 if pctx.node() in replacemap:
517 ui.debug('histedit: parent is already replaced\n')
518 replacemap[r] = replacemap[pctx.node()]
519 else:
520 replacemap[r] = pctx.node()
521 ui.debug('histedit: %s best replaced by %s\n' % (
522 node.short(r), node.short(replacemap[r])))
523 else:
524 assert len(created_) == 1
525 for r in replaced_:
526 ui.debug('histedit: %s replaced by %s\n' % (
527 node.short(r), node.short(created_[0])))
528 replacemap[r] = created_[0]
529 else:
530 assert False, (
531 'Unhandled case in replacement mapping! '
532 'replacing %d changes with %d changes' % (rlen, clen))
533 created.extend(created_)
534 replaced.extend(replaced_)
535 tmpnodes.extend(tmpnodes_)
536
491
537 hg.update(repo, parentctx.node())
492 hg.update(repo, parentctx.node())
538
493
494 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
495 if mapping:
496 for prec, succs in mapping.iteritems():
497 if not succs:
498 ui.debug('histedit: %s is dropped\n' % node.short(prec))
499 else:
500 ui.debug('histedit: %s is replaced by %s\n' % (
501 node.short(prec), node.short(succs[0])))
502 if len(succs) > 1:
503 m = 'histedit: %s'
504 for n in succs[1:]:
505 ui.debug(m % node.short(n))
506
539 if not keep:
507 if not keep:
540 if replacemap:
508 if mapping:
541 movebookmarks(ui, repo, replacemap, tmpnodes, created)
509 movebookmarks(ui, repo, mapping, topmost, ntm)
542 # TODO update mq state
510 # TODO update mq state
543 cleanupnode(ui, repo, 'replaced', replaced)
511 cleanupnode(ui, repo, 'replaced', mapping)
544
512
545 cleanupnode(ui, repo, 'temp', tmpnodes)
513 cleanupnode(ui, repo, 'temp', tmpnodes)
546 os.unlink(os.path.join(repo.path, 'histedit-state'))
514 os.unlink(os.path.join(repo.path, 'histedit-state'))
@@ -548,9 +516,9 b' def histedit(ui, repo, *parent, **opts):'
548 os.unlink(repo.sjoin('undo'))
516 os.unlink(repo.sjoin('undo'))
549
517
550
518
551 def bootstrapcontinue(ui, repo, parentctx, existing, replacemap, rules,
519 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
552 tmpnodes, created, replaced, opts):
553 action, currentnode = rules.pop(0)
520 action, currentnode = rules.pop(0)
521 ctx = repo[currentnode]
554 # is there any new commit between the expected parent and "."
522 # is there any new commit between the expected parent and "."
555 #
523 #
556 # note: does not take non linear new change in account (but previous
524 # note: does not take non linear new change in account (but previous
@@ -564,45 +532,46 b' def bootstrapcontinue(ui, repo, parentct'
564 '--continue" again') % parentctx
532 '--continue" again') % parentctx
565 raise util.Abort(msg % parentctx, hint=hint)
533 raise util.Abort(msg % parentctx, hint=hint)
566 newchildren.pop(0) # remove parentctxnode
534 newchildren.pop(0) # remove parentctxnode
567 if action in ('f', 'fold'):
535 # Commit dirty working directory if necessary
568 tmpnodes.extend(newchildren)
536 new = None
569 else:
570 created.extend(newchildren)
571
572 m, a, r, d = repo.status()[:4]
537 m, a, r, d = repo.status()[:4]
573 oldctx = repo[currentnode]
574 message = oldctx.description() + '\n'
575 if action in ('e', 'edit', 'm', 'mess'):
576 message = ui.edit(message, ui.username())
577 elif action in ('f', 'fold'):
578 message = 'fold-temp-revision %s' % currentnode
579 new = None
580 if m or a or r or d:
538 if m or a or r or d:
581 new = repo.commit(text=message, user=oldctx.user(),
539 # prepare the message for the commit to comes
582 date=oldctx.date(), extra=oldctx.extra())
540 if action in ('f', 'fold'):
541 message = 'fold-temp-revision %s' % currentnode
542 else:
543 message = ctx.description() + '\n'
544 if action in ('e', 'edit', 'm', 'mess'):
545 editor = cmdutil.commitforceeditor
546 else:
547 editor = False
548 new = repo.commit(text=message, user=ctx.user(),
549 date=ctx.date(), extra=ctx.extra(),
550 editor=editor)
551 if new is not None:
552 newchildren.append(new)
583
553
584 # If we're resuming a fold and we have new changes, mark the
554 replacements = []
585 # replacements and finish the fold. If not, it's more like a
555 # track replacements
586 # drop of the changesets that disappeared, and we can skip
556 if ctx.node() not in newchildren:
587 # this step.
557 # note: new children may be empty when the changeset is dropped.
588 if action in ('f', 'fold') and (new or newchildren):
558 # this happen e.g during conflicting pick where we revert content
589 if new:
559 # to parent.
590 tmpnodes.append(new)
560 replacements.append((ctx.node(), tuple(newchildren)))
591 else:
561
562 if action in ('f', 'fold'):
563 # finalize fold operation if applicable
564 if new is None:
592 new = newchildren[-1]
565 new = newchildren[-1]
593 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
566 else:
594 ui, repo, parentctx, oldctx, new, opts, newchildren)
567 newchildren.pop() # remove new from internal changes
595 replaced.extend(replaced_)
568 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
596 created.extend(created_)
569 newchildren)
597 tmpnodes.extend(tmpnodes_)
570 replacements.extend(repl)
598 elif action not in ('d', 'drop'):
571 elif newchildren:
599 if new != oldctx.node():
572 # otherwize update "parentctx" before proceding to further operation
600 replaced.append(oldctx.node())
573 parentctx = repo[newchildren[-1]]
601 if new:
574 return parentctx, replacements
602 if new != oldctx.node():
603 created.append(new)
604 parentctx = repo[new]
605 return parentctx
606
575
607
576
608 def between(repo, old, new, keep):
577 def between(repo, old, new, keep):
@@ -627,17 +596,13 b' def between(repo, old, new, keep):'
627 return revs
596 return revs
628
597
629
598
630 def writestate(repo, parentctxnode, created, replaced,
599 def writestate(repo, parentnode, rules, keep, topmost, replacements):
631 tmpnodes, existing, rules, keep, topmost, replacemap):
632 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
600 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
633 pickle.dump((parentctxnode, created, replaced,
601 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
634 tmpnodes, existing, rules, keep, topmost, replacemap),
635 fp)
636 fp.close()
602 fp.close()
637
603
638 def readstate(repo):
604 def readstate(repo):
639 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
605 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
640 keep, topmost, replacemap ).
641 """
606 """
642 fp = open(os.path.join(repo.path, 'histedit-state'))
607 fp = open(os.path.join(repo.path, 'histedit-state'))
643 return pickle.load(fp)
608 return pickle.load(fp)
@@ -684,37 +649,97 b' def verifyrules(rules, repo, ctxs):'
684 parsed.append([action, ha])
649 parsed.append([action, ha])
685 return parsed
650 return parsed
686
651
687 def movebookmarks(ui, repo, replacemap, tmpnodes, created):
652 def processreplacement(repo, replacements):
688 """Move bookmark from old to newly created node"""
653 """process the list of replacements to return
689 ui.note(_('histedit: Should update metadata for the following '
654
690 'changes:\n'))
655 1) the final mapping between original and created nodes
656 2) the list of temporary node created by histedit
657 3) the list of new commit created by histedit"""
658 allsuccs = set()
659 replaced = set()
660 fullmapping = {}
661 # initialise basic set
662 # fullmapping record all operation recorded in replacement
663 for rep in replacements:
664 allsuccs.update(rep[1])
665 replaced.add(rep[0])
666 fullmapping.setdefault(rep[0], set()).update(rep[1])
667 new = allsuccs - replaced
668 tmpnodes = allsuccs & replaced
669 # Reduce content fullmapping into direct relation between original nodes
670 # and final node created during history edition
671 # Dropped changeset are replaced by an empty list
672 toproceed = set(fullmapping)
673 final = {}
674 while toproceed:
675 for x in list(toproceed):
676 succs = fullmapping[x]
677 for s in list(succs):
678 if s in toproceed:
679 # non final node with unknown closure
680 # We can't process this now
681 break
682 elif s in final:
683 # non final node, replace with closure
684 succs.remove(s)
685 succs.update(final[s])
686 else:
687 final[x] = succs
688 toproceed.remove(x)
689 # remove tmpnodes from final mapping
690 for n in tmpnodes:
691 del final[n]
692 # we expect all changes involved in final to exist in the repo
693 # turn `final` into list (topologically sorted)
694 nm = repo.changelog.nodemap
695 for prec, succs in final.items():
696 final[prec] = sorted(succs, key=nm.get)
691
697
692 def copybms(old, new):
698 # computed topmost element (necessary for bookmark)
693 if old in tmpnodes or old in created:
699 if new:
694 # can't have any metadata we'd want to update
700 newtopmost = max(new, key=repo.changelog.rev)
695 return
701 elif not final:
696 while new in replacemap:
702 # Nothing rewritten at all. we won't need `newtopmost`
697 new = replacemap[new]
703 # It is the same as `oldtopmost` and `processreplacement` know it
698 octx = repo[old]
704 newtopmost = None
699 marks = octx.bookmarks()
705 else:
700 if marks:
706 # every body died. The newtopmost is the parent of the root.
701 for mark in marks:
707 newtopmost = repo[min(final, key=repo.changelog.rev)].p1().node()
702 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
708
703 % (mark, octx, node.short(new)))
709 return final, tmpnodes, new, newtopmost
704 repo._bookmarks[mark] = new
705 bookmarks.write(repo)
706
710
707 # We assume that bookmarks on the tip should remain
711 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
708 # tipmost, but bookmarks on non-tip changesets should go
712 """Move bookmark from old to newly created node"""
709 # to their most reasonable successor. As a result, find
713 if not mapping:
710 # the old tip and new tip and copy those bookmarks first,
714 # if nothing got rewritten there is not purpose for this function
711 # then do the rest of the bookmark copies.
715 return
712 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
716 moves = []
713 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
717 for bk, old in repo._bookmarks.iteritems():
714 copybms(oldtip, newtip)
718 if old == oldtopmost:
715
719 # special case ensure bookmark stay on tip.
716 for old, new in sorted(replacemap.iteritems()):
720 #
717 copybms(old, new)
721 # This is arguably a feature and we may only want that for the
722 # active bookmark. But the behavior is kept compatible with the old
723 # version for now.
724 moves.append((bk, newtopmost))
725 continue
726 base = old
727 new = mapping.get(base, None)
728 if new is None:
729 continue
730 while not new:
731 # base is killed, trying with parent
732 base = repo[base].p1().node()
733 new = mapping.get(base, (base,))
734 # nothing to move
735 moves.append((bk, new[-1]))
736 if moves:
737 for mark, new in moves:
738 old = repo._bookmarks[mark]
739 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
740 % (mark, node.short(old), node.short(new)))
741 repo._bookmarks[mark] = new
742 bookmarks.write(repo)
718
743
719 def cleanupnode(ui, repo, name, nodes):
744 def cleanupnode(ui, repo, name, nodes):
720 """strip a group of nodes from the repository
745 """strip a group of nodes from the repository
@@ -737,4 +762,3 b' def cleanupnode(ui, repo, name, nodes):'
737 repair.strip(ui, repo, c)
762 repair.strip(ui, repo, c)
738 finally:
763 finally:
739 lockmod.release(lock)
764 lockmod.release(lock)
740
@@ -84,13 +84,12 b''
84 > pick 652413bf663e 5 f
84 > pick 652413bf663e 5 f
85 > EOF
85 > EOF
86 $ hg histedit 1 --commands commands.txt --verbose | grep histedit
86 $ hg histedit 1 --commands commands.txt --verbose | grep histedit
87 histedit: Should update metadata for the following changes:
87 histedit: moving bookmarks two from 177f92b77385 to d36c0562f908
88 histedit: moving bookmarks three from 055a42cdd887 to ae467701c500
88 histedit: moving bookmarks three from 055a42cdd887 to ae467701c500
89 histedit: moving bookmarks four from e860deea161a to ae467701c500
89 histedit: moving bookmarks also-two from 177f92b77385 to d36c0562f908
90 histedit: moving bookmarks also-two from 177f92b77385 to d36c0562f908
90 histedit: moving bookmarks two from 177f92b77385 to d36c0562f908
91 histedit: moving bookmarks will-move-backwards from d2ae7f538514 to cb9a9f314b8b
91 histedit: moving bookmarks five from 652413bf663e to 0efacef7cb48
92 histedit: moving bookmarks five from 652413bf663e to 0efacef7cb48
92 histedit: moving bookmarks will-move-backwards from d2ae7f538514 to cb9a9f314b8b
93 histedit: moving bookmarks four from e860deea161a to ae467701c500
94 saved backup bundle to $TESTTMP/r/.hg/strip-backup/d2ae7f538514-backup.hg (glob)
93 saved backup bundle to $TESTTMP/r/.hg/strip-backup/d2ae7f538514-backup.hg (glob)
95 saved backup bundle to $TESTTMP/r/.hg/strip-backup/34a9919932c1-backup.hg (glob)
94 saved backup bundle to $TESTTMP/r/.hg/strip-backup/34a9919932c1-backup.hg (glob)
96 $ hg log --graph
95 $ hg log --graph
@@ -142,10 +141,9 b''
142 > pick ae467701c500 2 d
141 > pick ae467701c500 2 d
143 > EOF
142 > EOF
144 $ hg histedit 1 --commands commands.txt --verbose | grep histedit
143 $ hg histedit 1 --commands commands.txt --verbose | grep histedit
145 histedit: Should update metadata for the following changes:
144 histedit: moving bookmarks three from ae467701c500 to 1be9c35b4cb2
145 histedit: moving bookmarks four from ae467701c500 to 1be9c35b4cb2
146 histedit: moving bookmarks five from 0efacef7cb48 to 1be9c35b4cb2
146 histedit: moving bookmarks five from 0efacef7cb48 to 1be9c35b4cb2
147 histedit: moving bookmarks four from ae467701c500 to 1be9c35b4cb2
148 histedit: moving bookmarks three from ae467701c500 to 1be9c35b4cb2
149 saved backup bundle to $TESTTMP/r/.hg/strip-backup/ae467701c500-backup.hg (glob)
147 saved backup bundle to $TESTTMP/r/.hg/strip-backup/ae467701c500-backup.hg (glob)
150
148
151 We expect 'five' to stay at tip, since the tipmost bookmark is most
149 We expect 'five' to stay at tip, since the tipmost bookmark is most
General Comments 0
You need to be logged in to leave comments. Login now