Mercurial > evolve
comparison hgext/evolve.py @ 589:8945a62f9096
merge with stable
author | Pierre-Yves David <pierre-yves.david@logilab.fr> |
---|---|
date | Tue, 23 Oct 2012 16:53:11 +0200 |
parents | f6063ef211fd 89c8550019d0 |
children | 02cadd3dc9f4 |
comparison
equal
deleted
inserted
replaced
583:95089805c3fc | 589:8945a62f9096 |
---|---|
17 - alters core commands and extensions that rewrite history to use | 17 - alters core commands and extensions that rewrite history to use |
18 this feature, | 18 this feature, |
19 - improves some aspect of the early implementation in 2.3 | 19 - improves some aspect of the early implementation in 2.3 |
20 ''' | 20 ''' |
21 | 21 |
22 testedwith = '2.3 2.3.1 2.3.2' | |
23 buglink = 'https://bitbucket.org/marmoute/mutable-history/issues' | |
24 | |
25 | |
22 import random | 26 import random |
23 | 27 |
24 from mercurial import util | 28 from mercurial import util |
25 | 29 |
26 try: | 30 try: |
27 from mercurial import obsolete | 31 from mercurial import obsolete |
28 if not obsolete._enabled: | 32 if not obsolete._enabled: |
29 obsolete._enabled = True | 33 obsolete._enabled = True |
30 except ImportError: | 34 except ImportError: |
31 raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)') | 35 raise util.Abort('Evolve extension requires Mercurial 2.3 (or later)') |
36 | |
37 try: | |
38 getattr(obsolete, 'getrevs') # 2.4 specific | |
39 raise util.Abort('Your version of Mercurial is too recent for this ' | |
40 'version of evolve', | |
41 hint="upgrade your evolve") | |
42 except AttributeError: | |
43 pass | |
44 | |
32 | 45 |
33 from mercurial import bookmarks | 46 from mercurial import bookmarks |
34 from mercurial import cmdutil | 47 from mercurial import cmdutil |
35 from mercurial import commands | 48 from mercurial import commands |
36 from mercurial import context | 49 from mercurial import context |
322 ### Complete troubles computation logic ### | 335 ### Complete troubles computation logic ### |
323 ##################################################################### | 336 ##################################################################### |
324 | 337 |
325 # there is two kind of trouble not handled by core right now: | 338 # there is two kind of trouble not handled by core right now: |
326 # - latecomer: (successors for public changeset) | 339 # - latecomer: (successors for public changeset) |
327 # - conflicting: (two changeset try to succeed to the same precursors) | 340 # - divergent: (two changeset try to succeed to the same precursors) |
328 # | 341 # |
329 # This section add support for those two addition trouble | 342 # This section add support for those two addition trouble |
330 # | 343 # |
331 # - Cache computation | 344 # - Cache computation |
332 # - revset and ctx method | 345 # - revset and ctx method |
341 candidates = _allsuccessors(repo, repo.revs('public()'), | 354 candidates = _allsuccessors(repo, repo.revs('public()'), |
342 haltonflags=latediff) | 355 haltonflags=latediff) |
343 query = '%ld - obsolete() - public()' | 356 query = '%ld - obsolete() - public()' |
344 return set(repo.revs(query, candidates)) | 357 return set(repo.revs(query, candidates)) |
345 | 358 |
346 @cachefor('conflicting') | 359 @cachefor('divergent') |
347 def _computeconflictingset(repo): | 360 def _computedivergentset(repo): |
348 """the set of rev trying to obsolete public revision""" | 361 """the set of rev trying to obsolete public revision""" |
349 conflicting = set() | 362 divergent = set() |
350 obsstore = repo.obsstore | 363 obsstore = repo.obsstore |
351 newermap = {} | 364 newermap = {} |
352 for ctx in repo.set('(not public()) - obsolete()'): | 365 for ctx in repo.set('(not public()) - obsolete()'): |
353 prec = obsstore.successors.get(ctx.node(), ()) | 366 mark = obsstore.successors.get(ctx.node(), ()) |
354 toprocess = set(prec) | 367 toprocess = set(mark) |
355 while toprocess: | 368 while toprocess: |
356 prec = toprocess.pop()[0] | 369 prec = toprocess.pop()[0] |
357 if prec not in newermap: | 370 if prec not in newermap: |
358 newermap[prec] = newerversion(repo, prec) | 371 successorssets(repo, prec, newermap) |
359 newer = [n for n in newermap[prec] if n] # filter kill | 372 newer = [n for n in newermap[prec] if n] |
360 if len(newer) > 1: | 373 if len(newer) > 1: |
361 conflicting.add(ctx.rev()) | 374 divergent.add(ctx.rev()) |
362 break | 375 break |
363 toprocess.update(obsstore.successors.get(prec, ())) | 376 toprocess.update(obsstore.successors.get(prec, ())) |
364 return conflicting | 377 return divergent |
365 | 378 |
366 ### changectx method | 379 ### changectx method |
367 | 380 |
368 @eh.addattr(context.changectx, 'latecomer') | 381 @eh.addattr(context.changectx, 'latecomer') |
369 def latecomer(ctx): | 382 def latecomer(ctx): |
371 if ctx.node() is None: | 384 if ctx.node() is None: |
372 return False | 385 return False |
373 return ctx.rev() in getobscache(ctx._repo, 'latecomer') | 386 return ctx.rev() in getobscache(ctx._repo, 'latecomer') |
374 | 387 |
375 @eh.addattr(context.changectx, 'conflicting') | 388 @eh.addattr(context.changectx, 'conflicting') |
376 def conflicting(ctx): | 389 @eh.addattr(context.changectx, 'divergent') |
377 """is the changeset conflicting (Try to succeed to public change)""" | 390 def divergent(ctx): |
391 """is the changeset divergent (Try to succeed to public change)""" | |
378 if ctx.node() is None: | 392 if ctx.node() is None: |
379 return False | 393 return False |
380 return ctx.rev() in getobscache(ctx._repo, 'conflicting') | 394 return ctx.rev() in getobscache(ctx._repo, 'divergent') |
381 | 395 |
382 ### revset symbol | 396 ### revset symbol |
383 | 397 |
384 @eh.revset('latecomer') | 398 @eh.revset('latecomer') |
385 def revsetlatecomer(repo, subset, x): | 399 def revsetlatecomer(repo, subset, x): |
389 args = revset.getargs(x, 0, 0, 'latecomer takes no arguments') | 403 args = revset.getargs(x, 0, 0, 'latecomer takes no arguments') |
390 lates = getobscache(repo, 'latecomer') | 404 lates = getobscache(repo, 'latecomer') |
391 return [r for r in subset if r in lates] | 405 return [r for r in subset if r in lates] |
392 | 406 |
393 @eh.revset('conflicting') | 407 @eh.revset('conflicting') |
394 def revsetconflicting(repo, subset, x): | |
395 """``conflicting()`` | |
396 Changesets marked as successors of a same changeset. | |
397 """ | |
398 args = revset.getargs(x, 0, 0, 'conflicting takes no arguments') | |
399 conf = getobscache(repo, 'conflicting') | |
400 return [r for r in subset if r in conf] | |
401 | |
402 @eh.revset('divergent') | 408 @eh.revset('divergent') |
403 def revsetdivergent(repo, subset, x): | 409 def revsetdivergent(repo, subset, x): |
404 """``divergent()`` | 410 """``divergent()`` |
405 Changesets marked as successors of a same changeset. | 411 Changesets marked as successors of a same changeset. |
406 """ | 412 """ |
407 args = revset.getargs(x, 0, 0, 'divergent takes no arguments') | 413 args = revset.getargs(x, 0, 0, 'divergent takes no arguments') |
408 conf = getobscache(repo, 'conflicting') | 414 conf = getobscache(repo, 'divergent') |
409 return [r for r in subset if r in conf] | 415 return [r for r in subset if r in conf] |
416 | |
410 | 417 |
411 | 418 |
412 ### Discovery wrapping | 419 ### Discovery wrapping |
413 | 420 |
414 @eh.wrapfunction(discovery, 'checkheads') | 421 @eh.wrapfunction(discovery, 'checkheads') |
423 # obsolete or unstable. | 430 # obsolete or unstable. |
424 ctx = repo[h] | 431 ctx = repo[h] |
425 if ctx.latecomer(): | 432 if ctx.latecomer(): |
426 raise util.Abort(_("push includes a latecomer changeset: %s!") | 433 raise util.Abort(_("push includes a latecomer changeset: %s!") |
427 % ctx) | 434 % ctx) |
428 if ctx.conflicting(): | 435 if ctx.divergent(): |
429 raise util.Abort(_("push includes a conflicting changeset: %s!") | 436 raise util.Abort(_("push includes a divergent changeset: %s!") |
430 % ctx) | 437 % ctx) |
431 return orig(repo, remote, outgoing, *args, **kwargs) | 438 return orig(repo, remote, outgoing, *args, **kwargs) |
432 | 439 |
433 ##################################################################### | 440 ##################################################################### |
434 ### Filter extinct changesets from common operations ### | 441 ### Filter extinct changesets from common operations ### |
496 | 503 |
497 @eh.addattr(context.changectx, 'troubles') | 504 @eh.addattr(context.changectx, 'troubles') |
498 def troubles(ctx): | 505 def troubles(ctx): |
499 """Return a tuple listing all the troubles that affect a changeset | 506 """Return a tuple listing all the troubles that affect a changeset |
500 | 507 |
501 Troubles may be "unstable", "latecomer" or "conflicting". | 508 Troubles may be "unstable", "latecomer" or "divergent". |
502 """ | 509 """ |
503 troubles = [] | 510 troubles = [] |
504 if ctx.unstable(): | 511 if ctx.unstable(): |
505 troubles.append('unstable') | 512 troubles.append('unstable') |
506 if ctx.latecomer(): | 513 if ctx.latecomer(): |
507 troubles.append('latecomer') | 514 troubles.append('latecomer') |
508 if ctx.conflicting(): | 515 if ctx.divergent(): |
509 troubles.append('conflicting') | 516 troubles.append('divergent') |
510 return tuple(troubles) | 517 return tuple(troubles) |
511 | 518 |
512 ### Troubled revset symbol | 519 ### Troubled revset symbol |
513 | 520 |
514 @eh.revset('troubled') | 521 @eh.revset('troubled') |
515 def revsetlatecomer(repo, subset, x): | 522 def revsetlatecomer(repo, subset, x): |
516 """``troubled()`` | 523 """``troubled()`` |
517 Changesets with troubles. | 524 Changesets with troubles. |
518 """ | 525 """ |
519 _ = revset.getargs(x, 0, 0, 'troubled takes no arguments') | 526 _ = revset.getargs(x, 0, 0, 'troubled takes no arguments') |
520 return repo.revs('%ld and (unstable() + latecomer() + conflicting())', | 527 return repo.revs('%ld and (unstable() + latecomer() + divergent())', |
521 subset) | 528 subset) |
522 | 529 |
523 | 530 |
524 ### Obsolescence graph | 531 ### Obsolescence graph |
525 | 532 |
595 sr = nm.get(s) | 602 sr = nm.get(s) |
596 if sr is not None: | 603 if sr is not None: |
597 cs.add(sr) | 604 cs.add(sr) |
598 return cs | 605 return cs |
599 | 606 |
600 | 607 nodemod = node |
601 | 608 def successorssets(repo, initialnode, cache=None): |
602 def newerversion(repo, obs): | |
603 """Return the newer version of an obsolete changeset""" | 609 """Return the newer version of an obsolete changeset""" |
604 toproceed = set([(obs,)]) | 610 |
605 # XXX known optimization available | 611 # prec -> markers mapping |
606 newer = set() | 612 markersfor = repo.obsstore.precursors |
607 objectrels = repo.obsstore.precursors | 613 |
614 # Stack of node need to know the last successors set | |
615 toproceed = [initialnode] | |
616 # set version of toproceed for fast loop detection | |
617 stackedset = set(toproceed) | |
618 if cache is None: | |
619 cache = {} | |
608 while toproceed: | 620 while toproceed: |
609 current = toproceed.pop() | 621 # work on the last node of the stack |
610 assert len(current) <= 1, 'splitting not handled yet. %r' % current | 622 node = toproceed[-1] |
611 current = [n for n in current if n != nullid] | 623 if node in cache: |
612 if current: | 624 # We already have a value for it. |
613 n, = current | 625 # Keep working on something else. |
614 if n in objectrels: | 626 stackedset.remove(toproceed.pop()) |
615 markers = objectrels[n] | 627 elif node not in markersfor: |
616 for mark in markers: | 628 # The node is not obsolete. |
617 toproceed.add(tuple(mark[1])) | 629 # This mean it is its own last successors. |
630 if node in repo: | |
631 # We have a valid last successors. | |
632 cache[node] = [(node,)] | |
618 else: | 633 else: |
619 newer.add(tuple(current)) | 634 # final obsolete version is unknown locally. |
635 # Do not count that as a valid successors | |
636 cache[node] = [] | |
620 else: | 637 else: |
621 newer.add(()) | 638 # <lss> stand for Last Successors Sets |
622 return sorted(newer) | 639 # it contains the list of all last successors for the current node. |
640 lss = [] | |
641 for mark in markersfor[node]: | |
642 # <mlss> stand for Marker Last Successors Sets | |
643 # it contains the list of last successors set introduced by | |
644 # this marker. | |
645 mlss = [[]] | |
646 # iterate over possible multiple successors | |
647 for suc in mark[1]: | |
648 if suc not in cache: | |
649 # We do not know the last successors of that yet. | |
650 if suc in stackedset: | |
651 # Loop detected! | |
652 # | |
653 # we won't be able to ever compute a proper last | |
654 # successors the naive and simple approve is to | |
655 # consider it killed | |
656 cache[suc] = [] | |
657 else: | |
658 # Add the successor to the stack and break the next | |
659 # iteration will work on this successors and the | |
660 # algorithm will eventually process the current | |
661 # node again. | |
662 toproceed.append(suc) | |
663 stackedset.add(suc) | |
664 break | |
665 # if we did not break, we can extend the possible set of | |
666 # last successors. | |
667 # | |
668 # I say "extends" because if the marker have multiple | |
669 # successors we have to generate | |
670 # | |
671 # if successors have multiple successors set (when ther are | |
672 # divergent themself), we do a cartesian product of | |
673 # possible successors set of already processed successors | |
674 # and newly obtains successors set. | |
675 newmlss = [] | |
676 for prefix in mlss: | |
677 for suffix in cache[suc]: | |
678 newss = list(prefix) | |
679 for part in suffix: | |
680 # do not duplicated entry in successors set. | |
681 # first entry win. | |
682 if part not in newss: | |
683 newss.append(part) | |
684 newmlss.append(newss) | |
685 mlss = newmlss | |
686 else: | |
687 # note: mlss is still empty if the marker was a bare killing | |
688 # of this changeset | |
689 # | |
690 # We extends the list of all possible successors sets with | |
691 # successors set continuted by this marker | |
692 lss.extend(mlss) | |
693 # we use continue here to skip the break right bellow | |
694 continue | |
695 # propagate "nested for" break. | |
696 # if the nested for exited on break, it did not ran the else | |
697 # clause and didn't "continue | |
698 break | |
699 else: | |
700 # computation was succesful for *all* marker. | |
701 # Add computed successors set to the cache | |
702 # (will be poped from to proceeed) on the new iteration | |
703 # | |
704 # We remove successors set that are subset of another one | |
705 # this fil | |
706 candsucset = sorted(((len(ss), set(ss), ss) for ss in lss), | |
707 reverse=True) | |
708 finalsucset = [] | |
709 for cl, cs, css in candsucset: | |
710 if not css: | |
711 # remove empty successors set | |
712 continue | |
713 for fs, fss in finalsucset: | |
714 if cs.issubset(fs): | |
715 break | |
716 else: | |
717 finalsucset.append((cs, css)) | |
718 finalsucset = [s[1] for s in finalsucset] | |
719 finalsucset.reverse() | |
720 cache[node] = finalsucset | |
721 return cache[initialnode] | |
722 | |
723 | |
623 | 724 |
624 | 725 |
625 ##################################################################### | 726 ##################################################################### |
626 ### Extending revset and template ### | 727 ### Extending revset and template ### |
627 ##################################################################### | 728 ##################################################################### |
722 @eh.wrapcommand("unbundle") | 823 @eh.wrapcommand("unbundle") |
723 def warnobserrors(orig, ui, repo, *args, **kwargs): | 824 def warnobserrors(orig, ui, repo, *args, **kwargs): |
724 """display warning is the command resulted in more instable changeset""" | 825 """display warning is the command resulted in more instable changeset""" |
725 priorunstables = len(repo.revs('unstable()')) | 826 priorunstables = len(repo.revs('unstable()')) |
726 priorlatecomers = len(repo.revs('latecomer()')) | 827 priorlatecomers = len(repo.revs('latecomer()')) |
727 priorconflictings = len(repo.revs('conflicting()')) | 828 priordivergents = len(repo.revs('divergent()')) |
728 ret = orig(ui, repo, *args, **kwargs) | 829 ret = orig(ui, repo, *args, **kwargs) |
729 # workaround phase stupidity | 830 # workaround phase stupidity |
730 phases._filterunknown(ui, repo.changelog, repo._phasecache.phaseroots) | 831 phases._filterunknown(ui, repo.changelog, repo._phasecache.phaseroots) |
731 newunstables = len(repo.revs('unstable()')) - priorunstables | 832 newunstables = len(repo.revs('unstable()')) - priorunstables |
732 newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers | 833 newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers |
733 newconflictings = len(repo.revs('conflicting()')) - priorconflictings | 834 newdivergents = len(repo.revs('divergent()')) - priordivergents |
734 if newunstables > 0: | 835 if newunstables > 0: |
735 ui.warn(_('%i new unstable changesets\n') % newunstables) | 836 ui.warn(_('%i new unstable changesets\n') % newunstables) |
736 if newlatecomers > 0: | 837 if newlatecomers > 0: |
737 ui.warn(_('%i new latecomer changesets\n') % newlatecomers) | 838 ui.warn(_('%i new latecomer changesets\n') % newlatecomers) |
738 if newconflictings > 0: | 839 if newdivergents > 0: |
739 ui.warn(_('%i new conflicting changesets\n') % newconflictings) | 840 ui.warn(_('%i new divergent changesets\n') % newdivergents) |
740 return ret | 841 return ret |
741 | 842 |
742 @eh.reposetup | 843 @eh.reposetup |
743 def _repostabilizesetup(ui, repo): | 844 def _repostabilizesetup(ui, repo): |
744 """Add a hint for "hg evolve" when troubles make push fails | 845 """Add a hint for "hg evolve" when troubles make push fails |
774 ui.note(s) | 875 ui.note(s) |
775 | 876 |
776 ret = orig(ui, repo, *args, **kwargs) | 877 ret = orig(ui, repo, *args, **kwargs) |
777 nbunstable = len(getobscache(repo, 'unstable')) | 878 nbunstable = len(getobscache(repo, 'unstable')) |
778 nblatecomer = len(getobscache(repo, 'latecomer')) | 879 nblatecomer = len(getobscache(repo, 'latecomer')) |
779 nbconflicting = len(getobscache(repo, 'unstable')) | 880 nbdivergent = len(getobscache(repo, 'unstable')) |
780 write('unstable: %i changesets\n', nbunstable) | 881 write('unstable: %i changesets\n', nbunstable) |
781 write('latecomer: %i changesets\n', nblatecomer) | 882 write('latecomer: %i changesets\n', nblatecomer) |
782 write('conflicting: %i changesets\n', nbconflicting) | 883 write('divergent: %i changesets\n', nbdivergent) |
783 return ret | 884 return ret |
784 | 885 |
785 | 886 |
786 ##################################################################### | 887 ##################################################################### |
787 ### Core Other extension compat ### | 888 ### Core Other extension compat ### |
996 def evolve(ui, repo, **opts): | 1097 def evolve(ui, repo, **opts): |
997 """Solve trouble in your repository | 1098 """Solve trouble in your repository |
998 | 1099 |
999 - rebase unstable changeset to make it stable again, | 1100 - rebase unstable changeset to make it stable again, |
1000 - create proper diff from latecomer changeset, | 1101 - create proper diff from latecomer changeset, |
1001 - merge conflicting changeset. | 1102 - merge divergent changeset. |
1002 | 1103 |
1003 By default, take the first troubles changeset that looks relevant. | 1104 By default, take the first troubles changeset that looks relevant. |
1004 | 1105 |
1005 (The logic is still a bit fuzzy) | 1106 (The logic is still a bit fuzzy) |
1006 | 1107 |
1007 - For unstable, that mean the first which could be rebased as child of the | 1108 - For unstable, that mean the first which could be rebased as child of the |
1008 working directory parent revision or one of its descendants and rebase | 1109 working directory parent revision or one of its descendants and rebase |
1009 it. | 1110 it. |
1010 | 1111 |
1011 - For conflicting this mean "." if applicable. | 1112 - For divergent this mean "." if applicable. |
1012 | 1113 |
1013 With --any, evolve pick any troubled changeset to solve | 1114 With --any, evolve pick any troubled changeset to solve |
1014 | 1115 |
1015 The working directory is updated to the newly created revision. | 1116 The working directory is updated to the newly created revision. |
1016 """ | 1117 """ |
1039 troubles = tr.troubles() | 1140 troubles = tr.troubles() |
1040 if 'unstable' in troubles: | 1141 if 'unstable' in troubles: |
1041 return _solveunstable(ui, repo, tr, opts['dry_run']) | 1142 return _solveunstable(ui, repo, tr, opts['dry_run']) |
1042 elif 'latecomer' in troubles: | 1143 elif 'latecomer' in troubles: |
1043 return _solvelatecomer(ui, repo, tr, opts['dry_run']) | 1144 return _solvelatecomer(ui, repo, tr, opts['dry_run']) |
1044 elif 'conflicting' in troubles: | 1145 elif 'divergent' in troubles: |
1045 return _solveconflicting(ui, repo, tr, opts['dry_run']) | 1146 return _solvedivergent(ui, repo, tr, opts['dry_run']) |
1046 else: | 1147 else: |
1047 assert False # WHAT? unknown troubles | 1148 assert False # WHAT? unknown troubles |
1048 | 1149 |
1049 def _picknexttroubled(ui, repo, pickany=False): | 1150 def _picknexttroubled(ui, repo, pickany=False): |
1050 """Pick a the next trouble changeset to solve""" | 1151 """Pick a the next trouble changeset to solve""" |
1051 tr = _stabilizableunstable(repo, repo['.']) | 1152 tr = _stabilizableunstable(repo, repo['.']) |
1052 if tr is None: | 1153 if tr is None: |
1053 wdp = repo['.'] | 1154 wdp = repo['.'] |
1054 if 'conflicting' in wdp.troubles(): | 1155 if 'divergent' in wdp.troubles(): |
1055 tr = wdp | 1156 tr = wdp |
1056 if tr is None and pickany: | 1157 if tr is None and pickany: |
1057 troubled = list(repo.set('unstable()')) | 1158 troubled = list(repo.set('unstable()')) |
1058 if not troubled: | 1159 if not troubled: |
1059 troubled = list(repo.set('latecomer()')) | 1160 troubled = list(repo.set('latecomer()')) |
1060 if not troubled: | 1161 if not troubled: |
1061 troubled = list(repo.set('conflicting()')) | 1162 troubled = list(repo.set('divergent()')) |
1062 if troubled: | 1163 if troubled: |
1063 tr = troubled[0] | 1164 tr = troubled[0] |
1064 | 1165 |
1065 return tr | 1166 return tr |
1066 | 1167 |
1087 """Stabilize a unstable changeset""" | 1188 """Stabilize a unstable changeset""" |
1088 obs = orig.parents()[0] | 1189 obs = orig.parents()[0] |
1089 if not obs.obsolete(): | 1190 if not obs.obsolete(): |
1090 obs = orig.parents()[1] | 1191 obs = orig.parents()[1] |
1091 assert obs.obsolete() | 1192 assert obs.obsolete() |
1092 newer = newerversion(repo, obs.node()) | 1193 newer = successorssets(repo, obs.node()) |
1093 # search of a parent which is not killed | 1194 # search of a parent which is not killed |
1094 while newer == [()]: | 1195 while not newer or newer == [()]: |
1095 ui.debug("stabilize target %s is plain dead," | 1196 ui.debug("stabilize target %s is plain dead," |
1096 " trying to stabilize on its parent") | 1197 " trying to stabilize on its parent") |
1097 obs = obs.parents()[0] | 1198 obs = obs.parents()[0] |
1098 newer = newerversion(repo, obs.node()) | 1199 newer = successorssets(repo, obs.node()) |
1099 if len(newer) > 1: | 1200 if len(newer) > 1: |
1100 ui.write_err(_("conflict rewriting. can't choose destination\n")) | 1201 ui.write_err(_("conflict rewriting. can't choose destination\n")) |
1101 return 2 | 1202 return 2 |
1102 targets = newer[0] | 1203 targets = newer[0] |
1103 assert targets | 1204 assert targets |
1229 # reroute the working copy parent to the new changeset | 1330 # reroute the working copy parent to the new changeset |
1230 repo.dirstate.setparents(newid, node.nullid) | 1331 repo.dirstate.setparents(newid, node.nullid) |
1231 finally: | 1332 finally: |
1232 wlock.release() | 1333 wlock.release() |
1233 | 1334 |
1234 def _solveconflicting(ui, repo, conflicting, dryrun=False): | 1335 def _solvedivergent(ui, repo, divergent, dryrun=False): |
1235 base, others = conflictingdata(conflicting) | 1336 base, others = divergentdata(divergent) |
1236 if len(others) > 1: | 1337 if len(others) > 1: |
1237 raise util.Abort("We do not handle split yet") | 1338 raise util.Abort("We do not handle split yet") |
1238 other = others[0] | 1339 other = others[0] |
1239 if conflicting.phase() <= phases.public: | 1340 if divergent.phase() <= phases.public: |
1240 raise util.Abort("We can't resolve this conflict from the public side") | 1341 raise util.Abort("We can't resolve this conflict from the public side") |
1241 if len(other.parents()) > 1: | 1342 if len(other.parents()) > 1: |
1242 raise util.Abort("conflicting changeset can't be a merge (yet)") | 1343 raise util.Abort("divergent changeset can't be a merge (yet)") |
1243 if other.p1() not in conflicting.parents(): | 1344 if other.p1() not in divergent.parents(): |
1244 raise util.Abort("parents are not common (not handled yet)") | 1345 raise util.Abort("parents are not common (not handled yet)") |
1245 | 1346 |
1246 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) | 1347 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1247 ui.status(_('merge:')) | 1348 ui.status(_('merge:')) |
1248 if not ui.quiet: | 1349 if not ui.quiet: |
1249 displayer.show(conflicting) | 1350 displayer.show(divergent) |
1250 ui.status(_('with: ')) | 1351 ui.status(_('with: ')) |
1251 if not ui.quiet: | 1352 if not ui.quiet: |
1252 displayer.show(other) | 1353 displayer.show(other) |
1253 ui.status(_('base: ')) | 1354 ui.status(_('base: ')) |
1254 if not ui.quiet: | 1355 if not ui.quiet: |
1255 displayer.show(base) | 1356 displayer.show(base) |
1256 if dryrun: | 1357 if dryrun: |
1257 ui.write('hg update -c %s &&\n' % conflicting) | 1358 ui.write('hg update -c %s &&\n' % divergent) |
1258 ui.write('hg merge %s &&\n' % other) | 1359 ui.write('hg merge %s &&\n' % other) |
1259 ui.write('hg commit -m "auto merge resolving conflict between ' | 1360 ui.write('hg commit -m "auto merge resolving conflict between ' |
1260 '%s and %s"&&\n' % (conflicting, other)) | 1361 '%s and %s"&&\n' % (divergent, other)) |
1261 ui.write('hg up -C %s &&\n' % base) | 1362 ui.write('hg up -C %s &&\n' % base) |
1262 ui.write('hg revert --all --rev tip &&\n') | 1363 ui.write('hg revert --all --rev tip &&\n') |
1263 ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' | 1364 ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' |
1264 % conflicting) | 1365 % divergent) |
1265 return | 1366 return |
1266 wlock = lock = None | 1367 wlock = lock = None |
1267 try: | 1368 try: |
1268 wlock = repo.wlock() | 1369 wlock = repo.wlock() |
1269 lock = repo.lock() | 1370 lock = repo.lock() |
1270 if conflicting not in repo[None].parents(): | 1371 if divergent not in repo[None].parents(): |
1271 repo.ui.status(_('updating to "local" conflict\n')) | 1372 repo.ui.status(_('updating to "local" conflict\n')) |
1272 hg.update(repo, conflicting.rev()) | 1373 hg.update(repo, divergent.rev()) |
1273 repo.ui.note(_('merging conflicting changeset\n')) | 1374 repo.ui.note(_('merging divergent changeset\n')) |
1274 stats = merge.update(repo, | 1375 stats = merge.update(repo, |
1275 other.node(), | 1376 other.node(), |
1276 branchmerge=True, | 1377 branchmerge=True, |
1277 force=False, | 1378 force=False, |
1278 partial=None, | 1379 partial=None, |
1289 /!\ * hg up to the parent of the amended changeset (which are named W and Z) | 1390 /!\ * hg up to the parent of the amended changeset (which are named W and Z) |
1290 /!\ * hg revert --all -r X | 1391 /!\ * hg revert --all -r X |
1291 /!\ * hg ci -m "same message as the amended changeset" => new cset Y | 1392 /!\ * hg ci -m "same message as the amended changeset" => new cset Y |
1292 /!\ * hg kill -n Y W Z | 1393 /!\ * hg kill -n Y W Z |
1293 """) | 1394 """) |
1294 tr = repo.transaction('stabilize-conflicting') | 1395 tr = repo.transaction('stabilize-divergent') |
1295 try: | 1396 try: |
1296 repo.dirstate.setparents(conflicting.node(), node.nullid) | 1397 repo.dirstate.setparents(divergent.node(), node.nullid) |
1297 oldlen = len(repo) | 1398 oldlen = len(repo) |
1298 amend(ui, repo) | 1399 amend(ui, repo) |
1299 if oldlen == len(repo): | 1400 if oldlen == len(repo): |
1300 new = conflicting | 1401 new = divergent |
1301 # no changes | 1402 # no changes |
1302 else: | 1403 else: |
1303 new = repo['.'] | 1404 new = repo['.'] |
1304 createmarkers(repo, [(other, (new,))]) | 1405 createmarkers(repo, [(other, (new,))]) |
1305 phases.retractboundary(repo, other.phase(), [new.node()]) | 1406 phases.retractboundary(repo, other.phase(), [new.node()]) |
1308 tr.release() | 1409 tr.release() |
1309 finally: | 1410 finally: |
1310 lockmod.release(lock, wlock) | 1411 lockmod.release(lock, wlock) |
1311 | 1412 |
1312 | 1413 |
1313 def conflictingdata(ctx): | 1414 def divergentdata(ctx): |
1314 """return base, other part of a conflict | 1415 """return base, other part of a conflict |
1315 | 1416 |
1316 This only return the first one. | 1417 This only return the first one. |
1317 | 1418 |
1318 XXX this woobly function won't survive XXX | 1419 XXX this woobly function won't survive XXX |
1319 """ | 1420 """ |
1320 for base in ctx._repo.set('reverse(precursors(%d))', ctx): | 1421 for base in ctx._repo.set('reverse(precursors(%d))', ctx): |
1321 newer = newerversion(ctx._repo, base.node()) | 1422 newer = successorssets(ctx._repo, base.node()) |
1322 # drop filter and solution including the original ctx | 1423 # drop filter and solution including the original ctx |
1323 newer = [n for n in newer if n and ctx.node() not in n] | 1424 newer = [n for n in newer if n and ctx.node() not in n] |
1324 if newer: | 1425 if newer: |
1325 return base, tuple(ctx._repo[o] for o in newer[0]) | 1426 return base, tuple(ctx._repo[o] for o in newer[0]) |
1326 raise KeyError('Base seem unknown. This case is not handled yet.') | 1427 raise KeyError('Base seem unknown. This case is not handled yet.') |
1776 if repo.revs('. and %ld', revs): | 1877 if repo.revs('. and %ld', revs): |
1777 hg.update(repo, newid) | 1878 hg.update(repo, newid) |
1778 finally: | 1879 finally: |
1779 lockmod.release(lock, wlock) | 1880 lockmod.release(lock, wlock) |
1780 | 1881 |
1882 if 'debugsuccessorssets' not in commands.table: | |
1883 | |
1884 @command('debugsuccessorssets', | |
1885 [], | |
1886 _('[REV]')) | |
1887 def debugsuccessorssets(ui, repo, *revs): | |
1888 """show set of successors for revision | |
1889 | |
1890 Successors set of changeset A are a consistent group of revision that | |
1891 succeed to A. Successors set contains non-obsolete changeset only. | |
1892 | |
1893 In most case a changeset A have zero (changeset pruned) or a single | |
1894 successors set that contains a single successors (changeset A replacement | |
1895 by A') | |
1896 | |
1897 But splitted changeset will result with successors set containing more than | |
1898 a single element. Divergent rewritting will result in multiple successor | |
1899 set. | |
1900 | |
1901 result is displayed as follows:: | |
1902 | |
1903 <rev1> | |
1904 <successors-1A> | |
1905 <rev2> | |
1906 <successors-2A> | |
1907 <successors-2B1> <successors-2B1> <successors-2B1> | |
1908 | |
1909 here rev2 have two possible successors sets. One hold three elements. | |
1910 | |
1911 add --debug if you want full size node id. | |
1912 """ | |
1913 cache = {} | |
1914 s = str | |
1915 if ui.debug: | |
1916 def s(ctx): | |
1917 return ctx.hex() | |
1918 for rev in scmutil.revrange(repo, revs): | |
1919 ctx = repo[rev] | |
1920 if ui.debug(): | |
1921 ui.write('%s\n'% ctx.hex()) | |
1922 s = node.hex | |
1923 else: | |
1924 ui.write('%s\n'% ctx) | |
1925 s = node.short | |
1926 for ss in successorssets(repo, ctx.node(), cache): | |
1927 if ss: | |
1928 ui.write(' ') | |
1929 ui.write(s(ss[0])) | |
1930 for n in ss[1:]: | |
1931 ui.write(' ') | |
1932 ui.write(s(n)) | |
1933 ui.write('\n') | |
1934 pass | |
1935 | |
1781 | 1936 |
1782 @eh.wrapcommand('graft') | 1937 @eh.wrapcommand('graft') |
1783 def graftwrapper(orig, ui, repo, *revs, **kwargs): | 1938 def graftwrapper(orig, ui, repo, *revs, **kwargs): |
1784 kwargs = dict(kwargs) | 1939 kwargs = dict(kwargs) |
1785 revs = list(revs) + kwargs.get('rev', []) | 1940 revs = list(revs) + kwargs.get('rev', []) |