hgext/evolve.py
changeset 573 5a0a01c4c7c2
parent 572 dc107acd0bd2
child 576 cf116ffc9cc5
equal deleted inserted replaced
498:53d7e3413337 573:5a0a01c4c7c2
     1 # states.py - introduce the state concept for mercurial changeset
       
     2 #
       
     3 # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
     1 # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
     4 #                Logilab SA        <contact@logilab.fr>
     2 #                Logilab SA        <contact@logilab.fr>
     5 #                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
     3 #                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
       
     4 #                Patrick Mezard <patrick@mezard.eu>
     6 #
     5 #
     7 # This software may be used and distributed according to the terms of the
     6 # This software may be used and distributed according to the terms of the
     8 # GNU General Public License version 2 or any later version.
     7 # GNU General Public License version 2 or any later version.
     9 
     8 
    10 '''Extends Mercurial feature related to Changeset Evolution
     9 '''Extends Mercurial feature related to Changeset Evolution
    11 
    10 
    12 This extension Provide several command tommutate history and deal with issue it may raise.
    11 This extension provides several commands to mutate history and deal with
       
    12 issues it may raise.
    13 
    13 
    14 It also:
    14 It also:
    15 
    15 
    16     - enable the "Changeset Obsolescence" feature of mercurial,
    16     - enables the "Changeset Obsolescence" feature of mercurial,
    17     - alter core command and extension that rewrite history to use this feature,
    17     - alters core commands and extensions that rewrite history to use
    18     - improve some aspect of the early implementation in 2.3
    18       this feature,
       
    19     - improves some aspect of the early implementation in 2.3
    19 '''
    20 '''
    20 
    21 
    21 import random
    22 import random
    22 
    23 
    23 from mercurial import util
    24 from mercurial import util
    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 #
   292 
   296 
   293 #####################################################################
   297 #####################################################################
   294 ### Obsolescence Caching Logic                                    ###
   298 ### Obsolescence Caching Logic                                    ###
   295 #####################################################################
   299 #####################################################################
   296 
   300 
       
   301 # IN CORE fb72eec7efd8
       
   302 
   297 # Obsolescence related logic can be very slow if we don't have efficient cache.
   303 # Obsolescence related logic can be very slow if we don't have efficient cache.
   298 #
   304 #
   299 # This section implements a cache mechanism that did not make it into core for
   305 # This section implements a cache mechanism that did not make it into core for
   300 # time reason. It store meaningful set of revision related to obsolescence
   306 # time reason. It stores meaningful set of revisions related to obsolescence
   301 # (obsolete, unstabletble ...
   307 # (obsolete, unstable, etc.)
   302 #
   308 #
   303 # Here is:
   309 # Here is:
   304 #
   310 #
   305 # - Computation of meaningful set,
   311 # - Computation of meaningful sets
   306 # - Cache access logic,
   312 # - Cache access logic,
   307 # - Cache invalidation logic,
   313 # - Cache invalidation logic,
   308 # - revset and ctx using this cache.
   314 # - revset and ctx using this cache.
   309 #
   315 #
   310 
   316 
   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 
   562             raise util.Abort(_("push includes a conflicting changeset: %s!")
   575             raise util.Abort(_("push includes a conflicting changeset: %s!")
   563                              % ctx)
   576                              % ctx)
   564     return orig(repo, remote, outgoing, *args, **kwargs)
   577     return orig(repo, remote, outgoing, *args, **kwargs)
   565 
   578 
   566 #####################################################################
   579 #####################################################################
   567 ### Filter extinct changeset from common operation                ###
   580 ### Filter extinct changesets from common operations              ###
   568 #####################################################################
   581 #####################################################################
   569 
   582 
   570 @eh.wrapfunction(merge, 'update')
   583 @eh.wrapfunction(merge, 'update')
   571 def wrapmergeupdate(orig, repo, node, *args, **kwargs):
   584 def wrapmergeupdate(orig, repo, node, *args, **kwargs):
   572     """ensure we don't automatically update on hidden changeset"""
   585     """ensure we don't automatically update on hidden changeset"""
   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
   673 def revsetlatecomer(repo, subset, x):
   698 def revsetlatecomer(repo, subset, x):
   674     """``troubled()``
   699     """``troubled()``
   675     Changesets with troubles.
   700     Changesets with troubles.
   676     """
   701     """
   677     _ = revset.getargs(x, 0, 0, 'troubled takes no arguments')
   702     _ = revset.getargs(x, 0, 0, 'troubled takes no arguments')
   678     return list(repo.revs('%ld and (unstable() + latecomer() + conflicting())',
   703     return repo.revs('%ld and (unstable() + latecomer() + conflicting())',
   679                           subset))
   704                      subset)
   680 
   705 
   681 
   706 
   682 ### Obsolescence graph
   707 ### Obsolescence graph
   683 
   708 
   684 # XXX SOME MAJOR CLEAN UP TO DO HERE XXX
   709 # XXX SOME MAJOR CLEAN UP TO DO HERE XXX
   785 #####################################################################
   810 #####################################################################
   786 
   811 
   787 # this section add several useful revset symbol not yet in core.
   812 # this section add several useful revset symbol not yet in core.
   788 # they are subject to changes
   813 # they are subject to changes
   789 
   814 
   790 ### hidden revset is not in core yet
   815 
   791 
   816 if 'hidden' not in revset.symbols:
   792 @eh.revset('hidden')
   817     # in 2.3+
   793 def revsethidden(repo, subset, x):
   818     @eh.revset('hidden')
   794     """``hidden()``
   819     def revsethidden(repo, subset, x):
   795     Changeset is hidden.
   820         """``hidden()``
   796     """
   821         Changeset is hidden.
   797     args = revset.getargs(x, 0, 0, 'hidden takes no argument')
   822         """
   798     return [r for r in subset if r in repo.hiddenrevs]
   823         args = revset.getargs(x, 0, 0, 'hidden takes no argument')
       
   824         return [r for r in subset if r in repo.hiddenrevs]
   799 
   825 
   800 ### XXX I'm not sure this revset is useful
   826 ### XXX I'm not sure this revset is useful
   801 @eh.revset('suspended')
   827 @eh.revset('suspended')
   802 def revsetsuspended(repo, subset, x):
   828 def revsetsuspended(repo, subset, x):
   803     """``suspended()``
   829     """``suspended()``
   891     """display warning is the command resulted in more instable changeset"""
   917     """display warning is the command resulted in more instable changeset"""
   892     priorunstables = len(repo.revs('unstable()'))
   918     priorunstables = len(repo.revs('unstable()'))
   893     priorlatecomers = len(repo.revs('latecomer()'))
   919     priorlatecomers = len(repo.revs('latecomer()'))
   894     priorconflictings = len(repo.revs('conflicting()'))
   920     priorconflictings = len(repo.revs('conflicting()'))
   895     ret = orig(ui, repo, *args, **kwargs)
   921     ret = orig(ui, repo, *args, **kwargs)
       
   922     # workaround phase stupidity
       
   923     phases._filterunknown(ui, repo.changelog, repo._phasecache.phaseroots)
   896     newunstables = len(repo.revs('unstable()')) - priorunstables
   924     newunstables = len(repo.revs('unstable()')) - priorunstables
   897     newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
   925     newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
   898     newconflictings = len(repo.revs('conflicting()')) - priorconflictings
   926     newconflictings = len(repo.revs('conflicting()')) - priorconflictings
   899     if newunstables > 0:
   927     if newunstables > 0:
   900         ui.warn(_('%i new unstable changesets\n') % newunstables)
   928         ui.warn(_('%i new unstable changesets\n') % newunstables)
   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
  1254     except util.Abort:
  1290     except util.Abort:
  1255         # Invalidate the previous setparents
  1291         # Invalidate the previous setparents
  1256         repo.dirstate.invalidate()
  1292         repo.dirstate.invalidate()
  1257         raise
  1293         raise
  1258 
  1294 
  1259 def _stabilizableunstable(repo, pctx):
       
  1260     """Return a changectx for an unstable changeset which can be
       
  1261     stabilized on top of pctx or one of its descendants. None if none
       
  1262     can be found.
       
  1263     """
       
  1264     def selfanddescendants(repo, pctx):
       
  1265         yield pctx
       
  1266         for ctx in pctx.descendants():
       
  1267             yield ctx
       
  1268 
       
  1269     # Look for an unstable which can be stabilized as a child of
       
  1270     # node. The unstable must be a child of one of node predecessors.
       
  1271     for ctx in selfanddescendants(repo, pctx):
       
  1272         unstables = list(repo.set('unstable() and children(allprecursors(%d))',
       
  1273                                   ctx.rev()))
       
  1274         if unstables:
       
  1275             return unstables[0]
       
  1276     return None
       
  1277 
       
  1278 def _bookmarksupdater(repo, oldid):
  1295 def _bookmarksupdater(repo, oldid):
  1279     """Return a callable update(newid) updating the current bookmark
  1296     """Return a callable update(newid) updating the current bookmark
  1280     and bookmarks bound to oldid to newid.
  1297     and bookmarks bound to oldid to newid.
  1281     """
  1298     """
  1282     bm = bookmarks.readcurrent(repo)
  1299     bm = bookmarks.readcurrent(repo)
  1333         if anyopt:
  1350         if anyopt:
  1334             raise util.Abort('can not specify both "--any" and "--continue"')
  1351             raise util.Abort('can not specify both "--any" and "--continue"')
  1335         graftcmd = commands.table['graft'][0]
  1352         graftcmd = commands.table['graft'][0]
  1336         return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
  1353         return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
  1337 
  1354 
  1338     troubled = list(repo.revs('troubled()'))
       
  1339     tr = _picknexttroubled(ui, repo, anyopt)
  1355     tr = _picknexttroubled(ui, repo, anyopt)
  1340     if tr is None:
  1356     if tr is None:
       
  1357         troubled = repo.revs('troubled()')
  1341         if troubled:
  1358         if troubled:
  1342             ui.write_err(_('nothing to evolve here\n'))
  1359             ui.write_err(_('nothing to evolve here\n'))
  1343             ui.status(_('(%i troubled changesets, do you want --any ?)\n')
  1360             ui.status(_('(%i troubled changesets, do you want --any ?)\n')
  1344                       % len(troubled))
  1361                       % len(troubled))
  1345             return 2
  1362             return 2
  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():
  1384     assert obs.obsolete()
  1419     assert obs.obsolete()
  1385     newer = newerversion(repo, obs.node())
  1420     newer = newerversion(repo, obs.node())
  1386     # search of a parent which is not killed
  1421     # search of a parent which is not killed
  1387     while newer == [()]:
  1422     while newer == [()]:
  1388         ui.debug("stabilize target %s is plain dead,"
  1423         ui.debug("stabilize target %s is plain dead,"
  1389                  " trying to stabilize on it's parent")
  1424                  " trying to stabilize on its parent")
  1390         obs = obs.parents()[0]
  1425         obs = obs.parents()[0]
  1391         newer = newerversion(repo, obs.node())
  1426         newer = newerversion(repo, obs.node())
  1392     if len(newer) > 1:
  1427     if len(newer) > 1:
  1393         ui.write_err(_("conflict rewriting. can't choose destination\n"))
  1428         ui.write_err(_("conflict rewriting. can't choose destination\n"))
  1394         return 2
  1429         return 2
  1404     if not ui.quiet:
  1439     if not ui.quiet:
  1405         displayer.show(orig)
  1440         displayer.show(orig)
  1406     repo.ui.status(_('atop:'))
  1441     repo.ui.status(_('atop:'))
  1407     if not ui.quiet:
  1442     if not ui.quiet:
  1408         displayer.show(target)
  1443         displayer.show(target)
  1409     todo = 'hg rebase -Dr %s -d %s\n' % (orig, target)
  1444     todo = 'hg rebase -r %s -d %s\n' % (orig, target)
  1410     if dryrun:
  1445     if dryrun:
  1411         repo.ui.write(todo)
  1446         repo.ui.write(todo)
  1412     else:
  1447     else:
  1413         repo.ui.note(todo)
  1448         repo.ui.note(todo)
  1414         lock = repo.lock()
  1449         lock = repo.lock()
  1440         displayer.show(latecomer)
  1475         displayer.show(latecomer)
  1441     repo.ui.status(_('atop:'))
  1476     repo.ui.status(_('atop:'))
  1442     if not ui.quiet:
  1477     if not ui.quiet:
  1443         displayer.show(prec)
  1478         displayer.show(prec)
  1444     if dryrun:
  1479     if dryrun:
  1445         todo = 'hg rebase --rev %s --detach %s;\n' % (latecomer, prec.p1())
  1480         todo = 'hg rebase --rev %s --dest %s;\n' % (latecomer, prec.p1())
  1446         repo.ui.write(todo)
  1481         repo.ui.write(todo)
  1447         repo.ui.write('hg update %s;\n' % prec)
  1482         repo.ui.write('hg update %s;\n' % prec)
  1448         repo.ui.write('hg revert --all --rev %s;\n' % latecomer)
  1483         repo.ui.write('hg revert --all --rev %s;\n' % latecomer)
  1449         repo.ui.write('hg commit --msg "latecomer update to %s"')
  1484         repo.ui.write('hg commit --msg "latecomer update to %s"')
  1450         return 0
  1485         return 0
  1532     if conflicting.phase() <= phases.public:
  1567     if conflicting.phase() <= phases.public:
  1533         raise util.Abort("We can't resolve this conflict from the public side")
  1568         raise util.Abort("We can't resolve this conflict from the public side")
  1534     if len(other.parents()) > 1:
  1569     if len(other.parents()) > 1:
  1535         raise util.Abort("conflicting changeset can't be a merge (yet)")
  1570         raise util.Abort("conflicting changeset can't be a merge (yet)")
  1536     if other.p1() not in conflicting.parents():
  1571     if other.p1() not in conflicting.parents():
  1537         raise util.Abort("parent are not common (not handled yet)")
  1572         raise util.Abort("parents are not common (not handled yet)")
  1538 
  1573 
  1539     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1574     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1540     ui.status(_('merge:'))
  1575     ui.status(_('merge:'))
  1541     if not ui.quiet:
  1576     if not ui.quiet:
  1542         displayer.show(conflicting)
  1577         displayer.show(conflicting)
  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 
  1685     try:
  1723     try:
  1686         lock = repo.lock()
  1724         lock = repo.lock()
  1687         try:
  1725         try:
  1688             new = set(noderange(repo, opts['new']))
  1726             new = set(noderange(repo, opts['new']))
  1689             targetnodes = set(noderange(repo, revs))
  1727             targetnodes = set(noderange(repo, revs))
       
  1728             if not targetnodes:
       
  1729                 raise util.Abort('nothing to prune')
  1690             if new:
  1730             if new:
  1691                 sucs = tuple(repo[n] for n in new)
  1731                 sucs = tuple(repo[n] for n in new)
  1692             else:
  1732             else:
  1693                 sucs = ()
  1733                 sucs = ()
  1694             markers = []
  1734             markers = []
  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)
  2101     try:
  2137     try:
  2102         rebase = extensions.find('rebase')
  2138         rebase = extensions.find('rebase')
  2103     except KeyError:
  2139     except KeyError:
  2104         raise error.Abort(_('evolution extension requires rebase extension.'))
  2140         raise error.Abort(_('evolution extension requires rebase extension.'))
  2105 
  2141 
  2106     for cmd in ['amend', 'kill', 'uncommit']:
  2142     for cmd in ['amend', 'kill', 'uncommit', 'touch', 'fold']:
  2107         entry = extensions.wrapcommand(cmdtable, cmd,
  2143         entry = extensions.wrapcommand(cmdtable, cmd,
  2108                                        warnobserrors)
  2144                                        warnobserrors)
  2109 
  2145 
  2110     entry = cmdutil.findcmd('commit', commands.table)[1]
  2146     entry = cmdutil.findcmd('commit', commands.table)[1]
  2111     entry[1].append(('o', 'obsolete', [],
  2147     entry[1].append(('o', 'obsolete', [],