37 from mercurial import discovery |
38 from mercurial import discovery |
38 from mercurial import error |
39 from mercurial import error |
39 from mercurial import extensions |
40 from mercurial import extensions |
40 from mercurial import hg |
41 from mercurial import hg |
41 from mercurial import localrepo |
42 from mercurial import localrepo |
|
43 from mercurial import lock as lockmod |
42 from mercurial import merge |
44 from mercurial import merge |
43 from mercurial import node |
45 from mercurial import node |
44 from mercurial import phases |
46 from mercurial import phases |
45 from mercurial import revset |
47 from mercurial import revset |
46 from mercurial import scmutil |
48 from mercurial import scmutil |
47 from mercurial import templatekw |
49 from mercurial import templatekw |
48 from mercurial.i18n import _ |
50 from mercurial.i18n import _ |
49 from mercurial.commands import walkopts, commitopts, commitopts2 |
51 from mercurial.commands import walkopts, commitopts, commitopts2 |
50 from mercurial.node import nullid |
52 from mercurial.node import nullid |
|
53 |
|
54 import mercurial.hgweb.hgweb_mod |
51 |
55 |
52 |
56 |
53 |
57 |
54 # This extension contains the following code |
58 # This extension contains the following code |
55 # |
59 # |
315 |
321 |
316 #: { set name -> function to compute this set } mapping |
322 #: { set name -> function to compute this set } mapping |
317 #: function take a single "repo" argument. |
323 #: function take a single "repo" argument. |
318 #: |
324 #: |
319 #: Use the `cachefor` decorator to register new cache function |
325 #: Use the `cachefor` decorator to register new cache function |
320 cachefuncs = {} |
326 try: |
321 def cachefor(name): |
327 cachefuncs = obsolete.cachefuncs |
322 """Decorator to register a function as computing the cache for a set""" |
328 cachefor = obsolete.cachefor |
323 def decorator(func): |
329 getobscache = obsolete.getobscache |
324 assert name not in cachefuncs |
330 clearobscaches = obsolete.clearobscaches |
325 cachefuncs[name] = func |
331 except AttributeError: |
326 return func |
332 cachefuncs = {} |
327 return decorator |
333 |
328 |
334 def cachefor(name): |
329 @cachefor('obsolete') |
335 """Decorator to register a function as computing the cache for a set""" |
330 def _computeobsoleteset(repo): |
336 def decorator(func): |
331 """the set of obsolete revisions""" |
337 assert name not in cachefuncs |
332 obs = set() |
338 cachefuncs[name] = func |
333 nm = repo.changelog.nodemap |
339 return func |
334 for prec in repo.obsstore.precursors: |
340 return decorator |
335 rev = nm.get(prec) |
341 |
336 if rev is not None: |
342 @cachefor('obsolete') |
337 obs.add(rev) |
343 def _computeobsoleteset(repo): |
338 return set(repo.revs('%ld - public()', obs)) |
344 """the set of obsolete revisions""" |
339 |
345 obs = set() |
340 @cachefor('unstable') |
346 nm = repo.changelog.nodemap |
341 def _computeunstableset(repo): |
347 for prec in repo.obsstore.precursors: |
342 """the set of non obsolete revisions with obsolete parents""" |
348 rev = nm.get(prec) |
343 return set(repo.revs('(obsolete()::) - obsolete()')) |
349 if rev is not None: |
344 |
350 obs.add(rev) |
345 @cachefor('suspended') |
351 return set(repo.revs('%ld - public()', obs)) |
346 def _computesuspendedset(repo): |
352 |
347 """the set of obsolete parents with non obsolete descendants""" |
353 @cachefor('unstable') |
348 return set(repo.revs('obsolete() and obsolete()::unstable()')) |
354 def _computeunstableset(repo): |
349 |
355 """the set of non obsolete revisions with obsolete parents""" |
350 @cachefor('extinct') |
356 return set(repo.revs('(obsolete()::) - obsolete()')) |
351 def _computeextinctset(repo): |
357 |
352 """the set of obsolete parents without non obsolete descendants""" |
358 @cachefor('suspended') |
353 return set(repo.revs('obsolete() - obsolete()::unstable()')) |
359 def _computesuspendedset(repo): |
354 |
360 """the set of obsolete parents with non obsolete descendants""" |
355 @eh.wrapfunction(obsolete.obsstore, '__init__') |
361 return set(repo.revs('obsolete() and obsolete()::unstable()')) |
356 def _initobsstorecache(orig, obsstore, *args, **kwargs): |
362 |
357 """add a cache attribute to obsstore""" |
363 @cachefor('extinct') |
358 obsstore.caches = {} |
364 def _computeextinctset(repo): |
359 return orig(obsstore, *args, **kwargs) |
365 """the set of obsolete parents without non obsolete descendants""" |
|
366 return set(repo.revs('obsolete() - obsolete()::unstable()')) |
|
367 |
|
368 @eh.wrapfunction(obsolete.obsstore, '__init__') |
|
369 def _initobsstorecache(orig, obsstore, *args, **kwargs): |
|
370 """add a cache attribute to obsstore""" |
|
371 obsstore.caches = {} |
|
372 return orig(obsstore, *args, **kwargs) |
360 |
373 |
361 ### Cache access |
374 ### Cache access |
362 |
375 |
363 def getobscache(repo, name): |
376 def getobscache(repo, name): |
364 """Return the set of revision that belong to the <name> set |
377 """Return the set of revision that belong to the <name> set |
365 |
378 |
366 Such access may compute the set and cache it for future use""" |
379 Such access may compute the set and cache it for future use""" |
367 if not repo.obsstore: |
380 if not repo.obsstore: |
368 return () |
381 return () |
369 if name not in repo.obsstore.caches: |
382 if name not in repo.obsstore.caches: |
370 repo.obsstore.caches[name] = cachefuncs[name](repo) |
383 repo.obsstore.caches[name] = cachefuncs[name](repo) |
371 return repo.obsstore.caches[name] |
384 return repo.obsstore.caches[name] |
372 |
385 |
373 ### Cache clean up |
386 ### Cache clean up |
374 # |
387 # |
375 # To be simple we need to invalidate obsolescence cache when: |
388 # To be simple we need to invalidate obsolescence cache when: |
376 # |
389 # |
378 # - public phase is changed |
391 # - public phase is changed |
379 # - obsolescence marker are added |
392 # - obsolescence marker are added |
380 # - strip is used a repo |
393 # - strip is used a repo |
381 |
394 |
382 |
395 |
383 def clearobscaches(repo): |
396 def clearobscaches(repo): |
384 """Remove all obsolescence related cache from a repo |
397 """Remove all obsolescence related cache from a repo |
385 |
398 |
386 This remove all cache in obsstore is the obsstore already exist on the |
399 This remove all cache in obsstore is the obsstore already exist on the |
387 repo. |
400 repo. |
388 |
401 |
389 (We could be smarter here)""" |
402 (We could be smarter here)""" |
390 if 'obsstore' in repo._filecache: |
403 if 'obsstore' in repo._filecache: |
391 repo.obsstore.caches.clear() |
404 repo.obsstore.caches.clear() |
392 |
405 |
393 @eh.wrapfunction(localrepo.localrepository, 'addchangegroup') # new changeset |
406 @eh.wrapfunction(localrepo.localrepository, 'addchangegroup') # new changeset |
394 @eh.wrapfunction(phases, 'retractboundary') # phase movement |
407 @eh.wrapfunction(phases, 'retractboundary') # phase movement |
395 @eh.wrapfunction(phases, 'advanceboundary') # phase movement |
408 @eh.wrapfunction(phases, 'advanceboundary') # phase movement |
396 @eh.wrapfunction(localrepo.localrepository, 'destroyed') # strip |
409 @eh.wrapfunction(localrepo.localrepository, 'destroyed') # strip |
397 def wrapclearcache(orig, repo, *args, **kwargs): |
410 def wrapclearcache(orig, repo, *args, **kwargs): |
398 try: |
411 try: |
399 return orig(repo, *args, **kwargs) |
412 return orig(repo, *args, **kwargs) |
400 finally: |
413 finally: |
401 # we are a bit wide here |
414 # we are a bit wide here |
402 # we could restrict to: |
415 # we could restrict to: |
403 # advanceboundary + phase==public |
416 # advanceboundary + phase==public |
404 # retractboundary + phase==draft |
417 # retractboundary + phase==draft |
405 clearobscaches(repo) |
418 clearobscaches(repo) |
406 |
419 |
407 @eh.wrapfunction(obsolete.obsstore, 'add') # new marker |
420 @eh.wrapfunction(obsolete.obsstore, 'add') # new marker |
408 def clearonadd(orig, obsstore, *args, **kwargs): |
421 def clearonadd(orig, obsstore, *args, **kwargs): |
409 try: |
422 try: |
410 return orig(obsstore, *args, **kwargs) |
423 return orig(obsstore, *args, **kwargs) |
411 finally: |
424 finally: |
412 obsstore.caches.clear() |
425 obsstore.caches.clear() |
413 |
426 |
414 ### Use the case |
427 ### Use the case |
415 # Function in core that could benefic from the cache are overwritten by cache using version |
428 # Function in core that could benefic from the cache are overwritten by cache using version |
416 |
429 |
417 # changectx method |
430 # changectx method |
418 |
431 |
419 @eh.addattr(context.changectx, 'unstable') |
432 @eh.addattr(context.changectx, 'unstable') |
420 def unstable(ctx): |
433 def unstable(ctx): |
421 """is the changeset unstable (have obsolete ancestor)""" |
434 """is the changeset unstable (have obsolete ancestor)""" |
422 if ctx.node() is None: |
435 if ctx.node() is None: |
423 return False |
436 return False |
424 return ctx.rev() in getobscache(ctx._repo, 'unstable') |
437 return ctx.rev() in getobscache(ctx._repo, 'unstable') |
425 |
438 |
426 |
439 |
427 @eh.addattr(context.changectx, 'extinct') |
440 @eh.addattr(context.changectx, 'extinct') |
428 def extinct(ctx): |
441 def extinct(ctx): |
429 """is the changeset extinct by other""" |
442 """is the changeset extinct by other""" |
430 if ctx.node() is None: |
443 if ctx.node() is None: |
431 return False |
444 return False |
432 return ctx.rev() in getobscache(ctx._repo, 'extinct') |
445 return ctx.rev() in getobscache(ctx._repo, 'extinct') |
433 |
446 |
434 # revset |
447 # revset |
435 |
448 |
436 @eh.revset('obsolete') |
449 @eh.revset('obsolete') |
437 def revsetobsolete(repo, subset, x): |
450 def revsetobsolete(repo, subset, x): |
438 """``obsolete()`` |
451 """``obsolete()`` |
439 Changeset is obsolete. |
452 Changeset is obsolete. |
440 """ |
453 """ |
441 args = revset.getargs(x, 0, 0, 'obsolete takes no argument') |
454 args = revset.getargs(x, 0, 0, 'obsolete takes no argument') |
442 obsoletes = getobscache(repo, 'obsolete') |
455 obsoletes = getobscache(repo, 'obsolete') |
443 return [r for r in subset if r in obsoletes] |
456 return [r for r in subset if r in obsoletes] |
444 |
457 |
445 @eh.revset('unstable') |
458 @eh.revset('unstable') |
446 def revsetunstable(repo, subset, x): |
459 def revsetunstable(repo, subset, x): |
447 """``unstable()`` |
460 """``unstable()`` |
448 Unstable changesets are non-obsolete with obsolete ancestors. |
461 Unstable changesets are non-obsolete with obsolete ancestors. |
449 """ |
462 """ |
450 args = revset.getargs(x, 0, 0, 'unstable takes no arguments') |
463 args = revset.getargs(x, 0, 0, 'unstable takes no arguments') |
451 unstables = getobscache(repo, 'unstable') |
464 unstables = getobscache(repo, 'unstable') |
452 return [r for r in subset if r in unstables] |
465 return [r for r in subset if r in unstables] |
453 |
466 |
454 @eh.revset('extinct') |
467 @eh.revset('extinct') |
455 def revsetextinct(repo, subset, x): |
468 def revsetextinct(repo, subset, x): |
456 """``extinct()`` |
469 """``extinct()`` |
457 Obsolete changesets with obsolete descendants only. |
470 Obsolete changesets with obsolete descendants only. |
458 """ |
471 """ |
459 args = revset.getargs(x, 0, 0, 'extinct takes no arguments') |
472 args = revset.getargs(x, 0, 0, 'extinct takes no arguments') |
460 extincts = getobscache(repo, 'extinct') |
473 extincts = getobscache(repo, 'extinct') |
461 return [r for r in subset if r in extincts] |
474 return [r for r in subset if r in extincts] |
462 |
475 |
463 ##################################################################### |
476 ##################################################################### |
464 ### Complete troubles computation logic ### |
477 ### Complete troubles computation logic ### |
465 ##################################################################### |
478 ##################################################################### |
466 |
479 |
577 return orig(repo, node, *args, **kwargs) |
590 return orig(repo, node, *args, **kwargs) |
578 |
591 |
579 @eh.wrapfunction(localrepo.localrepository, 'branchtip') |
592 @eh.wrapfunction(localrepo.localrepository, 'branchtip') |
580 def obsbranchtip(orig, repo, branch): |
593 def obsbranchtip(orig, repo, branch): |
581 """ensure "stable" reference does not end on a hidden changeset""" |
594 """ensure "stable" reference does not end on a hidden changeset""" |
|
595 if not getattr(repo, '_dofilterbranchtip', True): |
|
596 return orig(repo, branch) |
582 result = () |
597 result = () |
583 heads = repo.branchmap().get(branch, ()) |
598 heads = repo.branchmap().get(branch, ()) |
584 if heads: |
599 if heads: |
585 result = list(repo.set('last(heads(branch(%n) - hidden()))', heads[0])) |
600 result = list(repo.set('last(heads(branch(%n) - hidden()))', heads[0])) |
586 if not result: |
601 if not result: |
587 raise error.RepoLookupError(_("unknown branch '%s'") % branch) |
602 raise error.RepoLookupError(_("unknown branch '%s'") % branch) |
588 return result[0].node() |
603 return result[0].node() |
589 |
604 |
590 |
605 |
|
606 @eh.wrapfunction(mercurial.hgweb.hgweb_mod.hgweb, '__init__') |
|
607 @eh.wrapfunction(mercurial.hgweb.hgweb_mod.hgweb, 'refresh') |
|
608 def nofilter(orig, hgweb, *args, **kwargs): |
|
609 orig(hgweb, *args, **kwargs) |
|
610 hgweb.repo._dofilterbranchtip = False |
|
611 |
|
612 |
591 ##################################################################### |
613 ##################################################################### |
592 ### Additional Utilities ### |
614 ### Additional Utilities ### |
593 ##################################################################### |
615 ##################################################################### |
594 |
616 |
595 # This section contains a lot of small utility function and method |
617 # This section contains a lot of small utility function and method |
599 # - "troubles" method on changectx |
621 # - "troubles" method on changectx |
600 # - function to travel throught the obsolescence graph |
622 # - function to travel throught the obsolescence graph |
601 # - function to find useful changeset to stabilize |
623 # - function to find useful changeset to stabilize |
602 |
624 |
603 ### Marker Create |
625 ### Marker Create |
604 |
626 # NOW IN CORE f85816af6294 |
605 def createmarkers(repo, relations, metadata=None, flag=0): |
627 try: |
606 """Add obsolete markers between changeset in a repo |
628 createmarkers = obsolete.createmarkers |
607 |
629 except AttributeError: |
608 <relations> must be an iterable of (<old>, (<new>, ...)) tuple. |
630 def createmarkers(repo, relations, metadata=None, flag=0): |
609 `old` and `news` are changectx. |
631 """Add obsolete markers between changeset in a repo |
610 |
632 |
611 Current user and date are used except if specified otherwise in the |
633 <relations> must be an iterable of (<old>, (<new>, ...)) tuple. |
612 metadata attribute. |
634 `old` and `news` are changectx. |
613 |
635 |
614 /!\ assume the repo have been locked by the user /!\ |
636 Current user and date are used except if specified otherwise in the |
615 """ |
637 metadata attribute. |
616 # prepare metadata |
638 |
617 if metadata is None: |
639 /!\ assume the repo have been locked by the user /!\ |
618 metadata = {} |
640 """ |
619 if 'date' not in metadata: |
641 # prepare metadata |
620 metadata['date'] = '%i %i' % util.makedate() |
642 if metadata is None: |
621 if 'user' not in metadata: |
643 metadata = {} |
622 metadata['user'] = repo.ui.username() |
644 if 'date' not in metadata: |
623 # check future marker |
645 metadata['date'] = '%i %i' % util.makedate() |
624 tr = repo.transaction('add-obsolescence-marker') |
646 if 'user' not in metadata: |
625 try: |
647 metadata['user'] = repo.ui.username() |
626 for prec, sucs in relations: |
648 # check future marker |
627 if not prec.mutable(): |
649 tr = repo.transaction('add-obsolescence-marker') |
628 raise util.Abort("Cannot obsolete immutable changeset: %s" % prec) |
650 try: |
629 nprec = prec.node() |
651 for prec, sucs in relations: |
630 nsucs = tuple(s.node() for s in sucs) |
652 if not prec.mutable(): |
631 if nprec in nsucs: |
653 raise util.Abort("cannot obsolete immutable changeset: %s" % prec) |
632 raise util.Abort("Changeset %s cannot obsolete himself" % prec) |
654 nprec = prec.node() |
633 repo.obsstore.create(tr, nprec, nsucs, flag, metadata) |
655 nsucs = tuple(s.node() for s in sucs) |
634 clearobscaches(repo) |
656 if nprec in nsucs: |
635 tr.close() |
657 raise util.Abort("changeset %s cannot obsolete himself" % prec) |
636 finally: |
658 repo.obsstore.create(tr, nprec, nsucs, flag, metadata) |
637 tr.release() |
659 clearobscaches(repo) |
|
660 tr.close() |
|
661 finally: |
|
662 tr.release() |
638 |
663 |
639 |
664 |
640 ### Useful alias |
665 ### Useful alias |
641 |
666 |
642 @eh.uisetup |
667 @eh.uisetup |
929 return result |
957 return result |
930 repo.__class__ = evolvingrepo |
958 repo.__class__ = evolvingrepo |
931 |
959 |
932 @eh.wrapcommand("summary") |
960 @eh.wrapcommand("summary") |
933 def obssummary(orig, ui, repo, *args, **kwargs): |
961 def obssummary(orig, ui, repo, *args, **kwargs): |
|
962 def write(fmt, count): |
|
963 s = fmt % count |
|
964 if count: |
|
965 ui.write(s) |
|
966 else: |
|
967 ui.note(s) |
|
968 |
934 ret = orig(ui, repo, *args, **kwargs) |
969 ret = orig(ui, repo, *args, **kwargs) |
935 nbunstable = len(getobscache(repo, 'unstable')) |
970 nbunstable = len(getobscache(repo, 'unstable')) |
936 nblatecomer = len(getobscache(repo, 'latecomer')) |
971 nblatecomer = len(getobscache(repo, 'latecomer')) |
937 nbconflicting = len(getobscache(repo, 'unstable')) |
972 nbconflicting = len(getobscache(repo, 'unstable')) |
938 if nbunstable: |
973 write('unstable: %i changesets\n', nbunstable) |
939 ui.write('unstable: %i changesets\n' % nbunstable) |
974 write('latecomer: %i changesets\n', nblatecomer) |
940 else: |
975 write('conflicting: %i changesets\n', nbconflicting) |
941 ui.note('unstable: 0 changesets\n') |
|
942 if nblatecomer: |
|
943 ui.write('latecomer: %i changesets\n' % nblatecomer) |
|
944 else: |
|
945 ui.note('latecomer: 0 changesets\n') |
|
946 if nbconflicting: |
|
947 ui.write('conflicting: %i changesets\n' % nbconflicting) |
|
948 else: |
|
949 ui.note('conflicting: 0 changesets\n') |
|
950 return ret |
976 return ret |
951 |
977 |
952 |
978 |
953 ##################################################################### |
979 ##################################################################### |
954 ### Core Other extension compat ### |
980 ### Core Other extension compat ### |
960 ### commit --amend |
986 ### commit --amend |
961 # make commit --amend create obsolete marker |
987 # make commit --amend create obsolete marker |
962 # |
988 # |
963 # The precursor is still strip from the repository. |
989 # The precursor is still strip from the repository. |
964 |
990 |
965 @eh.wrapfunction(cmdutil, 'amend') |
991 # IN CORE 63e45aee46d4 |
966 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs): |
992 |
967 oldnode = old.node() |
993 if getattr(cmdutil, 'obsolete', None) is None: |
968 new = orig(ui, repo, commitfunc, old, *args, **kwargs) |
994 @eh.wrapfunction(cmdutil, 'amend') |
969 if new != oldnode: |
995 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs): |
970 lock = repo.lock() |
996 oldnode = old.node() |
971 try: |
997 new = orig(ui, repo, commitfunc, old, *args, **kwargs) |
972 tr = repo.transaction('post-amend-obst') |
998 if new != oldnode: |
|
999 lock = repo.lock() |
973 try: |
1000 try: |
974 meta = { |
1001 tr = repo.transaction('post-amend-obst') |
975 'date': '%i %i' % util.makedate(), |
1002 try: |
976 'user': ui.username(), |
1003 meta = { |
977 } |
1004 'date': '%i %i' % util.makedate(), |
978 repo.obsstore.create(tr, oldnode, [new], 0, meta) |
1005 'user': ui.username(), |
979 tr.close() |
1006 } |
980 clearobscaches(repo) |
1007 repo.obsstore.create(tr, oldnode, [new], 0, meta) |
|
1008 tr.close() |
|
1009 clearobscaches(repo) |
|
1010 finally: |
|
1011 tr.release() |
981 finally: |
1012 finally: |
982 tr.release() |
1013 lock.release() |
983 finally: |
1014 return new |
984 lock.release() |
|
985 return new |
|
986 |
1015 |
987 ### rebase |
1016 ### rebase |
988 # |
1017 # |
989 # - ignore obsolete changeset |
1018 # - ignore obsolete changeset |
990 # - create obsolete marker *instead of* striping |
1019 # - create obsolete marker *instead of* striping |
995 rebaseset = repo.revs('%ld - extinct()', rebaseset) |
1024 rebaseset = repo.revs('%ld - extinct()', rebaseset) |
996 if not rebaseset: |
1025 if not rebaseset: |
997 repo.ui.warn(_('whole rebase set is extinct and ignored.\n')) |
1026 repo.ui.warn(_('whole rebase set is extinct and ignored.\n')) |
998 return {} |
1027 return {} |
999 root = min(rebaseset) |
1028 root = min(rebaseset) |
1000 if not repo._rebasekeep and not repo[root].mutable(): |
1029 if (not getattr(repo, '_rebasekeep', False) |
|
1030 and not repo[root].mutable()): |
1001 raise util.Abort(_("can't rebase immutable changeset %s") % repo[root], |
1031 raise util.Abort(_("can't rebase immutable changeset %s") % repo[root], |
1002 hint=_('see hg help phases for details')) |
1032 hint=_('see hg help phases for details')) |
1003 return orig(repo, dest, rebaseset, *ags, **kws) |
1033 return orig(repo, dest, rebaseset, *ags, **kws) |
1004 |
1034 |
1005 def defineparents(orig, repo, rev, target, state, *args, **kwargs): |
1035 def defineparents(orig, repo, rev, target, state, *args, **kwargs): |
1083 def _rebasewrapping(ui): |
1113 def _rebasewrapping(ui): |
1084 # warning about more obsolete |
1114 # warning about more obsolete |
1085 try: |
1115 try: |
1086 rebase = extensions.find('rebase') |
1116 rebase = extensions.find('rebase') |
1087 if rebase: |
1117 if rebase: |
1088 entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors) |
1118 incore = getattr(rebase, 'obsolete', None) is not None |
1089 extensions.wrapfunction(rebase, 'buildstate', buildstate) |
1119 if not incore: |
1090 extensions.wrapfunction(rebase, 'defineparents', defineparents) |
1120 extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase) |
1091 extensions.wrapfunction(rebase, 'concludenode', concludenode) |
1121 extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors) |
1092 extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase) |
1122 if not incore: |
|
1123 extensions.wrapfunction(rebase, 'buildstate', buildstate) |
|
1124 extensions.wrapfunction(rebase, 'defineparents', defineparents) |
|
1125 extensions.wrapfunction(rebase, 'concludenode', concludenode) |
1093 except KeyError: |
1126 except KeyError: |
1094 pass # rebase not found |
1127 pass # rebase not found |
1095 |
1128 |
1096 |
1129 |
1097 ##################################################################### |
1130 ##################################################################### |
1217 assert orig.p2().rev() == node.nullrev, 'no support yet' |
1250 assert orig.p2().rev() == node.nullrev, 'no support yet' |
1218 destbookmarks = repo.nodebookmarks(dest.node()) |
1251 destbookmarks = repo.nodebookmarks(dest.node()) |
1219 cmdutil.duplicatecopies(repo, orig.node(), dest.node()) |
1252 cmdutil.duplicatecopies(repo, orig.node(), dest.node()) |
1220 nodesrc = orig.node() |
1253 nodesrc = orig.node() |
1221 destphase = repo[nodesrc].phase() |
1254 destphase = repo[nodesrc].phase() |
1222 if rebase.rebasenode.func_code.co_argcount == 5: |
|
1223 # rebasenode collapse argument was introduced by |
|
1224 # d1afbf03e69a (2.3) |
|
1225 rebase.rebasenode(repo, orig.node(), dest.node(), |
|
1226 {node.nullrev: node.nullrev}, False) |
|
1227 else: |
|
1228 rebase.rebasenode(repo, orig.node(), dest.node(), |
|
1229 {node.nullrev: node.nullrev}) |
|
1230 try: |
1255 try: |
|
1256 if rebase.rebasenode.func_code.co_argcount == 5: |
|
1257 # rebasenode collapse argument was introduced by |
|
1258 # d1afbf03e69a (2.3) |
|
1259 r = rebase.rebasenode(repo, orig.node(), dest.node(), |
|
1260 {node.nullrev: node.nullrev}, False) |
|
1261 else: |
|
1262 r = rebase.rebasenode(repo, orig.node(), dest.node(), |
|
1263 {node.nullrev: node.nullrev}) |
|
1264 if r[-1]: #some conflict |
|
1265 raise util.Abort( |
|
1266 'unresolved merge conflicts (see hg help resolve)') |
1231 nodenew = rebase.concludenode(repo, orig.node(), dest.node(), |
1267 nodenew = rebase.concludenode(repo, orig.node(), dest.node(), |
1232 node.nullid) |
1268 node.nullid) |
1233 except util.Abort, exc: |
1269 except util.Abort, exc: |
1234 class LocalMergeFailure(MergeFailure, exc.__class__): |
1270 class LocalMergeFailure(MergeFailure, exc.__class__): |
1235 pass |
1271 pass |
1355 elif 'conflicting' in troubles: |
1372 elif 'conflicting' in troubles: |
1356 return _solveconflicting(ui, repo, tr, opts['dry_run']) |
1373 return _solveconflicting(ui, repo, tr, opts['dry_run']) |
1357 else: |
1374 else: |
1358 assert False # WHAT? unknown troubles |
1375 assert False # WHAT? unknown troubles |
1359 |
1376 |
1360 def _picknexttroubled(ui, repo, any=False): |
1377 def _picknexttroubled(ui, repo, pickany=False): |
1361 """Pick a the next trouble changeset to solve""" |
1378 """Pick a the next trouble changeset to solve""" |
1362 tr = _stabilizableunstable(repo, repo['.']) |
1379 tr = _stabilizableunstable(repo, repo['.']) |
1363 if tr is None: |
1380 if tr is None: |
1364 wdp = repo['.'] |
1381 wdp = repo['.'] |
1365 if 'conflicting' in wdp.troubles(): |
1382 if 'conflicting' in wdp.troubles(): |
1366 tr = wdp |
1383 tr = wdp |
1367 if tr is None and any: |
1384 if tr is None and pickany: |
1368 troubled = list(repo.set('unstable()')) |
1385 troubled = list(repo.set('unstable()')) |
1369 if not troubled: |
1386 if not troubled: |
1370 troubled = list(repo.set('latecomer()')) |
1387 troubled = list(repo.set('latecomer()')) |
1371 if not troubled: |
1388 if not troubled: |
1372 troubled = list(repo.set('conflicting()')) |
1389 troubled = list(repo.set('conflicting()')) |
1373 if troubled: |
1390 if troubled: |
1374 tr = troubled[0] |
1391 tr = troubled[0] |
1375 |
1392 |
1376 return tr |
1393 return tr |
1377 |
1394 |
|
1395 def _stabilizableunstable(repo, pctx): |
|
1396 """Return a changectx for an unstable changeset which can be |
|
1397 stabilized on top of pctx or one of its descendants. None if none |
|
1398 can be found. |
|
1399 """ |
|
1400 def selfanddescendants(repo, pctx): |
|
1401 yield pctx |
|
1402 for ctx in pctx.descendants(): |
|
1403 yield ctx |
|
1404 |
|
1405 # Look for an unstable which can be stabilized as a child of |
|
1406 # node. The unstable must be a child of one of node predecessors. |
|
1407 for ctx in selfanddescendants(repo, pctx): |
|
1408 unstables = list(repo.set('unstable() and children(allprecursors(%d))', |
|
1409 ctx.rev())) |
|
1410 if unstables: |
|
1411 return unstables[0] |
|
1412 return None |
1378 |
1413 |
1379 def _solveunstable(ui, repo, orig, dryrun=False): |
1414 def _solveunstable(ui, repo, orig, dryrun=False): |
1380 """Stabilize a unstable changeset""" |
1415 """Stabilize a unstable changeset""" |
1381 obs = orig.parents()[0] |
1416 obs = orig.parents()[0] |
1382 if not obs.obsolete(): |
1417 if not obs.obsolete(): |
1546 ui.status(_('base: ')) |
1581 ui.status(_('base: ')) |
1547 if not ui.quiet: |
1582 if not ui.quiet: |
1548 displayer.show(base) |
1583 displayer.show(base) |
1549 if dryrun: |
1584 if dryrun: |
1550 ui.write('hg update -c %s &&\n' % conflicting) |
1585 ui.write('hg update -c %s &&\n' % conflicting) |
1551 ui.write('hg merge %s && \n' % other) |
1586 ui.write('hg merge %s &&\n' % other) |
1552 ui.write('hg commit -m "auto merge resolving conflict between %s and %s"&&\n' |
1587 ui.write('hg commit -m "auto merge resolving conflict between ' |
1553 % (conflicting, other)) |
1588 '%s and %s"&&\n' % (conflicting, other)) |
1554 ui.write('hg up -C %s &&\n' % base) |
1589 ui.write('hg up -C %s &&\n' % base) |
1555 ui.write('hg revert --all --rev tip &&\n') |
1590 ui.write('hg revert --all --rev tip &&\n') |
1556 ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' % conflicting) |
1591 ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' |
|
1592 % conflicting) |
1557 return |
1593 return |
1558 #oldphase = max(conflicting.phase(), other.phase()) |
1594 wlock = lock = None |
1559 wlock = repo.wlock() |
|
1560 try: |
1595 try: |
|
1596 wlock = repo.wlock() |
1561 lock = repo.lock() |
1597 lock = repo.lock() |
|
1598 if conflicting not in repo[None].parents(): |
|
1599 repo.ui.status(_('updating to "local" conflict\n')) |
|
1600 hg.update(repo, conflicting.rev()) |
|
1601 repo.ui.note(_('merging conflicting changeset\n')) |
|
1602 stats = merge.update(repo, |
|
1603 other.node(), |
|
1604 branchmerge=True, |
|
1605 force=False, |
|
1606 partial=None, |
|
1607 ancestor=base.node(), |
|
1608 mergeancestor=True) |
|
1609 hg._showstats(repo, stats) |
|
1610 if stats[3]: |
|
1611 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges " |
|
1612 "or 'hg update -C .' to abandon\n")) |
|
1613 if stats[3] > 0: |
|
1614 raise util.Abort('Merge conflict between several amendments, and this is not yet automated', |
|
1615 hint="""/!\ You can try: |
|
1616 /!\ * manual merge + resolve => new cset X |
|
1617 /!\ * hg up to the parent of the amended changeset (which are named W and Z) |
|
1618 /!\ * hg revert --all -r X |
|
1619 /!\ * hg ci -m "same message as the amended changeset" => new cset Y |
|
1620 /!\ * hg kill -n Y W Z |
|
1621 """) |
|
1622 tr = repo.transaction('stabilize-conflicting') |
1562 try: |
1623 try: |
1563 if conflicting not in repo[None].parents(): |
1624 repo.dirstate.setparents(conflicting.node(), node.nullid) |
1564 repo.ui.status(_('updating to "local" conflict\n')) |
1625 oldlen = len(repo) |
1565 hg.update(repo, conflicting.rev()) |
1626 amend(ui, repo) |
1566 repo.ui.note(_('merging conflicting changeset\n')) |
1627 if oldlen == len(repo): |
1567 stats = merge.update(repo, |
1628 new = conflicting |
1568 other.node(), |
1629 # no changes |
1569 branchmerge=True, |
1630 else: |
1570 force=False, |
1631 new = repo['.'] |
1571 partial=None, |
1632 createmarkers(repo, [(other, (new,))]) |
1572 ancestor=base.node(), |
1633 phases.retractboundary(repo, other.phase(), [new.node()]) |
1573 mergeancestor=True) |
1634 tr.close() |
1574 hg._showstats(repo, stats) |
|
1575 if stats[3]: |
|
1576 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges " |
|
1577 "or 'hg update -C .' to abandon\n")) |
|
1578 #repo.dirstate.write() |
|
1579 if stats[3] > 0: |
|
1580 raise util.Abort('GASP! Merge Conflict! You are on you own chap!', |
|
1581 hint='/!\\ hg evolve --continue will NOT work /!\\') |
|
1582 tr = repo.transaction('stabilize-conflicting') |
|
1583 try: |
|
1584 repo.dirstate.setparents(conflicting.node(), node.nullid) |
|
1585 oldlen = len(repo) |
|
1586 amend(ui, repo) |
|
1587 if oldlen == len(repo): |
|
1588 new = conflicting |
|
1589 # no changes |
|
1590 else: |
|
1591 new = repo['.'] |
|
1592 createmarkers(repo, [(other, (new,))]) |
|
1593 phases.retractboundary(repo, other.phase(), [new.node()]) |
|
1594 tr.close() |
|
1595 finally: |
|
1596 tr.release() |
|
1597 finally: |
1635 finally: |
1598 lock.release() |
1636 tr.release() |
1599 finally: |
1637 finally: |
1600 wlock.release() |
1638 lockmod.release(lock, wlock) |
1601 |
1639 |
1602 |
1640 |
1603 def conflictingdata(ctx): |
1641 def conflictingdata(ctx): |
1604 """return base, other part of a conflict |
1642 """return base, other part of a conflict |
1605 |
1643 |
1981 [('r', 'rev', [], 'revision to update'),], |
2021 [('r', 'rev', [], 'revision to update'),], |
1982 # allow to choose the seed ? |
2022 # allow to choose the seed ? |
1983 _('[-r] revs')) |
2023 _('[-r] revs')) |
1984 def touch(ui, repo, *revs, **opts): |
2024 def touch(ui, repo, *revs, **opts): |
1985 """Create successors with exact same property but hash |
2025 """Create successors with exact same property but hash |
1986 |
2026 |
1987 This is used to "resurect" changeset""" |
2027 This is used to "resurrect" changesets |
|
2028 """ |
1988 revs = list(revs) |
2029 revs = list(revs) |
1989 revs.extend(opts['rev']) |
2030 revs.extend(opts['rev']) |
1990 if not revs: |
2031 if not revs: |
1991 revs = ['.'] |
2032 revs = ['.'] |
1992 revs = scmutil.revrange(repo, revs) |
2033 revs = scmutil.revrange(repo, revs) |
1993 if not revs: |
2034 if not revs: |
1994 ui.write_err('no revision to touch\n') |
2035 ui.write_err('no revision to touch\n') |
1995 return 1 |
2036 return 1 |
1996 if repo.revs('public() and %ld', revs): |
2037 if repo.revs('public() and %ld', revs): |
1997 raise util.Abort("can't touch public revision") |
2038 raise util.Abort("can't touch public revision") |
1998 wlock = repo.wlock() |
2039 wlock = lock = None |
1999 try: |
2040 try: |
|
2041 wlock = repo.wlock() |
2000 lock = repo.lock() |
2042 lock = repo.lock() |
|
2043 tr = repo.transaction('touch') |
2001 try: |
2044 try: |
2002 tr = repo.transaction('touch') |
2045 for r in revs: |
2003 try: |
2046 ctx = repo[r] |
2004 for r in revs: |
2047 extra = ctx.extra().copy() |
2005 ctx = repo[r] |
2048 extra['__touch-noise__'] = random.randint(0, 0xffffffff) |
2006 extra = ctx.extra().copy() |
2049 new, _ = rewrite(repo, ctx, [], ctx, |
2007 extra['__touch-noise__'] = random.randint(0, 0xffffffff) |
2050 [ctx.p1().node(), ctx.p2().node()], |
2008 new, _ = rewrite(repo, ctx, [], ctx, |
2051 commitopts={'extra': extra}) |
2009 [ctx.p1().node(), ctx.p2().node()], |
2052 createmarkers(repo, [(ctx, (repo[new],))]) |
2010 commitopts={'extra': extra}) |
2053 phases.retractboundary(repo, ctx.phase(), [new]) |
2011 createmarkers(repo, [(ctx, (repo[new],))]) |
2054 if ctx in repo[None].parents(): |
2012 phases.retractboundary(repo, ctx.phase(), [new]) |
2055 repo.dirstate.setparents(new, node.nullid) |
2013 if ctx in repo[None].parents(): |
2056 tr.close() |
2014 repo.dirstate.setparents(new, node.nullid) |
|
2015 tr.close() |
|
2016 finally: |
|
2017 tr.release() |
|
2018 finally: |
2057 finally: |
2019 lock.release() |
2058 tr.release() |
2020 finally: |
2059 finally: |
2021 wlock.release() |
2060 lockmod.release(lock, wlock) |
2022 |
2061 |
2023 @command('^fold', |
2062 @command('^fold', |
2024 [('r', 'rev', [], 'revision to fold'),], |
2063 [('r', 'rev', [], 'revisions to fold'),], |
2025 # allow to choose the seed ? |
2064 # allow to choose the seed ? |
2026 _('[-r] revs')) |
2065 _('[-r] revs')) |
2027 def fold(ui, repo, *revs, **opts): |
2066 def fold(ui, repo, *revs, **opts): |
2028 """Fold multiple revision into a single one""" |
2067 """Fold multiple revisions into a single one""" |
2029 revs = list(revs) |
2068 revs = list(revs) |
2030 revs.extend(opts['rev']) |
2069 revs.extend(opts['rev']) |
2031 if not revs: |
2070 if revs: |
2032 revs = ['.'] |
2071 revs = scmutil.revrange(repo, revs) |
2033 revs = scmutil.revrange(repo, revs) |
|
2034 if not revs: |
2072 if not revs: |
2035 ui.write_err('no revision to fold\n') |
2073 ui.write_err('no revision to fold\n') |
2036 return 1 |
2074 return 1 |
2037 roots = repo.revs('roots(%ld)', revs) |
2075 roots = repo.revs('roots(%ld)', revs) |
2038 if len(roots) > 1: |
2076 if len(roots) > 1: |
2039 raise util.Abort("set have multiple roots") |
2077 raise util.Abort("set has multiple roots") |
2040 root = repo[roots[0]] |
2078 root = repo[roots[0]] |
2041 if root.phase() <= phases.public: |
2079 if root.phase() <= phases.public: |
2042 raise util.Abort("can't touch public revision") |
2080 raise util.Abort("can't fold public revisions") |
2043 heads = repo.revs('heads(%ld)', revs) |
2081 heads = repo.revs('heads(%ld)', revs) |
2044 if len(heads) > 1: |
2082 if len(heads) > 1: |
2045 raise util.Abort("set have multiple heads") |
2083 raise util.Abort("set has multiple heads") |
2046 head = repo[heads[0]] |
2084 head = repo[heads[0]] |
2047 wlock = repo.wlock() |
2085 wlock = lock = None |
2048 try: |
2086 try: |
|
2087 wlock = repo.wlock() |
2049 lock = repo.lock() |
2088 lock = repo.lock() |
|
2089 tr = repo.transaction('touch') |
2050 try: |
2090 try: |
2051 tr = repo.transaction('touch') |
2091 allctx = [repo[r] for r in revs] |
2052 try: |
2092 targetphase = max(c.phase() for c in allctx) |
2053 allctx = [repo[r] for r in revs] |
2093 msg = '\n\n***\n\n'.join(c.description() for c in allctx) |
2054 targetphase = max(c.phase() for c in allctx) |
2094 newid, _ = rewrite(repo, root, allctx, head, |
2055 msg = '\n\n***\n\n'.join(c.description() for c in allctx) |
2095 [root.p1().node(), root.p2().node()], |
2056 newid, _ = rewrite(repo, root, allctx, head, |
2096 commitopts={'message': msg}) |
2057 [root.p1().node(), root.p2().node()], |
2097 phases.retractboundary(repo, targetphase, [newid]) |
2058 commitopts={'message': msg}) |
2098 createmarkers(repo, [(ctx, (repo[newid],)) |
2059 phases.retractboundary(repo, targetphase, [newid]) |
2099 for ctx in allctx]) |
2060 createmarkers(repo, [(ctx, (repo[newid],)) |
2100 tr.close() |
2061 for ctx in allctx]) |
|
2062 tr.close() |
|
2063 finally: |
|
2064 tr.release() |
|
2065 finally: |
2101 finally: |
2066 lock.release() |
2102 tr.release() |
2067 ui.status('%i changesets folded\n' % len(revs)) |
2103 ui.status('%i changesets folded\n' % len(revs)) |
2068 if repo.revs('. and %ld', revs): |
2104 if repo.revs('. and %ld', revs): |
2069 repo.dirstate.setparents(newid, node.nullid) |
2105 hg.update(repo, newid) |
2070 finally: |
2106 finally: |
2071 wlock.release() |
2107 lockmod.release(lock, wlock) |
2072 |
2108 |
2073 |
2109 |
2074 @eh.wrapcommand('graft') |
2110 @eh.wrapcommand('graft') |
2075 def graftwrapper(orig, ui, repo, *revs, **kwargs): |
2111 def graftwrapper(orig, ui, repo, *revs, **kwargs): |
2076 kwargs = dict(kwargs) |
2112 kwargs = dict(kwargs) |