hgext/evolve.py
branchmercurial-3.9
changeset 2110 f1ffd093ef30
parent 1816 bb665c99562a
parent 2109 90ab79764ce4
child 2111 ec04eb4d2c6e
child 2261 3e339f6717c7
equal deleted inserted replaced
1816:bb665c99562a 2110:f1ffd093ef30
     1 # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
       
     2 #                Logilab SA        <contact@logilab.fr>
       
     3 #                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
       
     4 #                Patrick Mezard <patrick@mezard.eu>
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 '''extends Mercurial feature related to Changeset Evolution
       
    10 
       
    11 This extension provides several commands to mutate history and deal with
       
    12 resulting issues.
       
    13 
       
    14 It also:
       
    15 
       
    16     - enables the "Changeset Obsolescence" feature of Mercurial,
       
    17     - alters core commands and extensions that rewrite history to use
       
    18       this feature,
       
    19     - improves some aspect of the early implementation in Mercurial core
       
    20 '''
       
    21 
       
    22 __version__ = '5.6.0'
       
    23 testedwith = '3.4.3 3.5.2 3.6.2 3.7.3 3.8.1 3.9 4.0 4.1'
       
    24 buglink = 'https://bz.mercurial-scm.org/'
       
    25 
       
    26 
       
    27 evolutionhelptext = """
       
    28 Obsolescence markers make it possible to mark changesets that have been
       
    29 deleted or superset in a new version of the changeset.
       
    30 
       
    31 Unlike the previous way of handling such changes, by stripping the old
       
    32 changesets from the repository, obsolescence markers can be propagated
       
    33 between repositories. This allows for a safe and simple way of exchanging
       
    34 mutable history and altering it after the fact. Changeset phases are
       
    35 respected, such that only draft and secret changesets can be altered (see
       
    36 :hg:`help phases` for details).
       
    37 
       
    38 Obsolescence is tracked using "obsolete markers", a piece of metadata
       
    39 tracking which changesets have been made obsolete, potential successors for
       
    40 a given changeset, the moment the changeset was marked as obsolete, and the
       
    41 user who performed the rewriting operation. The markers are stored
       
    42 separately from standard changeset data can be exchanged without any of the
       
    43 precursor changesets, preventing unnecessary exchange of obsolescence data.
       
    44 
       
    45 The complete set of obsolescence markers describes a history of changeset
       
    46 modifications that is orthogonal to the repository history of file
       
    47 modifications. This changeset history allows for detection and automatic
       
    48 resolution of edge cases arising from multiple users rewriting the same part
       
    49 of history concurrently.
       
    50 
       
    51 Current feature status
       
    52 ======================
       
    53 
       
    54 This feature is still in development.  If you see this help, you have enabled an
       
    55 extension that turned this feature on.
       
    56 
       
    57 Obsolescence markers will be exchanged between repositories that explicitly
       
    58 assert support for the obsolescence feature (this can currently only be done
       
    59 via an extension).""".strip()
       
    60 
       
    61 
       
    62 import sys, os
       
    63 import random
       
    64 try:
       
    65     import StringIO as io
       
    66     StringIO = io.StringIO
       
    67 except ImportError:
       
    68     import io
       
    69     StringIO = io.StringIO
       
    70 import re
       
    71 import collections
       
    72 import socket
       
    73 import errno
       
    74 import hashlib
       
    75 import struct
       
    76 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
       
    77 
       
    78 import mercurial
       
    79 from mercurial import util
       
    80 from mercurial import repair
       
    81 
       
    82 try:
       
    83     from mercurial import obsolete
       
    84     if not obsolete._enabled:
       
    85         obsolete._enabled = True
       
    86     from mercurial import wireproto
       
    87     gboptslist = getattr(wireproto, 'gboptslist', None)
       
    88     gboptsmap = getattr(wireproto, 'gboptsmap', None)
       
    89 except (ImportError, AttributeError):
       
    90     gboptslist = gboptsmap = None
       
    91 
       
    92 # Flags for enabling optional parts of evolve
       
    93 commandopt = 'allnewcommands'
       
    94 
       
    95 from mercurial import bookmarks as bookmarksmod
       
    96 from mercurial import cmdutil
       
    97 from mercurial import commands
       
    98 from mercurial import context
       
    99 from mercurial import copies
       
   100 from mercurial import error
       
   101 from mercurial import exchange
       
   102 from mercurial import extensions
       
   103 from mercurial import help
       
   104 from mercurial import httppeer
       
   105 from mercurial import hg
       
   106 from mercurial import lock as lockmod
       
   107 from mercurial import merge
       
   108 from mercurial import node
       
   109 from mercurial import phases
       
   110 from mercurial import patch
       
   111 from mercurial import revset
       
   112 from mercurial import scmutil
       
   113 from mercurial import templatekw
       
   114 from mercurial.i18n import _
       
   115 from mercurial.commands import walkopts, commitopts, commitopts2, mergetoolopts
       
   116 from mercurial.node import nullid
       
   117 from mercurial import wireproto
       
   118 from mercurial import localrepo
       
   119 from mercurial.hgweb import hgweb_mod
       
   120 
       
   121 cmdtable = {}
       
   122 command = cmdutil.command(cmdtable)
       
   123 
       
   124 _pack = struct.pack
       
   125 _unpack = struct.unpack
       
   126 
       
   127 if gboptsmap is not None:
       
   128     memfilectx = context.memfilectx
       
   129 elif gboptslist is not None:
       
   130     oldmemfilectx = context.memfilectx
       
   131     def memfilectx(repo, *args, **kwargs):
       
   132         return oldmemfilectx(*args, **kwargs)
       
   133 else:
       
   134     raise ImportError('evolve needs version %s or above' %
       
   135                       min(testedwith.split()))
       
   136 
       
   137 aliases, entry = cmdutil.findcmd('commit', commands.table)
       
   138 hasinteractivemode = any(['interactive' in e for e in entry[1]])
       
   139 if hasinteractivemode:
       
   140     interactiveopt = [['i', 'interactive', None, _('use interactive mode')]]
       
   141 else:
       
   142     interactiveopt = []
       
   143 # This extension contains the following code
       
   144 #
       
   145 # - Extension Helper code
       
   146 # - Obsolescence cache
       
   147 # - ...
       
   148 # - Older format compat
       
   149 
       
   150 
       
   151 #####################################################################
       
   152 ### Extension helper                                              ###
       
   153 #####################################################################
       
   154 
       
   155 class exthelper(object):
       
   156     """Helper for modular extension setup
       
   157 
       
   158     A single helper should be instantiated for each extension. Helper
       
   159     methods are then used as decorators for various purpose.
       
   160 
       
   161     All decorators return the original function and may be chained.
       
   162     """
       
   163 
       
   164     def __init__(self):
       
   165         self._uicallables = []
       
   166         self._extcallables = []
       
   167         self._repocallables = []
       
   168         self._revsetsymbols = []
       
   169         self._templatekws = []
       
   170         self._commandwrappers = []
       
   171         self._extcommandwrappers = []
       
   172         self._functionwrappers = []
       
   173         self._duckpunchers = []
       
   174 
       
   175     def final_uisetup(self, ui):
       
   176         """Method to be used as the extension uisetup
       
   177 
       
   178         The following operations belong here:
       
   179 
       
   180         - Changes to ui.__class__ . The ui object that will be used to run the
       
   181           command has not yet been created. Changes made here will affect ui
       
   182           objects created after this, and in particular the ui that will be
       
   183           passed to runcommand
       
   184         - Command wraps (extensions.wrapcommand)
       
   185         - Changes that need to be visible to other extensions: because
       
   186           initialization occurs in phases (all extensions run uisetup, then all
       
   187           run extsetup), a change made here will be visible to other extensions
       
   188           during extsetup
       
   189         - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch
       
   190           module members
       
   191         - Setup of pre-* and post-* hooks
       
   192         - pushkey setup
       
   193         """
       
   194         for cont, funcname, func in self._duckpunchers:
       
   195             setattr(cont, funcname, func)
       
   196         for command, wrapper, opts in self._commandwrappers:
       
   197             entry = extensions.wrapcommand(commands.table, command, wrapper)
       
   198             if opts:
       
   199                 for short, long, val, msg in opts:
       
   200                     entry[1].append((short, long, val, msg))
       
   201         for cont, funcname, wrapper in self._functionwrappers:
       
   202             extensions.wrapfunction(cont, funcname, wrapper)
       
   203         for c in self._uicallables:
       
   204             c(ui)
       
   205 
       
   206     def final_extsetup(self, ui):
       
   207         """Method to be used as a the extension extsetup
       
   208 
       
   209         The following operations belong here:
       
   210 
       
   211         - Changes depending on the status of other extensions. (if
       
   212           extensions.find('mq'))
       
   213         - Add a global option to all commands
       
   214         - Register revset functions
       
   215         """
       
   216         knownexts = {}
       
   217         for name, symbol in self._revsetsymbols:
       
   218             revset.symbols[name] = symbol
       
   219         for name, kw in self._templatekws:
       
   220             templatekw.keywords[name] = kw
       
   221         for ext, command, wrapper, opts in self._extcommandwrappers:
       
   222             if ext not in knownexts:
       
   223                 try:
       
   224                     e = extensions.find(ext)
       
   225                 except KeyError:
       
   226                     # Extension isn't enabled, so don't bother trying to wrap
       
   227                     # it.
       
   228                     continue
       
   229                 knownexts[ext] = e.cmdtable
       
   230             entry = extensions.wrapcommand(knownexts[ext], command, wrapper)
       
   231             if opts:
       
   232                 for short, long, val, msg in opts:
       
   233                     entry[1].append((short, long, val, msg))
       
   234 
       
   235         for c in self._extcallables:
       
   236             c(ui)
       
   237 
       
   238     def final_reposetup(self, ui, repo):
       
   239         """Method to be used as the extension reposetup
       
   240 
       
   241         The following operations belong here:
       
   242 
       
   243         - All hooks but pre-* and post-*
       
   244         - Modify configuration variables
       
   245         - Changes to repo.__class__, repo.dirstate.__class__
       
   246         """
       
   247         for c in self._repocallables:
       
   248             c(ui, repo)
       
   249 
       
   250     def uisetup(self, call):
       
   251         """Decorated function will be executed during uisetup
       
   252 
       
   253         example::
       
   254 
       
   255             @eh.uisetup
       
   256             def setupbabar(ui):
       
   257                 print 'this is uisetup!'
       
   258         """
       
   259         self._uicallables.append(call)
       
   260         return call
       
   261 
       
   262     def extsetup(self, call):
       
   263         """Decorated function will be executed during extsetup
       
   264 
       
   265         example::
       
   266 
       
   267             @eh.extsetup
       
   268             def setupcelestine(ui):
       
   269                 print 'this is extsetup!'
       
   270         """
       
   271         self._extcallables.append(call)
       
   272         return call
       
   273 
       
   274     def reposetup(self, call):
       
   275         """Decorated function will be executed during reposetup
       
   276 
       
   277         example::
       
   278 
       
   279             @eh.reposetup
       
   280             def setupzephir(ui, repo):
       
   281                 print 'this is reposetup!'
       
   282         """
       
   283         self._repocallables.append(call)
       
   284         return call
       
   285 
       
   286     def revset(self, symbolname):
       
   287         """Decorated function is a revset symbol
       
   288 
       
   289         The name of the symbol must be given as the decorator argument.
       
   290         The symbol is added during `extsetup`.
       
   291 
       
   292         example::
       
   293 
       
   294             @eh.revset('hidden')
       
   295             def revsetbabar(repo, subset, x):
       
   296                 args = revset.getargs(x, 0, 0, 'babar accept no argument')
       
   297                 return [r for r in subset if 'babar' in repo[r].description()]
       
   298         """
       
   299         def dec(symbol):
       
   300             self._revsetsymbols.append((symbolname, symbol))
       
   301             return symbol
       
   302         return dec
       
   303 
       
   304 
       
   305     def templatekw(self, keywordname):
       
   306         """Decorated function is a template keyword
       
   307 
       
   308         The name of the keyword must be given as the decorator argument.
       
   309         The symbol is added during `extsetup`.
       
   310 
       
   311         example::
       
   312 
       
   313             @eh.templatekw('babar')
       
   314             def kwbabar(ctx):
       
   315                 return 'babar'
       
   316         """
       
   317         def dec(keyword):
       
   318             self._templatekws.append((keywordname, keyword))
       
   319             return keyword
       
   320         return dec
       
   321 
       
   322     def wrapcommand(self, command, extension=None, opts=[]):
       
   323         """Decorated function is a command wrapper
       
   324 
       
   325         The name of the command must be given as the decorator argument.
       
   326         The wrapping is installed during `uisetup`.
       
   327 
       
   328         If the second option `extension` argument is provided, the wrapping
       
   329         will be applied in the extension commandtable. This argument must be a
       
   330         string that will be searched using `extension.find` if not found and
       
   331         Abort error is raised. If the wrapping applies to an extension, it is
       
   332         installed during `extsetup`.
       
   333 
       
   334         example::
       
   335 
       
   336             @eh.wrapcommand('summary')
       
   337             def wrapsummary(orig, ui, repo, *args, **kwargs):
       
   338                 ui.note('Barry!')
       
   339                 return orig(ui, repo, *args, **kwargs)
       
   340 
       
   341         The `opts` argument allows specifying additional arguments for the
       
   342         command.
       
   343 
       
   344         """
       
   345         def dec(wrapper):
       
   346             if extension is None:
       
   347                 self._commandwrappers.append((command, wrapper, opts))
       
   348             else:
       
   349                 self._extcommandwrappers.append((extension, command, wrapper,
       
   350                     opts))
       
   351             return wrapper
       
   352         return dec
       
   353 
       
   354     def wrapfunction(self, container, funcname):
       
   355         """Decorated function is a function wrapper
       
   356 
       
   357         This function takes two arguments, the container and the name of the
       
   358         function to wrap. The wrapping is performed during `uisetup`.
       
   359         (there is no extension support)
       
   360 
       
   361         example::
       
   362 
       
   363             @eh.function(discovery, 'checkheads')
       
   364             def wrapfunction(orig, *args, **kwargs):
       
   365                 ui.note('His head smashed in and his heart cut out')
       
   366                 return orig(*args, **kwargs)
       
   367         """
       
   368         def dec(wrapper):
       
   369             self._functionwrappers.append((container, funcname, wrapper))
       
   370             return wrapper
       
   371         return dec
       
   372 
       
   373     def addattr(self, container, funcname):
       
   374         """Decorated function is to be added to the container
       
   375 
       
   376         This function takes two arguments, the container and the name of the
       
   377         function to wrap. The wrapping is performed during `uisetup`.
       
   378 
       
   379         example::
       
   380 
       
   381             @eh.function(context.changectx, 'babar')
       
   382             def babar(ctx):
       
   383                 return 'babar' in ctx.description
       
   384         """
       
   385         def dec(func):
       
   386             self._duckpunchers.append((container, funcname, func))
       
   387             return func
       
   388         return dec
       
   389 
       
   390 eh = exthelper()
       
   391 uisetup = eh.final_uisetup
       
   392 extsetup = eh.final_extsetup
       
   393 reposetup = eh.final_reposetup
       
   394 
       
   395 #####################################################################
       
   396 ### Option configuration                                          ###
       
   397 #####################################################################
       
   398 
       
   399 @eh.reposetup # must be the first of its kin.
       
   400 def _configureoptions(ui, repo):
       
   401     # If no capabilities are specified, enable everything.
       
   402     # This is so existing evolve users don't need to change their config.
       
   403     evolveopts = ui.configlist('experimental', 'evolution')
       
   404     if not evolveopts:
       
   405         evolveopts = ['all']
       
   406         ui.setconfig('experimental', 'evolution', evolveopts, 'evolve')
       
   407 
       
   408 @eh.uisetup
       
   409 def _configurecmdoptions(ui):
       
   410     # Unregister evolve commands if the command capability is not specified.
       
   411     #
       
   412     # This must be in the same function as the option configuration above to
       
   413     # guarantee it happens after the above configuration, but before the
       
   414     # extsetup functions.
       
   415     evolvecommands = ui.configlist('experimental', 'evolutioncommands')
       
   416     evolveopts = ui.configlist('experimental', 'evolution')
       
   417     if evolveopts and (commandopt not in evolveopts and
       
   418                        'all' not in evolveopts):
       
   419         # We build whitelist containing the commands we want to enable
       
   420         whitelist = set()
       
   421         for cmd in evolvecommands:
       
   422             matchingevolvecommands = [e for e in cmdtable.keys() if cmd in e]
       
   423             if not matchingevolvecommands:
       
   424                 raise error.Abort(_('unknown command: %s') % cmd)
       
   425             elif len(matchingevolvecommands) > 1:
       
   426                 msg = _('ambiguous command specification: "%s" matches %r')
       
   427                 raise error.Abort(msg % (cmd, matchingevolvecommands))
       
   428             else:
       
   429                 whitelist.add(matchingevolvecommands[0])
       
   430         for disabledcmd in set(cmdtable) - whitelist:
       
   431             del cmdtable[disabledcmd]
       
   432 
       
   433 #####################################################################
       
   434 ### experimental behavior                                         ###
       
   435 #####################################################################
       
   436 
       
   437 commitopts3 = [
       
   438     ('D', 'current-date', None,
       
   439      _('record the current date as commit date')),
       
   440     ('U', 'current-user', None,
       
   441      _('record the current user as committer')),
       
   442 ]
       
   443 
       
   444 def _resolveoptions(ui, opts):
       
   445     """modify commit options dict to handle related options
       
   446 
       
   447     For now, all it does is figure out the commit date: respect -D unless
       
   448     -d was supplied.
       
   449     """
       
   450     # N.B. this is extremely similar to setupheaderopts() in mq.py
       
   451     if not opts.get('date') and opts.get('current_date'):
       
   452         opts['date'] = '%d %d' % util.makedate()
       
   453     if not opts.get('user') and opts.get('current_user'):
       
   454         opts['user'] = ui.username()
       
   455 
       
   456 getrevs = obsolete.getrevs
       
   457 
       
   458 #####################################################################
       
   459 ### Additional Utilities                                          ###
       
   460 #####################################################################
       
   461 
       
   462 # This section contains a lot of small utility function and method
       
   463 
       
   464 # - Function to create markers
       
   465 # - useful alias pstatus and pdiff (should probably go in evolve)
       
   466 # - "troubles" method on changectx
       
   467 # - function to travel through the obsolescence graph
       
   468 # - function to find useful changeset to stabilize
       
   469 
       
   470 
       
   471 ### Useful alias
       
   472 
       
   473 @eh.uisetup
       
   474 def _installalias(ui):
       
   475     if ui.config('alias', 'pstatus', None) is None:
       
   476         ui.setconfig('alias', 'pstatus', 'status --rev .^', 'evolve')
       
   477     if ui.config('alias', 'pdiff', None) is None:
       
   478         ui.setconfig('alias', 'pdiff', 'diff --rev .^', 'evolve')
       
   479     if ui.config('alias', 'olog', None) is None:
       
   480         ui.setconfig('alias', 'olog', "log -r 'precursors(.)' --hidden",
       
   481                      'evolve')
       
   482     if ui.config('alias', 'odiff', None) is None:
       
   483         ui.setconfig('alias', 'odiff',
       
   484             "diff --hidden --rev 'limit(precursors(.),1)' --rev .",
       
   485             'evolve')
       
   486     if ui.config('alias', 'grab', None) is None:
       
   487         if os.name == 'nt':
       
   488             ui.setconfig('alias', 'grab',
       
   489                 "! " + util.hgexecutable() + " rebase --dest . --rev $@ && "
       
   490                  + util.hgexecutable() + " up tip",
       
   491                          'evolve')
       
   492         else:
       
   493             ui.setconfig('alias', 'grab',
       
   494                 "! $HG rebase --dest . --rev $@ && $HG up tip",
       
   495                          'evolve')
       
   496 
       
   497 
       
   498 ### Troubled revset symbol
       
   499 
       
   500 @eh.revset('troubled')
       
   501 def revsettroubled(repo, subset, x):
       
   502     """``troubled()``
       
   503     Changesets with troubles.
       
   504     """
       
   505     revset.getargs(x, 0, 0, 'troubled takes no arguments')
       
   506     troubled = set()
       
   507     troubled.update(getrevs(repo, 'unstable'))
       
   508     troubled.update(getrevs(repo, 'bumped'))
       
   509     troubled.update(getrevs(repo, 'divergent'))
       
   510     troubled = revset.baseset(troubled)
       
   511     troubled.sort() # set is non-ordered, enforce order
       
   512     return subset & troubled
       
   513 
       
   514 ### Obsolescence graph
       
   515 
       
   516 # XXX SOME MAJOR CLEAN UP TO DO HERE XXX
       
   517 
       
   518 def _precursors(repo, s):
       
   519     """Precursor of a changeset"""
       
   520     cs = set()
       
   521     nm = repo.changelog.nodemap
       
   522     markerbysubj = repo.obsstore.precursors
       
   523     node = repo.changelog.node
       
   524     for r in s:
       
   525         for p in markerbysubj.get(node(r), ()):
       
   526             pr = nm.get(p[0])
       
   527             if pr is not None:
       
   528                 cs.add(pr)
       
   529     cs -= repo.changelog.filteredrevs # nodemap has no filtering
       
   530     return cs
       
   531 
       
   532 def _allprecursors(repo, s):  # XXX we need a better naming
       
   533     """transitive precursors of a subset"""
       
   534     node = repo.changelog.node
       
   535     toproceed = [node(r) for r in s]
       
   536     seen = set()
       
   537     allsubjects = repo.obsstore.precursors
       
   538     while toproceed:
       
   539         nc = toproceed.pop()
       
   540         for mark in allsubjects.get(nc, ()):
       
   541             np = mark[0]
       
   542             if np not in seen:
       
   543                 seen.add(np)
       
   544                 toproceed.append(np)
       
   545     nm = repo.changelog.nodemap
       
   546     cs = set()
       
   547     for p in seen:
       
   548         pr = nm.get(p)
       
   549         if pr is not None:
       
   550             cs.add(pr)
       
   551     cs -= repo.changelog.filteredrevs # nodemap has no filtering
       
   552     return cs
       
   553 
       
   554 def _successors(repo, s):
       
   555     """Successors of a changeset"""
       
   556     cs = set()
       
   557     node = repo.changelog.node
       
   558     nm = repo.changelog.nodemap
       
   559     markerbyobj = repo.obsstore.successors
       
   560     for r in s:
       
   561         for p in markerbyobj.get(node(r), ()):
       
   562             for sub in p[1]:
       
   563                 sr = nm.get(sub)
       
   564                 if sr is not None:
       
   565                     cs.add(sr)
       
   566     cs -= repo.changelog.filteredrevs # nodemap has no filtering
       
   567     return cs
       
   568 
       
   569 def _allsuccessors(repo, s, haltonflags=0):  # XXX we need a better naming
       
   570     """transitive successors of a subset
       
   571 
       
   572     haltonflags allows to provide flags which prevent the evaluation of a
       
   573     marker.  """
       
   574     node = repo.changelog.node
       
   575     toproceed = [node(r) for r in s]
       
   576     seen = set()
       
   577     allobjects = repo.obsstore.successors
       
   578     while toproceed:
       
   579         nc = toproceed.pop()
       
   580         for mark in allobjects.get(nc, ()):
       
   581             if mark[2] & haltonflags:
       
   582                 continue
       
   583             for sub in mark[1]:
       
   584                 if sub == nullid:
       
   585                     continue # should not be here!
       
   586                 if sub not in seen:
       
   587                     seen.add(sub)
       
   588                     toproceed.append(sub)
       
   589     nm = repo.changelog.nodemap
       
   590     cs = set()
       
   591     for s in seen:
       
   592         sr = nm.get(s)
       
   593         if sr is not None:
       
   594             cs.add(sr)
       
   595     cs -= repo.changelog.filteredrevs # nodemap has no filtering
       
   596     return cs
       
   597 
       
   598 
       
   599 
       
   600 
       
   601 #####################################################################
       
   602 ### Extending revset and template                                 ###
       
   603 #####################################################################
       
   604 
       
   605 # this section add several useful revset symbol not yet in core.
       
   606 # they are subject to changes
       
   607 
       
   608 
       
   609 ### XXX I'm not sure this revset is useful
       
   610 @eh.revset('suspended')
       
   611 def revsetsuspended(repo, subset, x):
       
   612     """``suspended()``
       
   613     Obsolete changesets with non-obsolete descendants.
       
   614     """
       
   615     revset.getargs(x, 0, 0, 'suspended takes no arguments')
       
   616     suspended = revset.baseset(getrevs(repo, 'suspended'))
       
   617     suspended.sort()
       
   618     return subset & suspended
       
   619 
       
   620 
       
   621 @eh.revset('precursors')
       
   622 def revsetprecursors(repo, subset, x):
       
   623     """``precursors(set)``
       
   624     Immediate precursors of changesets in set.
       
   625     """
       
   626     s = revset.getset(repo, revset.fullreposet(repo), x)
       
   627     s = revset.baseset(_precursors(repo, s))
       
   628     s.sort()
       
   629     return subset & s
       
   630 
       
   631 
       
   632 @eh.revset('allprecursors')
       
   633 def revsetallprecursors(repo, subset, x):
       
   634     """``allprecursors(set)``
       
   635     Transitive precursors of changesets in set.
       
   636     """
       
   637     s = revset.getset(repo, revset.fullreposet(repo), x)
       
   638     s = revset.baseset(_allprecursors(repo, s))
       
   639     s.sort()
       
   640     return subset & s
       
   641 
       
   642 
       
   643 @eh.revset('successors')
       
   644 def revsetsuccessors(repo, subset, x):
       
   645     """``successors(set)``
       
   646     Immediate successors of changesets in set.
       
   647     """
       
   648     s = revset.getset(repo, revset.fullreposet(repo), x)
       
   649     s = revset.baseset(_successors(repo, s))
       
   650     s.sort()
       
   651     return subset & s
       
   652 
       
   653 @eh.revset('allsuccessors')
       
   654 def revsetallsuccessors(repo, subset, x):
       
   655     """``allsuccessors(set)``
       
   656     Transitive successors of changesets in set.
       
   657     """
       
   658     s = revset.getset(repo, revset.fullreposet(repo), x)
       
   659     s = revset.baseset(_allsuccessors(repo, s))
       
   660     s.sort()
       
   661     return subset & s
       
   662 
       
   663 ### template keywords
       
   664 # XXX it does not handle troubles well :-/
       
   665 
       
   666 @eh.templatekw('obsolete')
       
   667 def obsoletekw(repo, ctx, templ, **args):
       
   668     """:obsolete: String. Whether the changeset is ``obsolete``.
       
   669     """
       
   670     if ctx.obsolete():
       
   671         return 'obsolete'
       
   672     return ''
       
   673 
       
   674 @eh.templatekw('troubles')
       
   675 def showtroubles(repo, ctx, **args):
       
   676     """:troubles: List of strings. Evolution troubles affecting the changeset
       
   677     (zero or more of "unstable", "divergent" or "bumped")."""
       
   678     return templatekw.showlist('trouble', ctx.troubles(), plural='troubles',
       
   679                                **args)
       
   680 
       
   681 #####################################################################
       
   682 ### Various trouble warning                                       ###
       
   683 #####################################################################
       
   684 
       
   685 # This section take care of issue warning to the user when troubles appear
       
   686 
       
   687 
       
   688 def _warnobsoletewc(ui, repo):
       
   689     if repo['.'].obsolete():
       
   690         ui.warn(_('working directory parent is obsolete!\n'))
       
   691         if (not ui.quiet) and obsolete.isenabled(repo, commandopt):
       
   692             ui.warn(_("(use 'hg evolve' to update to its successor)\n"))
       
   693 
       
   694 @eh.wrapcommand("update")
       
   695 @eh.wrapcommand("pull")
       
   696 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
       
   697     """Warn that the working directory parent is an obsolete changeset"""
       
   698     def warnobsolete():
       
   699         _warnobsoletewc(ui, repo)
       
   700     wlock = None
       
   701     try:
       
   702         wlock = repo.wlock()
       
   703         repo._afterlock(warnobsolete)
       
   704         res = origfn(ui, repo, *args, **opts)
       
   705     finally:
       
   706         lockmod.release(wlock)
       
   707     return res
       
   708 
       
   709 @eh.wrapcommand("parents")
       
   710 def wrapparents(origfn, ui, repo, *args, **opts):
       
   711     res = origfn(ui, repo, *args, **opts)
       
   712     _warnobsoletewc(ui, repo)
       
   713     return res
       
   714 
       
   715 # XXX this could wrap transaction code
       
   716 # XXX (but this is a bit a layer violation)
       
   717 @eh.wrapcommand("commit")
       
   718 @eh.wrapcommand("import")
       
   719 @eh.wrapcommand("push")
       
   720 @eh.wrapcommand("pull")
       
   721 @eh.wrapcommand("graft")
       
   722 @eh.wrapcommand("phase")
       
   723 @eh.wrapcommand("unbundle")
       
   724 def warnobserrors(orig, ui, repo, *args, **kwargs):
       
   725     """display warning is the command resulted in more instable changeset"""
       
   726     # part of the troubled stuff may be filtered (stash ?)
       
   727     # This needs a better implementation but will probably wait for core.
       
   728     filtered = repo.changelog.filteredrevs
       
   729     priorunstables = len(set(getrevs(repo, 'unstable')) - filtered)
       
   730     priorbumpeds = len(set(getrevs(repo, 'bumped')) - filtered)
       
   731     priordivergents = len(set(getrevs(repo, 'divergent')) - filtered)
       
   732     ret = orig(ui, repo, *args, **kwargs)
       
   733     # workaround phase stupidity
       
   734     #phases._filterunknown(ui, repo.changelog, repo._phasecache.phaseroots)
       
   735     filtered = repo.changelog.filteredrevs
       
   736     newunstables = \
       
   737         len(set(getrevs(repo, 'unstable')) - filtered) - priorunstables
       
   738     newbumpeds = \
       
   739         len(set(getrevs(repo, 'bumped')) - filtered) - priorbumpeds
       
   740     newdivergents = \
       
   741         len(set(getrevs(repo, 'divergent')) - filtered) - priordivergents
       
   742     if newunstables > 0:
       
   743         ui.warn(_('%i new unstable changesets\n') % newunstables)
       
   744     if newbumpeds > 0:
       
   745         ui.warn(_('%i new bumped changesets\n') % newbumpeds)
       
   746     if newdivergents > 0:
       
   747         ui.warn(_('%i new divergent changesets\n') % newdivergents)
       
   748     return ret
       
   749 
       
   750 @eh.wrapfunction(mercurial.exchange, 'push')
       
   751 def push(orig, repo, *args, **opts):
       
   752     """Add a hint for "hg evolve" when troubles make push fails
       
   753     """
       
   754     try:
       
   755         return orig(repo, *args, **opts)
       
   756     except error.Abort as ex:
       
   757         hint = _("use 'hg evolve' to get a stable history "
       
   758                  "or --force to ignore warnings")
       
   759         if (len(ex.args) >= 1
       
   760             and ex.args[0].startswith('push includes ')
       
   761             and ex.hint is None):
       
   762             ex.hint = hint
       
   763         raise
       
   764 
       
   765 def summaryhook(ui, repo):
       
   766     def write(fmt, count):
       
   767         s = fmt % count
       
   768         if count:
       
   769             ui.write(s)
       
   770         else:
       
   771             ui.note(s)
       
   772 
       
   773     # util.versiontuple was introduced in 3.6.2
       
   774     if not util.safehasattr(util, 'versiontuple'):
       
   775         nbunstable = len(getrevs(repo, 'unstable'))
       
   776         nbbumped = len(getrevs(repo, 'bumped'))
       
   777         nbdivergent = len(getrevs(repo, 'divergent'))
       
   778         write('unstable: %i changesets\n', nbunstable)
       
   779         write('bumped: %i changesets\n', nbbumped)
       
   780         write('divergent: %i changesets\n', nbdivergent)
       
   781     else:
       
   782         # In 3.6.2, summary in core gained this feature, no need to display it
       
   783         pass
       
   784     state = _evolvestateread(repo)
       
   785     if state is not None:
       
   786         # i18n: column positioning for "hg summary"
       
   787         ui.write(_('evolve: (evolve --continue)\n'))
       
   788 
       
   789 @eh.extsetup
       
   790 def obssummarysetup(ui):
       
   791     cmdutil.summaryhooks.add('evolve', summaryhook)
       
   792 
       
   793 
       
   794 #####################################################################
       
   795 ### Core Other extension compat                                   ###
       
   796 #####################################################################
       
   797 
       
   798 
       
   799 @eh.extsetup
       
   800 def _rebasewrapping(ui):
       
   801     # warning about more obsolete
       
   802     try:
       
   803         rebase = extensions.find('rebase')
       
   804         if rebase:
       
   805             extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
       
   806     except KeyError:
       
   807         pass # rebase not found
       
   808     try:
       
   809         histedit = extensions.find('histedit')
       
   810         if histedit:
       
   811             extensions.wrapcommand(histedit.cmdtable, 'histedit', warnobserrors)
       
   812     except KeyError:
       
   813         pass # histedit not found
       
   814 
       
   815 #####################################################################
       
   816 ### Old Evolve extension content                                  ###
       
   817 #####################################################################
       
   818 
       
   819 # XXX need clean up and proper sorting in other section
       
   820 
       
   821 ### util function
       
   822 #############################
       
   823 
       
   824 ### changeset rewriting logic
       
   825 #############################
       
   826 
       
   827 def rewrite(repo, old, updates, head, newbases, commitopts):
       
   828     """Return (nodeid, created) where nodeid is the identifier of the
       
   829     changeset generated by the rewrite process, and created is True if
       
   830     nodeid was actually created. If created is False, nodeid
       
   831     references a changeset existing before the rewrite call.
       
   832     """
       
   833     wlock = lock = tr = None
       
   834     try:
       
   835         wlock = repo.wlock()
       
   836         lock = repo.lock()
       
   837         tr = repo.transaction('rewrite')
       
   838         if len(old.parents()) > 1: #XXX remove this unnecessary limitation.
       
   839             raise error.Abort(_('cannot amend merge changesets'))
       
   840         base = old.p1()
       
   841         updatebookmarks = _bookmarksupdater(repo, old.node(), tr)
       
   842 
       
   843         # commit a new version of the old changeset, including the update
       
   844         # collect all files which might be affected
       
   845         files = set(old.files())
       
   846         for u in updates:
       
   847             files.update(u.files())
       
   848 
       
   849         # Recompute copies (avoid recording a -> b -> a)
       
   850         copied = copies.pathcopies(base, head)
       
   851 
       
   852 
       
   853         # prune files which were reverted by the updates
       
   854         def samefile(f):
       
   855             if f in head.manifest():
       
   856                 a = head.filectx(f)
       
   857                 if f in base.manifest():
       
   858                     b = base.filectx(f)
       
   859                     return (a.data() == b.data()
       
   860                             and a.flags() == b.flags())
       
   861                 else:
       
   862                     return False
       
   863             else:
       
   864                 return f not in base.manifest()
       
   865         files = [f for f in files if not samefile(f)]
       
   866         # commit version of these files as defined by head
       
   867         headmf = head.manifest()
       
   868         def filectxfn(repo, ctx, path):
       
   869             if path in headmf:
       
   870                 fctx = head[path]
       
   871                 flags = fctx.flags()
       
   872                 mctx = memfilectx(repo, fctx.path(), fctx.data(),
       
   873                                   islink='l' in flags,
       
   874                                   isexec='x' in flags,
       
   875                                   copied=copied.get(path))
       
   876                 return mctx
       
   877             return None
       
   878 
       
   879         message = cmdutil.logmessage(repo.ui, commitopts)
       
   880         if not message:
       
   881             message = old.description()
       
   882 
       
   883         user = commitopts.get('user') or old.user()
       
   884         date = commitopts.get('date') or None # old.date()
       
   885         extra = dict(commitopts.get('extra', old.extra()))
       
   886         extra['branch'] = head.branch()
       
   887 
       
   888         new = context.memctx(repo,
       
   889                              parents=newbases,
       
   890                              text=message,
       
   891                              files=files,
       
   892                              filectxfn=filectxfn,
       
   893                              user=user,
       
   894                              date=date,
       
   895                              extra=extra)
       
   896 
       
   897         if commitopts.get('edit'):
       
   898             new._text = cmdutil.commitforceeditor(repo, new, [])
       
   899         revcount = len(repo)
       
   900         newid = repo.commitctx(new)
       
   901         new = repo[newid]
       
   902         created = len(repo) != revcount
       
   903         updatebookmarks(newid)
       
   904 
       
   905         tr.close()
       
   906         return newid, created
       
   907     finally:
       
   908         lockmod.release(tr, lock, wlock)
       
   909 
       
   910 class MergeFailure(error.Abort):
       
   911     pass
       
   912 
       
   913 def relocate(repo, orig, dest, pctx=None, keepbranch=False):
       
   914     """rewrite <rev> on dest"""
       
   915     if orig.rev() == dest.rev():
       
   916         raise error.Abort(_('tried to relocate a node on top of itself'),
       
   917                          hint=_("This shouldn't happen. If you still "
       
   918                                 "need to move changesets, please do so "
       
   919                                 "manually with nothing to rebase - working "
       
   920                                 "directory parent is also destination"))
       
   921 
       
   922     if pctx is None:
       
   923         if len(orig.parents()) == 2:
       
   924             raise error.Abort(_("tried to relocate a merge commit without "
       
   925                                 "specifying which parent should be moved"),
       
   926                               hint=_("Specify the parent by passing in pctx"))
       
   927         pctx = orig.p1()
       
   928 
       
   929     destbookmarks = repo.nodebookmarks(dest.node())
       
   930     nodesrc = orig.node()
       
   931     destphase = repo[nodesrc].phase()
       
   932     commitmsg = orig.description()
       
   933 
       
   934     cache = {}
       
   935     sha1s = re.findall(sha1re, commitmsg)
       
   936     unfi = repo.unfiltered()
       
   937     for sha1 in sha1s:
       
   938         ctx = None
       
   939         try:
       
   940             ctx = unfi[sha1]
       
   941         except error.RepoLookupError:
       
   942             continue
       
   943 
       
   944         if not ctx.obsolete():
       
   945             continue
       
   946 
       
   947         successors = obsolete.successorssets(repo, ctx.node(), cache)
       
   948 
       
   949         # We can't make any assumptions about how to update the hash if the
       
   950         # cset in question was split or diverged.
       
   951         if len(successors) == 1 and len(successors[0]) == 1:
       
   952             newsha1 = node.hex(successors[0][0])
       
   953             commitmsg = commitmsg.replace(sha1, newsha1[:len(sha1)])
       
   954         else:
       
   955             repo.ui.note(_('The stale commit message reference to %s could '
       
   956                            'not be updated\n') % sha1)
       
   957 
       
   958     tr = repo.currenttransaction()
       
   959     assert tr is not None
       
   960     try:
       
   961         r = _evolvemerge(repo, orig, dest, pctx, keepbranch)
       
   962         if r[-1]:  #some conflict
       
   963             raise error.Abort(
       
   964                     'unresolved merge conflicts (see hg help resolve)')
       
   965         nodenew = _relocatecommit(repo, orig, commitmsg)
       
   966     except error.Abort as exc:
       
   967         repo.dirstate.beginparentchange()
       
   968         repo.setparents(repo['.'].node(), nullid)
       
   969         writedirstate(repo.dirstate, tr)
       
   970         # fix up dirstate for copies and renames
       
   971         copies.duplicatecopies(repo, dest.rev(), orig.p1().rev())
       
   972         repo.dirstate.endparentchange()
       
   973         class LocalMergeFailure(MergeFailure, exc.__class__):
       
   974             pass
       
   975         exc.__class__ = LocalMergeFailure
       
   976         tr.close() # to keep changes in this transaction (e.g. dirstate)
       
   977         raise
       
   978     oldbookmarks = repo.nodebookmarks(nodesrc)
       
   979     _finalizerelocate(repo, orig, dest, nodenew, tr)
       
   980     return nodenew
       
   981 
       
   982 def _bookmarksupdater(repo, oldid, tr):
       
   983     """Return a callable update(newid) updating the current bookmark
       
   984     and bookmarks bound to oldid to newid.
       
   985     """
       
   986     def updatebookmarks(newid):
       
   987         dirty = False
       
   988         oldbookmarks = repo.nodebookmarks(oldid)
       
   989         if oldbookmarks:
       
   990             for b in oldbookmarks:
       
   991                 repo._bookmarks[b] = newid
       
   992             dirty = True
       
   993         if dirty:
       
   994             repo._bookmarks.recordchange(tr)
       
   995     return updatebookmarks
       
   996 
       
   997 ### bookmarks api compatibility layer ###
       
   998 def bmdeactivate(repo):
       
   999     try:
       
  1000         return bookmarksmod.deactivate(repo)
       
  1001     except AttributeError:
       
  1002         return bookmarksmod.unsetcurrent(repo)
       
  1003 def bmactivate(repo, book):
       
  1004     try:
       
  1005         return bookmarksmod.activate(repo, book)
       
  1006     except AttributeError:
       
  1007         return bookmarksmod.setcurrent(repo, book)
       
  1008 
       
  1009 def bmactive(repo):
       
  1010     try:
       
  1011         return repo._activebookmark
       
  1012     except AttributeError:
       
  1013         return repo._bookmarkcurrent
       
  1014 
       
  1015 ### dirstate compatibility layer < hg 3.6
       
  1016 
       
  1017 def writedirstate(dirstate, tr):
       
  1018     if dirstate.write.func_code.co_argcount != 1: # mercurial 3.6 and above
       
  1019         return dirstate.write(tr)
       
  1020     return dirstate.write()
       
  1021 
       
  1022 
       
  1023 
       
  1024 ### new command
       
  1025 #############################
       
  1026 metadataopts = [
       
  1027     ('d', 'date', '',
       
  1028      _('record the specified date in metadata'), _('DATE')),
       
  1029     ('u', 'user', '',
       
  1030      _('record the specified user in metadata'), _('USER')),
       
  1031 ]
       
  1032 
       
  1033 @eh.uisetup
       
  1034 def _installimportobsolete(ui):
       
  1035     entry = cmdutil.findcmd('import', commands.table)[1]
       
  1036     entry[1].append(('', 'obsolete', False,
       
  1037                     _('mark the old node as obsoleted by '
       
  1038                       'the created commit')))
       
  1039 
       
  1040 @eh.wrapfunction(mercurial.cmdutil, 'tryimportone')
       
  1041 def tryimportone(orig, ui, repo, hunk, parents, opts, *args, **kwargs):
       
  1042     extracted = patch.extract(ui, hunk)
       
  1043     if util.safehasattr(extracted, 'get'):
       
  1044         # mercurial 3.6 return a dictionary there
       
  1045         expected = extracted.get('nodeid')
       
  1046     else:
       
  1047         expected = extracted[5]
       
  1048     if expected is not None:
       
  1049         expected = node.bin(expected)
       
  1050     oldextract = patch.extract
       
  1051     try:
       
  1052         patch.extract = lambda ui, hunk: extracted
       
  1053         ret = orig(ui, repo, hunk, parents, opts, *args, **kwargs)
       
  1054     finally:
       
  1055         patch.extract = oldextract
       
  1056     created = ret[1]
       
  1057     if (opts['obsolete'] and None not in (created, expected)
       
  1058         and created != expected):
       
  1059             tr = repo.transaction('import-obs')
       
  1060             try:
       
  1061                 metadata = {'user': ui.username()}
       
  1062                 repo.obsstore.create(tr, expected, (created,),
       
  1063                                      metadata=metadata)
       
  1064                 tr.close()
       
  1065             finally:
       
  1066                 tr.release()
       
  1067     return ret
       
  1068 
       
  1069 
       
  1070 def _deprecatealias(oldalias, newalias):
       
  1071     '''Deprecates an alias for a command in favour of another
       
  1072 
       
  1073     Creates a new entry in the command table for the old alias. It creates a
       
  1074     wrapper that has its synopsis set to show that is has been deprecated.
       
  1075     The documentation will be replace with a pointer to the new alias.
       
  1076     If a user invokes the command a deprecation warning will be printed and
       
  1077     the command of the *new* alias will be invoked.
       
  1078 
       
  1079     This function is loosely based on the extensions.wrapcommand function.
       
  1080     '''
       
  1081     try:
       
  1082         aliases, entry = cmdutil.findcmd(newalias, cmdtable)
       
  1083     except error.UnknownCommand:
       
  1084         # Commands may be disabled
       
  1085         return
       
  1086     for alias, e in cmdtable.items():
       
  1087         if e is entry:
       
  1088             break
       
  1089 
       
  1090     synopsis = '(DEPRECATED)'
       
  1091     if len(entry) > 2:
       
  1092         fn, opts, _syn = entry
       
  1093     else:
       
  1094         fn, opts, = entry
       
  1095     deprecationwarning = _('%s have been deprecated in favor of %s\n') % (
       
  1096         oldalias, newalias)
       
  1097     def newfn(*args, **kwargs):
       
  1098         ui = args[0]
       
  1099         ui.warn(deprecationwarning)
       
  1100         util.checksignature(fn)(*args, **kwargs)
       
  1101     newfn.__doc__  = deprecationwarning
       
  1102     cmdwrapper = command(oldalias, opts, synopsis)
       
  1103     cmdwrapper(newfn)
       
  1104 
       
  1105 @eh.extsetup
       
  1106 def deprecatealiases(ui):
       
  1107     _deprecatealias('gup', 'next')
       
  1108     _deprecatealias('gdown', 'previous')
       
  1109 
       
  1110 @command('debugrecordpruneparents', [], '')
       
  1111 def cmddebugrecordpruneparents(ui, repo):
       
  1112     """add parent data to prune markers when possible
       
  1113 
       
  1114     This command searches the repo for prune markers without parent information.
       
  1115     If the pruned node is locally known, it creates a new marker with parent
       
  1116     data.
       
  1117     """
       
  1118     pgop = 'reading markers'
       
  1119 
       
  1120     # lock from the beginning to prevent race
       
  1121     wlock = lock = tr = None
       
  1122     try:
       
  1123         wlock = repo.wlock()
       
  1124         lock = repo.lock()
       
  1125         tr = repo.transaction('recordpruneparents')
       
  1126         unfi = repo.unfiltered()
       
  1127         nm = unfi.changelog.nodemap
       
  1128         store = repo.obsstore
       
  1129         pgtotal = len(store._all)
       
  1130         for idx, mark in enumerate(list(store._all)):
       
  1131             if not mark[1]:
       
  1132                 rev = nm.get(mark[0])
       
  1133                 if rev is not None:
       
  1134                     ctx = unfi[rev]
       
  1135                     parents = tuple(p.node() for p in ctx.parents())
       
  1136                     before = len(store._all)
       
  1137                     store.create(tr, mark[0], mark[1], mark[2], mark[3],
       
  1138                                  parents=parents)
       
  1139                     if len(store._all) - before:
       
  1140                         ui.write(_('created new markers for %i\n') % rev)
       
  1141             ui.progress(pgop, idx, total=pgtotal)
       
  1142         tr.close()
       
  1143         ui.progress(pgop, None)
       
  1144     finally:
       
  1145         lockmod.release(tr, lock, wlock)
       
  1146 
       
  1147 @command('debugobsstorestat', [], '')
       
  1148 def cmddebugobsstorestat(ui, repo):
       
  1149     """print statistics about obsolescence markers in the repo"""
       
  1150     def _updateclustermap(nodes, mark, clustersmap):
       
  1151         c = (set(nodes), set([mark]))
       
  1152         toproceed = set(nodes)
       
  1153         while toproceed:
       
  1154             n = toproceed.pop()
       
  1155             other = clustersmap.get(n)
       
  1156             if (other is not None
       
  1157                 and other is not c):
       
  1158                 other[0].update(c[0])
       
  1159                 other[1].update(c[1])
       
  1160                 for on in c[0]:
       
  1161                     if on in toproceed:
       
  1162                         continue
       
  1163                     clustersmap[on] = other
       
  1164                 c = other
       
  1165             clustersmap[n] = c
       
  1166 
       
  1167     store = repo.obsstore
       
  1168     unfi = repo.unfiltered()
       
  1169     nm = unfi.changelog.nodemap
       
  1170     ui.write(_('markers total:              %9i\n') % len(store._all))
       
  1171     sucscount = [0, 0 , 0, 0]
       
  1172     known = 0
       
  1173     parentsdata = 0
       
  1174     metakeys = {}
       
  1175     # node -> cluster mapping
       
  1176     #   a cluster is a (set(nodes), set(markers)) tuple
       
  1177     clustersmap = {}
       
  1178     # same data using parent information
       
  1179     pclustersmap = {}
       
  1180     for mark in store:
       
  1181         if mark[0] in nm:
       
  1182             known += 1
       
  1183         nbsucs = len(mark[1])
       
  1184         sucscount[min(nbsucs, 3)] += 1
       
  1185         meta = mark[3]
       
  1186         for key, value in meta:
       
  1187             metakeys.setdefault(key, 0)
       
  1188             metakeys[key] += 1
       
  1189         meta = dict(meta)
       
  1190         parents = [meta.get('p1'), meta.get('p2')]
       
  1191         parents = [node.bin(p) for p in parents if p is not None]
       
  1192         if parents:
       
  1193             parentsdata += 1
       
  1194         # cluster handling
       
  1195         nodes = set(mark[1])
       
  1196         nodes.add(mark[0])
       
  1197         _updateclustermap(nodes, mark, clustersmap)
       
  1198         # same with parent data
       
  1199         nodes.update(parents)
       
  1200         _updateclustermap(nodes, mark, pclustersmap)
       
  1201 
       
  1202     # freezing the result
       
  1203     for c in clustersmap.values():
       
  1204         fc = (frozenset(c[0]), frozenset(c[1]))
       
  1205         for n in fc[0]:
       
  1206             clustersmap[n] = fc
       
  1207     # same with parent data
       
  1208     for c in pclustersmap.values():
       
  1209         fc = (frozenset(c[0]), frozenset(c[1]))
       
  1210         for n in fc[0]:
       
  1211             pclustersmap[n] = fc
       
  1212     ui.write(('    for known precursors:   %9i\n' % known))
       
  1213     ui.write(('    with parents data:      %9i\n' % parentsdata))
       
  1214     # successors data
       
  1215     ui.write(('markers with no successors: %9i\n' % sucscount[0]))
       
  1216     ui.write(('              1 successors: %9i\n' % sucscount[1]))
       
  1217     ui.write(('              2 successors: %9i\n' % sucscount[2]))
       
  1218     ui.write(('    more than 2 successors: %9i\n' % sucscount[3]))
       
  1219     # meta data info
       
  1220     ui.write(('    available  keys:\n'))
       
  1221     for key in sorted(metakeys):
       
  1222         ui.write(('    %15s:        %9i\n' % (key, metakeys[key])))
       
  1223 
       
  1224     allclusters = list(set(clustersmap.values()))
       
  1225     allclusters.sort(key=lambda x: len(x[1]))
       
  1226     ui.write(('disconnected clusters:      %9i\n' % len(allclusters)))
       
  1227 
       
  1228     ui.write('        any known node:     %9i\n'
       
  1229              % len([c for c in allclusters
       
  1230                     if [n for n in c[0] if nm.get(n) is not None]]))
       
  1231     if allclusters:
       
  1232         nbcluster = len(allclusters)
       
  1233         ui.write(('        smallest length:    %9i\n' % len(allclusters[0][1])))
       
  1234         ui.write(('        longer length:      %9i\n'
       
  1235                  % len(allclusters[-1][1])))
       
  1236         median = len(allclusters[nbcluster//2][1])
       
  1237         ui.write(('        median length:      %9i\n' % median))
       
  1238         mean = sum(len(x[1]) for x in allclusters) // nbcluster
       
  1239         ui.write(('        mean length:        %9i\n' % mean))
       
  1240     allpclusters = list(set(pclustersmap.values()))
       
  1241     allpclusters.sort(key=lambda x: len(x[1]))
       
  1242     ui.write(('    using parents data:     %9i\n' % len(allpclusters)))
       
  1243     ui.write('        any known node:     %9i\n'
       
  1244              % len([c for c in allclusters
       
  1245                     if [n for n in c[0] if nm.get(n) is not None]]))
       
  1246     if allpclusters:
       
  1247         nbcluster = len(allpclusters)
       
  1248         ui.write(('        smallest length:    %9i\n'
       
  1249                  % len(allpclusters[0][1])))
       
  1250         ui.write(('        longer length:      %9i\n'
       
  1251                  % len(allpclusters[-1][1])))
       
  1252         median = len(allpclusters[nbcluster//2][1])
       
  1253         ui.write(('        median length:      %9i\n' % median))
       
  1254         mean = sum(len(x[1]) for x in allpclusters) // nbcluster
       
  1255         ui.write(('        mean length:        %9i\n' % mean))
       
  1256 
       
  1257 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
       
  1258     """Resolve the troubles affecting one revision"""
       
  1259     wlock = lock = tr = None
       
  1260     try:
       
  1261         wlock = repo.wlock()
       
  1262         lock = repo.lock()
       
  1263         tr = repo.transaction("evolve")
       
  1264         if 'unstable' == category:
       
  1265             result = _solveunstable(ui, repo, ctx, dryrun, confirm, progresscb)
       
  1266         elif 'bumped' == category:
       
  1267             result = _solvebumped(ui, repo, ctx, dryrun, confirm, progresscb)
       
  1268         elif 'divergent' == category:
       
  1269             result = _solvedivergent(ui, repo, ctx, dryrun, confirm,
       
  1270                                    progresscb)
       
  1271         else:
       
  1272             assert False, "unknown trouble category: %s" % (category)
       
  1273         tr.close()
       
  1274         return result
       
  1275     finally:
       
  1276         lockmod.release(tr, lock, wlock)
       
  1277 
       
  1278 def _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat):
       
  1279     """Used by the evolve function to display an error message when
       
  1280     no troubles can be resolved"""
       
  1281     troublecategories = ['bumped', 'divergent', 'unstable']
       
  1282     unselectedcategories = [c for c in troublecategories if c != targetcat]
       
  1283     msg = None
       
  1284     hint = None
       
  1285 
       
  1286     troubled = {
       
  1287             "unstable": repo.revs("unstable()"),
       
  1288             "divergent": repo.revs("divergent()"),
       
  1289             "bumped": repo.revs("bumped()"),
       
  1290             "all": repo.revs("troubled()"),
       
  1291     }
       
  1292 
       
  1293 
       
  1294     hintmap = {
       
  1295             'bumped': _("do you want to use --bumped"),
       
  1296             'bumped+divergent': _("do you want to use --bumped or --divergent"),
       
  1297             'bumped+unstable': _("do you want to use --bumped or --unstable"),
       
  1298             'divergent': _("do you want to use --divergent"),
       
  1299             'divergent+unstable': _("do you want to use --divergent"
       
  1300                                     " or --unstable"),
       
  1301             'unstable': _("do you want to use --unstable"),
       
  1302             'any+bumped': _("do you want to use --any (or --rev) and --bumped"),
       
  1303             'any+bumped+divergent': _("do you want to use --any (or --rev) and"
       
  1304                                       " --bumped or --divergent"),
       
  1305             'any+bumped+unstable': _("do you want to use --any (or --rev) and"
       
  1306                                      "--bumped or --unstable"),
       
  1307             'any+divergent': _("do you want to use --any (or --rev) and"
       
  1308                                " --divergent"),
       
  1309             'any+divergent+unstable': _("do you want to use --any (or --rev)"
       
  1310                                         " and --divergent or --unstable"),
       
  1311             'any+unstable': _("do you want to use --any (or --rev)"
       
  1312                               "and --unstable"),
       
  1313     }
       
  1314 
       
  1315     if revopt:
       
  1316         revs = scmutil.revrange(repo, revopt)
       
  1317         if not revs:
       
  1318             msg = _("set of specified revisions is empty")
       
  1319         else:
       
  1320             msg = _("no %s changesets in specified revisions") % targetcat
       
  1321             othertroubles = []
       
  1322             for cat in unselectedcategories:
       
  1323                 if revs & troubled[cat]:
       
  1324                     othertroubles.append(cat)
       
  1325             if othertroubles:
       
  1326                 hint = hintmap['+'.join(othertroubles)]
       
  1327 
       
  1328     elif anyopt:
       
  1329         msg = _("no %s changesets to evolve") % targetcat
       
  1330         othertroubles = []
       
  1331         for cat in unselectedcategories:
       
  1332             if troubled[cat]:
       
  1333                 othertroubles.append(cat)
       
  1334         if othertroubles:
       
  1335             hint = hintmap['+'.join(othertroubles)]
       
  1336 
       
  1337     else:
       
  1338         # evolve without any option = relative to the current wdir
       
  1339         if targetcat == 'unstable':
       
  1340             msg = _("nothing to evolve on current working copy parent")
       
  1341         else:
       
  1342             msg = _("current working copy parent is not %s") % targetcat
       
  1343 
       
  1344         p1 = repo['.'].rev()
       
  1345         othertroubles = []
       
  1346         for cat in unselectedcategories:
       
  1347             if p1 in troubled[cat]:
       
  1348                 othertroubles.append(cat)
       
  1349         if othertroubles:
       
  1350             hint = hintmap['+'.join(othertroubles)]
       
  1351         else:
       
  1352             l = len(troubled[targetcat])
       
  1353             if l:
       
  1354                 hint = _("%d other %s in the repository, do you want --any "
       
  1355                         "or --rev") % (l, targetcat)
       
  1356             else:
       
  1357                 othertroubles = []
       
  1358                 for cat in unselectedcategories:
       
  1359                     if troubled[cat]:
       
  1360                         othertroubles.append(cat)
       
  1361                 if othertroubles:
       
  1362                     hint = hintmap['any+'+('+'.join(othertroubles))]
       
  1363                 else:
       
  1364                     msg = _("no troubled changesets")
       
  1365 
       
  1366     assert msg is not None
       
  1367     ui.write_err(msg+"\n")
       
  1368     if hint:
       
  1369         ui.write_err("("+hint+")\n")
       
  1370         return 2
       
  1371     else:
       
  1372         return 1
       
  1373 
       
  1374 def _cleanup(ui, repo, startnode, showprogress):
       
  1375     if showprogress:
       
  1376         ui.progress(_('evolve'), None)
       
  1377     if repo['.'] != startnode:
       
  1378         ui.status(_('working directory is now at %s\n') % repo['.'])
       
  1379 
       
  1380 class MultipleSuccessorsError(RuntimeError):
       
  1381     """Exception raised by _singlesuccessor when multiple successor sets exists
       
  1382 
       
  1383     The object contains the list of successorssets in its 'successorssets'
       
  1384     attribute to call to easily recover.
       
  1385     """
       
  1386 
       
  1387     def __init__(self, successorssets):
       
  1388         self.successorssets = successorssets
       
  1389 
       
  1390 def _singlesuccessor(repo, p):
       
  1391     """returns p (as rev) if not obsolete or its unique latest successors
       
  1392 
       
  1393     fail if there are no such successor"""
       
  1394 
       
  1395     if not p.obsolete():
       
  1396         return p.rev()
       
  1397     obs = repo[p]
       
  1398     ui = repo.ui
       
  1399     newer = obsolete.successorssets(repo, obs.node())
       
  1400     # search of a parent which is not killed
       
  1401     while not newer:
       
  1402         ui.debug("stabilize target %s is plain dead,"
       
  1403                  " trying to stabilize on its parent\n" %
       
  1404                  obs)
       
  1405         obs = obs.parents()[0]
       
  1406         newer = obsolete.successorssets(repo, obs.node())
       
  1407     if len(newer) > 1 or len(newer[0]) > 1:
       
  1408         raise MultipleSuccessorsError(newer)
       
  1409 
       
  1410     return repo[newer[0][0]].rev()
       
  1411 
       
  1412 def builddependencies(repo, revs):
       
  1413     """returns dependency graphs giving an order to solve instability of revs
       
  1414     (see _orderrevs for more information on usage)"""
       
  1415 
       
  1416     # For each troubled revision we keep track of what instability if any should
       
  1417     # be resolved in order to resolve it. Example:
       
  1418     # dependencies = {3: [6], 6:[]}
       
  1419     # Means that: 6 has no dependency, 3 depends on 6 to be solved
       
  1420     dependencies = {}
       
  1421     # rdependencies is the inverted dict of dependencies
       
  1422     rdependencies = collections.defaultdict(set)
       
  1423 
       
  1424     for r in revs:
       
  1425         dependencies[r] = set()
       
  1426         for p in repo[r].parents():
       
  1427             try:
       
  1428                 succ = _singlesuccessor(repo, p)
       
  1429             except MultipleSuccessorsError as exc:
       
  1430                 dependencies[r] = exc.successorssets
       
  1431                 continue
       
  1432             if succ in revs:
       
  1433                 dependencies[r].add(succ)
       
  1434                 rdependencies[succ].add(r)
       
  1435     return dependencies, rdependencies
       
  1436 
       
  1437 def _dedupedivergents(repo, revs):
       
  1438     """Dedupe the divergents revs in revs to get one from each group with the
       
  1439     lowest revision numbers
       
  1440     """
       
  1441     repo = repo.unfiltered()
       
  1442     res = set()
       
  1443     # To not reevaluate divergents of the same group once one is encountered
       
  1444     discarded = set()
       
  1445     for rev in revs:
       
  1446         if rev in discarded:
       
  1447             continue
       
  1448         divergent = repo[rev]
       
  1449         base, others = divergentdata(divergent)
       
  1450         othersrevs = [o.rev() for o in others]
       
  1451         res.add(min([divergent.rev()] + othersrevs))
       
  1452         discarded.update(othersrevs)
       
  1453     return res
       
  1454 
       
  1455 def _selectrevs(repo, allopt, revopt, anyopt, targetcat):
       
  1456     """select troubles in repo matching according to given options"""
       
  1457     revs = set()
       
  1458     if allopt or revopt:
       
  1459         revs = repo.revs(targetcat+'()')
       
  1460         if revopt:
       
  1461             revs = scmutil.revrange(repo, revopt) & revs
       
  1462         elif not anyopt:
       
  1463             topic = getattr(repo, 'currenttopic', '')
       
  1464             if topic:
       
  1465                 revs = repo.revs('topic(%s)', topic) & revs
       
  1466             elif targetcat == 'unstable':
       
  1467                 revs = _aspiringdescendant(repo,
       
  1468                                            repo.revs('(.::) - obsolete()::'))
       
  1469                 revs = set(revs)
       
  1470         if targetcat == 'divergent':
       
  1471             # Pick one divergent per group of divergents
       
  1472             revs = _dedupedivergents(repo, revs)
       
  1473     elif anyopt:
       
  1474         revs = repo.revs('first(%s())' % (targetcat))
       
  1475     elif targetcat == 'unstable':
       
  1476         revs = set(_aspiringchildren(repo, repo.revs('(.::) - obsolete()::')))
       
  1477         if 1 < len(revs):
       
  1478             msg = "multiple evolve candidates"
       
  1479             hint = (_("select one of %s with --rev")
       
  1480                     % ', '.join([str(repo[r]) for r in sorted(revs)]))
       
  1481             raise error.Abort(msg, hint=hint)
       
  1482     elif targetcat in repo['.'].troubles():
       
  1483         revs = set([repo['.'].rev()])
       
  1484     return revs
       
  1485 
       
  1486 
       
  1487 def _orderrevs(repo, revs):
       
  1488     """Compute an ordering to solve instability for the given revs
       
  1489 
       
  1490     revs is a list of unstable revisions.
       
  1491 
       
  1492     Returns the same revisions ordered to solve their instability from the
       
  1493     bottom to the top of the stack that the stabilization process will produce
       
  1494     eventually.
       
  1495 
       
  1496     This ensures the minimal number of stabilizations, as we can stabilize each
       
  1497     revision on its final stabilized destination.
       
  1498     """
       
  1499     # Step 1: Build the dependency graph
       
  1500     dependencies, rdependencies = builddependencies(repo, revs)
       
  1501     # Step 2: Build the ordering
       
  1502     # Remove the revisions with no dependency(A) and add them to the ordering.
       
  1503     # Removing these revisions leads to new revisions with no dependency (the
       
  1504     # one depending on A) that we can remove from the dependency graph and add
       
  1505     # to the ordering. We progress in a similar fashion until the ordering is
       
  1506     # built
       
  1507     solvablerevs = collections.deque([r for r in sorted(dependencies.keys())
       
  1508                                       if not dependencies[r]])
       
  1509     ordering = []
       
  1510     while solvablerevs:
       
  1511         rev = solvablerevs.popleft()
       
  1512         for dependent in rdependencies[rev]:
       
  1513             dependencies[dependent].remove(rev)
       
  1514             if not dependencies[dependent]:
       
  1515                 solvablerevs.append(dependent)
       
  1516         del dependencies[rev]
       
  1517         ordering.append(rev)
       
  1518 
       
  1519     ordering.extend(sorted(dependencies))
       
  1520     return ordering
       
  1521 
       
  1522 def divergentsets(repo, ctx):
       
  1523     """Compute sets of commits divergent with a given one"""
       
  1524     cache = {}
       
  1525     succsets = {}
       
  1526     base = {}
       
  1527     for n in obsolete.allprecursors(repo.obsstore, [ctx.node()]):
       
  1528         if n == ctx.node():
       
  1529             # a node can't be a base for divergence with itself
       
  1530             continue
       
  1531         nsuccsets = obsolete.successorssets(repo, n, cache)
       
  1532         for nsuccset in nsuccsets:
       
  1533             if ctx.node() in nsuccset:
       
  1534                 # we are only interested in *other* successor sets
       
  1535                 continue
       
  1536             if tuple(nsuccset) in base:
       
  1537                 # we already know the latest base for this divergency
       
  1538                 continue
       
  1539             base[tuple(nsuccset)] = n
       
  1540     divergence = []
       
  1541     for divset, b in base.iteritems():
       
  1542         divergence.append({
       
  1543             'divergentnodes': divset,
       
  1544             'commonprecursor': b
       
  1545         })
       
  1546 
       
  1547     return divergence
       
  1548 
       
  1549 def _preparelistctxs(items, condition):
       
  1550     return [item.hex() for item in items if condition(item)]
       
  1551 
       
  1552 def _formatctx(fm, ctx):
       
  1553     fm.data(node=ctx.hex())
       
  1554     fm.data(desc=ctx.description())
       
  1555     fm.data(date=ctx.date())
       
  1556     fm.data(user=ctx.user())
       
  1557 
       
  1558 def listtroubles(ui, repo, troublecategories, **opts):
       
  1559     """Print all the troubles for the repo (or given revset)"""
       
  1560     troublecategories = troublecategories or ['divergent', 'unstable', 'bumped']
       
  1561     showunstable = 'unstable' in troublecategories
       
  1562     showbumped = 'bumped' in troublecategories
       
  1563     showdivergent = 'divergent' in troublecategories
       
  1564 
       
  1565     revs = repo.revs('+'.join("%s()" % t for t in troublecategories))
       
  1566     if opts.get('rev'):
       
  1567         revs = revs & repo.revs(opts.get('rev'))
       
  1568 
       
  1569     fm = ui.formatter('evolvelist', opts)
       
  1570     for rev in revs:
       
  1571         ctx = repo[rev]
       
  1572         unpars = _preparelistctxs(ctx.parents(), lambda p: p.unstable())
       
  1573         obspars = _preparelistctxs(ctx.parents(), lambda p: p.obsolete())
       
  1574         imprecs = _preparelistctxs(repo.set("allprecursors(%n)", ctx.node()),
       
  1575                                    lambda p: not p.mutable())
       
  1576         dsets = divergentsets(repo, ctx)
       
  1577 
       
  1578         fm.startitem()
       
  1579         # plain formatter section
       
  1580         hashlen, desclen = 12, 60
       
  1581         desc = ctx.description()
       
  1582         if desc:
       
  1583             desc = desc.splitlines()[0]
       
  1584         desc = (desc[:desclen] + '...') if len(desc) > desclen else desc
       
  1585         fm.plain('%s: ' % ctx.hex()[:hashlen])
       
  1586         fm.plain('%s\n' % desc)
       
  1587         fm.data(node=ctx.hex(), rev=ctx.rev(), desc=desc, phase=ctx.phasestr())
       
  1588 
       
  1589         for unpar in unpars if showunstable else []:
       
  1590             fm.plain('  unstable: %s (unstable parent)\n' % unpar[:hashlen])
       
  1591         for obspar in obspars if showunstable else []:
       
  1592             fm.plain('  unstable: %s (obsolete parent)\n' % obspar[:hashlen])
       
  1593         for imprec in imprecs if showbumped else []:
       
  1594             fm.plain('  bumped: %s (immutable precursor)\n' % imprec[:hashlen])
       
  1595 
       
  1596         if dsets and showdivergent:
       
  1597             for dset in dsets:
       
  1598                 fm.plain('  divergent: ')
       
  1599                 first = True
       
  1600                 for n in dset['divergentnodes']:
       
  1601                     t = "%s (%s)" if first else " %s (%s)"
       
  1602                     first = False
       
  1603                     fm.plain(t % (node.hex(n)[:hashlen], repo[n].phasestr()))
       
  1604                 comprec = node.hex(dset['commonprecursor'])[:hashlen]
       
  1605                 fm.plain(" (precursor %s)\n" % comprec)
       
  1606         fm.plain("\n")
       
  1607 
       
  1608         # templater-friendly section
       
  1609         _formatctx(fm, ctx)
       
  1610         troubles = []
       
  1611         for unpar in unpars:
       
  1612             troubles.append({'troubletype': 'unstable', 'sourcenode': unpar,
       
  1613                              'sourcetype': 'unstableparent'})
       
  1614         for obspar in obspars:
       
  1615             troubles.append({'troubletype': 'unstable', 'sourcenode': obspar,
       
  1616                              'sourcetype': 'obsoleteparent'})
       
  1617         for imprec in imprecs:
       
  1618             troubles.append({'troubletype': 'bumped', 'sourcenode': imprec,
       
  1619                              'sourcetype': 'immutableprecursor'})
       
  1620         for dset in dsets:
       
  1621             divnodes = [{'node': node.hex(n),
       
  1622                          'phase': repo[n].phasestr(),
       
  1623                         } for n in dset['divergentnodes']]
       
  1624             troubles.append({'troubletype': 'divergent',
       
  1625                              'commonprecursor': node.hex(dset['commonprecursor']),
       
  1626                              'divergentnodes': divnodes})
       
  1627         fm.data(troubles=troubles)
       
  1628 
       
  1629     fm.end()
       
  1630 
       
  1631 @command('^evolve|stabilize|solve',
       
  1632     [('n', 'dry-run', False,
       
  1633         _('do not perform actions, just print what would be done')),
       
  1634      ('', 'confirm', False,
       
  1635         _('ask for confirmation before performing the action')),
       
  1636     ('A', 'any', False,
       
  1637         _('also consider troubled changesets unrelated to current working '
       
  1638           'directory')),
       
  1639     ('r', 'rev', [], _('solves troubles of these revisions')),
       
  1640     ('', 'bumped', False, _('solves only bumped changesets')),
       
  1641     ('', 'divergent', False, _('solves only divergent changesets')),
       
  1642     ('', 'unstable', False, _('solves only unstable changesets (default)')),
       
  1643     ('a', 'all', False, _('evolve all troubled changesets related to the '
       
  1644                           'current  working directory and its descendants')),
       
  1645     ('c', 'continue', False, _('continue an interrupted evolution')),
       
  1646     ('l', 'list', False, 'provide details on troubled changesets in the repo'),
       
  1647     ] + mergetoolopts,
       
  1648     _('[OPTIONS]...'))
       
  1649 def evolve(ui, repo, **opts):
       
  1650     """solve troubled changesets in your repository
       
  1651 
       
  1652     Modifying history can lead to various types of troubled changesets:
       
  1653     unstable, bumped, or divergent. The evolve command resolves your troubles
       
  1654     by executing one of the following actions:
       
  1655 
       
  1656     - update working copy to a successor
       
  1657     - rebase an unstable changeset
       
  1658     - extract the desired changes from a bumped changeset
       
  1659     - fuse divergent changesets back together
       
  1660 
       
  1661     If you pass no arguments, evolve works in automatic mode: it will execute a
       
  1662     single action to reduce instability related to your working copy. There are
       
  1663     two cases for this action. First, if the parent of your working copy is
       
  1664     obsolete, evolve updates to the parent's successor. Second, if the working
       
  1665     copy parent is not obsolete but has obsolete predecessors, then evolve
       
  1666     determines if there is an unstable changeset that can be rebased onto the
       
  1667     working copy parent in order to reduce instability.
       
  1668     If so, evolve rebases that changeset. If not, evolve refuses to guess your
       
  1669     intention, and gives a hint about what you might want to do next.
       
  1670 
       
  1671     Any time evolve creates a changeset, it updates the working copy to the new
       
  1672     changeset. (Currently, every successful evolve operation involves an update
       
  1673     as well; this may change in future.)
       
  1674 
       
  1675     Automatic mode only handles common use cases. For example, it avoids taking
       
  1676     action in the case of ambiguity, and it ignores unstable changesets that
       
  1677     are not related to your working copy.
       
  1678     It also refuses to solve bumped or divergent changesets unless you explicity
       
  1679     request such behavior (see below).
       
  1680 
       
  1681     Eliminating all instability around your working copy may require multiple
       
  1682     invocations of :hg:`evolve`. Alternately, use ``--all`` to recursively
       
  1683     select and evolve all unstable changesets that can be rebased onto the
       
  1684     working copy parent.
       
  1685     This is more powerful than successive invocations, since ``--all`` handles
       
  1686     ambiguous cases (e.g. unstable changesets with multiple children) by
       
  1687     evolving all branches.
       
  1688 
       
  1689     When your repository cannot be handled by automatic mode, you might need to
       
  1690     use ``--rev`` to specify a changeset to evolve. For example, if you have
       
  1691     an unstable changeset that is not related to the working copy parent,
       
  1692     you could use ``--rev`` to evolve it. Or, if some changeset has multiple
       
  1693     unstable children, evolve in automatic mode refuses to guess which one to
       
  1694     evolve; you have to use ``--rev`` in that case.
       
  1695 
       
  1696     Alternately, ``--any`` makes evolve search for the next evolvable changeset
       
  1697     regardless of whether it is related to the working copy parent.
       
  1698 
       
  1699     You can supply multiple revisions to evolve multiple troubled changesets
       
  1700     in a single invocation. In revset terms, ``--any`` is equivalent to ``--rev
       
  1701     first(unstable())``. ``--rev`` and ``--all`` are mutually exclusive, as are
       
  1702     ``--rev`` and ``--any``.
       
  1703 
       
  1704     ``hg evolve --any --all`` is useful for cleaning up instability across all
       
  1705     branches, letting evolve figure out the appropriate order and destination.
       
  1706 
       
  1707     When you have troubled changesets that are not unstable, :hg:`evolve`
       
  1708     refuses to consider them unless you specify the category of trouble you
       
  1709     wish to resolve, with ``--bumped`` or ``--divergent``. These options are
       
  1710     currently mutually exclusive with each other and with ``--unstable``
       
  1711     (the default). You can combine ``--bumped`` or ``--divergent`` with
       
  1712     ``--rev``, ``--all``, or ``--any``.
       
  1713 
       
  1714     You can also use the evolve command to list the troubles affecting your
       
  1715     repository by using the --list flag. You can choose to display only some
       
  1716     categories of troubles with the --unstable, --divergent or --bumped flags.
       
  1717     """
       
  1718 
       
  1719     # Options
       
  1720     listopt = opts['list']
       
  1721     contopt = opts['continue']
       
  1722     anyopt = opts['any']
       
  1723     allopt = opts['all']
       
  1724     startnode = repo['.']
       
  1725     dryrunopt = opts['dry_run']
       
  1726     confirmopt = opts['confirm']
       
  1727     revopt = opts['rev']
       
  1728     troublecategories = ['bumped', 'divergent', 'unstable']
       
  1729     specifiedcategories = [t for t in troublecategories if opts[t]]
       
  1730     if listopt:
       
  1731         listtroubles(ui, repo, specifiedcategories, **opts)
       
  1732         return
       
  1733 
       
  1734     targetcat = 'unstable'
       
  1735     if 1 < len(specifiedcategories):
       
  1736         msg = _('cannot specify more than one trouble category to solve (yet)')
       
  1737         raise error.Abort(msg)
       
  1738     elif len(specifiedcategories) == 1:
       
  1739         targetcat = specifiedcategories[0]
       
  1740     elif repo['.'].obsolete():
       
  1741         displayer = cmdutil.show_changeset(ui, repo,
       
  1742                                            {'template': shorttemplate})
       
  1743         # no args and parent is obsolete, update to successors
       
  1744         try:
       
  1745             ctx = repo[_singlesuccessor(repo, repo['.'])]
       
  1746         except MultipleSuccessorsError as exc:
       
  1747             repo.ui.write_err('parent is obsolete with multiple successors:\n')
       
  1748             for ln in exc.successorssets:
       
  1749                 for n in ln:
       
  1750                     displayer.show(repo[n])
       
  1751             return 2
       
  1752 
       
  1753 
       
  1754         ui.status(_('update:'))
       
  1755         if not ui.quiet:
       
  1756             displayer.show(ctx)
       
  1757 
       
  1758         if dryrunopt:
       
  1759             return 0
       
  1760         res = hg.update(repo, ctx.rev())
       
  1761         if ctx != startnode:
       
  1762             ui.status(_('working directory is now at %s\n') % ctx)
       
  1763         return res
       
  1764 
       
  1765     ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve')
       
  1766     troubled = set(repo.revs('troubled()'))
       
  1767 
       
  1768     # Progress handling
       
  1769     seen = 1
       
  1770     count = allopt and len(troubled) or 1
       
  1771     showprogress = allopt
       
  1772 
       
  1773     def progresscb():
       
  1774         if revopt or allopt:
       
  1775             ui.progress(_('evolve'), seen, unit=_('changesets'), total=count)
       
  1776 
       
  1777     # Continuation handling
       
  1778     if contopt:
       
  1779         if anyopt:
       
  1780             raise error.Abort('cannot specify both "--any" and "--continue"')
       
  1781         if allopt:
       
  1782             raise error.Abort('cannot specify both "--all" and "--continue"')
       
  1783         state = _evolvestateread(repo)
       
  1784         if state is None:
       
  1785             raise error.Abort('no evolve to continue')
       
  1786         orig = repo[state['current']]
       
  1787         # XXX This is a terrible terrible hack, please get rid of it.
       
  1788         lock = repo.wlock()
       
  1789         try:
       
  1790             repo.opener.write('graftstate', orig.hex() + '\n')
       
  1791             try:
       
  1792                 graftcmd = commands.table['graft'][0]
       
  1793                 ret = graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
       
  1794                 _evolvestatedelete(repo)
       
  1795                 return ret
       
  1796             finally:
       
  1797                 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
       
  1798         finally:
       
  1799             lock.release()
       
  1800     cmdutil.bailifchanged(repo)
       
  1801 
       
  1802 
       
  1803     if revopt and allopt:
       
  1804         raise error.Abort('cannot specify both "--rev" and "--all"')
       
  1805     if revopt and anyopt:
       
  1806         raise error.Abort('cannot specify both "--rev" and "--any"')
       
  1807 
       
  1808     revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat)
       
  1809 
       
  1810     if not revs:
       
  1811         return _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat)
       
  1812 
       
  1813     # For the progress bar to show
       
  1814     count = len(revs)
       
  1815     # Order the revisions
       
  1816     if targetcat == 'unstable':
       
  1817         revs = _orderrevs(repo, revs)
       
  1818     for rev in revs:
       
  1819         progresscb()
       
  1820         _solveone(ui, repo, repo[rev], dryrunopt, confirmopt,
       
  1821                 progresscb, targetcat)
       
  1822         seen += 1
       
  1823     progresscb()
       
  1824     _cleanup(ui, repo, startnode, showprogress)
       
  1825 
       
  1826 def _possibledestination(repo, rev):
       
  1827     """return all changesets that may be a new parent for REV"""
       
  1828     tonode = repo.changelog.node
       
  1829     parents = repo.changelog.parentrevs
       
  1830     torev = repo.changelog.rev
       
  1831     dest = set()
       
  1832     tovisit = list(parents(rev))
       
  1833     while tovisit:
       
  1834         r = tovisit.pop()
       
  1835         succsets = obsolete.successorssets(repo, tonode(r))
       
  1836         if not succsets:
       
  1837             tovisit.extend(parents(r))
       
  1838         else:
       
  1839             # We should probably pick only one destination from split
       
  1840             # (case where '1 < len(ss)'), This could be the currently tipmost
       
  1841             # but logic is less clear when result of the split are now on
       
  1842             # multiple branches.
       
  1843             for ss in succsets:
       
  1844                 for n in ss:
       
  1845                     dest.add(torev(n))
       
  1846     return dest
       
  1847 
       
  1848 def _aspiringchildren(repo, revs):
       
  1849     """Return a list of changectx which can be stabilized on top of pctx or
       
  1850     one of its descendants. Empty list if none can be found."""
       
  1851     target = set(revs)
       
  1852     result = []
       
  1853     for r in repo.revs('unstable() - %ld', revs):
       
  1854         dest = _possibledestination(repo, r)
       
  1855         if target & dest:
       
  1856             result.append(r)
       
  1857     return result
       
  1858 
       
  1859 def _aspiringdescendant(repo, revs):
       
  1860     """Return a list of changectx which can be stabilized on top of pctx or
       
  1861     one of its descendants recursively. Empty list if none can be found."""
       
  1862     target = set(revs)
       
  1863     result = set(target)
       
  1864     paths = collections.defaultdict(set)
       
  1865     for r in repo.revs('unstable() - %ld', revs):
       
  1866         for d in _possibledestination(repo, r):
       
  1867             paths[d].add(r)
       
  1868 
       
  1869     result = set(target)
       
  1870     tovisit = list(revs)
       
  1871     while tovisit:
       
  1872         base = tovisit.pop()
       
  1873         for unstable in paths[base]:
       
  1874             if unstable not in result:
       
  1875                 tovisit.append(unstable)
       
  1876                 result.add(unstable)
       
  1877     return sorted(result - target)
       
  1878 
       
  1879 def _solveunstable(ui, repo, orig, dryrun=False, confirm=False,
       
  1880                    progresscb=None):
       
  1881     """Stabilize an unstable changeset"""
       
  1882     pctx = orig.p1()
       
  1883     if len(orig.parents()) == 2:
       
  1884         if not pctx.obsolete():
       
  1885             pctx = orig.p2()  # second parent is obsolete ?
       
  1886         elif orig.p2().obsolete():
       
  1887             hint = _("Redo the merge (%s) and use `hg prune <old> "
       
  1888                      "--succ <new>` to obsolete the old one") % orig.hex()[:12]
       
  1889             ui.warn(_("warning: no support for evolving merge changesets "
       
  1890                       "with two obsolete parents yet\n") +
       
  1891                     _("(%s)\n") % hint)
       
  1892             return False
       
  1893 
       
  1894     if not pctx.obsolete():
       
  1895         ui.warn(_("cannot solve instability of %s, skipping\n") % orig)
       
  1896         return False
       
  1897     obs = pctx
       
  1898     newer = obsolete.successorssets(repo, obs.node())
       
  1899     # search of a parent which is not killed
       
  1900     while not newer or newer == [()]:
       
  1901         ui.debug("stabilize target %s is plain dead,"
       
  1902                  " trying to stabilize on its parent\n" %
       
  1903                  obs)
       
  1904         obs = obs.parents()[0]
       
  1905         newer = obsolete.successorssets(repo, obs.node())
       
  1906     if len(newer) > 1:
       
  1907         msg = _("skipping %s: divergent rewriting. can't choose "
       
  1908                 "destination\n") % obs
       
  1909         ui.write_err(msg)
       
  1910         return 2
       
  1911     targets = newer[0]
       
  1912     assert targets
       
  1913     if len(targets) > 1:
       
  1914         # split target, figure out which one to pick, are they all in line?
       
  1915         targetrevs = [repo[r].rev() for r in targets]
       
  1916         roots = repo.revs('roots(%ld)', targetrevs)
       
  1917         heads = repo.revs('heads(%ld)', targetrevs)
       
  1918         if len(roots) > 1 or len(heads) > 1:
       
  1919             msg = "cannot solve split accross two branches\n"
       
  1920             ui.write_err(msg)
       
  1921             return 2
       
  1922         target = repo[heads.first()]
       
  1923     else:
       
  1924         target = targets[0]
       
  1925     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
       
  1926     target = repo[target]
       
  1927     if not ui.quiet or confirm:
       
  1928         repo.ui.write(_('move:'))
       
  1929         displayer.show(orig)
       
  1930         repo.ui.write(_('atop:'))
       
  1931         displayer.show(target)
       
  1932     if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
       
  1933             raise error.Abort(_('evolve aborted by user'))
       
  1934     if progresscb: progresscb()
       
  1935     todo = 'hg rebase -r %s -d %s\n' % (orig, target)
       
  1936     if dryrun:
       
  1937         repo.ui.write(todo)
       
  1938     else:
       
  1939         repo.ui.note(todo)
       
  1940         if progresscb: progresscb()
       
  1941         keepbranch = orig.p1().branch() != orig.branch()
       
  1942         try:
       
  1943             relocate(repo, orig, target, pctx, keepbranch)
       
  1944         except MergeFailure:
       
  1945             _evolvestatewrite(repo, {'current': orig.node()})
       
  1946             repo.ui.write_err(_('evolve failed!\n'))
       
  1947             repo.ui.write_err(
       
  1948                 _("fix conflict and run 'hg evolve --continue'"
       
  1949                   " or use 'hg update -C .' to abort\n"))
       
  1950             raise
       
  1951 
       
  1952 def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False,
       
  1953                  progresscb=None):
       
  1954     """Stabilize a bumped changeset"""
       
  1955     repo = repo.unfiltered()
       
  1956     bumped = repo[bumped.rev()]
       
  1957     # For now we deny bumped merge
       
  1958     if len(bumped.parents()) > 1:
       
  1959         msg = _('skipping %s : we do not handle merge yet\n') % bumped
       
  1960         ui.write_err(msg)
       
  1961         return 2
       
  1962     prec = repo.set('last(allprecursors(%d) and public())', bumped).next()
       
  1963     # For now we deny target merge
       
  1964     if len(prec.parents()) > 1:
       
  1965         msg = _('skipping: %s: public version is a merge, '
       
  1966                 'this is not handled yet\n') % prec
       
  1967         ui.write_err(msg)
       
  1968         return 2
       
  1969 
       
  1970     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
       
  1971     if not ui.quiet or confirm:
       
  1972         repo.ui.write(_('recreate:'))
       
  1973         displayer.show(bumped)
       
  1974         repo.ui.write(_('atop:'))
       
  1975         displayer.show(prec)
       
  1976     if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
       
  1977         raise error.Abort(_('evolve aborted by user'))
       
  1978     if dryrun:
       
  1979         todo = 'hg rebase --rev %s --dest %s;\n' % (bumped, prec.p1())
       
  1980         repo.ui.write(todo)
       
  1981         repo.ui.write(('hg update %s;\n' % prec))
       
  1982         repo.ui.write(('hg revert --all --rev %s;\n' % bumped))
       
  1983         repo.ui.write(('hg commit --msg "bumped update to %s"'))
       
  1984         return 0
       
  1985     if progresscb: progresscb()
       
  1986     newid = tmpctx = None
       
  1987     tmpctx = bumped
       
  1988     # Basic check for common parent. Far too complicated and fragile
       
  1989     tr = repo.currenttransaction()
       
  1990     assert tr is not None
       
  1991     bmupdate = _bookmarksupdater(repo, bumped.node(), tr)
       
  1992     if not list(repo.set('parents(%d) and parents(%d)', bumped, prec)):
       
  1993         # Need to rebase the changeset at the right place
       
  1994         repo.ui.status(
       
  1995             _('rebasing to destination parent: %s\n') % prec.p1())
       
  1996         try:
       
  1997             tmpid = relocate(repo, bumped, prec.p1())
       
  1998             if tmpid is not None:
       
  1999                 tmpctx = repo[tmpid]
       
  2000                 obsolete.createmarkers(repo, [(bumped, (tmpctx,))])
       
  2001         except MergeFailure:
       
  2002             repo.opener.write('graftstate', bumped.hex() + '\n')
       
  2003             repo.ui.write_err(_('evolution failed!\n'))
       
  2004             repo.ui.write_err(
       
  2005                 _("fix conflict and run 'hg evolve --continue'\n"))
       
  2006             raise
       
  2007     # Create the new commit context
       
  2008     repo.ui.status(_('computing new diff\n'))
       
  2009     files = set()
       
  2010     copied = copies.pathcopies(prec, bumped)
       
  2011     precmanifest = prec.manifest().copy()
       
  2012     # 3.3.2 needs a list.
       
  2013     # future 3.4 don't detect the size change during iteration
       
  2014     # this is fishy
       
  2015     for key, val in list(bumped.manifest().iteritems()):
       
  2016         precvalue = precmanifest.get(key, None)
       
  2017         if precvalue is not None:
       
  2018             del precmanifest[key]
       
  2019         if precvalue != val:
       
  2020             files.add(key)
       
  2021     files.update(precmanifest)  # add missing files
       
  2022     # commit it
       
  2023     if files: # something to commit!
       
  2024         def filectxfn(repo, ctx, path):
       
  2025             if path in bumped:
       
  2026                 fctx = bumped[path]
       
  2027                 flags = fctx.flags()
       
  2028                 mctx = memfilectx(repo, fctx.path(), fctx.data(),
       
  2029                                   islink='l' in flags,
       
  2030                                   isexec='x' in flags,
       
  2031                                   copied=copied.get(path))
       
  2032                 return mctx
       
  2033             return None
       
  2034         text = 'bumped update to %s:\n\n' % prec
       
  2035         text += bumped.description()
       
  2036 
       
  2037         new = context.memctx(repo,
       
  2038                              parents=[prec.node(), node.nullid],
       
  2039                              text=text,
       
  2040                              files=files,
       
  2041                              filectxfn=filectxfn,
       
  2042                              user=bumped.user(),
       
  2043                              date=bumped.date(),
       
  2044                              extra=bumped.extra())
       
  2045 
       
  2046         newid = repo.commitctx(new)
       
  2047     if newid is None:
       
  2048         obsolete.createmarkers(repo, [(tmpctx, ())])
       
  2049         newid = prec.node()
       
  2050     else:
       
  2051         phases.retractboundary(repo, tr, bumped.phase(), [newid])
       
  2052         obsolete.createmarkers(repo, [(tmpctx, (repo[newid],))],
       
  2053                                flag=obsolete.bumpedfix)
       
  2054     bmupdate(newid)
       
  2055     repo.ui.status(_('committed as %s\n') % node.short(newid))
       
  2056     # reroute the working copy parent to the new changeset
       
  2057     repo.dirstate.beginparentchange()
       
  2058     repo.dirstate.setparents(newid, node.nullid)
       
  2059     repo.dirstate.endparentchange()
       
  2060 
       
  2061 def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False,
       
  2062                     progresscb=None):
       
  2063     repo = repo.unfiltered()
       
  2064     divergent = repo[divergent.rev()]
       
  2065     base, others = divergentdata(divergent)
       
  2066     if len(others) > 1:
       
  2067         othersstr = "[%s]" % (','.join([str(i) for i in others]))
       
  2068         msg = _("skipping %d:divergent with a changeset that got splitted"
       
  2069                 " into multiple ones:\n"
       
  2070                  "|[%s]\n"
       
  2071                  "| This is not handled by automatic evolution yet\n"
       
  2072                  "| You have to fallback to manual handling with commands "
       
  2073                  "such as:\n"
       
  2074                  "| - hg touch -D\n"
       
  2075                  "| - hg prune\n"
       
  2076                  "| \n"
       
  2077                  "| You should contact your local evolution Guru for help.\n"
       
  2078                  ) % (divergent, othersstr)
       
  2079         ui.write_err(msg)
       
  2080         return 2
       
  2081     other = others[0]
       
  2082     if len(other.parents()) > 1:
       
  2083         msg = _("skipping %s: divergent changeset can't be "
       
  2084                 "a merge (yet)\n") % divergent
       
  2085         ui.write_err(msg)
       
  2086         hint = _("You have to fallback to solving this by hand...\n"
       
  2087                  "| This probably means redoing the merge and using \n"
       
  2088                  "| `hg prune` to kill older version.\n")
       
  2089         ui.write_err(hint)
       
  2090         return 2
       
  2091     if other.p1() not in divergent.parents():
       
  2092         msg = _("skipping %s: have a different parent than %s "
       
  2093                 "(not handled yet)\n") % (divergent, other)
       
  2094         hint = _("| %(d)s, %(o)s are not based on the same changeset.\n"
       
  2095                  "| With the current state of its implementation, \n"
       
  2096                  "| evolve does not work in that case.\n"
       
  2097                  "| rebase one of them next to the other and run \n"
       
  2098                  "| this command again.\n"
       
  2099                  "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n"
       
  2100                  "| - or:     hg rebase --dest 'p1(%(o)s)' -r %(d)s\n"
       
  2101                  ) % {'d': divergent, 'o': other}
       
  2102         ui.write_err(msg)
       
  2103         ui.write_err(hint)
       
  2104         return 2
       
  2105 
       
  2106     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
       
  2107     if not ui.quiet or confirm:
       
  2108         ui.write(_('merge:'))
       
  2109         displayer.show(divergent)
       
  2110         ui.write(_('with: '))
       
  2111         displayer.show(other)
       
  2112         ui.write(_('base: '))
       
  2113         displayer.show(base)
       
  2114     if confirm and ui.prompt(_('perform evolve? [Ny]'), 'n') != 'y':
       
  2115         raise error.Abort(_('evolve aborted by user'))
       
  2116     if dryrun:
       
  2117         ui.write(('hg update -c %s &&\n' % divergent))
       
  2118         ui.write(('hg merge %s &&\n' % other))
       
  2119         ui.write(('hg commit -m "auto merge resolving conflict between '
       
  2120                  '%s and %s"&&\n' % (divergent, other)))
       
  2121         ui.write(('hg up -C %s &&\n' % base))
       
  2122         ui.write(('hg revert --all --rev tip &&\n'))
       
  2123         ui.write(('hg commit -m "`hg log -r %s --template={desc}`";\n'
       
  2124                  % divergent))
       
  2125         return
       
  2126     if divergent not in repo[None].parents():
       
  2127         repo.ui.status(_('updating to "local" conflict\n'))
       
  2128         hg.update(repo, divergent.rev())
       
  2129     repo.ui.note(_('merging divergent changeset\n'))
       
  2130     if progresscb: progresscb()
       
  2131     try:
       
  2132         stats = merge.update(repo,
       
  2133                              other.node(),
       
  2134                              branchmerge=True,
       
  2135                              force=False,
       
  2136                              ancestor=base.node(),
       
  2137                              mergeancestor=True)
       
  2138     except TypeError:
       
  2139         # Mercurial  < 43c00ca887d1 (3.7)
       
  2140         stats = merge.update(repo,
       
  2141                              other.node(),
       
  2142                              branchmerge=True,
       
  2143                              force=False,
       
  2144                              partial=None,
       
  2145                              ancestor=base.node(),
       
  2146                              mergeancestor=True)
       
  2147 
       
  2148     hg._showstats(repo, stats)
       
  2149     if stats[3]:
       
  2150         repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
       
  2151                          "or 'hg update -C .' to abort\n"))
       
  2152     if stats[3] > 0:
       
  2153         raise error.Abort('merge conflict between several amendments '
       
  2154             '(this is not automated yet)',
       
  2155             hint="""/!\ You can try:
       
  2156 /!\ * manual merge + resolve => new cset X
       
  2157 /!\ * hg up to the parent of the amended changeset (which are named W and Z)
       
  2158 /!\ * hg revert --all -r X
       
  2159 /!\ * hg ci -m "same message as the amended changeset" => new cset Y
       
  2160 /!\ * hg prune -n Y W Z
       
  2161 """)
       
  2162     if progresscb: progresscb()
       
  2163     emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit')
       
  2164     tr = repo.currenttransaction()
       
  2165     assert tr is not None
       
  2166     try:
       
  2167         repo.ui.setconfig('ui', 'allowemptycommit', True, 'evolve')
       
  2168         repo.dirstate.beginparentchange()
       
  2169         repo.dirstate.setparents(divergent.node(), node.nullid)
       
  2170         repo.dirstate.endparentchange()
       
  2171         oldlen = len(repo)
       
  2172         amend(ui, repo, message='', logfile='')
       
  2173         if oldlen == len(repo):
       
  2174             new = divergent
       
  2175             # no changes
       
  2176         else:
       
  2177             new = repo['.']
       
  2178         obsolete.createmarkers(repo, [(other, (new,))])
       
  2179         phases.retractboundary(repo, tr, other.phase(), [new.node()])
       
  2180     finally:
       
  2181         repo.ui.restoreconfig(emtpycommitallowed)
       
  2182 
       
  2183 def divergentdata(ctx):
       
  2184     """return base, other part of a conflict
       
  2185 
       
  2186     This only return the first one.
       
  2187 
       
  2188     XXX this woobly function won't survive XXX
       
  2189     """
       
  2190     repo = ctx._repo.unfiltered()
       
  2191     for base in repo.set('reverse(allprecursors(%d))', ctx):
       
  2192         newer = obsolete.successorssets(ctx._repo, base.node())
       
  2193         # drop filter and solution including the original ctx
       
  2194         newer = [n for n in newer if n and ctx.node() not in n]
       
  2195         if newer:
       
  2196             return base, tuple(ctx._repo[o] for o in newer[0])
       
  2197     raise error.Abort("base of divergent changeset %s not found" % ctx,
       
  2198                      hint='this case is not yet handled')
       
  2199 
       
  2200 
       
  2201 
       
  2202 shorttemplate = '[{rev}] {desc|firstline}\n'
       
  2203 
       
  2204 @command('^previous',
       
  2205          [('B', 'move-bookmark', False,
       
  2206              _('move active bookmark after update')),
       
  2207           ('', 'merge', False, _('bring uncommitted change along')),
       
  2208           ('', 'no-topic', False, _('ignore topic and move topologically')),
       
  2209           ('n', 'dry-run', False,
       
  2210              _('do not perform actions, just print what would be done'))],
       
  2211          '[OPTION]...')
       
  2212 def cmdprevious(ui, repo, **opts):
       
  2213     """update to parent revision
       
  2214 
       
  2215     Displays the summary line of the destination for clarity."""
       
  2216     wlock = None
       
  2217     dryrunopt = opts['dry_run']
       
  2218     if not dryrunopt:
       
  2219         wlock = repo.wlock()
       
  2220     try:
       
  2221         wkctx = repo[None]
       
  2222         wparents = wkctx.parents()
       
  2223         if len(wparents) != 1:
       
  2224             raise error.Abort('merge in progress')
       
  2225         if not opts['merge']:
       
  2226             try:
       
  2227                 cmdutil.bailifchanged(repo)
       
  2228             except error.Abort as exc:
       
  2229                 exc.hint = _('do you want --merge?')
       
  2230                 raise
       
  2231 
       
  2232         parents = wparents[0].parents()
       
  2233         topic = getattr(repo, 'currenttopic', '')
       
  2234         if topic and not opts.get("no_topic", False):
       
  2235             parents = [ctx for ctx in parents if ctx.topic() == topic]
       
  2236         displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
       
  2237         if not parents:
       
  2238             ui.warn(_('no parent in topic "%s"\n') % topic)
       
  2239             ui.warn(_('(do you want --no-topic)\n'))
       
  2240         elif len(parents) == 1:
       
  2241             p = parents[0]
       
  2242             bm = bmactive(repo)
       
  2243             shouldmove = opts.get('move_bookmark') and bm is not None
       
  2244             if dryrunopt:
       
  2245                 ui.write(('hg update %s;\n' % p.rev()))
       
  2246                 if shouldmove:
       
  2247                     ui.write(('hg bookmark %s -r %s;\n' % (bm, p.rev())))
       
  2248             else:
       
  2249                 ret = hg.update(repo, p.rev())
       
  2250                 if not ret:
       
  2251                     tr = lock = None
       
  2252                     try:
       
  2253                         lock = repo.lock()
       
  2254                         tr = repo.transaction('previous')
       
  2255                         if shouldmove:
       
  2256                             repo._bookmarks[bm] = p.node()
       
  2257                             repo._bookmarks.recordchange(tr)
       
  2258                         else:
       
  2259                             bmdeactivate(repo)
       
  2260                         tr.close()
       
  2261                     finally:
       
  2262                         lockmod.release(tr, lock)
       
  2263 
       
  2264             displayer.show(p)
       
  2265             return 0
       
  2266         else:
       
  2267             for p in parents:
       
  2268                 displayer.show(p)
       
  2269             ui.warn(_('multiple parents, explicitly update to one\n'))
       
  2270             return 1
       
  2271     finally:
       
  2272         lockmod.release(wlock)
       
  2273 
       
  2274 @command('^next',
       
  2275          [('B', 'move-bookmark', False,
       
  2276              _('move active bookmark after update')),
       
  2277           ('', 'merge', False, _('bring uncommitted change along')),
       
  2278           ('', 'evolve', False, _('evolve the next changeset if necessary')),
       
  2279           ('', 'no-topic', False, _('ignore topic and move topologically')),
       
  2280           ('n', 'dry-run', False,
       
  2281               _('do not perform actions, just print what would be done'))],
       
  2282               '[OPTION]...')
       
  2283 def cmdnext(ui, repo, **opts):
       
  2284     """update to next child revision
       
  2285 
       
  2286     Use the ``--evolve`` flag to evolve unstable children on demand.
       
  2287 
       
  2288     Displays the summary line of the destination for clarity.
       
  2289     """
       
  2290     wlock = None
       
  2291     dryrunopt = opts['dry_run']
       
  2292     if not dryrunopt:
       
  2293         wlock = repo.wlock()
       
  2294     try:
       
  2295         wkctx = repo[None]
       
  2296         wparents = wkctx.parents()
       
  2297         if len(wparents) != 1:
       
  2298             raise error.Abort('merge in progress')
       
  2299         if not opts['merge']:
       
  2300             try:
       
  2301                 cmdutil.bailifchanged(repo)
       
  2302             except error.Abort as exc:
       
  2303                 exc.hint = _('do you want --merge?')
       
  2304                 raise
       
  2305 
       
  2306         children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
       
  2307         topic = getattr(repo, 'currenttopic', '')
       
  2308         filtered = []
       
  2309         if topic and not opts.get("no_topic", False):
       
  2310             filtered = [ctx for ctx in children if ctx.topic() != topic]
       
  2311             # XXX N-square membership on children
       
  2312             children = [ctx for ctx in children if ctx not in filtered]
       
  2313         displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
       
  2314         if len(children) == 1:
       
  2315             c = children[0]
       
  2316             bm = bmactive(repo)
       
  2317             shouldmove = opts.get('move_bookmark') and bm is not None
       
  2318             if dryrunopt:
       
  2319                 ui.write(('hg update %s;\n' % c.rev()))
       
  2320                 if shouldmove:
       
  2321                     ui.write(('hg bookmark %s -r %s;\n' % (bm, c.rev())))
       
  2322             else:
       
  2323                 ret = hg.update(repo, c.rev())
       
  2324                 if not ret:
       
  2325                     lock = tr = None
       
  2326                     try:
       
  2327                         lock = repo.lock()
       
  2328                         tr = repo.transaction('next')
       
  2329                         if shouldmove:
       
  2330                             repo._bookmarks[bm] = c.node()
       
  2331                             repo._bookmarks.recordchange(tr)
       
  2332                         else:
       
  2333                             bmdeactivate(repo)
       
  2334                         tr.close()
       
  2335                     finally:
       
  2336                         lockmod.release(tr, lock)
       
  2337             displayer.show(c)
       
  2338             result = 0
       
  2339         elif children:
       
  2340             ui.warn(_("ambigious next changeset:\n"))
       
  2341             for c in children:
       
  2342                 displayer.show(c)
       
  2343             ui.warn(_('explicitly update to one of them\n'))
       
  2344             result = 1
       
  2345         else:
       
  2346             aspchildren = _aspiringchildren(repo, [repo['.'].rev()])
       
  2347             if topic:
       
  2348                 filtered.extend(repo[c] for c in children
       
  2349                                 if repo[c].topic() != topic)
       
  2350                 # XXX N-square membership on children
       
  2351                 aspchildren = [ctx for ctx in aspchildren if ctx not in filtered]
       
  2352             if not opts['evolve'] or not aspchildren:
       
  2353                 if filtered:
       
  2354                     ui.warn(_('no children on topic "%s"\n') % topic)
       
  2355                     ui.warn(_('do you want --no-topic\n'))
       
  2356                 else:
       
  2357                     ui.warn(_('no children\n'))
       
  2358                 if aspchildren:
       
  2359                     msg = _('(%i unstable changesets to be evolved here, '
       
  2360                             'do you want --evolve?)\n')
       
  2361                     ui.warn(msg % len(aspchildren))
       
  2362                 result = 1
       
  2363             elif 1 < len(aspchildren):
       
  2364                 ui.warn(_("ambigious next (unstable) changeset:\n"))
       
  2365                 for c in aspchildren:
       
  2366                     displayer.show(repo[c])
       
  2367                 ui.warn(_("(run 'hg evolve --rev REV' on one of them)\n"))
       
  2368                 return 1
       
  2369             else:
       
  2370                 cmdutil.bailifchanged(repo)
       
  2371                 result = _solveone(ui, repo, repo[aspchildren[0]], dryrunopt,
       
  2372                                    False, lambda:None, category='unstable')
       
  2373                 if not result:
       
  2374                     ui.status(_('working directory now at %s\n') % repo['.'])
       
  2375                 return result
       
  2376             return 1
       
  2377         return result
       
  2378     finally:
       
  2379         lockmod.release(wlock)
       
  2380 
       
  2381 def _reachablefrombookmark(repo, revs, bookmarks):
       
  2382     """filter revisions and bookmarks reachable from the given bookmark
       
  2383     yoinked from mq.py
       
  2384     """
       
  2385     repomarks = repo._bookmarks
       
  2386     if not bookmarks.issubset(repomarks):
       
  2387         raise error.Abort(_("bookmark '%s' not found") %
       
  2388             ','.join(sorted(bookmarks - set(repomarks.keys()))))
       
  2389 
       
  2390     # If the requested bookmark is not the only one pointing to a
       
  2391     # a revision we have to only delete the bookmark and not strip
       
  2392     # anything. revsets cannot detect that case.
       
  2393     nodetobookmarks = {}
       
  2394     for mark, node in repomarks.iteritems():
       
  2395         nodetobookmarks.setdefault(node, []).append(mark)
       
  2396     for marks in nodetobookmarks.values():
       
  2397         if bookmarks.issuperset(marks):
       
  2398            if util.safehasattr(repair, 'stripbmrevset'):
       
  2399                rsrevs = repair.stripbmrevset(repo, marks[0])
       
  2400            else:
       
  2401                rsrevs = repo.revs("ancestors(bookmark(%s)) - "
       
  2402                                   "ancestors(head() and not bookmark(%s)) - "
       
  2403                                   "ancestors(bookmark() and not bookmark(%s)) - "
       
  2404                                   "obsolete()",
       
  2405                                   marks[0], marks[0], marks[0])
       
  2406            revs = set(revs)
       
  2407            revs.update(set(rsrevs))
       
  2408            revs = sorted(revs)
       
  2409     return repomarks, revs
       
  2410 
       
  2411 def _deletebookmark(repo, repomarks, bookmarks):
       
  2412     wlock = lock = tr = None
       
  2413     try:
       
  2414         wlock = repo.wlock()
       
  2415         lock = repo.lock()
       
  2416         tr = repo.transaction('prune')
       
  2417         for bookmark in bookmarks:
       
  2418             del repomarks[bookmark]
       
  2419         repomarks.recordchange(tr)
       
  2420         tr.close()
       
  2421         for bookmark in sorted(bookmarks):
       
  2422             repo.ui.write(_("bookmark '%s' deleted\n") % bookmark)
       
  2423     finally:
       
  2424         lockmod.release(tr, lock, wlock)
       
  2425 
       
  2426 
       
  2427 
       
  2428 def _getmetadata(**opts):
       
  2429     metadata = {}
       
  2430     date = opts.get('date')
       
  2431     user = opts.get('user')
       
  2432     if date:
       
  2433         metadata['date'] = '%i %i' % util.parsedate(date)
       
  2434     if user:
       
  2435         metadata['user'] = user
       
  2436     return metadata
       
  2437 
       
  2438 
       
  2439 @command('^prune|obsolete',
       
  2440     [('n', 'new', [], _("successor changeset (DEPRECATED)")),
       
  2441      ('s', 'succ', [], _("successor changeset")),
       
  2442      ('r', 'rev', [], _("revisions to prune")),
       
  2443      ('k', 'keep', None, _("does not modify working copy during prune")),
       
  2444      ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
       
  2445      ('', 'fold', False,
       
  2446         _("record a fold (multiple precursors, one successors)")),
       
  2447      ('', 'split', False,
       
  2448         _("record a split (on precursor, multiple successors)")),
       
  2449      ('B', 'bookmark', [], _("remove revs only reachable from given"
       
  2450                              " bookmark"))] + metadataopts,
       
  2451     _('[OPTION] [-r] REV...'))
       
  2452     # -U  --noupdate option to prevent wc update and or bookmarks update ?
       
  2453 def cmdprune(ui, repo, *revs, **opts):
       
  2454     """hide changesets by marking them obsolete
       
  2455 
       
  2456     Pruned changesets are obsolete with no successors. If they also have no
       
  2457     descendants, they are hidden (invisible to all commands).
       
  2458 
       
  2459     Non-obsolete descendants of pruned changesets become "unstable". Use :hg:`evolve`
       
  2460     to handle this situation.
       
  2461 
       
  2462     When you prune the parent of your working copy, Mercurial updates the working
       
  2463     copy to a non-obsolete parent.
       
  2464 
       
  2465     You can use ``--succ`` to tell Mercurial that a newer version (successor) of the
       
  2466     pruned changeset exists. Mercurial records successor revisions in obsolescence
       
  2467     markers.
       
  2468 
       
  2469     You can use the ``--biject`` option to specify a 1-1 mapping (bijection) between
       
  2470     revisions to pruned (precursor) and successor changesets. This option may be
       
  2471     removed in a future release (with the functionality provided automatically).
       
  2472 
       
  2473     If you specify multiple revisions in ``--succ``, you are recording a "split" and
       
  2474     must acknowledge it by passing ``--split``. Similarly, when you prune multiple
       
  2475     changesets with a single successor, you must pass the ``--fold`` option.
       
  2476     """
       
  2477     revs = scmutil.revrange(repo, list(revs) + opts.get('rev'))
       
  2478     succs = opts['new'] + opts['succ']
       
  2479     bookmarks = set(opts.get('bookmark'))
       
  2480     metadata = _getmetadata(**opts)
       
  2481     biject = opts.get('biject')
       
  2482     fold = opts.get('fold')
       
  2483     split = opts.get('split')
       
  2484 
       
  2485     options = [o for o in ('biject', 'fold', 'split') if opts.get(o)]
       
  2486     if 1 < len(options):
       
  2487         raise error.Abort(_("can only specify one of %s") % ', '.join(options))
       
  2488 
       
  2489     if bookmarks:
       
  2490         repomarks, revs = _reachablefrombookmark(repo, revs, bookmarks)
       
  2491         if not revs:
       
  2492             # no revisions to prune - delete bookmark immediately
       
  2493             _deletebookmark(repo, repomarks, bookmarks)
       
  2494 
       
  2495     if not revs:
       
  2496         raise error.Abort(_('nothing to prune'))
       
  2497 
       
  2498     wlock = lock = tr = None
       
  2499     try:
       
  2500         wlock = repo.wlock()
       
  2501         lock = repo.lock()
       
  2502         tr = repo.transaction('prune')
       
  2503         # defines pruned changesets
       
  2504         precs = []
       
  2505         revs.sort()
       
  2506         for p in revs:
       
  2507             cp = repo[p]
       
  2508             if not cp.mutable():
       
  2509                 # note: createmarkers() would have raised something anyway
       
  2510                 raise error.Abort('cannot prune immutable changeset: %s' % cp,
       
  2511                                  hint="see 'hg help phases' for details")
       
  2512             precs.append(cp)
       
  2513         if not precs:
       
  2514             raise error.Abort('nothing to prune')
       
  2515 
       
  2516         if _disallowednewunstable(repo, revs):
       
  2517             raise error.Abort(_("cannot prune in the middle of a stack"),
       
  2518                         hint = _("new unstable changesets are not allowed"))
       
  2519 
       
  2520         # defines successors changesets
       
  2521         sucs = scmutil.revrange(repo, succs)
       
  2522         sucs.sort()
       
  2523         sucs = tuple(repo[n] for n in sucs)
       
  2524         if not biject and len(sucs) > 1 and len(precs) > 1:
       
  2525             msg = "Can't use multiple successors for multiple precursors"
       
  2526             hint = _("use --biject to mark a series as a replacement"
       
  2527                      " for another")
       
  2528             raise error.Abort(msg, hint=hint)
       
  2529         elif biject and len(sucs) != len(precs):
       
  2530             msg = "Can't use %d successors for %d precursors" \
       
  2531                 % (len(sucs), len(precs))
       
  2532             raise error.Abort(msg)
       
  2533         elif (len(precs) == 1 and len(sucs) > 1) and not split:
       
  2534             msg = "please add --split if you want to do a split"
       
  2535             raise error.Abort(msg)
       
  2536         elif len(sucs) == 1 and len(precs) > 1 and not fold:
       
  2537             msg = "please add --fold if you want to do a fold"
       
  2538             raise error.Abort(msg)
       
  2539         elif biject:
       
  2540             relations = [(p, (s,)) for p, s in zip(precs, sucs)]
       
  2541         else:
       
  2542             relations = [(p, sucs) for p in precs]
       
  2543 
       
  2544         wdp = repo['.']
       
  2545 
       
  2546         if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
       
  2547             # '.' killed, so update to the successor
       
  2548             newnode = sucs[0]
       
  2549         else:
       
  2550             # update to an unkilled parent
       
  2551             newnode = wdp
       
  2552 
       
  2553             while newnode in precs or newnode.obsolete():
       
  2554                 newnode = newnode.parents()[0]
       
  2555 
       
  2556 
       
  2557         if newnode.node() != wdp.node():
       
  2558             if opts.get('keep', False):
       
  2559                 # This is largely the same as the implementation in
       
  2560                 # strip.stripcmd(). We might want to refactor this somewhere
       
  2561                 # common at some point.
       
  2562 
       
  2563                 # only reset the dirstate for files that would actually change
       
  2564                 # between the working context and uctx
       
  2565                 descendantrevs = repo.revs("%d::." % newnode.rev())
       
  2566                 changedfiles = []
       
  2567                 for rev in descendantrevs:
       
  2568                     # blindly reset the files, regardless of what actually
       
  2569                     # changed
       
  2570                     changedfiles.extend(repo[rev].files())
       
  2571 
       
  2572                 # reset files that only changed in the dirstate too
       
  2573                 dirstate = repo.dirstate
       
  2574                 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
       
  2575                 changedfiles.extend(dirchanges)
       
  2576                 repo.dirstate.rebuild(newnode.node(), newnode.manifest(),
       
  2577                                       changedfiles)
       
  2578                 writedirstate(dirstate, tr)
       
  2579             else:
       
  2580                 bookactive = bmactive(repo)
       
  2581                 # Active bookmark that we don't want to delete (with -B option)
       
  2582                 # we deactivate and move it before the update and reactivate it
       
  2583                 # after
       
  2584                 movebookmark = bookactive and not bookmarks
       
  2585                 if movebookmark:
       
  2586                     bmdeactivate(repo)
       
  2587                     repo._bookmarks[bookactive] = newnode.node()
       
  2588                     repo._bookmarks.recordchange(tr)
       
  2589                 commands.update(ui, repo, newnode.rev())
       
  2590                 ui.status(_('working directory now at %s\n') % newnode)
       
  2591                 if movebookmark:
       
  2592                     bmactivate(repo, bookactive)
       
  2593 
       
  2594         # update bookmarks
       
  2595         if bookmarks:
       
  2596             _deletebookmark(repo, repomarks, bookmarks)
       
  2597 
       
  2598         # create markers
       
  2599         obsolete.createmarkers(repo, relations, metadata=metadata)
       
  2600 
       
  2601         # informs that changeset have been pruned
       
  2602         ui.status(_('%i changesets pruned\n') % len(precs))
       
  2603 
       
  2604         for ctx in repo.unfiltered().set('bookmark() and %ld', precs):
       
  2605             # used to be:
       
  2606             #
       
  2607             #   ldest = list(repo.set('max((::%d) - obsolete())', ctx))
       
  2608             #   if ldest:
       
  2609             #      c = ldest[0]
       
  2610             #
       
  2611             # but then revset took a lazy arrow in the knee and became much
       
  2612             # slower. The new forms makes as much sense and a much faster.
       
  2613             for dest in ctx.ancestors():
       
  2614                 if not dest.obsolete():
       
  2615                     updatebookmarks = _bookmarksupdater(repo, ctx.node(), tr)
       
  2616                     updatebookmarks(dest.node())
       
  2617                     break
       
  2618 
       
  2619         tr.close()
       
  2620     finally:
       
  2621         lockmod.release(tr, lock, wlock)
       
  2622 
       
  2623 @command('amend|refresh',
       
  2624     [('A', 'addremove', None,
       
  2625      _('mark new/missing files as added/removed before committing')),
       
  2626     ('e', 'edit', False, _('invoke editor on commit messages')),
       
  2627     ('', 'close-branch', None,
       
  2628      _('mark a branch as closed, hiding it from the branch list')),
       
  2629     ('s', 'secret', None, _('use the secret phase for committing')),
       
  2630     ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt,
       
  2631     _('[OPTION]... [FILE]...'))
       
  2632 def amend(ui, repo, *pats, **opts):
       
  2633     """combine a changeset with updates and replace it with a new one
       
  2634 
       
  2635     Commits a new changeset incorporating both the changes to the given files
       
  2636     and all the changes from the current parent changeset into the repository.
       
  2637 
       
  2638     See :hg:`commit` for details about committing changes.
       
  2639 
       
  2640     If you don't specify -m, the parent's message will be reused.
       
  2641 
       
  2642     Behind the scenes, Mercurial first commits the update as a regular child
       
  2643     of the current parent. Then it creates a new commit on the parent's parents
       
  2644     with the updated contents. Then it changes the working copy parent to this
       
  2645     new combined changeset. Finally, the old changeset and its update are hidden
       
  2646     from :hg:`log` (unless you use --hidden with log).
       
  2647 
       
  2648     Returns 0 on success, 1 if nothing changed.
       
  2649     """
       
  2650     opts = opts.copy()
       
  2651     edit = opts.pop('edit', False)
       
  2652     log = opts.get('logfile')
       
  2653     opts['amend'] = True
       
  2654     if not (edit or opts['message'] or log):
       
  2655         opts['message'] = repo['.'].description()
       
  2656     _resolveoptions(ui, opts)
       
  2657     _alias, commitcmd = cmdutil.findcmd('commit', commands.table)
       
  2658     return commitcmd[0](ui, repo, *pats, **opts)
       
  2659 
       
  2660 
       
  2661 def _touchedbetween(repo, source, dest, match=None):
       
  2662     touched = set()
       
  2663     for files in repo.status(source, dest, match=match)[:3]:
       
  2664         touched.update(files)
       
  2665     return touched
       
  2666 
       
  2667 def _commitfiltered(repo, ctx, match, target=None):
       
  2668     """Recommit ctx with changed files not in match. Return the new
       
  2669     node identifier, or None if nothing changed.
       
  2670     """
       
  2671     base = ctx.p1()
       
  2672     if target is None:
       
  2673         target = base
       
  2674     # ctx
       
  2675     initialfiles = _touchedbetween(repo, base, ctx)
       
  2676     if base == target:
       
  2677         affected = set(f for f in initialfiles if match(f))
       
  2678         newcontent = set()
       
  2679     else:
       
  2680         affected = _touchedbetween(repo, target, ctx, match=match)
       
  2681         newcontent = _touchedbetween(repo, target, base, match=match)
       
  2682     # The commit touchs all existing files
       
  2683     # + all file that needs a new content
       
  2684     # - the file affected bny uncommit with the same content than base.
       
  2685     files = (initialfiles - affected) | newcontent
       
  2686     if not newcontent and files == initialfiles:
       
  2687         return None
       
  2688 
       
  2689     # Filter copies
       
  2690     copied = copies.pathcopies(target, ctx)
       
  2691     copied = dict((dst, src) for dst, src in copied.iteritems()
       
  2692                   if dst in files)
       
  2693     def filectxfn(repo, memctx, path, contentctx=ctx, redirect=newcontent):
       
  2694         if path in redirect:
       
  2695             return filectxfn(repo, memctx, path, contentctx=target, redirect=())
       
  2696         if path not in contentctx:
       
  2697             return None
       
  2698         fctx = contentctx[path]
       
  2699         flags = fctx.flags()
       
  2700         mctx = memfilectx(repo, fctx.path(), fctx.data(),
       
  2701                           islink='l' in flags,
       
  2702                           isexec='x' in flags,
       
  2703                           copied=copied.get(path))
       
  2704         return mctx
       
  2705 
       
  2706     new = context.memctx(repo,
       
  2707                          parents=[base.node(), node.nullid],
       
  2708                          text=ctx.description(),
       
  2709                          files=files,
       
  2710                          filectxfn=filectxfn,
       
  2711                          user=ctx.user(),
       
  2712                          date=ctx.date(),
       
  2713                          extra=ctx.extra())
       
  2714     # commitctx always create a new revision, no need to check
       
  2715     newid = repo.commitctx(new)
       
  2716     return newid
       
  2717 
       
  2718 def _uncommitdirstate(repo, oldctx, match):
       
  2719     """Fix the dirstate after switching the working directory from
       
  2720     oldctx to a copy of oldctx not containing changed files matched by
       
  2721     match.
       
  2722     """
       
  2723     ctx = repo['.']
       
  2724     ds = repo.dirstate
       
  2725     copies = dict(ds.copies())
       
  2726     m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
       
  2727     for f in m:
       
  2728         if ds[f] == 'r':
       
  2729             # modified + removed -> removed
       
  2730             continue
       
  2731         ds.normallookup(f)
       
  2732 
       
  2733     for f in a:
       
  2734         if ds[f] == 'r':
       
  2735             # added + removed -> unknown
       
  2736             ds.drop(f)
       
  2737         elif ds[f] != 'a':
       
  2738             ds.add(f)
       
  2739 
       
  2740     for f in r:
       
  2741         if ds[f] == 'a':
       
  2742             # removed + added -> normal
       
  2743             ds.normallookup(f)
       
  2744         elif ds[f] != 'r':
       
  2745             ds.remove(f)
       
  2746 
       
  2747     # Merge old parent and old working dir copies
       
  2748     oldcopies = {}
       
  2749     for f in (m + a):
       
  2750         src = oldctx[f].renamed()
       
  2751         if src:
       
  2752             oldcopies[f] = src[0]
       
  2753     oldcopies.update(copies)
       
  2754     copies = dict((dst, oldcopies.get(src, src))
       
  2755                   for dst, src in oldcopies.iteritems())
       
  2756     # Adjust the dirstate copies
       
  2757     for dst, src in copies.iteritems():
       
  2758         if (src not in ctx or dst in ctx or ds[dst] != 'a'):
       
  2759             src = None
       
  2760         ds.copy(src, dst)
       
  2761 
       
  2762 @command('^uncommit',
       
  2763     [('a', 'all', None, _('uncommit all changes when no arguments given')),
       
  2764      ('r', 'rev', '', _('revert commit content to REV instead')),
       
  2765      ] + commands.walkopts,
       
  2766     _('[OPTION]... [NAME]'))
       
  2767 def uncommit(ui, repo, *pats, **opts):
       
  2768     """move changes from parent revision to working directory
       
  2769 
       
  2770     Changes to selected files in the checked out revision appear again as
       
  2771     uncommitted changed in the working directory. A new revision
       
  2772     without the selected changes is created, becomes the checked out
       
  2773     revision, and obsoletes the previous one.
       
  2774 
       
  2775     The --include option specifies patterns to uncommit.
       
  2776     The --exclude option specifies patterns to keep in the commit.
       
  2777 
       
  2778     The --rev argument let you change the commit file to a content of another
       
  2779     revision. It still does not change the content of your file in the working
       
  2780     directory.
       
  2781 
       
  2782     Return 0 if changed files are uncommitted.
       
  2783     """
       
  2784 
       
  2785     wlock = lock = tr = None
       
  2786     try:
       
  2787         wlock = repo.wlock()
       
  2788         lock = repo.lock()
       
  2789         wctx = repo[None]
       
  2790         if len(wctx.parents()) <= 0:
       
  2791             raise error.Abort(_("cannot uncommit null changeset"))
       
  2792         if len(wctx.parents()) > 1:
       
  2793             raise error.Abort(_("cannot uncommit while merging"))
       
  2794         old = repo['.']
       
  2795         if old.phase() == phases.public:
       
  2796             raise error.Abort(_("cannot rewrite immutable changeset"))
       
  2797         if len(old.parents()) > 1:
       
  2798             raise error.Abort(_("cannot uncommit merge changeset"))
       
  2799         oldphase = old.phase()
       
  2800 
       
  2801 
       
  2802         rev = None
       
  2803         if opts.get('rev'):
       
  2804             rev = scmutil.revsingle(repo, opts.get('rev'))
       
  2805             ctx = repo[None]
       
  2806             if ctx.p1() == rev or ctx.p2() == rev:
       
  2807                 raise error.Abort(_("cannot uncommit to parent changeset"))
       
  2808 
       
  2809         onahead = old.rev() in repo.changelog.headrevs()
       
  2810         disallowunstable = not obsolete.isenabled(repo,
       
  2811                                                   obsolete.allowunstableopt)
       
  2812         if disallowunstable and not onahead:
       
  2813             raise error.Abort(_("cannot uncommit in the middle of a stack"))
       
  2814 
       
  2815         # Recommit the filtered changeset
       
  2816         tr = repo.transaction('uncommit')
       
  2817         updatebookmarks = _bookmarksupdater(repo, old.node(), tr)
       
  2818         newid = None
       
  2819         includeorexclude = opts.get('include') or opts.get('exclude')
       
  2820         if (pats or includeorexclude or opts.get('all')):
       
  2821             match = scmutil.match(old, pats, opts)
       
  2822             newid = _commitfiltered(repo, old, match, target=rev)
       
  2823         if newid is None:
       
  2824             raise error.Abort(_('nothing to uncommit'),
       
  2825                              hint=_("use --all to uncommit all files"))
       
  2826         # Move local changes on filtered changeset
       
  2827         obsolete.createmarkers(repo, [(old, (repo[newid],))])
       
  2828         phases.retractboundary(repo, tr, oldphase, [newid])
       
  2829         repo.dirstate.beginparentchange()
       
  2830         repo.dirstate.setparents(newid, node.nullid)
       
  2831         _uncommitdirstate(repo, old, match)
       
  2832         repo.dirstate.endparentchange()
       
  2833         updatebookmarks(newid)
       
  2834         if not repo[newid].files():
       
  2835             ui.warn(_("new changeset is empty\n"))
       
  2836             ui.status(_("(use 'hg prune .' to remove it)\n"))
       
  2837         tr.close()
       
  2838     finally:
       
  2839         lockmod.release(tr, lock, wlock)
       
  2840 
       
  2841 @eh.wrapcommand('commit')
       
  2842 def commitwrapper(orig, ui, repo, *arg, **kwargs):
       
  2843     tr = None
       
  2844     if kwargs.get('amend', False):
       
  2845         wlock = lock = None
       
  2846     else:
       
  2847         wlock = repo.wlock()
       
  2848         lock = repo.lock()
       
  2849     try:
       
  2850         obsoleted = kwargs.get('obsolete', [])
       
  2851         if obsoleted:
       
  2852             obsoleted = repo.set('%lr', obsoleted)
       
  2853         result = orig(ui, repo, *arg, **kwargs)
       
  2854         if not result: # commit succeeded
       
  2855             new = repo['-1']
       
  2856             oldbookmarks = []
       
  2857             markers = []
       
  2858             for old in obsoleted:
       
  2859                 oldbookmarks.extend(repo.nodebookmarks(old.node()))
       
  2860                 markers.append((old, (new,)))
       
  2861             if markers:
       
  2862                 obsolete.createmarkers(repo, markers)
       
  2863             for book in oldbookmarks:
       
  2864                 repo._bookmarks[book] = new.node()
       
  2865             if oldbookmarks:
       
  2866                 if not wlock:
       
  2867                     wlock = repo.wlock()
       
  2868                 if not lock:
       
  2869                     lock = repo.lock()
       
  2870                 tr = repo.transaction('commit')
       
  2871                 repo._bookmarks.recordchange(tr)
       
  2872                 tr.close()
       
  2873         return result
       
  2874     finally:
       
  2875         lockmod.release(tr, lock, wlock)
       
  2876 
       
  2877 @command('^split',
       
  2878     [('r', 'rev', [], _("revision to split")),
       
  2879     ] + commitopts + commitopts2,
       
  2880     _('hg split [OPTION]... [-r] REV'))
       
  2881 def cmdsplit(ui, repo, *revs, **opts):
       
  2882     """split a changeset into smaller changesets
       
  2883 
       
  2884     By default, split the current revision by prompting for all its hunks to be
       
  2885     redistributed into new changesets.
       
  2886 
       
  2887     Use --rev to split a given changeset instead.
       
  2888     """
       
  2889     tr = wlock = lock = None
       
  2890     newcommits = []
       
  2891 
       
  2892     revarg = (list(revs) + opts.get('rev')) or ['.']
       
  2893     if len(revarg) != 1:
       
  2894         msg = _("more than one revset is given")
       
  2895         hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both")
       
  2896         raise error.Abort(msg, hint=hnt)
       
  2897 
       
  2898     rev = scmutil.revsingle(repo, revarg[0])
       
  2899     try:
       
  2900         wlock = repo.wlock()
       
  2901         lock = repo.lock()
       
  2902         cmdutil.bailifchanged(repo)
       
  2903         tr = repo.transaction('split')
       
  2904         ctx = repo[rev]
       
  2905         r = ctx.rev()
       
  2906         disallowunstable = not obsolete.isenabled(repo,
       
  2907                                                   obsolete.allowunstableopt)
       
  2908         if disallowunstable:
       
  2909             # XXX We should check head revs
       
  2910             if repo.revs("(%d::) - %d", rev, rev):
       
  2911                 raise error.Abort(_("cannot split commit: %s not a head") % ctx)
       
  2912 
       
  2913         if len(ctx.parents()) > 1:
       
  2914             raise error.Abort(_("cannot split merge commits"))
       
  2915         prev = ctx.p1()
       
  2916         bmupdate = _bookmarksupdater(repo, ctx.node(), tr)
       
  2917         bookactive = bmactive(repo)
       
  2918         if bookactive is not None:
       
  2919             repo.ui.status(_("(leaving bookmark %s)\n") % bmactive(repo))
       
  2920         bmdeactivate(repo)
       
  2921         hg.update(repo, prev)
       
  2922 
       
  2923         commands.revert(ui, repo, rev=r, all=True)
       
  2924         def haschanges():
       
  2925             modified, added, removed, deleted = repo.status()[:4]
       
  2926             return modified or added or removed or deleted
       
  2927         msg = ("HG: This is the original pre-split commit message. "
       
  2928                "Edit it as appropriate.\n\n")
       
  2929         msg += ctx.description()
       
  2930         opts['message'] = msg
       
  2931         opts['edit'] = True
       
  2932         while haschanges():
       
  2933             pats = ()
       
  2934             cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
       
  2935                              cmdutil.recordfilter, *pats, **opts)
       
  2936             # TODO: Does no seem like the best way to do this
       
  2937             # We should make dorecord return the newly created commit
       
  2938             newcommits.append(repo['.'])
       
  2939             if haschanges():
       
  2940                 if ui.prompt('Done splitting? [yN]', default='n') == 'y':
       
  2941                     commands.commit(ui, repo, **opts)
       
  2942                     newcommits.append(repo['.'])
       
  2943                     break
       
  2944             else:
       
  2945                 ui.status(_("no more change to split\n"))
       
  2946 
       
  2947         if newcommits:
       
  2948             tip = repo[newcommits[-1]]
       
  2949             bmupdate(tip.node())
       
  2950             if bookactive is not None:
       
  2951                 bmactivate(repo, bookactive)
       
  2952             obsolete.createmarkers(repo, [(repo[r], newcommits)])
       
  2953         tr.close()
       
  2954     finally:
       
  2955         lockmod.release(tr, lock, wlock)
       
  2956 
       
  2957 
       
  2958 @eh.wrapcommand('strip', extension='strip', opts=[
       
  2959     ('', 'bundle', None, _("delete the commit entirely and move it to a "
       
  2960         "backup bundle")),
       
  2961     ])
       
  2962 def stripwrapper(orig, ui, repo, *revs, **kwargs):
       
  2963     if (not ui.configbool('experimental', 'prunestrip') or
       
  2964         kwargs.get('bundle', False)):
       
  2965         return orig(ui, repo, *revs, **kwargs)
       
  2966 
       
  2967     if kwargs.get('force'):
       
  2968         ui.warn(_("warning: --force has no effect during strip with evolve "
       
  2969                   "enabled\n"))
       
  2970     if kwargs.get('no_backup', False):
       
  2971         ui.warn(_("warning: --no-backup has no effect during strips with "
       
  2972                   "evolve enabled\n"))
       
  2973 
       
  2974     revs = list(revs) + kwargs.pop('rev', [])
       
  2975     revs = set(scmutil.revrange(repo, revs))
       
  2976     revs = repo.revs("(%ld)::", revs)
       
  2977     kwargs['rev'] = []
       
  2978     kwargs['new'] = []
       
  2979     kwargs['succ'] = []
       
  2980     kwargs['biject'] = False
       
  2981     return cmdprune(ui, repo, *revs, **kwargs)
       
  2982 
       
  2983 @command('^touch',
       
  2984     [('r', 'rev', [], 'revision to update'),
       
  2985      ('D', 'duplicate', False,
       
  2986       'do not mark the new revision as successor of the old one'),
       
  2987      ('A', 'allowdivergence', False,
       
  2988       'mark the new revision as successor of the old one potentially creating '
       
  2989       'divergence')],
       
  2990     # allow to choose the seed ?
       
  2991     _('[-r] revs'))
       
  2992 def touch(ui, repo, *revs, **opts):
       
  2993     """create successors that are identical to their predecessors except
       
  2994     for the changeset ID
       
  2995 
       
  2996     This is used to "resurrect" changesets
       
  2997     """
       
  2998     duplicate = opts['duplicate']
       
  2999     allowdivergence = opts['allowdivergence']
       
  3000     revs = list(revs)
       
  3001     revs.extend(opts['rev'])
       
  3002     if not revs:
       
  3003         revs = ['.']
       
  3004     revs = scmutil.revrange(repo, revs)
       
  3005     if not revs:
       
  3006         ui.write_err('no revision to touch\n')
       
  3007         return 1
       
  3008     if not duplicate and repo.revs('public() and %ld', revs):
       
  3009         raise error.Abort("can't touch public revision")
       
  3010     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
       
  3011     wlock = lock = tr = None
       
  3012     try:
       
  3013         wlock = repo.wlock()
       
  3014         lock = repo.lock()
       
  3015         tr = repo.transaction('touch')
       
  3016         revs.sort() # ensure parent are run first
       
  3017         newmapping = {}
       
  3018         for r in revs:
       
  3019             ctx = repo[r]
       
  3020             extra = ctx.extra().copy()
       
  3021             extra['__touch-noise__'] = random.randint(0, 0xffffffff)
       
  3022             # search for touched parent
       
  3023             p1 = ctx.p1().node()
       
  3024             p2 = ctx.p2().node()
       
  3025             p1 = newmapping.get(p1, p1)
       
  3026             p2 = newmapping.get(p2, p2)
       
  3027 
       
  3028             if not (duplicate or allowdivergence):
       
  3029                 # The user hasn't yet decided what to do with the revived
       
  3030                 # cset, let's ask
       
  3031                 sset = obsolete.successorssets(repo, ctx.node())
       
  3032                 nodivergencerisk = len(sset) == 0 or (
       
  3033                                     len(sset) == 1 and
       
  3034                                     len(sset[0]) == 1 and
       
  3035                                     repo[sset[0][0]].rev() == ctx.rev()
       
  3036                                    )
       
  3037                 if nodivergencerisk:
       
  3038                     duplicate = False
       
  3039                 else:
       
  3040                     displayer.show(ctx)
       
  3041                     index = ui.promptchoice(
       
  3042                         _("reviving this changeset will create divergence"
       
  3043                         " unless you make a duplicate.\n(a)llow divergence or"
       
  3044                         " (d)uplicate the changeset? $$ &Allowdivergence $$ "
       
  3045                         "&Duplicate"), 0)
       
  3046                     choice = ['allowdivergence', 'duplicate'][index]
       
  3047                     if choice == 'allowdivergence':
       
  3048                         duplicate = False
       
  3049                     else:
       
  3050                         duplicate = True
       
  3051 
       
  3052             new, unusedvariable = rewrite(repo, ctx, [], ctx,
       
  3053                                           [p1, p2],
       
  3054                                           commitopts={'extra': extra})
       
  3055             # store touched version to help potential children
       
  3056             newmapping[ctx.node()] = new
       
  3057 
       
  3058             if not duplicate:
       
  3059                 obsolete.createmarkers(repo, [(ctx, (repo[new],))])
       
  3060             phases.retractboundary(repo, tr, ctx.phase(), [new])
       
  3061             if ctx in repo[None].parents():
       
  3062                 repo.dirstate.beginparentchange()
       
  3063                 repo.dirstate.setparents(new, node.nullid)
       
  3064                 repo.dirstate.endparentchange()
       
  3065         tr.close()
       
  3066     finally:
       
  3067         lockmod.release(tr, lock, wlock)
       
  3068 
       
  3069 @command('^fold|squash',
       
  3070     [('r', 'rev', [], _("revision to fold")),
       
  3071      ('', 'exact', None, _("only fold specified revisions")),
       
  3072      ('', 'from', None, _("fold revisions linearly to working copy parent"))
       
  3073     ] + commitopts + commitopts2,
       
  3074     _('hg fold [OPTION]... [-r] REV'))
       
  3075 def fold(ui, repo, *revs, **opts):
       
  3076     """fold multiple revisions into a single one
       
  3077 
       
  3078     With --from, folds all the revisions linearly between the given revisions
       
  3079     and the parent of the working directory.
       
  3080 
       
  3081     With --exact, folds only the specified revisions while ignoring the
       
  3082     parent of the working directory. In this case, the given revisions must
       
  3083     form a linear unbroken chain.
       
  3084 
       
  3085     .. container:: verbose
       
  3086 
       
  3087      Some examples:
       
  3088 
       
  3089      - Fold the current revision with its parent::
       
  3090 
       
  3091          hg fold --from .^
       
  3092 
       
  3093      - Fold all draft revisions with working directory parent::
       
  3094 
       
  3095          hg fold --from 'draft()'
       
  3096 
       
  3097        See :hg:`help phases` for more about draft revisions and
       
  3098        :hg:`help revsets` for more about the `draft()` keyword
       
  3099 
       
  3100      - Fold revisions between 3 and 6 with the working directory parent::
       
  3101 
       
  3102          hg fold --from 3::6
       
  3103 
       
  3104      - Fold revisions 3 and 4:
       
  3105 
       
  3106         hg fold "3 + 4" --exact
       
  3107 
       
  3108      - Only fold revisions linearly between foo and @::
       
  3109 
       
  3110          hg fold foo::@ --exact
       
  3111     """
       
  3112     revs = list(revs)
       
  3113     revs.extend(opts['rev'])
       
  3114     if not revs:
       
  3115         raise error.Abort(_('no revisions specified'))
       
  3116 
       
  3117     revs = scmutil.revrange(repo, revs)
       
  3118 
       
  3119     if opts['from'] and opts['exact']:
       
  3120         raise error.Abort(_('cannot use both --from and --exact'))
       
  3121     elif opts['from']:
       
  3122         # Try to extend given revision starting from the working directory
       
  3123         extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs)
       
  3124         discardedrevs = [r for r in revs if r not in extrevs]
       
  3125         if discardedrevs:
       
  3126             raise error.Abort(_("cannot fold non-linear revisions"),
       
  3127                                hint=_("given revisions are unrelated to parent "
       
  3128                                       "of working directory"))
       
  3129         revs = extrevs
       
  3130     elif opts['exact']:
       
  3131         # Nothing to do; "revs" is already set correctly
       
  3132         pass
       
  3133     else:
       
  3134         raise error.Abort(_('must specify either --from or --exact'))
       
  3135 
       
  3136     if not revs:
       
  3137         raise error.Abort(_('specified revisions evaluate to an empty set'),
       
  3138                           hint=_('use different revision arguments'))
       
  3139     elif len(revs) == 1:
       
  3140         ui.write_err(_('single revision specified, nothing to fold\n'))
       
  3141         return 1
       
  3142 
       
  3143     wlock = lock = None
       
  3144     try:
       
  3145         wlock = repo.wlock()
       
  3146         lock = repo.lock()
       
  3147 
       
  3148         root, head = _foldcheck(repo, revs)
       
  3149 
       
  3150         tr = repo.transaction('fold')
       
  3151         try:
       
  3152             commitopts = opts.copy()
       
  3153             allctx = [repo[r] for r in revs]
       
  3154             targetphase = max(c.phase() for c in allctx)
       
  3155 
       
  3156             if commitopts.get('message') or commitopts.get('logfile'):
       
  3157                 commitopts['edit'] = False
       
  3158             else:
       
  3159                 msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
       
  3160                 msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
       
  3161                          (c.rev(), c.description()) for c in allctx]
       
  3162                 commitopts['message'] =  "\n".join(msgs)
       
  3163                 commitopts['edit'] = True
       
  3164 
       
  3165             newid, unusedvariable = rewrite(repo, root, allctx, head,
       
  3166                                             [root.p1().node(),
       
  3167                                              root.p2().node()],
       
  3168                                             commitopts=commitopts)
       
  3169             phases.retractboundary(repo, tr, targetphase, [newid])
       
  3170             obsolete.createmarkers(repo, [(ctx, (repo[newid],))
       
  3171                                  for ctx in allctx])
       
  3172             tr.close()
       
  3173         finally:
       
  3174             tr.release()
       
  3175         ui.status('%i changesets folded\n' % len(revs))
       
  3176         if repo['.'].rev() in revs:
       
  3177             hg.update(repo, newid)
       
  3178     finally:
       
  3179         lockmod.release(lock, wlock)
       
  3180 
       
  3181 @command('^metaedit',
       
  3182          [('r', 'rev', [], _("revision to edit")),
       
  3183          ('', 'fold', None, _("also fold specified revisions into one")),
       
  3184          ] + commitopts + commitopts2,
       
  3185          _('hg metaedit [OPTION]... [-r] [REV]'))
       
  3186 def metaedit(ui, repo, *revs, **opts):
       
  3187     """edit commit information
       
  3188 
       
  3189     Edits the commit information for the specified revisions. By default, edits
       
  3190     commit information for the working directory parent.
       
  3191 
       
  3192     With --fold, also folds multiple revisions into one if necessary. In this
       
  3193     case, the given revisions must form a linear unbroken chain.
       
  3194 
       
  3195     .. container:: verbose
       
  3196 
       
  3197      Some examples:
       
  3198 
       
  3199      - Edit the commit message for the working directory parent::
       
  3200 
       
  3201          hg metaedit
       
  3202 
       
  3203      - Change the username for the working directory parent::
       
  3204 
       
  3205          hg metaedit --user 'New User <new-email@example.com>'
       
  3206 
       
  3207      - Combine all draft revisions that are ancestors of foo but not of @ into
       
  3208        one::
       
  3209 
       
  3210          hg metaedit --fold 'draft() and only(foo,@)'
       
  3211 
       
  3212        See :hg:`help phases` for more about draft revisions, and
       
  3213        :hg:`help revsets` for more about the `draft()` and `only()` keywords.
       
  3214     """
       
  3215     revs = list(revs)
       
  3216     revs.extend(opts['rev'])
       
  3217     if not revs:
       
  3218         if opts['fold']:
       
  3219             raise error.Abort(_('revisions must be specified with --fold'))
       
  3220         revs = ['.']
       
  3221 
       
  3222     wlock = lock = None
       
  3223     try:
       
  3224         wlock = repo.wlock()
       
  3225         lock = repo.lock()
       
  3226 
       
  3227         revs = scmutil.revrange(repo, revs)
       
  3228         if not opts['fold'] and len(revs) > 1:
       
  3229             # TODO: handle multiple revisions. This is somewhat tricky because
       
  3230             # if we want to edit a series of commits:
       
  3231             #
       
  3232             #   a ---- b ---- c
       
  3233             #
       
  3234             # we need to rewrite a first, then directly rewrite b on top of the
       
  3235             # new a, then rewrite c on top of the new b. So we need to handle
       
  3236             # revisions in topological order.
       
  3237             raise error.Abort(_('editing multiple revisions without --fold is '
       
  3238                                 'not currently supported'))
       
  3239 
       
  3240         if opts['fold']:
       
  3241             root, head = _foldcheck(repo, revs)
       
  3242         else:
       
  3243             if repo.revs("%ld and public()", revs):
       
  3244                 raise error.Abort(_('cannot edit commit information for public '
       
  3245                                     'revisions'))
       
  3246             newunstable = _disallowednewunstable(repo, revs)
       
  3247             if newunstable:
       
  3248                 raise error.Abort(
       
  3249                     _('cannot edit commit information in the middle of a '\
       
  3250                     'stack'), hint=_('%s will become unstable and new unstable'\
       
  3251                     ' changes are not allowed') % repo[newunstable.first()])
       
  3252             root = head = repo[revs.first()]
       
  3253 
       
  3254         wctx = repo[None]
       
  3255         p1 = wctx.p1()
       
  3256         tr = repo.transaction('metaedit')
       
  3257         newp1 = None
       
  3258         try:
       
  3259             commitopts = opts.copy()
       
  3260             allctx = [repo[r] for r in revs]
       
  3261             targetphase = max(c.phase() for c in allctx)
       
  3262 
       
  3263             if commitopts.get('message') or commitopts.get('logfile'):
       
  3264                 commitopts['edit'] = False
       
  3265             else:
       
  3266                 if opts['fold']:
       
  3267                     msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
       
  3268                     msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
       
  3269                              (c.rev(), c.description()) for c in allctx]
       
  3270                 else:
       
  3271                     msgs = [head.description()]
       
  3272                 commitopts['message'] =  "\n".join(msgs)
       
  3273                 commitopts['edit'] = True
       
  3274 
       
  3275             # TODO: if the author and message are the same, don't create a new
       
  3276             # hash. Right now we create a new hash because the date can be
       
  3277             # different.
       
  3278             newid, created = rewrite(repo, root, allctx, head,
       
  3279                                      [root.p1().node(), root.p2().node()],
       
  3280                                      commitopts=commitopts)
       
  3281             if created:
       
  3282                 if p1.rev() in revs:
       
  3283                     newp1 = newid
       
  3284                 phases.retractboundary(repo, tr, targetphase, [newid])
       
  3285                 obsolete.createmarkers(repo, [(ctx, (repo[newid],))
       
  3286                                               for ctx in allctx])
       
  3287             else:
       
  3288                 ui.status(_("nothing changed\n"))
       
  3289             tr.close()
       
  3290         finally:
       
  3291             tr.release()
       
  3292 
       
  3293         if opts['fold']:
       
  3294             ui.status('%i changesets folded\n' % len(revs))
       
  3295         if newp1 is not None:
       
  3296             hg.update(repo, newp1)
       
  3297     finally:
       
  3298         lockmod.release(lock, wlock)
       
  3299 
       
  3300 def _foldcheck(repo, revs):
       
  3301     roots = repo.revs('roots(%ld)', revs)
       
  3302     if len(roots) > 1:
       
  3303         raise error.Abort(_("cannot fold non-linear revisions "
       
  3304                            "(multiple roots given)"))
       
  3305     root = repo[roots.first()]
       
  3306     if root.phase() <= phases.public:
       
  3307         raise error.Abort(_("cannot fold public revisions"))
       
  3308     heads = repo.revs('heads(%ld)', revs)
       
  3309     if len(heads) > 1:
       
  3310         raise error.Abort(_("cannot fold non-linear revisions "
       
  3311                            "(multiple heads given)"))
       
  3312     head = repo[heads.first()]
       
  3313     if _disallowednewunstable(repo, revs):
       
  3314         raise error.Abort(_("cannot fold chain not ending with a head "\
       
  3315                             "or with branching"), hint = _("new unstable"\
       
  3316                             " changesets are not allowed"))
       
  3317     return root, head
       
  3318 
       
  3319 def _disallowednewunstable(repo, revs):
       
  3320     allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
       
  3321     if allowunstable:
       
  3322         return revset.baseset()
       
  3323     return repo.revs("(%ld::) - %ld", revs, revs)
       
  3324 
       
  3325 @eh.wrapcommand('graft')
       
  3326 def graftwrapper(orig, ui, repo, *revs, **kwargs):
       
  3327     kwargs = dict(kwargs)
       
  3328     revs = list(revs) + kwargs.get('rev', [])
       
  3329     kwargs['rev'] = []
       
  3330     obsoleted = kwargs.setdefault('obsolete', [])
       
  3331 
       
  3332     wlock = lock = None
       
  3333     try:
       
  3334         wlock = repo.wlock()
       
  3335         lock = repo.lock()
       
  3336         if kwargs.get('old_obsolete'):
       
  3337             if kwargs.get('continue'):
       
  3338                 obsoleted.extend(repo.opener.read('graftstate').splitlines())
       
  3339             else:
       
  3340                 obsoleted.extend(revs)
       
  3341         # convert obsolete target into revs to avoid alias joke
       
  3342         obsoleted[:] = [str(i) for i in repo.revs('%lr', obsoleted)]
       
  3343         if obsoleted and len(revs) > 1:
       
  3344 
       
  3345             raise error.Abort(_('cannot graft multiple revisions while '
       
  3346                                 'obsoleting (for now).'))
       
  3347 
       
  3348         return commitwrapper(orig, ui, repo,*revs, **kwargs)
       
  3349     finally:
       
  3350         lockmod.release(lock, wlock)
       
  3351 
       
  3352 @eh.extsetup
       
  3353 def oldevolveextsetup(ui):
       
  3354     for cmd in ['prune', 'uncommit', 'touch', 'fold']:
       
  3355         try:
       
  3356             entry = extensions.wrapcommand(cmdtable, cmd,
       
  3357                                            warnobserrors)
       
  3358         except error.UnknownCommand:
       
  3359             # Commands may be disabled
       
  3360             continue
       
  3361 
       
  3362     entry = cmdutil.findcmd('commit', commands.table)[1]
       
  3363     entry[1].append(('o', 'obsolete', [],
       
  3364                      _("make commit obsolete this revision (DEPRECATED)")))
       
  3365     entry = cmdutil.findcmd('graft', commands.table)[1]
       
  3366     entry[1].append(('o', 'obsolete', [],
       
  3367                      _("make graft obsoletes this revision (DEPRECATED)")))
       
  3368     entry[1].append(('O', 'old-obsolete', False,
       
  3369                      _("make graft obsoletes its source (DEPRECATED)")))
       
  3370 
       
  3371 #####################################################################
       
  3372 ### Obsolescence marker exchange experimenation                   ###
       
  3373 #####################################################################
       
  3374 
       
  3375 def obsexcmsg(ui, message, important=False):
       
  3376     verbose = ui.configbool('experimental', 'verbose-obsolescence-exchange',
       
  3377                              False)
       
  3378     if verbose:
       
  3379         message = 'OBSEXC: ' + message
       
  3380     if important or verbose:
       
  3381         ui.status(message)
       
  3382 
       
  3383 def obsexcprg(ui, *args, **kwargs):
       
  3384     topic = 'obsmarkers exchange'
       
  3385     if ui.configbool('experimental', 'verbose-obsolescence-exchange', False):
       
  3386         topic = 'OBSEXC'
       
  3387     ui.progress(topic, *args, **kwargs)
       
  3388 
       
  3389 @eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers')
       
  3390 def _pushdiscoveryobsmarkers(orig, pushop):
       
  3391     if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
       
  3392         and pushop.repo.obsstore
       
  3393         and 'obsolete' in pushop.remote.listkeys('namespaces')):
       
  3394         repo = pushop.repo
       
  3395         obsexcmsg(repo.ui, "computing relevant nodes\n")
       
  3396         revs = list(repo.revs('::%ln', pushop.futureheads))
       
  3397         unfi = repo.unfiltered()
       
  3398         cl = unfi.changelog
       
  3399         if not pushop.remote.capable('_evoext_obshash_0'):
       
  3400             # do not trust core yet
       
  3401             # return orig(pushop)
       
  3402             nodes = [cl.node(r) for r in revs]
       
  3403             if nodes:
       
  3404                 obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
       
  3405                                    % len(nodes))
       
  3406                 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
       
  3407             else:
       
  3408                 obsexcmsg(repo.ui, "markers already in sync\n")
       
  3409                 pushop.outobsmarkers = []
       
  3410                 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
       
  3411             return
       
  3412 
       
  3413         common = []
       
  3414         obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
       
  3415                            % len(revs))
       
  3416         commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads))
       
  3417         common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote,
       
  3418                                       commonrevs)
       
  3419 
       
  3420         revs = list(unfi.revs('%ld - (::%ln)', revs, common))
       
  3421         nodes = [cl.node(r) for r in revs]
       
  3422         if nodes:
       
  3423             obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
       
  3424                                % len(nodes))
       
  3425             pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
       
  3426         else:
       
  3427             obsexcmsg(repo.ui, "markers already in sync\n")
       
  3428             pushop.outobsmarkers = []
       
  3429 
       
  3430 @eh.wrapfunction(wireproto, 'capabilities')
       
  3431 def discocapabilities(orig, repo, proto):
       
  3432     """wrapper to advertise new capability"""
       
  3433     caps = orig(repo, proto)
       
  3434     if obsolete.isenabled(repo, obsolete.exchangeopt):
       
  3435         caps += ' _evoext_obshash_0'
       
  3436     return caps
       
  3437 
       
  3438 @eh.extsetup
       
  3439 def _installobsmarkersdiscovery(ui):
       
  3440     hgweb_mod.perms['evoext_obshash'] = 'pull'
       
  3441     hgweb_mod.perms['evoext_obshash1'] = 'pull'
       
  3442     # wrap command content
       
  3443     oldcap, args = wireproto.commands['capabilities']
       
  3444     def newcap(repo, proto):
       
  3445         return discocapabilities(oldcap, repo, proto)
       
  3446     wireproto.commands['capabilities'] = (newcap, args)
       
  3447     wireproto.commands['evoext_obshash'] = (srv_obshash, 'nodes')
       
  3448     wireproto.commands['evoext_obshash1'] = (srv_obshash1, 'nodes')
       
  3449     if getattr(exchange, '_pushdiscoveryobsmarkers', None) is None:
       
  3450         ui.warn(_('evolve: your mercurial version is too old\n'
       
  3451                   'evolve: (running in degraded mode, push will '
       
  3452                   'includes all markers)\n'))
       
  3453     else:
       
  3454         olddisco = exchange.pushdiscoverymapping['obsmarker']
       
  3455         def newdisco(pushop):
       
  3456             _pushdiscoveryobsmarkers(olddisco, pushop)
       
  3457         exchange.pushdiscoverymapping['obsmarker'] = newdisco
       
  3458 
       
  3459 ### Set discovery START
       
  3460 
       
  3461 from mercurial import dagutil
       
  3462 from mercurial import setdiscovery
       
  3463 
       
  3464 def _obshash(repo, nodes, version=0):
       
  3465     if version == 0:
       
  3466         hashs = _obsrelsethashtreefm0(repo)
       
  3467     elif version ==1:
       
  3468         hashs = _obsrelsethashtreefm1(repo)
       
  3469     else:
       
  3470         assert False
       
  3471     nm = repo.changelog.nodemap
       
  3472     revs = [nm.get(n) for n in nodes]
       
  3473     return [r is None and nullid or hashs[r][1] for r in revs]
       
  3474 
       
  3475 def srv_obshash(repo, proto, nodes):
       
  3476     return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes)))
       
  3477 
       
  3478 def srv_obshash1(repo, proto, nodes):
       
  3479     return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes),
       
  3480                                 version=1))
       
  3481 
       
  3482 @eh.addattr(localrepo.localpeer, 'evoext_obshash')
       
  3483 def local_obshash(peer, nodes):
       
  3484     return _obshash(peer._repo, nodes)
       
  3485 
       
  3486 @eh.addattr(localrepo.localpeer, 'evoext_obshash1')
       
  3487 def local_obshash1(peer, nodes):
       
  3488     return _obshash(peer._repo, nodes, version=1)
       
  3489 
       
  3490 @eh.addattr(wireproto.wirepeer, 'evoext_obshash')
       
  3491 def peer_obshash(self, nodes):
       
  3492     d = self._call("evoext_obshash", nodes=wireproto.encodelist(nodes))
       
  3493     try:
       
  3494         return wireproto.decodelist(d)
       
  3495     except ValueError:
       
  3496         self._abort(error.ResponseError(_("unexpected response:"), d))
       
  3497 
       
  3498 @eh.addattr(wireproto.wirepeer, 'evoext_obshash1')
       
  3499 def peer_obshash1(self, nodes):
       
  3500     d = self._call("evoext_obshash1", nodes=wireproto.encodelist(nodes))
       
  3501     try:
       
  3502         return wireproto.decodelist(d)
       
  3503     except ValueError:
       
  3504         self._abort(error.ResponseError(_("unexpected response:"), d))
       
  3505 
       
  3506 def findcommonobsmarkers(ui, local, remote, probeset,
       
  3507                          initialsamplesize=100,
       
  3508                          fullsamplesize=200):
       
  3509     # from discovery
       
  3510     roundtrips = 0
       
  3511     cl = local.changelog
       
  3512     dag = dagutil.revlogdag(cl)
       
  3513     missing = set()
       
  3514     common = set()
       
  3515     undecided = set(probeset)
       
  3516     totalnb = len(undecided)
       
  3517     ui.progress(_("comparing with other"), 0, total=totalnb)
       
  3518     _takefullsample = setdiscovery._takefullsample
       
  3519     if remote.capable('_evoext_obshash_1'):
       
  3520         getremotehash = remote.evoext_obshash1
       
  3521         localhash = _obsrelsethashtreefm1(local)
       
  3522     else:
       
  3523         getremotehash = remote.evoext_obshash
       
  3524         localhash = _obsrelsethashtreefm0(local)
       
  3525 
       
  3526     while undecided:
       
  3527 
       
  3528         ui.note(_("sampling from both directions\n"))
       
  3529         if len(undecided) < fullsamplesize:
       
  3530             sample = set(undecided)
       
  3531         else:
       
  3532             sample = _takefullsample(dag, undecided, size=fullsamplesize)
       
  3533 
       
  3534         roundtrips += 1
       
  3535         ui.progress(_("comparing with other"), totalnb - len(undecided),
       
  3536                     total=totalnb)
       
  3537         ui.debug("query %i; still undecided: %i, sample size is: %i\n"
       
  3538                  % (roundtrips, len(undecided), len(sample)))
       
  3539         # indices between sample and externalized version must match
       
  3540         sample = list(sample)
       
  3541         remotehash = getremotehash(dag.externalizeall(sample))
       
  3542 
       
  3543         yesno = [localhash[ix][1] == remotehash[si]
       
  3544                  for si, ix in enumerate(sample)]
       
  3545 
       
  3546         commoninsample = set(n for i, n in enumerate(sample) if yesno[i])
       
  3547         common.update(dag.ancestorset(commoninsample, common))
       
  3548 
       
  3549         missinginsample = [n for i, n in enumerate(sample) if not yesno[i]]
       
  3550         missing.update(dag.descendantset(missinginsample, missing))
       
  3551 
       
  3552         undecided.difference_update(missing)
       
  3553         undecided.difference_update(common)
       
  3554 
       
  3555 
       
  3556     ui.progress(_("comparing with other"), None)
       
  3557     result = dag.headsetofconnecteds(common)
       
  3558     ui.debug("%d total queries\n" % roundtrips)
       
  3559 
       
  3560     if not result:
       
  3561         return set([nullid])
       
  3562     return dag.externalizeall(result)
       
  3563 
       
  3564 
       
  3565 _pushkeyescape = getattr(obsolete, '_pushkeyescape', None)
       
  3566 
       
  3567 class pushobsmarkerStringIO(StringIO):
       
  3568     """hacky string io for progress"""
       
  3569 
       
  3570     @util.propertycache
       
  3571     def length(self):
       
  3572         return len(self.getvalue())
       
  3573 
       
  3574     def read(self, size=None):
       
  3575         obsexcprg(self.ui, self.tell(), unit=_("bytes"), total=self.length)
       
  3576         return StringIO.read(self, size)
       
  3577 
       
  3578     def __iter__(self):
       
  3579         d = self.read(4096)
       
  3580         while d:
       
  3581             yield d
       
  3582             d = self.read(4096)
       
  3583 
       
  3584 @eh.wrapfunction(exchange, '_pushobsolete')
       
  3585 def _pushobsolete(orig, pushop):
       
  3586     """utility function to push obsolete markers to a remote"""
       
  3587     stepsdone = getattr(pushop, 'stepsdone', None)
       
  3588     if stepsdone is not None:
       
  3589         if 'obsmarkers' in stepsdone:
       
  3590             return
       
  3591         stepsdone.add('obsmarkers')
       
  3592     if util.safehasattr(pushop, 'cgresult'):
       
  3593         cgresult = pushop.cgresult
       
  3594     else:
       
  3595         cgresult = pushop.ret
       
  3596     if cgresult == 0:
       
  3597         return
       
  3598     pushop.ui.debug('try to push obsolete markers to remote\n')
       
  3599     repo = pushop.repo
       
  3600     remote = pushop.remote
       
  3601     if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore and
       
  3602         'obsolete' in remote.listkeys('namespaces')):
       
  3603         markers = pushop.outobsmarkers
       
  3604         if not markers:
       
  3605             obsexcmsg(repo.ui, "no marker to push\n")
       
  3606         elif remote.capable('_evoext_pushobsmarkers_0'):
       
  3607             obsdata = pushobsmarkerStringIO()
       
  3608             for chunk in obsolete.encodemarkers(markers, True):
       
  3609                 obsdata.write(chunk)
       
  3610             obsdata.seek(0)
       
  3611             obsdata.ui = repo.ui
       
  3612             obsexcmsg(repo.ui, "pushing %i obsolescence markers (%i bytes)\n"
       
  3613                                % (len(markers), len(obsdata.getvalue())),
       
  3614                       True)
       
  3615             remote.evoext_pushobsmarkers_0(obsdata)
       
  3616             obsexcprg(repo.ui, None)
       
  3617         else:
       
  3618             rslts = []
       
  3619             remotedata = _pushkeyescape(markers).items()
       
  3620             totalbytes = sum(len(d) for k, d in remotedata)
       
  3621             sentbytes = 0
       
  3622             obsexcmsg(repo.ui, "pushing %i obsolescence markers in %i "
       
  3623                                "pushkey payload (%i bytes)\n"
       
  3624                                % (len(markers), len(remotedata), totalbytes),
       
  3625                       True)
       
  3626             for key, data in remotedata:
       
  3627                 obsexcprg(repo.ui, sentbytes, item=key, unit=_("bytes"),
       
  3628                           total=totalbytes)
       
  3629                 rslts.append(remote.pushkey('obsolete', key, '', data))
       
  3630                 sentbytes += len(data)
       
  3631                 obsexcprg(repo.ui, sentbytes, item=key, unit=_("bytes"),
       
  3632                           total=totalbytes)
       
  3633             obsexcprg(repo.ui, None)
       
  3634             if [r for r in rslts if not r]:
       
  3635                 msg = _('failed to push some obsolete markers!\n')
       
  3636                 repo.ui.warn(msg)
       
  3637         obsexcmsg(repo.ui, "DONE\n")
       
  3638 
       
  3639 
       
  3640 @eh.addattr(wireproto.wirepeer, 'evoext_pushobsmarkers_0')
       
  3641 def client_pushobsmarkers(self, obsfile):
       
  3642     """wireprotocol peer method"""
       
  3643     self.requirecap('_evoext_pushobsmarkers_0',
       
  3644                     _('push obsolete markers faster'))
       
  3645     ret, output = self._callpush('evoext_pushobsmarkers_0', obsfile)
       
  3646     for l in output.splitlines(True):
       
  3647         self.ui.status(_('remote: '), l)
       
  3648     return ret
       
  3649 
       
  3650 @eh.addattr(httppeer.httppeer, 'evoext_pushobsmarkers_0')
       
  3651 def httpclient_pushobsmarkers(self, obsfile):
       
  3652     """httpprotocol peer method
       
  3653     (Cannot simply use _callpush as http is doing some special handling)"""
       
  3654     self.requirecap('_evoext_pushobsmarkers_0',
       
  3655                     _('push obsolete markers faster'))
       
  3656     try:
       
  3657         r = self._call('evoext_pushobsmarkers_0', data=obsfile)
       
  3658         vals = r.split('\n', 1)
       
  3659         if len(vals) < 2:
       
  3660             raise error.ResponseError(_("unexpected response:"), r)
       
  3661 
       
  3662         for l in vals[1].splitlines(True):
       
  3663             if l.strip():
       
  3664                 self.ui.status(_('remote: '), l)
       
  3665         return vals[0]
       
  3666     except socket.error as err:
       
  3667         if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
       
  3668             raise error.Abort(_('push failed: %s') % err.args[1])
       
  3669         raise error.Abort(err.args[1])
       
  3670 
       
  3671 @eh.wrapfunction(localrepo.localrepository, '_restrictcapabilities')
       
  3672 def local_pushobsmarker_capabilities(orig, repo, caps):
       
  3673     caps = orig(repo, caps)
       
  3674     caps.add('_evoext_pushobsmarkers_0')
       
  3675     return caps
       
  3676 
       
  3677 def _pushobsmarkers(repo, data):
       
  3678     tr = lock = None
       
  3679     try:
       
  3680         lock = repo.lock()
       
  3681         tr = repo.transaction('pushkey: obsolete markers')
       
  3682         new = repo.obsstore.mergemarkers(tr, data)
       
  3683         if new is not None:
       
  3684             obsexcmsg(repo.ui, "%i obsolescence markers added\n" % new, True)
       
  3685         tr.close()
       
  3686     finally:
       
  3687         lockmod.release(tr, lock)
       
  3688     repo.hook('evolve_pushobsmarkers')
       
  3689 
       
  3690 @eh.addattr(localrepo.localpeer, 'evoext_pushobsmarkers_0')
       
  3691 def local_pushobsmarkers(peer, obsfile):
       
  3692     data = obsfile.read()
       
  3693     _pushobsmarkers(peer._repo, data)
       
  3694 
       
  3695 def srv_pushobsmarkers(repo, proto):
       
  3696     """wireprotocol command"""
       
  3697     fp = StringIO()
       
  3698     proto.redirect()
       
  3699     proto.getfile(fp)
       
  3700     data = fp.getvalue()
       
  3701     fp.close()
       
  3702     _pushobsmarkers(repo, data)
       
  3703     return wireproto.pushres(0)
       
  3704 
       
  3705 def _buildpullobsmarkersboundaries(pullop):
       
  3706     """small funtion returning the argument for pull markers call
       
  3707     may to contains 'heads' and 'common'. skip the key for None.
       
  3708 
       
  3709     Its a separed functio to play around with strategy for that."""
       
  3710     repo = pullop.repo
       
  3711     remote = pullop.remote
       
  3712     unfi = repo.unfiltered()
       
  3713     revs = unfi.revs('::(%ln - null)', pullop.common)
       
  3714     common = [nullid]
       
  3715     if remote.capable('_evoext_obshash_0'):
       
  3716         obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
       
  3717                            % len(revs))
       
  3718         common = findcommonobsmarkers(repo.ui, repo, remote, revs)
       
  3719     return {'heads': pullop.pulledsubset, 'common': common}
       
  3720 
       
  3721 @eh.uisetup
       
  3722 def addgetbundleargs(self):
       
  3723     gboptsmap['evo_obscommon'] = 'nodes'
       
  3724 
       
  3725 @eh.wrapfunction(exchange, '_pullbundle2extraprepare')
       
  3726 def _addobscommontob2pull(orig, pullop, kwargs):
       
  3727     ret = orig(pullop, kwargs)
       
  3728     if ('obsmarkers' in kwargs and
       
  3729         pullop.remote.capable('_evoext_getbundle_obscommon')):
       
  3730         boundaries = _buildpullobsmarkersboundaries(pullop)
       
  3731         common = boundaries['common']
       
  3732         if common != [nullid]:
       
  3733             kwargs['evo_obscommon'] = common
       
  3734     return ret
       
  3735 
       
  3736 @eh.wrapfunction(exchange, '_getbundleobsmarkerpart')
       
  3737 def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs):
       
  3738     if 'evo_obscommon' not in kwargs:
       
  3739         return orig(bundler, repo, source, **kwargs)
       
  3740 
       
  3741     heads = kwargs.get('heads')
       
  3742     if kwargs.get('obsmarkers', False):
       
  3743         if heads is None:
       
  3744             heads = repo.heads()
       
  3745         obscommon = kwargs.get('evo_obscommon', ())
       
  3746         assert obscommon
       
  3747         obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon)
       
  3748         subset = [c.node() for c in obsset]
       
  3749         markers = repo.obsstore.relevantmarkers(subset)
       
  3750         exchange.buildobsmarkerspart(bundler, markers)
       
  3751 
       
  3752 @eh.uisetup
       
  3753 def installgetbundlepartgen(ui):
       
  3754     origfunc = exchange.getbundle2partsmapping['obsmarkers']
       
  3755     def newfunc(*args, **kwargs):
       
  3756         return _getbundleobsmarkerpart(origfunc, *args, **kwargs)
       
  3757     exchange.getbundle2partsmapping['obsmarkers'] = newfunc
       
  3758 
       
  3759 @eh.wrapfunction(exchange, '_pullobsolete')
       
  3760 def _pullobsolete(orig, pullop):
       
  3761     if not obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
       
  3762         return None
       
  3763     if 'obsmarkers' not in getattr(pullop, 'todosteps', ['obsmarkers']):
       
  3764         return None
       
  3765     if 'obsmarkers' in getattr(pullop, 'stepsdone', []):
       
  3766         return None
       
  3767     wirepull = pullop.remote.capable('_evoext_pullobsmarkers_0')
       
  3768     if not wirepull:
       
  3769         return orig(pullop)
       
  3770     if 'obsolete' not in pullop.remote.listkeys('namespaces'):
       
  3771         return None # remote opted out of obsolescence marker exchange
       
  3772     tr = None
       
  3773     ui = pullop.repo.ui
       
  3774     boundaries = _buildpullobsmarkersboundaries(pullop)
       
  3775     if not set(boundaries['heads']) - set(boundaries['common']):
       
  3776         obsexcmsg(ui, "nothing to pull\n")
       
  3777         return None
       
  3778 
       
  3779     obsexcmsg(ui, "pull obsolescence markers\n", True)
       
  3780     new = 0
       
  3781 
       
  3782     if wirepull:
       
  3783         obsdata = pullop.remote.evoext_pullobsmarkers_0(**boundaries)
       
  3784         obsdata = obsdata.read()
       
  3785         if len(obsdata) > 5:
       
  3786             obsexcmsg(ui, "merging obsolescence markers (%i bytes)\n"
       
  3787                            % len(obsdata))
       
  3788             tr = pullop.gettransaction()
       
  3789             old = len(pullop.repo.obsstore._all)
       
  3790             pullop.repo.obsstore.mergemarkers(tr, obsdata)
       
  3791             new = len(pullop.repo.obsstore._all) - old
       
  3792             obsexcmsg(ui, "%i obsolescence markers added\n" % new, True)
       
  3793         else:
       
  3794             obsexcmsg(ui, "no unknown remote markers\n")
       
  3795         obsexcmsg(ui, "DONE\n")
       
  3796     if new:
       
  3797         pullop.repo.invalidatevolatilesets()
       
  3798     return tr
       
  3799 
       
  3800 def _getobsmarkersstream(repo, heads=None, common=None):
       
  3801     revset = ''
       
  3802     args = []
       
  3803     repo = repo.unfiltered()
       
  3804     if heads is None:
       
  3805         revset = 'all()'
       
  3806     elif heads:
       
  3807         revset += "(::%ln)"
       
  3808         args.append(heads)
       
  3809     else:
       
  3810         assert False, 'pulling no heads?'
       
  3811     if common:
       
  3812         revset += ' - (::%ln)'
       
  3813         args.append(common)
       
  3814     nodes = [c.node() for c in repo.set(revset, *args)]
       
  3815     markers = repo.obsstore.relevantmarkers(nodes)
       
  3816     obsdata = StringIO()
       
  3817     for chunk in obsolete.encodemarkers(markers, True):
       
  3818         obsdata.write(chunk)
       
  3819     obsdata.seek(0)
       
  3820     return obsdata
       
  3821 
       
  3822 @eh.addattr(wireproto.wirepeer, 'evoext_pullobsmarkers_0')
       
  3823 def client_pullobsmarkers(self, heads=None, common=None):
       
  3824     self.requirecap('_evoext_pullobsmarkers_0', _('look up remote obsmarkers'))
       
  3825     opts = {}
       
  3826     if heads is not None:
       
  3827         opts['heads'] = wireproto.encodelist(heads)
       
  3828     if common is not None:
       
  3829         opts['common'] = wireproto.encodelist(common)
       
  3830     if util.safehasattr(self, '_callcompressable'):
       
  3831         f = self._callcompressable("evoext_pullobsmarkers_0", **opts)
       
  3832     else:
       
  3833         f = self._callstream("evoext_pullobsmarkers_0", **opts)
       
  3834         f = self._decompress(f)
       
  3835     length = int(f.read(20))
       
  3836     chunk = 4096
       
  3837     current = 0
       
  3838     data = StringIO()
       
  3839     ui = self.ui
       
  3840     obsexcprg(ui, current, unit=_("bytes"), total=length)
       
  3841     while current < length:
       
  3842         readsize = min(length - current, chunk)
       
  3843         data.write(f.read(readsize))
       
  3844         current += readsize
       
  3845         obsexcprg(ui, current, unit=_("bytes"), total=length)
       
  3846     obsexcprg(ui, None)
       
  3847     data.seek(0)
       
  3848     return data
       
  3849 
       
  3850 @eh.addattr(localrepo.localpeer, 'evoext_pullobsmarkers_0')
       
  3851 def local_pullobsmarkers(self, heads=None, common=None):
       
  3852     return _getobsmarkersstream(self._repo, heads=heads, common=common)
       
  3853 
       
  3854 # The wireproto.streamres API changed, handling chunking and compression
       
  3855 # directly. Handle either case.
       
  3856 if util.safehasattr(wireproto.abstractserverproto, 'groupchunks'):
       
  3857     # We need to handle chunking and compression directly
       
  3858     def streamres(d, proto):
       
  3859         return wireproto.streamres(proto.groupchunks(d))
       
  3860 else:
       
  3861     # Leave chunking and compression to streamres
       
  3862     def streamres(d, proto):
       
  3863         return wireproto.streamres(reader=d, v1compressible=True)
       
  3864 
       
  3865 def srv_pullobsmarkers(repo, proto, others):
       
  3866     opts = wireproto.options('', ['heads', 'common'], others)
       
  3867     for k, v in opts.iteritems():
       
  3868         if k in ('heads', 'common'):
       
  3869             opts[k] = wireproto.decodelist(v)
       
  3870     obsdata = _getobsmarkersstream(repo, **opts)
       
  3871     finaldata = StringIO()
       
  3872     obsdata = obsdata.getvalue()
       
  3873     finaldata.write('%20i' % len(obsdata))
       
  3874     finaldata.write(obsdata)
       
  3875     finaldata.seek(0)
       
  3876     return streamres(finaldata, proto)
       
  3877 
       
  3878 def _obsrelsethashtreefm0(repo):
       
  3879     return _obsrelsethashtree(repo, obsolete._fm0encodeonemarker)
       
  3880 
       
  3881 def _obsrelsethashtreefm1(repo):
       
  3882     return _obsrelsethashtree(repo, obsolete._fm1encodeonemarker)
       
  3883 
       
  3884 def _obsrelsethashtree(repo, encodeonemarker):
       
  3885     cache = []
       
  3886     unfi = repo.unfiltered()
       
  3887     markercache = {}
       
  3888     repo.ui.progress(_("preparing locally"), 0, total=len(unfi))
       
  3889     for i in unfi:
       
  3890         ctx = unfi[i]
       
  3891         entry = 0
       
  3892         sha = hashlib.sha1()
       
  3893         # add data from p1
       
  3894         for p in ctx.parents():
       
  3895             p = p.rev()
       
  3896             if p < 0:
       
  3897                 p = nullid
       
  3898             else:
       
  3899                 p = cache[p][1]
       
  3900             if p != nullid:
       
  3901                 entry += 1
       
  3902                 sha.update(p)
       
  3903         tmarkers = repo.obsstore.relevantmarkers([ctx.node()])
       
  3904         if tmarkers:
       
  3905             bmarkers = []
       
  3906             for m in tmarkers:
       
  3907                 if not m in markercache:
       
  3908                     markercache[m] = encodeonemarker(m)
       
  3909                 bmarkers.append(markercache[m])
       
  3910             bmarkers.sort()
       
  3911             for m in bmarkers:
       
  3912                 entry += 1
       
  3913                 sha.update(m)
       
  3914         if entry:
       
  3915             cache.append((ctx.node(), sha.digest()))
       
  3916         else:
       
  3917             cache.append((ctx.node(), nullid))
       
  3918         repo.ui.progress(_("preparing locally"), i, total=len(unfi))
       
  3919     repo.ui.progress(_("preparing locally"), None)
       
  3920     return cache
       
  3921 
       
  3922 @command('debugobsrelsethashtree',
       
  3923         [('', 'v0', None, 'hash on marker format "0"'),
       
  3924          ('', 'v1', None, 'hash on marker format "1" (default)')] , _(''))
       
  3925 def debugobsrelsethashtree(ui, repo, v0=False, v1=False):
       
  3926     """display Obsolete markers, Relevant Set, Hash Tree
       
  3927     changeset-node obsrelsethashtree-node
       
  3928 
       
  3929     It computed form the "orsht" of its parent and markers
       
  3930     relevant to the changeset itself."""
       
  3931     if v0 and v1:
       
  3932         raise error.Abort('cannot only specify one format')
       
  3933     elif v0:
       
  3934         treefunc = _obsrelsethashtreefm0
       
  3935     else:
       
  3936         treefunc = _obsrelsethashtreefm1
       
  3937 
       
  3938     for chg, obs in treefunc(repo):
       
  3939         ui.status('%s %s\n' % (node.hex(chg), node.hex(obs)))
       
  3940 
       
  3941 _bestformat = max(obsolete.formats.keys())
       
  3942 
       
  3943 
       
  3944 @eh.wrapfunction(obsolete, '_checkinvalidmarkers')
       
  3945 def _checkinvalidmarkers(orig, markers):
       
  3946     """search for marker with invalid data and raise error if needed
       
  3947 
       
  3948     Exist as a separated function to allow the evolve extension for a more
       
  3949     subtle handling.
       
  3950     """
       
  3951     if 'debugobsconvert' in sys.argv:
       
  3952         return
       
  3953     for mark in markers:
       
  3954         if node.nullid in mark[1]:
       
  3955             raise error.Abort(_('bad obsolescence marker detected: '
       
  3956                                'invalid successors nullid'),
       
  3957                              hint=_('You should run `hg debugobsconvert`'))
       
  3958 
       
  3959 @command(
       
  3960     'debugobsconvert',
       
  3961     [('', 'new-format', _bestformat, _('Destination format for markers.'))],
       
  3962     '')
       
  3963 def debugobsconvert(ui, repo, new_format):
       
  3964     origmarkers = repo.obsstore._all  # settle version
       
  3965     if new_format == repo.obsstore._version:
       
  3966         msg = _('New format is the same as the old format, not upgrading!')
       
  3967         raise error.Abort(msg)
       
  3968     f = repo.svfs('obsstore', 'wb', atomictemp=True)
       
  3969     known = set()
       
  3970     markers = []
       
  3971     for m in origmarkers:
       
  3972         # filter out invalid markers
       
  3973         if nullid in m[1]:
       
  3974             m = list(m)
       
  3975             m[1] = tuple(s for s in m[1] if s != nullid)
       
  3976             m = tuple(m)
       
  3977         if m in known:
       
  3978             continue
       
  3979         known.add(m)
       
  3980         markers.append(m)
       
  3981     ui.write(_('Old store is version %d, will rewrite in version %d\n') % (
       
  3982         repo.obsstore._version, new_format))
       
  3983     map(f.write, obsolete.encodemarkers(markers, True, new_format))
       
  3984     f.close()
       
  3985     ui.write(_('Done!\n'))
       
  3986 
       
  3987 
       
  3988 @eh.wrapfunction(wireproto, 'capabilities')
       
  3989 def capabilities(orig, repo, proto):
       
  3990     """wrapper to advertise new capability"""
       
  3991     caps = orig(repo, proto)
       
  3992     if obsolete.isenabled(repo, obsolete.exchangeopt):
       
  3993         caps += ' _evoext_pushobsmarkers_0'
       
  3994         caps += ' _evoext_pullobsmarkers_0'
       
  3995         caps += ' _evoext_obshash_0'
       
  3996         caps += ' _evoext_obshash_1'
       
  3997         caps += ' _evoext_getbundle_obscommon'
       
  3998     return caps
       
  3999 
       
  4000 
       
  4001 @eh.extsetup
       
  4002 def _installwireprotocol(ui):
       
  4003     localrepo.moderncaps.add('_evoext_pullobsmarkers_0')
       
  4004     hgweb_mod.perms['evoext_pushobsmarkers_0'] = 'push'
       
  4005     hgweb_mod.perms['evoext_pullobsmarkers_0'] = 'pull'
       
  4006     wireproto.commands['evoext_pushobsmarkers_0'] = (srv_pushobsmarkers, '')
       
  4007     wireproto.commands['evoext_pullobsmarkers_0'] = (srv_pullobsmarkers, '*')
       
  4008     # wrap command content
       
  4009     oldcap, args = wireproto.commands['capabilities']
       
  4010     def newcap(repo, proto):
       
  4011         return capabilities(oldcap, repo, proto)
       
  4012     wireproto.commands['capabilities'] = (newcap, args)
       
  4013 
       
  4014 # Mercurial >= 3.6 passes ui
       
  4015 def _helploader(ui=None):
       
  4016     return help.gettext(evolutionhelptext)
       
  4017 
       
  4018 @eh.uisetup
       
  4019 def _setuphelp(ui):
       
  4020     for entry in help.helptable:
       
  4021         if entry[0] == "evolution":
       
  4022             break
       
  4023     else:
       
  4024         help.helptable.append((["evolution"], _("Safely Rewriting History"),
       
  4025                       _helploader))
       
  4026         help.helptable.sort()
       
  4027 
       
  4028 def _relocatecommit(repo, orig, commitmsg):
       
  4029     if commitmsg is None:
       
  4030         commitmsg = orig.description()
       
  4031     extra = dict(orig.extra())
       
  4032     if 'branch' in extra:
       
  4033         del extra['branch']
       
  4034     extra['rebase_source'] = orig.hex()
       
  4035 
       
  4036     backup = repo.ui.backupconfig('phases', 'new-commit')
       
  4037     try:
       
  4038         targetphase = max(orig.phase(), phases.draft)
       
  4039         repo.ui.setconfig('phases', 'new-commit', targetphase, 'evolve')
       
  4040         # Commit might fail if unresolved files exist
       
  4041         nodenew = repo.commit(text=commitmsg, user=orig.user(),
       
  4042                               date=orig.date(), extra=extra)
       
  4043     finally:
       
  4044         repo.ui.restoreconfig(backup)
       
  4045     return nodenew
       
  4046 
       
  4047 def _finalizerelocate(repo, orig, dest, nodenew, tr):
       
  4048     destbookmarks = repo.nodebookmarks(dest.node())
       
  4049     nodesrc = orig.node()
       
  4050     destphase = repo[nodesrc].phase()
       
  4051     oldbookmarks = repo.nodebookmarks(nodesrc)
       
  4052     if nodenew is not None:
       
  4053         phases.retractboundary(repo, tr, destphase, [nodenew])
       
  4054         obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
       
  4055         for book in oldbookmarks:
       
  4056             repo._bookmarks[book] = nodenew
       
  4057     else:
       
  4058         obsolete.createmarkers(repo, [(repo[nodesrc], ())])
       
  4059         # Behave like rebase, move bookmarks to dest
       
  4060         for book in oldbookmarks:
       
  4061             repo._bookmarks[book] = dest.node()
       
  4062     for book in destbookmarks: # restore bookmark that rebase move
       
  4063         repo._bookmarks[book] = dest.node()
       
  4064     if oldbookmarks or destbookmarks:
       
  4065         repo._bookmarks.recordchange(tr)
       
  4066 
       
  4067 evolvestateversion = 0
       
  4068 
       
  4069 @eh.uisetup
       
  4070 def setupevolveunfinished(ui):
       
  4071     data = ('evolvestate', True, False, _('evolve in progress'),
       
  4072            _("use 'hg evolve --continue' or 'hg update -C .' to abort"))
       
  4073     cmdutil.unfinishedstates.append(data)
       
  4074 
       
  4075 @eh.wrapfunction(hg, 'clean')
       
  4076 def clean(orig, repo, *args, **kwargs):
       
  4077     ret = orig(repo, *args, **kwargs)
       
  4078     util.unlinkpath(repo.join('evolvestate'), ignoremissing=True)
       
  4079     return ret
       
  4080 
       
  4081 def _evolvestatewrite(repo, state):
       
  4082     # [version]
       
  4083     # [type][length][content]
       
  4084     #
       
  4085     # `version` is a 4 bytes integer (handled at higher level)
       
  4086     # `type` is a single character, `length` is a 4 byte integer, and
       
  4087     # `content` is an arbitrary byte sequence of length `length`.
       
  4088     f = repo.vfs('evolvestate', 'w')
       
  4089     try:
       
  4090         f.write(_pack('>I', evolvestateversion))
       
  4091         current = state['current']
       
  4092         key = 'C' # as in 'current'
       
  4093         format = '>sI%is' % len(current)
       
  4094         f.write(_pack(format, key, len(current), current))
       
  4095     finally:
       
  4096         f.close()
       
  4097 
       
  4098 def _evolvestateread(repo):
       
  4099     try:
       
  4100         f = repo.vfs('evolvestate')
       
  4101     except IOError as err:
       
  4102         if err.errno != errno.ENOENT:
       
  4103             raise
       
  4104         return None
       
  4105     try:
       
  4106         versionblob = f.read(4)
       
  4107         if len(versionblob) < 4:
       
  4108             repo.ui.debug('ignoring corrupted evolvestte (file contains %i bits)'
       
  4109                           % len(versionblob))
       
  4110             return None
       
  4111         version = _unpack('>I', versionblob)[0]
       
  4112         if version != evolvestateversion:
       
  4113             raise error.Abort(_('unknown evolvestate version %i')
       
  4114                                 % version, hint=_('upgrade your evolve'))
       
  4115         records = []
       
  4116         data = f.read()
       
  4117         off = 0
       
  4118         end = len(data)
       
  4119         while off < end:
       
  4120             rtype = data[off]
       
  4121             off += 1
       
  4122             length = _unpack('>I', data[off:(off + 4)])[0]
       
  4123             off += 4
       
  4124             record = data[off:(off + length)]
       
  4125             off += length
       
  4126             if rtype == 't':
       
  4127                 rtype, record = record[0], record[1:]
       
  4128             records.append((rtype, record))
       
  4129         state = {}
       
  4130         for rtype, rdata in records:
       
  4131             if rtype == 'C':
       
  4132                 state['current'] = rdata
       
  4133             elif rtype.lower():
       
  4134                 repo.ui.debug('ignore evolve state record type %s' % rtype)
       
  4135             else:
       
  4136                 raise error.Abort(_('unknown evolvestate field type %r')
       
  4137                                   % rtype, hint=_('upgrade your evolve'))
       
  4138         return state
       
  4139     finally:
       
  4140         f.close()
       
  4141 
       
  4142 def _evolvestatedelete(repo):
       
  4143     util.unlinkpath(repo.join('evolvestate'), ignoremissing=True)
       
  4144 
       
  4145 def _evolvemerge(repo, orig, dest, pctx, keepbranch):
       
  4146     """Used by the evolve function to merge dest on top of pctx.
       
  4147     return the same tuple as merge.graft"""
       
  4148     if repo['.'].rev() != dest.rev():
       
  4149         #assert False
       
  4150         try:
       
  4151             merge.update(repo,
       
  4152                          dest,
       
  4153                          branchmerge=False,
       
  4154                          force=True)
       
  4155         except TypeError:
       
  4156             # Mercurial  < 43c00ca887d1 (3.7)
       
  4157             merge.update(repo,
       
  4158                          dest,
       
  4159                          branchmerge=False,
       
  4160                          force=True,
       
  4161                          partial=False)
       
  4162     if bmactive(repo):
       
  4163        repo.ui.status(_("(leaving bookmark %s)\n") % bmactive(repo))
       
  4164     bmdeactivate(repo)
       
  4165     if keepbranch:
       
  4166        repo.dirstate.setbranch(orig.branch())
       
  4167     if util.safehasattr(repo, 'currenttopic'):
       
  4168         # uurrgs
       
  4169         # there no other topic setter yet
       
  4170         if not orig.topic() and repo.vfs.exists('topic'):
       
  4171                 repo.vfs.unlink('topic')
       
  4172         else:
       
  4173             with repo.vfs.open('topic', 'w') as f:
       
  4174                 f.write(orig.topic())
       
  4175 
       
  4176     try:
       
  4177        r = merge.graft(repo, orig, pctx, ['local', 'graft'], True)
       
  4178     except TypeError:
       
  4179        # not using recent enough mercurial
       
  4180        if len(orig.parents()) == 2:
       
  4181            raise error.Abort(
       
  4182                _("no support for evolving merge changesets yet"),
       
  4183                hint=_("Redo the merge and use `hg prune <old> --succ "
       
  4184                       "<new>` to obsolete the old one"))
       
  4185 
       
  4186        r = merge.graft(repo, orig, pctx, ['local', 'graft'])
       
  4187     return r