hgext/evolve.py
branchstable
changeset 1450 5f6e78aea094
parent 1449 9be1cadf7a07
child 1451 73eb4f33f9dc
equal deleted inserted replaced
1438:3295353b1363 1450:5f6e78aea094
    21 
    21 
    22 __version__ = '5.1.5'
    22 __version__ = '5.1.5'
    23 testedwith = '3.3.3 3.4.1'
    23 testedwith = '3.3.3 3.4.1'
    24 buglink = 'http://bz.selenic.com/'
    24 buglink = 'http://bz.selenic.com/'
    25 
    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:`hg 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 enable 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 
    26 import sys, os
    62 import sys, os
    27 import random
    63 import random
    28 from StringIO import StringIO
    64 from StringIO import StringIO
    29 import struct
    65 import struct
    30 import re
    66 import re
       
    67 import collections
    31 import socket
    68 import socket
    32 import errno
    69 import errno
    33 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
    70 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
    34 
    71 
    35 import mercurial
    72 import mercurial
    43     gboptslist = getattr(wireproto, 'gboptslist', None)
    80     gboptslist = getattr(wireproto, 'gboptslist', None)
    44     gboptsmap = getattr(wireproto, 'gboptsmap', None)
    81     gboptsmap = getattr(wireproto, 'gboptsmap', None)
    45 except (ImportError, AttributeError):
    82 except (ImportError, AttributeError):
    46     gboptslist = gboptsmap = None
    83     gboptslist = gboptsmap = None
    47 
    84 
       
    85 # Flags for enabling optional parts of evolve
       
    86 commandopt = 'allnewcommands'
    48 
    87 
    49 from mercurial import bookmarks
    88 from mercurial import bookmarks
    50 from mercurial import cmdutil
    89 from mercurial import cmdutil
    51 from mercurial import commands
    90 from mercurial import commands
    52 from mercurial import context
    91 from mercurial import context
    53 from mercurial import copies
    92 from mercurial import copies
    54 from mercurial import error
    93 from mercurial import error
    55 from mercurial import exchange
    94 from mercurial import exchange
    56 from mercurial import extensions
    95 from mercurial import extensions
       
    96 from mercurial import help
    57 from mercurial import httppeer
    97 from mercurial import httppeer
    58 from mercurial import hg
    98 from mercurial import hg
    59 from mercurial import lock as lockmod
    99 from mercurial import lock as lockmod
    60 from mercurial import merge
   100 from mercurial import merge
    61 from mercurial import node
   101 from mercurial import node
   142         - Setup of pre-* and post-* hooks
   182         - Setup of pre-* and post-* hooks
   143         - pushkey setup
   183         - pushkey setup
   144         """
   184         """
   145         for cont, funcname, func in self._duckpunchers:
   185         for cont, funcname, func in self._duckpunchers:
   146             setattr(cont, funcname, func)
   186             setattr(cont, funcname, func)
   147         for command, wrapper in self._commandwrappers:
   187         for command, wrapper, opts in self._commandwrappers:
   148             extensions.wrapcommand(commands.table, command, wrapper)
   188             entry = extensions.wrapcommand(commands.table, command, wrapper)
       
   189             if opts:
       
   190                 for short, long, val, msg in opts:
       
   191                     entry[1].append((short, long, val, msg))
   149         for cont, funcname, wrapper in self._functionwrappers:
   192         for cont, funcname, wrapper in self._functionwrappers:
   150             extensions.wrapfunction(cont, funcname, wrapper)
   193             extensions.wrapfunction(cont, funcname, wrapper)
   151         for c in self._uicallables:
   194         for c in self._uicallables:
   152             c(ui)
   195             c(ui)
   153 
   196 
   164         knownexts = {}
   207         knownexts = {}
   165         for name, symbol in self._revsetsymbols:
   208         for name, symbol in self._revsetsymbols:
   166             revset.symbols[name] = symbol
   209             revset.symbols[name] = symbol
   167         for name, kw in self._templatekws:
   210         for name, kw in self._templatekws:
   168             templatekw.keywords[name] = kw
   211             templatekw.keywords[name] = kw
   169         for ext, command, wrapper in self._extcommandwrappers:
   212         for ext, command, wrapper, opts in self._extcommandwrappers:
   170             if ext not in knownexts:
   213             if ext not in knownexts:
   171                 e = extensions.find(ext)
   214                 try:
   172                 if e is None:
   215                     e = extensions.find(ext)
   173                     raise util.Abort('extension %s not found' % ext)
   216                 except KeyError:
       
   217                     # Extension isn't enabled, so don't bother trying to wrap
       
   218                     # it.
       
   219                     continue
   174                 knownexts[ext] = e.cmdtable
   220                 knownexts[ext] = e.cmdtable
   175             extensions.wrapcommand(knownexts[ext], commands, wrapper)
   221             entry = extensions.wrapcommand(knownexts[ext], command, wrapper)
       
   222             if opts:
       
   223                 for short, long, val, msg in opts:
       
   224                     entry[1].append((short, long, val, msg))
       
   225 
   176         for c in self._extcallables:
   226         for c in self._extcallables:
   177             c(ui)
   227             c(ui)
   178 
   228 
   179     def final_reposetup(self, ui, repo):
   229     def final_reposetup(self, ui, repo):
   180         """Method to be used as a the extension reposetup
   230         """Method to be used as a the extension reposetup
   258         def dec(keyword):
   308         def dec(keyword):
   259             self._templatekws.append((keywordname, keyword))
   309             self._templatekws.append((keywordname, keyword))
   260             return keyword
   310             return keyword
   261         return dec
   311         return dec
   262 
   312 
   263     def wrapcommand(self, command, extension=None):
   313     def wrapcommand(self, command, extension=None, opts=[]):
   264         """Decorated function is a command wrapper
   314         """Decorated function is a command wrapper
   265 
   315 
   266         The name of the command must be given as the decorator argument.
   316         The name of the command must be given as the decorator argument.
   267         The wrapping is installed during `uisetup`.
   317         The wrapping is installed during `uisetup`.
   268 
   318 
   277             @eh.wrapcommand('summary')
   327             @eh.wrapcommand('summary')
   278             def wrapsummary(orig, ui, repo, *args, **kwargs):
   328             def wrapsummary(orig, ui, repo, *args, **kwargs):
   279                 ui.note('Barry!')
   329                 ui.note('Barry!')
   280                 return orig(ui, repo, *args, **kwargs)
   330                 return orig(ui, repo, *args, **kwargs)
   281 
   331 
       
   332         The `opts` argument allows specifying additional arguments for the
       
   333         command.
       
   334 
   282         """
   335         """
   283         def dec(wrapper):
   336         def dec(wrapper):
   284             if extension is None:
   337             if extension is None:
   285                 self._commandwrappers.append((command, wrapper))
   338                 self._commandwrappers.append((command, wrapper, opts))
   286             else:
   339             else:
   287                 self._extcommandwrappers.append((extension, command, wrapper))
   340                 self._extcommandwrappers.append((extension, command, wrapper,
       
   341                     opts))
   288             return wrapper
   342             return wrapper
   289         return dec
   343         return dec
   290 
   344 
   291     def wrapfunction(self, container, funcname):
   345     def wrapfunction(self, container, funcname):
   292         """Decorated function is a function wrapper
   346         """Decorated function is a function wrapper
   326 
   380 
   327 eh = exthelper()
   381 eh = exthelper()
   328 uisetup = eh.final_uisetup
   382 uisetup = eh.final_uisetup
   329 extsetup = eh.final_extsetup
   383 extsetup = eh.final_extsetup
   330 reposetup = eh.final_reposetup
   384 reposetup = eh.final_reposetup
       
   385 
       
   386 #####################################################################
       
   387 ### Option configuration                                          ###
       
   388 #####################################################################
       
   389 
       
   390 @eh.reposetup # must be the first of its kin.
       
   391 def _configureoptions(ui, repo):
       
   392     # If no capabilities are specified, enable everything.
       
   393     # This is so existing evolve users don't need to change their config.
       
   394     evolveopts = ui.configlist('experimental', 'evolution')
       
   395     if not evolveopts:
       
   396         evolveopts = ['all']
       
   397         ui.setconfig('experimental', 'evolution', evolveopts)
       
   398 
       
   399 @eh.uisetup
       
   400 def _configurecmdoptions(ui):
       
   401     # Unregister evolve commands if the command capability is not specified.
       
   402     #
       
   403     # This must be in the same function as the option configuration above to
       
   404     # guarantee it happens after the above configuration, but before the
       
   405     # extsetup functions.
       
   406     evolvecommands = ui.configlist('experimental', 'evolutioncommands')
       
   407     evolveopts = ui.configlist('experimental', 'evolution')
       
   408     if evolveopts and (commandopt not in evolveopts and
       
   409                        'all' not in evolveopts):
       
   410         # We build whitelist containing the commands we want to enable
       
   411         whitelist = set()
       
   412         for cmd in evolvecommands:
       
   413             matchingevolvecommands = [e for e in cmdtable.keys() if cmd in e]
       
   414             if not matchingevolvecommands:
       
   415                 raise error.Abort(_('unknown command: %s') % cmd)
       
   416             elif len(matchingevolvecommands) > 1:
       
   417                 raise error.Abort(_('ambiguous command specification: "%s" matches %r')
       
   418                                   % (cmd, matchingevolvecommands))
       
   419             else:
       
   420                 whitelist.add(matchingevolvecommands[0])
       
   421         for disabledcmd in set(cmdtable) - whitelist:
       
   422             del cmdtable[disabledcmd]
   331 
   423 
   332 #####################################################################
   424 #####################################################################
   333 ### experimental behavior                                         ###
   425 ### experimental behavior                                         ###
   334 #####################################################################
   426 #####################################################################
   335 
   427 
   588 @eh.wrapcommand("update")
   680 @eh.wrapcommand("update")
   589 @eh.wrapcommand("parents")
   681 @eh.wrapcommand("parents")
   590 @eh.wrapcommand("pull")
   682 @eh.wrapcommand("pull")
   591 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
   683 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
   592     """Warn that the working directory parent is an obsolete changeset"""
   684     """Warn that the working directory parent is an obsolete changeset"""
   593     res = origfn(ui, repo, *args, **opts)
   685     def warnobsolete():
   594     if repo['.'].obsolete():
   686         if repo['.'].obsolete():
   595         ui.warn(_('working directory parent is obsolete!\n'))
   687             ui.warn(_('working directory parent is obsolete!\n'))
       
   688             if (not ui.quiet) and obsolete.isenabled(repo, commandopt):
       
   689                 ui.warn(_('(use "hg evolve" to update to its successor)\n'))
       
   690     wlock = None
       
   691     try:
       
   692         wlock = repo.wlock()
       
   693         repo._afterlock(warnobsolete)
       
   694         res = origfn(ui, repo, *args, **opts)
       
   695     finally:
       
   696         lockmod.release(wlock)
   596     return res
   697     return res
   597 
   698 
   598 # XXX this could wrap transaction code
   699 # XXX this could wrap transaction code
   599 # XXX (but this is a bit a layer violation)
   700 # XXX (but this is a bit a layer violation)
   600 @eh.wrapcommand("commit")
   701 @eh.wrapcommand("commit")
   960     If a user invokes the command a deprecation warning will be printed and
  1061     If a user invokes the command a deprecation warning will be printed and
   961     the command of the *new* alias will be invoked.
  1062     the command of the *new* alias will be invoked.
   962 
  1063 
   963     This function is loosely based on the extensions.wrapcommand function.
  1064     This function is loosely based on the extensions.wrapcommand function.
   964     '''
  1065     '''
   965     aliases, entry = cmdutil.findcmd(newalias, cmdtable)
  1066     try:
       
  1067         aliases, entry = cmdutil.findcmd(newalias, cmdtable)
       
  1068     except error.UnknownCommand:
       
  1069         # Commands may be disabled
       
  1070         return
   966     for alias, e in cmdtable.iteritems():
  1071     for alias, e in cmdtable.iteritems():
   967         if e is entry:
  1072         if e is entry:
   968             break
  1073             break
   969 
  1074 
   970     synopsis = '(DEPRECATED)'
  1075     synopsis = '(DEPRECATED)'
  1023     finally:
  1128     finally:
  1024         lockmod.release(tr, lock, wlock)
  1129         lockmod.release(tr, lock, wlock)
  1025 
  1130 
  1026 @command('debugobsstorestat', [], '')
  1131 @command('debugobsstorestat', [], '')
  1027 def cmddebugobsstorestat(ui, repo):
  1132 def cmddebugobsstorestat(ui, repo):
       
  1133     def _updateclustermap(nodes, mark, clustersmap):
       
  1134         c = (set(nodes), set([mark]))
       
  1135         toproceed = set(nodes)
       
  1136         while toproceed:
       
  1137             n = toproceed.pop()
       
  1138             other = clustersmap.get(n)
       
  1139             if (other is not None
       
  1140                 and other is not c):
       
  1141                 other[0].update(c[0])
       
  1142                 other[1].update(c[1])
       
  1143                 for on in c[0]:
       
  1144                     if on in toproceed:
       
  1145                         continue
       
  1146                     clustersmap[on] = other
       
  1147                 c = other
       
  1148             clustersmap[n] = c
       
  1149 
  1028     """print statistic about obsolescence markers in the repo"""
  1150     """print statistic about obsolescence markers in the repo"""
  1029     store = repo.obsstore
  1151     store = repo.obsstore
  1030     unfi = repo.unfiltered()
  1152     unfi = repo.unfiltered()
  1031     nm = unfi.changelog.nodemap
  1153     nm = unfi.changelog.nodemap
  1032     ui.write('markers total:              %9i\n' % len(store._all))
  1154     ui.write('markers total:              %9i\n' % len(store._all))
  1052         parents = [meta.get('p1'), meta.get('p2')]
  1174         parents = [meta.get('p1'), meta.get('p2')]
  1053         parents = [node.bin(p) for p in parents if p is not None]
  1175         parents = [node.bin(p) for p in parents if p is not None]
  1054         if parents:
  1176         if parents:
  1055             parentsdata += 1
  1177             parentsdata += 1
  1056         # cluster handling
  1178         # cluster handling
  1057         nodes = set()
  1179         nodes = set(mark[1])
  1058         nodes.add(mark[0])
  1180         nodes.add(mark[0])
  1059         nodes.update(mark[1])
  1181         _updateclustermap(nodes, mark, clustersmap) 
  1060         c = (set(nodes), set([mark]))
       
  1061 
       
  1062         toproceed = set(nodes)
       
  1063         while toproceed:
       
  1064             n = toproceed.pop()
       
  1065             other = clustersmap.get(n)
       
  1066             if (other is not None
       
  1067                 and other is not c):
       
  1068                 other[0].update(c[0])
       
  1069                 other[1].update(c[1])
       
  1070                 for on in c[0]:
       
  1071                     if on in toproceed:
       
  1072                         continue
       
  1073                     clustersmap[on] = other
       
  1074                 c = other
       
  1075             clustersmap[n] = c
       
  1076         # same with parent data
  1182         # same with parent data
  1077         nodes.update(parents)
  1183         nodes.update(parents)
  1078         c = (set(nodes), set([mark]))
  1184         _updateclustermap(nodes, mark, pclustersmap) 
  1079         toproceed = set(nodes)
       
  1080         while toproceed:
       
  1081             n = toproceed.pop()
       
  1082             other = pclustersmap.get(n)
       
  1083             if (other is not None
       
  1084                 and other is not c):
       
  1085                 other[0].update(c[0])
       
  1086                 other[1].update(c[1])
       
  1087                 for on in c[0]:
       
  1088                     if on in toproceed:
       
  1089                         continue
       
  1090                     pclustersmap[on] = other
       
  1091                 c = other
       
  1092             pclustersmap[n] = c
       
  1093 
  1185 
  1094     # freezing the result
  1186     # freezing the result
  1095     for c in clustersmap.values():
  1187     for c in clustersmap.values():
  1096         fc = (frozenset(c[0]), frozenset(c[1]))
  1188         fc = (frozenset(c[0]), frozenset(c[1]))
  1097         for n in fc[0]:
  1189         for n in fc[0]:
  1141         median = len(allpclusters[nbcluster//2][1])
  1233         median = len(allpclusters[nbcluster//2][1])
  1142         ui.write('        median length:      %9i\n' % median)
  1234         ui.write('        median length:      %9i\n' % median)
  1143         mean = sum(len(x[1]) for x in allpclusters) // nbcluster
  1235         mean = sum(len(x[1]) for x in allpclusters) // nbcluster
  1144         ui.write('        mean length:        %9i\n' % mean)
  1236         ui.write('        mean length:        %9i\n' % mean)
  1145 
  1237 
       
  1238 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
       
  1239     """Resolve the troubles affecting one revision"""
       
  1240     wlock = lock = tr = None
       
  1241     try:
       
  1242         wlock = repo.wlock()
       
  1243         lock = repo.lock()
       
  1244         tr = repo.transaction("evolve")
       
  1245         if 'unstable' == category:
       
  1246             result = _solveunstable(ui, repo, ctx, dryrun, confirm, progresscb)
       
  1247         elif 'bumped' == category:
       
  1248             result = _solvebumped(ui, repo, ctx, dryrun, confirm, progresscb)
       
  1249         elif 'divergent' == category:
       
  1250             result = _solvedivergent(ui, repo, ctx, dryrun, confirm,
       
  1251                                    progresscb)
       
  1252         else:
       
  1253             assert False, "unknown trouble category: %s" % (category)
       
  1254         tr.close()
       
  1255         return result
       
  1256     finally:
       
  1257         lockmod.release(tr, lock, wlock)
       
  1258 
       
  1259 def _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat):
       
  1260     """Used by the evolve function to display an error message when
       
  1261     no troubles can be resolved"""
       
  1262     troublecategories = ['bumped', 'divergent', 'unstable']
       
  1263     unselectedcategories = [c for c in troublecategories if c != targetcat]
       
  1264     msg = None
       
  1265     hint = None
       
  1266 
       
  1267     troubled = {
       
  1268             "unstable": repo.revs("unstable()"),
       
  1269             "divergent": repo.revs("divergent()"),
       
  1270             "bumped": repo.revs("bumped()"),
       
  1271             "all": repo.revs("troubled()"),
       
  1272     }
       
  1273 
       
  1274 
       
  1275     hintmap = {
       
  1276             'bumped': _("do you want to use --bumped"),
       
  1277             'bumped+divergent': _("do you want to use --bumped or --divergent"),
       
  1278             'bumped+unstable': _("do you want to use --bumped or --unstable"),
       
  1279             'divergent': _("do you want to use --divergent"),
       
  1280             'divergent+unstable': _("do you want to use --divergent"
       
  1281                                     " or --unstable"),
       
  1282             'unstable': _("do you want to use --unstable"),
       
  1283             'any+bumped': _("do you want to use --any (or --rev) and --bumped"),
       
  1284             'any+bumped+divergent': _("do you want to use --any (or --rev) and"
       
  1285                                       " --bumped or --divergent"),
       
  1286             'any+bumped+unstable': _("do you want to use --any (or --rev) and"
       
  1287                                      "--bumped or --unstable"),
       
  1288             'any+divergent': _("do you want to use --any (or --rev) and"
       
  1289                                " --divergent"),
       
  1290             'any+divergent+unstable': _("do you want to use --any (or --rev)"
       
  1291                                         " and --divergent or --unstable"),
       
  1292             'any+unstable': _("do you want to use --any (or --rev)"
       
  1293                               "and --unstable"),
       
  1294     }
       
  1295 
       
  1296     if revopt:
       
  1297         revs = scmutil.revrange(repo, revopt)
       
  1298         if not revs:
       
  1299             msg = _("set of specified revisions is empty")
       
  1300         else:
       
  1301             msg = _("no %s changesets in specified revisions") % targetcat
       
  1302             othertroubles = []
       
  1303             for cat in unselectedcategories:
       
  1304                 if revs & troubled[cat]:
       
  1305                     othertroubles.append(cat)
       
  1306             if othertroubles:
       
  1307                 hint = hintmap['+'.join(othertroubles)]
       
  1308 
       
  1309     elif anyopt:
       
  1310         msg = _("no %s changesets to evolve") % targetcat
       
  1311         othertroubles = []
       
  1312         for cat in unselectedcategories:
       
  1313             if troubled[cat]:
       
  1314                 othertroubles.append(cat)
       
  1315         if othertroubles:
       
  1316             hint = hintmap['+'.join(othertroubles)]
       
  1317 
       
  1318     else:
       
  1319         # evolve without any option = relative to the current wdir
       
  1320         if targetcat == 'unstable':
       
  1321             msg = _("nothing to evolve on current working copy parent")
       
  1322         else:
       
  1323             msg = _("current working copy parent is not %s") % targetcat
       
  1324 
       
  1325         p1 = repo['.'].rev()
       
  1326         othertroubles = []
       
  1327         for cat in unselectedcategories:
       
  1328             if p1 in troubled[cat]:
       
  1329                 othertroubles.append(cat)
       
  1330         if othertroubles:
       
  1331             hint = hintmap['+'.join(othertroubles)]
       
  1332         else:
       
  1333             l = len(troubled[targetcat])
       
  1334             if l:
       
  1335                 hint = (_("%d other %s in the repository, do you want --any or --rev")
       
  1336                         % (l, targetcat))
       
  1337             else:
       
  1338                 othertroubles = []
       
  1339                 for cat in unselectedcategories:
       
  1340                     if troubled[cat]:
       
  1341                         othertroubles.append(cat)
       
  1342                 if othertroubles:
       
  1343                     hint = hintmap['any+'+('+'.join(othertroubles))]
       
  1344                 else:
       
  1345                     msg = _("no troubled changesets")
       
  1346 
       
  1347     assert msg is not None
       
  1348     ui.write_err(msg+"\n")
       
  1349     if hint:
       
  1350         ui.write_err("("+hint+")\n")
       
  1351         return 2
       
  1352     else:
       
  1353         return 1
       
  1354 
       
  1355 def _cleanup(ui, repo, startnode, showprogress):
       
  1356     if showprogress:
       
  1357         ui.progress('evolve', None)
       
  1358     if repo['.'] != startnode:
       
  1359         ui.status(_('working directory is now at %s\n') % repo['.'])
       
  1360 
       
  1361 class MultipleSuccessorsError(RuntimeError):
       
  1362     """Exception raised by _singlesuccessor when multiple sucessors sets exists
       
  1363 
       
  1364     The object contains the list of successorssets in its 'successorssets'
       
  1365     attribute to call to easily recover.
       
  1366     """
       
  1367 
       
  1368     def __init__(self, successorssets):
       
  1369         self.successorssets = successorssets
       
  1370 
       
  1371 def _singlesuccessor(repo, p):
       
  1372     """returns p (as rev) if not obsolete or its unique latest successors
       
  1373 
       
  1374     fail if there are no such successor"""
       
  1375 
       
  1376     if not p.obsolete():
       
  1377         return p.rev()
       
  1378     obs = repo[p]
       
  1379     ui = repo.ui
       
  1380     newer = obsolete.successorssets(repo, obs.node())
       
  1381     # search of a parent which is not killed
       
  1382     while not newer:
       
  1383         ui.debug("stabilize target %s is plain dead,"
       
  1384                  " trying to stabilize on its parent\n" %
       
  1385                  obs)
       
  1386         obs = obs.parents()[0]
       
  1387         newer = obsolete.successorssets(repo, obs.node())
       
  1388     if len(newer) > 1 or len(newer[0]) > 1:
       
  1389         raise MultipleSuccessorsError(newer)
       
  1390 
       
  1391     return repo[newer[0][0]].rev()
       
  1392 
       
  1393 def builddependencies(repo, revs):
       
  1394     """returns dependency graphs giving an order to solve instability of revs
       
  1395     (see _orderrevs for more information on usage)"""
       
  1396 
       
  1397     # For each troubled revision we keep track of what instability if any should
       
  1398     # be resolved in order to resolve it. Example:
       
  1399     # dependencies = {3: [6], 6:[]}
       
  1400     # Means that: 6 has no dependency, 3 depends on 6 to be solved
       
  1401     dependencies = {}
       
  1402     # rdependencies is the inverted dict of dependencies
       
  1403     rdependencies = collections.defaultdict(set)
       
  1404 
       
  1405     for r in revs:
       
  1406         dependencies[r] = set()
       
  1407         for p in repo[r].parents():
       
  1408             try:
       
  1409                 succ = _singlesuccessor(repo, p)
       
  1410             except MultipleSuccessorsError, exc:
       
  1411                 dependencies[r] = exc.successorssets
       
  1412                 continue
       
  1413             if succ in revs:
       
  1414                 dependencies[r].add(succ)
       
  1415                 rdependencies[succ].add(r)
       
  1416     return dependencies, rdependencies
       
  1417 
       
  1418 def _selectrevs(repo, allopt, revopt, anyopt, targetcat):
       
  1419     """select troubles in repo matching according to given options"""
       
  1420     revs = set()
       
  1421     if allopt or revopt:
       
  1422         revs = repo.revs(targetcat+'()')
       
  1423         if revopt:
       
  1424             revs = scmutil.revrange(repo, revopt) & revs
       
  1425         elif not anyopt and targetcat == 'unstable':
       
  1426             revs = set(_aspiringdescendant(repo, repo.revs('(.::) - obsolete()::')))
       
  1427     elif anyopt:
       
  1428         revs = repo.revs('first(%s())' % (targetcat))
       
  1429     elif targetcat == 'unstable':
       
  1430         revs = set(_aspiringchildren(repo, repo.revs('(.::) - obsolete()::')))
       
  1431         if 1 < len(revs):
       
  1432             msg = "multiple evolve candidates"
       
  1433             hint = (_("select one of %s with --rev")
       
  1434                     % ', '.join([str(repo[r]) for r in sorted(revs)]))
       
  1435             raise error.Abort(msg, hint=hint)
       
  1436     elif targetcat in repo['.'].troubles():
       
  1437         revs = set([repo['.'].rev()])
       
  1438     return revs
       
  1439 
       
  1440 
       
  1441 def _orderrevs(repo, revs):
       
  1442     """Compute an ordering to solve instability for the given revs
       
  1443 
       
  1444     - Takes revs a list of instable revisions
       
  1445 
       
  1446     - Returns the same revisions ordered to solve their instability from the
       
  1447     bottom to the top of the stack that the stabilization process will produce
       
  1448     eventually.
       
  1449 
       
  1450     This ensure the minimal number of stabilization as we can stabilize each
       
  1451     revision on its final, stabilized, destination.
       
  1452     """
       
  1453     # Step 1: Build the dependency graph
       
  1454     dependencies, rdependencies = builddependencies(repo, revs)
       
  1455     # Step 2: Build the ordering
       
  1456     # Remove the revisions with no dependency(A) and add them to the ordering.
       
  1457     # Removing these revisions leads to new revisions with no dependency (the
       
  1458     # one depending on A) that we can remove from the dependency graph and add
       
  1459     # to the ordering. We progress in a similar fashion until the ordering is
       
  1460     # built
       
  1461     solvablerevs = collections.deque([r for r in sorted(dependencies.keys())
       
  1462                                       if not dependencies[r]])
       
  1463     ordering = []
       
  1464     while solvablerevs:
       
  1465         rev = solvablerevs.popleft()
       
  1466         for dependent in rdependencies[rev]:
       
  1467             dependencies[dependent].remove(rev)
       
  1468             if not dependencies[dependent]:
       
  1469                 solvablerevs.append(dependent)
       
  1470         del dependencies[rev]
       
  1471         ordering.append(rev)
       
  1472 
       
  1473     ordering.extend(sorted(dependencies))
       
  1474     return ordering
       
  1475 
  1146 @command('^evolve|stabilize|solve',
  1476 @command('^evolve|stabilize|solve',
  1147     [('n', 'dry-run', False,
  1477     [('n', 'dry-run', False,
  1148         'do not perform actions, just print what would be done'),
  1478         'do not perform actions, just print what would be done'),
  1149      ('', 'confirm', False,
  1479      ('', 'confirm', False,
  1150         'ask for confirmation before performing the action'),
  1480         'ask for confirmation before performing the action'),
  1151     ('A', 'any', False, 'also consider troubled changesets unrelated to current working directory'),
  1481     ('A', 'any', False, 'also consider troubled changesets unrelated to current working directory'),
  1152     ('a', 'all', False, 'evolve all troubled changesets in the repo '
  1482     ('r', 'rev', [], 'solves troubles of these revisions'),
  1153                         '(implies any)'),
  1483     ('', 'bumped', False, 'solves only bumped changesets'),
       
  1484     ('', 'divergent', False, 'solves only divergent changesets'),
       
  1485     ('', 'unstable', False, 'solves only unstable changesets (default)'),
       
  1486     ('a', 'all', False, 'evolve all troubled changesets related to the current '
       
  1487                          'working directory and its descendants'),
  1154     ('c', 'continue', False, 'continue an interrupted evolution'),
  1488     ('c', 'continue', False, 'continue an interrupted evolution'),
  1155     ] + mergetoolopts,
  1489     ] + mergetoolopts,
  1156     _('[OPTIONS]...'))
  1490     _('[OPTIONS]...'))
  1157 def evolve(ui, repo, **opts):
  1491 def evolve(ui, repo, **opts):
  1158     """solve trouble in your repository
  1492     """solve troubles in your repository
  1159 
  1493 
  1160     - rebase unstable changesets to make them stable again,
  1494     - rebase unstable changesets to make them stable again,
  1161     - create proper diffs from bumped changesets,
  1495     - create proper diffs from bumped changesets,
  1162     - merge divergent changesets,
  1496     - fuse divergent changesets back together,
  1163     - update to a successor if the working directory parent is
  1497     - update to a successor if the working directory parent is
  1164       obsolete
  1498       obsolete
  1165 
  1499 
  1166     By default a single changeset is evolved for each invocation and only
  1500     If no argument are passed and the current working copy parent is obsolete,
  1167     troubled changesets that would evolve as a descendant of the current
  1501     :hg:`evolve` will update the working copy to the successors of this working
  1168     working directory will be considered. See --all and --any options to change
  1502     copy parent. If the working copy parent is not obsolete (and still no
  1169     this behavior.
  1503     argument passed) each invocation of :hg:`evolve` will evolve a single
  1170 
  1504     unstable changeset, It will only select a changeset to be evolved if it
  1171     - For unstable, this means taking the first which could be rebased as a
  1505     will result in a new children for the current working copy parent or its
  1172       child of the working directory parent revision or one of its descendants
  1506     descendants. The working copy will be updated on the result
  1173       and rebasing it.
  1507     (this last behavior will most likely to change in the future).
  1174 
  1508     You can evolve all the unstable changesets that will be evolved on the
  1175     - For divergent, this means taking "." if applicable.
  1509     parent of the working copy and all its descendants recursively by using
  1176 
  1510     :hg:`evolve` --all.
  1177     With --any, evolve picks any troubled changeset to repair.
  1511 
       
  1512     You can decide to evolve other categories of trouble using the --divergent
       
  1513     and --bumped flags. If no other option are specified, this will try to
       
  1514     solve the specified troubles for the working copy parent.
       
  1515 
       
  1516     You can also evolve changesets affected by troubles of the selected
       
  1517     category using the --rev options. You can pick the next one anywhere in the
       
  1518     repo using --any.
       
  1519 
       
  1520     You can evolve all the changesets affected by troubles of the selected
       
  1521     category using --all --any.
  1178 
  1522 
  1179     The working directory is updated to the newly created revision.
  1523     The working directory is updated to the newly created revision.
  1180     """
  1524     """
  1181 
  1525 
       
  1526     # Options
  1182     contopt = opts['continue']
  1527     contopt = opts['continue']
  1183     anyopt = opts['any']
  1528     anyopt = opts['any']
  1184     allopt = opts['all']
  1529     allopt = opts['all']
       
  1530     startnode = repo['.']
  1185     dryrunopt = opts['dry_run']
  1531     dryrunopt = opts['dry_run']
  1186     confirmopt = opts['confirm']
  1532     confirmopt = opts['confirm']
       
  1533     revopt = opts['rev']
       
  1534     troublecategories = ['bumped', 'divergent', 'unstable']
       
  1535     specifiedcategories = [t for t in troublecategories if opts[t]]
       
  1536     targetcat = 'unstable'
       
  1537     if 1 < len(specifiedcategories):
       
  1538         msg = _('cannot specify more than one trouble category to solve (yet)')
       
  1539         raise util.Abort(msg)
       
  1540     elif len(specifiedcategories) == 1:
       
  1541         targetcat = specifiedcategories[0]
       
  1542     elif repo['.'].obsolete():
       
  1543         displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
       
  1544         # no args and parent is obsolete, update to successors
       
  1545         try:
       
  1546             ctx = repo[_singlesuccessor(repo, repo['.'])]
       
  1547         except MultipleSuccessorsError, exc:
       
  1548             repo.ui.write_err('parent is obsolete with multiple successors:\n')
       
  1549             for ln in exc.successorssets:
       
  1550                 for n in ln:
       
  1551                     displayer.show(repo[n])
       
  1552             return 2
       
  1553 
       
  1554 
       
  1555         ui.status(_('update:'))
       
  1556         if not ui.quiet:
       
  1557             displayer.show(ctx)
       
  1558 
       
  1559         if dryrunopt:
       
  1560             return 0
       
  1561         res = hg.update(repo, ctx.rev())
       
  1562         if ctx != startnode:
       
  1563             ui.status(_('working directory is now at %s\n') % ctx)
       
  1564         return res
       
  1565 
  1187     ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve')
  1566     ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve')
  1188 
  1567     troubled = set(repo.revs('troubled()'))
  1189     startnode = repo['.']
  1568 
  1190 
  1569     # Progress handling
       
  1570     seen = 1
       
  1571     count = allopt and len(troubled) or 1
       
  1572     showprogress = allopt
       
  1573 
       
  1574     def progresscb():
       
  1575         if revopt or allopt:
       
  1576             ui.progress('evolve', seen, unit='changesets', total=count)
       
  1577 
       
  1578     # Continuation handling
  1191     if contopt:
  1579     if contopt:
  1192         if anyopt:
  1580         if anyopt:
  1193             raise util.Abort('cannot specify both "--any" and "--continue"')
  1581             raise util.Abort('cannot specify both "--any" and "--continue"')
  1194         if allopt:
  1582         if allopt:
  1195             raise util.Abort('cannot specify both "--all" and "--continue"')
  1583             raise util.Abort('cannot specify both "--all" and "--continue"')
  1196         graftcmd = commands.table['graft'][0]
  1584         graftcmd = commands.table['graft'][0]
  1197         return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
  1585         return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
  1198 
  1586     cmdutil.bailifchanged(repo)
  1199     tro = _picknexttroubled(ui, repo, anyopt or allopt)
  1587 
  1200     if tro is None:
  1588 
  1201         if repo['.'].obsolete():
  1589     if revopt and allopt:
  1202             displayer = cmdutil.show_changeset(
  1590         raise util.Abort('cannot specify both "--rev" and "--all"')
  1203                 ui, repo, {'template': shorttemplate})
  1591     if revopt and anyopt:
  1204             successors = set()
  1592         raise util.Abort('cannot specify both "--rev" and "--any"')
  1205 
  1593 
  1206             for successorsset in obsolete.successorssets(repo, repo['.'].node()):
  1594     revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat)
  1207                 for nodeid in successorsset:
  1595 
  1208                     successors.add(repo[nodeid])
  1596     if not revs:
  1209 
  1597         return _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat)
  1210             if not successors:
  1598 
  1211                 ui.warn(_('parent is obsolete without successors; ' +
  1599     # For the progress bar to show
  1212                           'likely killed\n'))
  1600     count = len(revs)
  1213                 return 2
  1601     # Order the revisions
  1214 
  1602     if targetcat == 'unstable':
  1215             elif len(successors) > 1:
  1603         revs = _orderrevs(repo, revs)
  1216                 ui.warn(_('parent is obsolete with multiple successors:\n'))
  1604     for rev in revs:
  1217 
  1605         progresscb()
  1218                 for ctx in sorted(successors, key=lambda ctx: ctx.rev()):
  1606         _solveone(ui, repo, repo[rev], dryrunopt, confirmopt,
  1219                     displayer.show(ctx)
  1607                 progresscb, targetcat)
  1220 
  1608         seen += 1
  1221                 return 2
  1609     progresscb()
  1222 
  1610     _cleanup(ui, repo, startnode, showprogress)
  1223             else:
  1611 
  1224                 ctx = successors.pop()
  1612 def _possibledestination(repo, rev):
  1225 
  1613     """return all changesets that may be a new parent for REV"""
  1226                 ui.status(_('update:'))
  1614     tonode = repo.changelog.node
  1227                 if not ui.quiet:
  1615     parents = repo.changelog.parentrevs
  1228                     displayer.show(ctx)
  1616     torev = repo.changelog.rev
  1229 
  1617     dest = set()
  1230                 if dryrunopt:
  1618     tovisit = list(parents(rev))
  1231                     return 0
  1619     while tovisit:
  1232                 else:
  1620         r = tovisit.pop()
  1233                     res = hg.update(repo, ctx.rev())
  1621         succsets = obsolete.successorssets(repo, tonode(r))
  1234                     if ctx != startnode:
  1622         if not succsets:
  1235                         ui.status(_('working directory is now at %s\n') % ctx)
  1623             tovisit.extend(parents(r))
  1236                     return res
       
  1237 
       
  1238         troubled = repo.revs('troubled()')
       
  1239         if troubled:
       
  1240             ui.write_err(_('nothing to evolve here\n'))
       
  1241             ui.status(_('(%i troubled changesets, do you want --any ?)\n')
       
  1242                       % len(troubled))
       
  1243             return 2
       
  1244         else:
  1624         else:
  1245             ui.write_err(_('no troubled changesets\n'))
  1625             # We should probably pick only one destination from split
  1246             return 1
  1626             # (case where '1 < len(ss)'), This could be the currently tipmost
  1247 
  1627             # but logic is less clear when result of the split are now on
  1248     def progresscb():
  1628             # multiple branches.
  1249         if allopt:
  1629             for ss in succsets:
  1250             ui.progress('evolve', seen, unit='changesets', total=count)
  1630                 for n in ss:
  1251     seen = 1
  1631                     dest.add(torev(n))
  1252     count = allopt and _counttroubled(ui, repo) or 1
  1632     return dest
  1253 
  1633 
  1254     while tro is not None:
  1634 def _aspiringchildren(repo, revs):
  1255         progresscb()
  1635     """Return a list of changectx which can be stabilized on top of pctx or
  1256         wlock = lock = tr = None
  1636     one of its descendants. Empty list if none can be found."""
  1257         try:
  1637     target = set(revs)
  1258             wlock = repo.wlock()
  1638     result = []
  1259             lock = repo.lock()
  1639     for r in repo.revs('unstable() - %ld', revs):
  1260             tr = repo.transaction("evolve")
  1640         dest = _possibledestination(repo, r)
  1261             result = _evolveany(ui, repo, tro, dryrunopt, confirmopt,
  1641         if target & dest:
  1262                                 progresscb=progresscb)
  1642             result.append(r)
  1263             tr.close()
  1643     return result
  1264         finally:
  1644 
  1265             lockmod.release(tr, lock, wlock)
  1645 def _aspiringdescendant(repo, revs):
  1266         progresscb()
  1646     """Return a list of changectx which can be stabilized on top of pctx or
  1267         seen += 1
  1647     one of its descendants recursively. Empty list if none can be found."""
  1268         if not allopt:
  1648     target = set(revs)
  1269             if repo['.'] != startnode:
  1649     result = set(target)
  1270                 ui.status(_('working directory is now at %s\n') % repo['.'])
  1650     paths = collections.defaultdict(set)
  1271             return result
  1651     for r in repo.revs('unstable() - %ld', revs):
  1272         progresscb()
  1652         for d in _possibledestination(repo, r):
  1273         tro = _picknexttroubled(ui, repo, anyopt or allopt)
  1653             paths[d].add(r)
  1274 
  1654 
  1275     if allopt:
  1655     result = set(target)
  1276         ui.progress('evolve', None)
  1656     tovisit = list(revs)
  1277 
  1657     while tovisit:
  1278     if repo['.'] != startnode:
  1658         base = tovisit.pop()
  1279         ui.status(_('working directory is now at %s\n') % repo['.'])
  1659         for unstable in paths[base]:
  1280 
  1660             if unstable not in result:
  1281 
  1661                 tovisit.append(unstable)
  1282 def _evolveany(ui, repo, tro, dryrunopt, confirmopt, progresscb):
  1662                 result.add(unstable)
  1283     repo = repo.unfiltered()
  1663     return sorted(result - target)
  1284     tro = repo[tro.rev()]
       
  1285     cmdutil.bailifchanged(repo)
       
  1286     troubles = tro.troubles()
       
  1287     if 'unstable' in troubles:
       
  1288         return _solveunstable(ui, repo, tro, dryrunopt, confirmopt, progresscb)
       
  1289     elif 'bumped' in troubles:
       
  1290         return _solvebumped(ui, repo, tro, dryrunopt, confirmopt, progresscb)
       
  1291     elif 'divergent' in troubles:
       
  1292         return _solvedivergent(ui, repo, tro, dryrunopt, confirmopt,
       
  1293                                progresscb)
       
  1294     else:
       
  1295         assert False  # WHAT? unknown troubles
       
  1296 
       
  1297 def _counttroubled(ui, repo):
       
  1298     """Count the amount of troubled changesets"""
       
  1299     troubled = set()
       
  1300     troubled.update(getrevs(repo, 'unstable'))
       
  1301     troubled.update(getrevs(repo, 'bumped'))
       
  1302     troubled.update(getrevs(repo, 'divergent'))
       
  1303     return len(troubled)
       
  1304 
       
  1305 def _picknexttroubled(ui, repo, pickany=False, progresscb=None):
       
  1306     """Pick a the next trouble changeset to solve"""
       
  1307     if progresscb: progresscb()
       
  1308     tro = _stabilizableunstable(repo, repo['.'])
       
  1309     if tro is None:
       
  1310         wdp = repo['.']
       
  1311         if 'divergent' in wdp.troubles():
       
  1312             tro = wdp
       
  1313     if tro is None and pickany:
       
  1314         troubled = list(repo.set('unstable()'))
       
  1315         if not troubled:
       
  1316             troubled = list(repo.set('bumped()'))
       
  1317         if not troubled:
       
  1318             troubled = list(repo.set('divergent()'))
       
  1319         if troubled:
       
  1320             tro = troubled[0]
       
  1321 
       
  1322     return tro
       
  1323 
       
  1324 def _stabilizableunstable(repo, pctx):
       
  1325     """Return a changectx for an unstable changeset which can be
       
  1326     stabilized on top of pctx or one of its descendants. None if none
       
  1327     can be found.
       
  1328     """
       
  1329     def selfanddescendants(repo, pctx):
       
  1330         yield pctx
       
  1331         for prec in repo.set('allprecursors(%d)', pctx):
       
  1332             yield prec
       
  1333         for ctx in pctx.descendants():
       
  1334             yield ctx
       
  1335             for prec in repo.set('allprecursors(%d)', ctx):
       
  1336                 yield prec
       
  1337 
       
  1338     # Look for an unstable which can be stabilized as a child of
       
  1339     # node. The unstable must be a child of one of node predecessors.
       
  1340     directdesc = set([pctx.rev()])
       
  1341     for ctx in selfanddescendants(repo, pctx):
       
  1342         for child in ctx.children():
       
  1343             if ctx.rev() in directdesc and not child.obsolete():
       
  1344                 directdesc.add(child.rev())
       
  1345             elif child.unstable():
       
  1346                 return child
       
  1347     return None
       
  1348 
  1664 
  1349 def _solveunstable(ui, repo, orig, dryrun=False, confirm=False,
  1665 def _solveunstable(ui, repo, orig, dryrun=False, confirm=False,
  1350                    progresscb=None):
  1666                    progresscb=None):
  1351     """Stabilize a unstable changeset"""
  1667     """Stabilize a unstable changeset"""
  1352     obs = orig.parents()[0]
  1668     obs = orig.parents()[0]
       
  1669     if not obs.obsolete() and len(orig.parents()) == 2:
       
  1670         obs = orig.parents()[1] # second parent is obsolete ?
       
  1671 
  1353     if not obs.obsolete():
  1672     if not obs.obsolete():
  1354         obs = orig.parents()[1] # second parent is obsolete ?
  1673         ui.warn("cannot solve instability of %s, skipping\n" % orig)
  1355     assert obs.obsolete()
  1674         return False
  1356     newer = obsolete.successorssets(repo, obs.node())
  1675     newer = obsolete.successorssets(repo, obs.node())
  1357     # search of a parent which is not killed
  1676     # search of a parent which is not killed
  1358     while not newer or newer == [()]:
  1677     while not newer or newer == [()]:
  1359         ui.debug("stabilize target %s is plain dead,"
  1678         ui.debug("stabilize target %s is plain dead,"
  1360                  " trying to stabilize on its parent\n" %
  1679                  " trying to stabilize on its parent\n" %
  1361                  obs)
  1680                  obs)
  1362         obs = obs.parents()[0]
  1681         obs = obs.parents()[0]
  1363         newer = obsolete.successorssets(repo, obs.node())
  1682         newer = obsolete.successorssets(repo, obs.node())
  1364     if len(newer) > 1:
  1683     if len(newer) > 1:
  1365         raise util.Abort(_("conflict rewriting. can't choose destination\n"))
  1684         msg = _("skipping %s: divergent rewriting. can't choose destination\n" % obs)
       
  1685         ui.write_err(msg)
       
  1686         return 2
  1366     targets = newer[0]
  1687     targets = newer[0]
  1367     assert targets
  1688     assert targets
  1368     if len(targets) > 1:
  1689     if len(targets) > 1:
  1369         raise util.Abort(_("does not handle split parents yet\n"))
  1690         msg = _("does not handle split parents yet\n")
       
  1691         ui.write_err(msg)
  1370         return 2
  1692         return 2
  1371     target = targets[0]
  1693     target = targets[0]
  1372     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1694     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1373     target = repo[target]
  1695     target = repo[target]
  1374     if not ui.quiet or confirm:
  1696     if not ui.quiet or confirm:
  1397             raise
  1719             raise
  1398 
  1720 
  1399 def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False,
  1721 def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False,
  1400                  progresscb=None):
  1722                  progresscb=None):
  1401     """Stabilize a bumped changeset"""
  1723     """Stabilize a bumped changeset"""
       
  1724     repo = repo.unfiltered()
       
  1725     bumped = repo[bumped.rev()]
  1402     # For now we deny bumped merge
  1726     # For now we deny bumped merge
  1403     if len(bumped.parents()) > 1:
  1727     if len(bumped.parents()) > 1:
  1404         raise util.Abort('late comer stabilization is confused by bumped'
  1728         msg = _('skipping %s : we do not handle merge yet\n' % bumped)
  1405                          ' %s being a merge' % bumped)
  1729         ui.write_err(msg)
       
  1730         return 2
  1406     prec = repo.set('last(allprecursors(%d) and public())', bumped).next()
  1731     prec = repo.set('last(allprecursors(%d) and public())', bumped).next()
  1407     # For now we deny target merge
  1732     # For now we deny target merge
  1408     if len(prec.parents()) > 1:
  1733     if len(prec.parents()) > 1:
  1409         raise util.Abort('late comer evolution is confused by precursors'
  1734         msg = _('skipping: %s: public version is a merge, this not handled yet\n' % prec)
  1410                          ' %s being a merge' % prec)
  1735         ui.write_err(msg)
       
  1736         return 2
  1411 
  1737 
  1412     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1738     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1413     if not ui.quiet or confirm:
  1739     if not ui.quiet or confirm:
  1414         repo.ui.write(_('recreate:'))
  1740         repo.ui.write(_('recreate:'))
  1415         displayer.show(bumped)
  1741         displayer.show(bumped)
  1449         # Create the new commit context
  1775         # Create the new commit context
  1450         repo.ui.status(_('computing new diff\n'))
  1776         repo.ui.status(_('computing new diff\n'))
  1451         files = set()
  1777         files = set()
  1452         copied = copies.pathcopies(prec, bumped)
  1778         copied = copies.pathcopies(prec, bumped)
  1453         precmanifest = prec.manifest()
  1779         precmanifest = prec.manifest()
       
  1780         # 3.3.2 needs a list.
       
  1781         # future 3.4 don't detect the size change during iteration
       
  1782         # this is fishy
  1454         for key, val in list(bumped.manifest().iteritems()):
  1783         for key, val in list(bumped.manifest().iteritems()):
  1455             precvalue = precmanifest.get(key, None)
  1784             precvalue = precmanifest.get(key, None)
  1456             if precvalue is not None:
  1785             if precvalue is not None:
  1457                 del precmanifest[key]
  1786                 del precmanifest[key]
  1458             if precvalue != val:
  1787             if precvalue != val:
  1500     repo.dirstate.setparents(newid, node.nullid)
  1829     repo.dirstate.setparents(newid, node.nullid)
  1501     repo.dirstate.endparentchange()
  1830     repo.dirstate.endparentchange()
  1502 
  1831 
  1503 def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False,
  1832 def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False,
  1504                     progresscb=None):
  1833                     progresscb=None):
       
  1834     repo = repo.unfiltered()
       
  1835     divergent = repo[divergent.rev()]
  1505     base, others = divergentdata(divergent)
  1836     base, others = divergentdata(divergent)
  1506     if len(others) > 1:
  1837     if len(others) > 1:
  1507         othersstr = "[%s]" % (','.join([str(i) for i in others]))
  1838         othersstr = "[%s]" % (','.join([str(i) for i in others]))
  1508         hint = ("changeset %d is divergent with a changeset that got splitted "
  1839         msg = _("skipping %d:divergent with a changeset that got splitted into multiple ones:\n"
  1509                 "| into multiple ones:\n[%s]\n"
  1840                  "|[%s]\n"
  1510                 "| This is not handled by automatic evolution yet\n"
  1841                  "| This is not handled by automatic evolution yet\n"
  1511                 "| You have to fallback to manual handling with commands "
  1842                  "| You have to fallback to manual handling with commands "
  1512                 "such as:\n"
  1843                  "such as:\n"
  1513                 "| - hg touch -D\n"
  1844                  "| - hg touch -D\n"
  1514                 "| - hg prune\n"
  1845                  "| - hg prune\n"
  1515                 "| \n"
  1846                  "| \n"
  1516                 "| You should contact your local evolution Guru for help.\n"
  1847                  "| You should contact your local evolution Guru for help.\n"
  1517                 % (divergent, othersstr))
  1848                  % (divergent, othersstr))
  1518         raise util.Abort("we do not handle divergence with split yet",
  1849         ui.write_err(msg)
  1519                          hint=hint)
  1850         return 2
  1520     other = others[0]
  1851     other = others[0]
  1521     if divergent.phase() <= phases.public:
  1852     if divergent.phase() <= phases.public:
  1522         raise util.Abort("we can't resolve this conflict from the public side",
  1853         msg = _("skipping %s: we can't resolve divergence from the public side\n") % divergent
  1523                     hint="%s is public, try from %s" % (divergent, other))
  1854         ui.write_err(msg)
       
  1855         hint = _("(%s is public, try from %s)\n" % (divergent, other))
       
  1856         ui.write_err(hint)
       
  1857         return 2
  1524     if len(other.parents()) > 1:
  1858     if len(other.parents()) > 1:
  1525         raise util.Abort("divergent changeset can't be a merge (yet)",
  1859         msg = _("skipping %s: divergent changeset can't be a merge (yet)\n" % divergent)
  1526                     hint="You have to fallback to solving this by hand...\n"
  1860         ui.write_err(msg)
  1527                          "| This probably means redoing the merge and using "
  1861         hint = _("You have to fallback to solving this by hand...\n"
  1528                          "| `hg prune` to kill older version.")
  1862                  "| This probably means redoing the merge and using \n"
       
  1863                  "| `hg prune` to kill older version.\n")
       
  1864         ui.write_err(hint)
       
  1865         return 2
  1529     if other.p1() not in divergent.parents():
  1866     if other.p1() not in divergent.parents():
  1530         raise util.Abort("parents are not common (not handled yet)",
  1867         msg = _("skipping %s: have a different parent than %s (not handled yet)\n") % (divergent, other)
  1531                     hint="| %(d)s, %(o)s are not based on the same changeset.\n"
  1868         hint = _("| %(d)s, %(o)s are not based on the same changeset.\n"
  1532                          "| With the current state of its implementation, \n"
  1869                  "| With the current state of its implementation, \n"
  1533                          "| evolve does not work in that case.\n"
  1870                  "| evolve does not work in that case.\n"
  1534                          "| rebase one of them next to the other and run \n"
  1871                  "| rebase one of them next to the other and run \n"
  1535                          "| this command again.\n"
  1872                  "| this command again.\n"
  1536                          "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n"
  1873                  "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n"
  1537                          "| - or:     hg rebase --dest 'p1(%(o)s)' -r %(d)s"
  1874                  "| - or:     hg rebase --dest 'p1(%(o)s)' -r %(d)s\n"
  1538                               % {'d': divergent, 'o': other})
  1875                  % {'d': divergent, 'o': other})
       
  1876         ui.write_err(msg)
       
  1877         ui.write_err(hint)
       
  1878         return 2
  1539 
  1879 
  1540     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1880     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1541     if not ui.quiet or confirm:
  1881     if not ui.quiet or confirm:
  1542         ui.write(_('merge:'))
  1882         ui.write(_('merge:'))
  1543         displayer.show(divergent)
  1883         displayer.show(divergent)
  1623 
  1963 
  1624 shorttemplate = '[{rev}] {desc|firstline}\n'
  1964 shorttemplate = '[{rev}] {desc|firstline}\n'
  1625 
  1965 
  1626 @command('^previous',
  1966 @command('^previous',
  1627          [('B', 'move-bookmark', False,
  1967          [('B', 'move-bookmark', False,
  1628              _('Move active bookmark after update'))],
  1968              _('Move active bookmark after update')),
       
  1969           ('', 'merge', False, _('bring uncommited change along'))],
  1629          '[-B]')
  1970          '[-B]')
  1630 def cmdprevious(ui, repo, **opts):
  1971 def cmdprevious(ui, repo, **opts):
  1631     """update to parent and display summary lines"""
  1972     """update to parent and display summary lines"""
  1632     wkctx = repo[None]
  1973     wkctx = repo[None]
  1633     wparents = wkctx.parents()
  1974     wparents = wkctx.parents()
  1634     if len(wparents) != 1:
  1975     if len(wparents) != 1:
  1635         raise util.Abort('merge in progress')
  1976         raise util.Abort('merge in progress')
       
  1977     if not opts['merge']:
       
  1978         try:
       
  1979             cmdutil.bailifchanged(repo)
       
  1980         except error.Abort, exc:
       
  1981             exc.hint = _('do you want --merge?')
       
  1982             raise
  1636 
  1983 
  1637     parents = wparents[0].parents()
  1984     parents = wparents[0].parents()
  1638     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1985     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1639     if len(parents) == 1:
  1986     if len(parents) == 1:
  1640         p = parents[0]
  1987         p = parents[0]
  1655         ui.warn(_('multiple parents, explicitly update to one\n'))
  2002         ui.warn(_('multiple parents, explicitly update to one\n'))
  1656         return 1
  2003         return 1
  1657 
  2004 
  1658 @command('^next',
  2005 @command('^next',
  1659          [('B', 'move-bookmark', False,
  2006          [('B', 'move-bookmark', False,
  1660              _('Move active bookmark after update'))],
  2007              _('Move active bookmark after update')),
       
  2008           ('', 'merge', False, _('bring uncommited change along')),
       
  2009           ('', 'evolve', False, _('evolve the next changeset if necessary'))],
  1661          '[-B]')
  2010          '[-B]')
  1662 def cmdnext(ui, repo, **opts):
  2011 def cmdnext(ui, repo, **opts):
  1663     """update to child and display summary lines"""
  2012     """update to next child
       
  2013 
       
  2014     You can use the --evolve flag to get unstable children evolved on demand.
       
  2015 
       
  2016     The summary line of the destination is displayed for clarity"""
  1664     wkctx = repo[None]
  2017     wkctx = repo[None]
  1665     wparents = wkctx.parents()
  2018     wparents = wkctx.parents()
  1666     if len(wparents) != 1:
  2019     if len(wparents) != 1:
  1667         raise util.Abort('merge in progress')
  2020         raise util.Abort('merge in progress')
       
  2021     if not opts['merge']:
       
  2022         try:
       
  2023             cmdutil.bailifchanged(repo)
       
  2024         except error.Abort, exc:
       
  2025             exc.hint = _('do you want --merge?')
       
  2026             raise
  1668 
  2027 
  1669     children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
  2028     children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
  1670     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  2029     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
  1671     if not children:
       
  1672         ui.warn(_('no non-obsolete children\n'))
       
  1673         return 1
       
  1674     if len(children) == 1:
  2030     if len(children) == 1:
  1675         c = children[0]
  2031         c = children[0]
  1676         bm = bmactive(repo)
  2032         bm = bmactive(repo)
  1677         shouldmove = opts.get('move_bookmark') and bm is not None
  2033         shouldmove = opts.get('move_bookmark') and bm is not None
  1678         ret = hg.update(repo, c.rev())
  2034         ret = hg.update(repo, c.rev())
  1681                 repo._bookmarks[bm] = c.node()
  2037                 repo._bookmarks[bm] = c.node()
  1682                 repo._bookmarks.write()
  2038                 repo._bookmarks.write()
  1683             else:
  2039             else:
  1684                 bmdeactivate(repo)
  2040                 bmdeactivate(repo)
  1685         displayer.show(c)
  2041         displayer.show(c)
  1686         return 0
  2042         result = 0
  1687     else:
  2043     elif children:
       
  2044         ui.warn("ambigious next changeset:\n")
  1688         for c in children:
  2045         for c in children:
  1689             displayer.show(c)
  2046             displayer.show(c)
  1690         ui.warn(_('multiple non-obsolete children, '
  2047         ui.warn(_('explicitly update to one of them\n'))
  1691             'explicitly update to one of them\n'))
  2048         result = 1
       
  2049     else:
       
  2050         aspchildren = _aspiringchildren(repo, [repo['.'].rev()])
       
  2051         if not opts['evolve']:
       
  2052             ui.warn(_('no children\n'))
       
  2053             if aspchildren:
       
  2054                 msg = _('(%i unstable changesets to be evolved here, '
       
  2055                         'do you want --evolve?)\n')
       
  2056                 ui.warn(msg % len(aspchildren))
       
  2057             result = 1
       
  2058         elif 1 < len(aspchildren):
       
  2059             ui.warn("ambigious next (unstable) changeset:\n")
       
  2060             for c in aspchildren:
       
  2061                 displayer.show(repo[c])
       
  2062             ui.warn(_('(run "hg evolve --rev REV" on one of them)\n'))
       
  2063             return 1
       
  2064         else:
       
  2065             cmdutil.bailifchanged(repo)
       
  2066             result = _solveone(ui, repo, repo[aspchildren[0]], False,
       
  2067                                False, lambda:None, category='unstable')
       
  2068             if not result:
       
  2069                 ui.status(_('working directory now at %s\n') % repo['.'])
       
  2070             return result
  1692         return 1
  2071         return 1
       
  2072     return result
  1693 
  2073 
  1694 def _reachablefrombookmark(repo, revs, mark):
  2074 def _reachablefrombookmark(repo, revs, mark):
  1695     """filter revisions and bookmarks reachable from the given bookmark
  2075     """filter revisions and bookmarks reachable from the given bookmark
  1696     yoinked from mq.py
  2076     yoinked from mq.py
  1697     """
  2077     """
  1738 
  2118 
  1739 @command('^prune|obsolete|kill',
  2119 @command('^prune|obsolete|kill',
  1740     [('n', 'new', [], _("successor changeset (DEPRECATED)")),
  2120     [('n', 'new', [], _("successor changeset (DEPRECATED)")),
  1741      ('s', 'succ', [], _("successor changeset")),
  2121      ('s', 'succ', [], _("successor changeset")),
  1742      ('r', 'rev', [], _("revisions to prune")),
  2122      ('r', 'rev', [], _("revisions to prune")),
       
  2123      ('k', 'keep', None, _("does not modify working copy during prune")),
  1743      ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
  2124      ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
  1744      ('B', 'bookmark', '', _("remove revs only reachable from given"
  2125      ('B', 'bookmark', '', _("remove revs only reachable from given"
  1745                              " bookmark"))] + metadataopts,
  2126                              " bookmark"))] + metadataopts,
  1746     _('[OPTION] [-r] REV...'))
  2127     _('[OPTION] [-r] REV...'))
  1747     # -U  --noupdate option to prevent wc update and or bookmarks update ?
  2128     # -U  --noupdate option to prevent wc update and or bookmarks update ?
  1777             _deletebookmark(ui, marks, bookmark)
  2158             _deletebookmark(ui, marks, bookmark)
  1778 
  2159 
  1779     if not revs:
  2160     if not revs:
  1780         raise util.Abort(_('nothing to prune'))
  2161         raise util.Abort(_('nothing to prune'))
  1781 
  2162 
  1782     wlock = lock = None
  2163     wlock = lock = tr = None
  1783     try:
  2164     try:
  1784         wlock = repo.wlock()
  2165         wlock = repo.wlock()
  1785         lock = repo.lock()
  2166         lock = repo.lock()
       
  2167         tr = repo.transaction('prune')
  1786         # defines pruned changesets
  2168         # defines pruned changesets
  1787         precs = []
  2169         precs = []
  1788         revs.sort()
  2170         revs.sort()
  1789         for p in revs:
  2171         for p in revs:
  1790             cp = repo[p]
  2172             cp = repo[p]
  1794                                  hint='see "hg help phases" for details')
  2176                                  hint='see "hg help phases" for details')
  1795             precs.append(cp)
  2177             precs.append(cp)
  1796         if not precs:
  2178         if not precs:
  1797             raise util.Abort('nothing to prune')
  2179             raise util.Abort('nothing to prune')
  1798 
  2180 
       
  2181         if not obsolete.isenabled(repo, obsolete.allowunstableopt):
       
  2182             if repo.revs("(%ld::) - %ld", revs, revs):
       
  2183                 raise util.Abort(_("cannot prune in the middle of a stack"))
       
  2184 
  1799         # defines successors changesets
  2185         # defines successors changesets
  1800         sucs = scmutil.revrange(repo, succs)
  2186         sucs = scmutil.revrange(repo, succs)
  1801         sucs.sort()
  2187         sucs.sort()
  1802         sucs = tuple(repo[n] for n in sucs)
  2188         sucs = tuple(repo[n] for n in sucs)
  1803         if not biject and len(sucs) > 1 and len(precs) > 1:
  2189         if not biject and len(sucs) > 1 and len(precs) > 1:
  1811 
  2197 
  1812         relations = [(p, sucs) for p in precs]
  2198         relations = [(p, sucs) for p in precs]
  1813         if biject:
  2199         if biject:
  1814             relations = [(p, (s,)) for p, s in zip(precs, sucs)]
  2200             relations = [(p, (s,)) for p, s in zip(precs, sucs)]
  1815 
  2201 
  1816         # create markers
       
  1817         obsolete.createmarkers(repo, relations, metadata=metadata)
       
  1818 
       
  1819         # informs that changeset have been pruned
       
  1820         ui.status(_('%i changesets pruned\n') % len(precs))
       
  1821 
       
  1822         wdp = repo['.']
  2202         wdp = repo['.']
  1823 
  2203 
  1824         if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
  2204         if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
  1825             # '.' killed, so update to the successor
  2205             # '.' killed, so update to the successor
  1826             newnode = sucs[0]
  2206             newnode = sucs[0]
  1827         else:
  2207         else:
  1828             # update to an unkilled parent
  2208             # update to an unkilled parent
  1829             newnode = wdp
  2209             newnode = wdp
  1830 
  2210 
  1831             while newnode.obsolete():
  2211             while newnode in precs or newnode.obsolete():
  1832                 newnode = newnode.parents()[0]
  2212                 newnode = newnode.parents()[0]
  1833 
  2213 
       
  2214 
  1834         if newnode.node() != wdp.node():
  2215         if newnode.node() != wdp.node():
  1835             bookactive = bmactive(repo)
  2216             if opts.get('keep', False):
  1836             # Active bookmark that we don't want to delete (with -B option)
  2217                 # This is largely the same as the implementation in
  1837             # we deactivate and move it before the update and reactivate it
  2218                 # strip.stripcmd(). We might want to refactor this somewhere
  1838             # after
  2219                 # common at some point.
  1839             movebookmark = bookactive and not bookmark
  2220 
  1840             if movebookmark:
  2221                 # only reset the dirstate for files that would actually change
  1841                 bmdeactivate(repo)
  2222                 # between the working context and uctx
  1842                 repo._bookmarks[bookactive] = newnode.node()
  2223                 descendantrevs = repo.revs("%d::." % newnode.rev())
  1843                 repo._bookmarks.write()
  2224                 changedfiles = []
  1844             commands.update(ui, repo, newnode.rev())
  2225                 for rev in descendantrevs:
  1845             ui.status(_('working directory now at %s\n') % newnode)
  2226                     # blindly reset the files, regardless of what actually changed
  1846             if movebookmark:
  2227                     changedfiles.extend(repo[rev].files())
  1847                 bmactivate(repo, bookactive)
  2228 
       
  2229                 # reset files that only changed in the dirstate too
       
  2230                 dirstate = repo.dirstate
       
  2231                 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
       
  2232                 changedfiles.extend(dirchanges)
       
  2233                 repo.dirstate.rebuild(newnode.node(), newnode.manifest(), changedfiles)
       
  2234                 repo.dirstate.write()
       
  2235             else:
       
  2236                 bookactive = bmactive(repo)
       
  2237                 # Active bookmark that we don't want to delete (with -B option)
       
  2238                 # we deactivate and move it before the update and reactivate it
       
  2239                 # after
       
  2240                 movebookmark = bookactive and not bookmark
       
  2241                 if movebookmark:
       
  2242                     bmdeactivate(repo)
       
  2243                     repo._bookmarks[bookactive] = newnode.node()
       
  2244                     repo._bookmarks.write()
       
  2245                 commands.update(ui, repo, newnode.rev())
       
  2246                 ui.status(_('working directory now at %s\n') % newnode)
       
  2247                 if movebookmark:
       
  2248                     bmactivate(repo, bookactive)
  1848 
  2249 
  1849         # update bookmarks
  2250         # update bookmarks
  1850         if bookmark:
  2251         if bookmark:
  1851             _deletebookmark(ui, marks, bookmark)
  2252             _deletebookmark(ui, marks, bookmark)
       
  2253 
       
  2254         # create markers
       
  2255         obsolete.createmarkers(repo, relations, metadata=metadata)
       
  2256         
       
  2257         # informs that changeset have been pruned
       
  2258         ui.status(_('%i changesets pruned\n') % len(precs))
       
  2259 
  1852         for ctx in repo.unfiltered().set('bookmark() and %ld', precs):
  2260         for ctx in repo.unfiltered().set('bookmark() and %ld', precs):
  1853             # used to be:
  2261             # used to be:
  1854             #
  2262             #
  1855             #   ldest = list(repo.set('max((::%d) - obsolete())', ctx))
  2263             #   ldest = list(repo.set('max((::%d) - obsolete())', ctx))
  1856             #   if ldest:
  2264             #   if ldest:
  1861             for dest in ctx.ancestors():
  2269             for dest in ctx.ancestors():
  1862                 if not dest.obsolete():
  2270                 if not dest.obsolete():
  1863                     updatebookmarks = _bookmarksupdater(repo, ctx.node())
  2271                     updatebookmarks = _bookmarksupdater(repo, ctx.node())
  1864                     updatebookmarks(dest.node())
  2272                     updatebookmarks(dest.node())
  1865                     break
  2273                     break
       
  2274 
       
  2275         tr.close()
  1866     finally:
  2276     finally:
  1867         lockmod.release(lock, wlock)
  2277         lockmod.release(tr, lock, wlock)
  1868 
  2278 
  1869 @command('amend|refresh',
  2279 @command('amend|refresh',
  1870     [('A', 'addremove', None,
  2280     [('A', 'addremove', None,
  1871      _('mark new/missing files as added/removed before committing')),
  2281      _('mark new/missing files as added/removed before committing')),
  1872     ('e', 'edit', False, _('invoke editor on commit messages')),
  2282     ('e', 'edit', False, _('invoke editor on commit messages')),
  2051             rev = scmutil.revsingle(repo, opts.get('rev'))
  2461             rev = scmutil.revsingle(repo, opts.get('rev'))
  2052             ctx = repo[None]
  2462             ctx = repo[None]
  2053             if ctx.p1() == rev or ctx.p2() == rev:
  2463             if ctx.p1() == rev or ctx.p2() == rev:
  2054                 raise util.Abort(_("cannot uncommit to parent changeset"))
  2464                 raise util.Abort(_("cannot uncommit to parent changeset"))
  2055 
  2465 
       
  2466         onahead = old.rev() in repo.changelog.headrevs()
       
  2467         disallowunstable = not obsolete.isenabled(repo, obsolete.allowunstableopt)
       
  2468         if disallowunstable and not onahead:
       
  2469             raise util.Abort(_("cannot uncommit in the middle of a stack"))
       
  2470 
  2056         # Recommit the filtered changeset
  2471         # Recommit the filtered changeset
  2057         tr = repo.transaction('uncommit')
  2472         tr = repo.transaction('uncommit')
  2058         newid = None
  2473         newid = None
  2059         if (pats or opts.get('include') or opts.get('exclude')
  2474         includeorexclude = opts.get('include') or opts.get('exclude')
  2060             or opts.get('all')):
  2475         if (pats or includeorexclude or opts.get('all')):
  2061             match = scmutil.match(old, pats, opts)
  2476             match = scmutil.match(old, pats, opts)
  2062             newid = _commitfiltered(repo, old, match, target=rev)
  2477             newid = _commitfiltered(repo, old, match, target=rev)
  2063         if newid is None:
  2478         if newid is None:
  2064             raise util.Abort(_('nothing to uncommit'),
  2479             raise util.Abort(_('nothing to uncommit'),
  2065                              hint=_("use --all to uncommit all files"))
  2480                              hint=_("use --all to uncommit all files"))
  2104             if oldbookmarks:
  2519             if oldbookmarks:
  2105                 repo._bookmarks.write()
  2520                 repo._bookmarks.write()
  2106         return result
  2521         return result
  2107     finally:
  2522     finally:
  2108         lockmod.release(lock, wlock)
  2523         lockmod.release(lock, wlock)
       
  2524 
       
  2525 @eh.wrapcommand('strip', extension='strip', opts=[
       
  2526     ('', 'bundle', None, _("delete the commit entirely and move it to a "
       
  2527         "backup bundle")),
       
  2528     ])
       
  2529 def stripwrapper(orig, ui, repo, *revs, **kwargs):
       
  2530     if (not ui.configbool('experimental', 'prunestrip') or
       
  2531         kwargs.get('bundle', False)):
       
  2532         return orig(ui, repo, *revs, **kwargs)
       
  2533 
       
  2534     if kwargs.get('force'):
       
  2535         ui.warn(_("warning: --force has no effect during strip with evolve "
       
  2536                   "enabled\n"))
       
  2537     if kwargs.get('no_backup', False):
       
  2538         ui.warn(_("warning: --no-backup has no effect during strips with "
       
  2539                   "evolve enabled\n"))
       
  2540 
       
  2541     revs = list(revs) + kwargs.pop('rev', [])
       
  2542     revs = set(scmutil.revrange(repo, revs))
       
  2543     revs = repo.revs("(%ld)::", revs)
       
  2544     kwargs['rev'] = []
       
  2545     kwargs['new'] = []
       
  2546     kwargs['succ'] = []
       
  2547     kwargs['biject'] = False
       
  2548     return cmdprune(ui, repo, *revs, **kwargs)
  2109 
  2549 
  2110 @command('^touch',
  2550 @command('^touch',
  2111     [('r', 'rev', [], 'revision to update'),
  2551     [('r', 'rev', [], 'revision to update'),
  2112      ('D', 'duplicate', False,
  2552      ('D', 'duplicate', False,
  2113       'do not mark the new revision as successor of the old one')],
  2553       'do not mark the new revision as successor of the old one')],
  2231     heads = repo.revs('heads(%ld)', revs)
  2671     heads = repo.revs('heads(%ld)', revs)
  2232     if len(heads) > 1:
  2672     if len(heads) > 1:
  2233         raise util.Abort(_("cannot fold non-linear revisions "
  2673         raise util.Abort(_("cannot fold non-linear revisions "
  2234                            "(multiple heads given)"))
  2674                            "(multiple heads given)"))
  2235     head = repo[heads.first()]
  2675     head = repo[heads.first()]
       
  2676     disallowunstable = not obsolete.isenabled(repo, obsolete.allowunstableopt)
       
  2677     if disallowunstable:
       
  2678         if repo.revs("(%ld::) - %ld", revs, revs):
       
  2679             raise util.Abort(_("cannot fold chain not ending with a head "\
       
  2680                                "or with branching"))
  2236     wlock = lock = None
  2681     wlock = lock = None
  2237     try:
  2682     try:
  2238         wlock = repo.wlock()
  2683         wlock = repo.wlock()
  2239         lock = repo.lock()
  2684         lock = repo.lock()
  2240         tr = repo.transaction('touch')
  2685         tr = repo.transaction('touch')
  2297         lockmod.release(lock, wlock)
  2742         lockmod.release(lock, wlock)
  2298 
  2743 
  2299 @eh.extsetup
  2744 @eh.extsetup
  2300 def oldevolveextsetup(ui):
  2745 def oldevolveextsetup(ui):
  2301     for cmd in ['kill', 'uncommit', 'touch', 'fold']:
  2746     for cmd in ['kill', 'uncommit', 'touch', 'fold']:
  2302         entry = extensions.wrapcommand(cmdtable, cmd,
  2747         try:
  2303                                        warnobserrors)
  2748             entry = extensions.wrapcommand(cmdtable, cmd,
       
  2749                                            warnobserrors)
       
  2750         except error.UnknownCommand:
       
  2751             # Commands may be disabled
       
  2752             continue
  2304 
  2753 
  2305     entry = cmdutil.findcmd('commit', commands.table)[1]
  2754     entry = cmdutil.findcmd('commit', commands.table)[1]
  2306     entry[1].append(('o', 'obsolete', [],
  2755     entry[1].append(('o', 'obsolete', [],
  2307                      _("make commit obsolete this revision (DEPRECATED)")))
  2756                      _("make commit obsolete this revision (DEPRECATED)")))
  2308     entry = cmdutil.findcmd('graft', commands.table)[1]
  2757     entry = cmdutil.findcmd('graft', commands.table)[1]
  2327     topic = 'obsmarkers exchange'
  2776     topic = 'obsmarkers exchange'
  2328     if ui.configbool('experimental', 'verbose-obsolescence-exchange', False):
  2777     if ui.configbool('experimental', 'verbose-obsolescence-exchange', False):
  2329         topic = 'OBSEXC'
  2778         topic = 'OBSEXC'
  2330     ui.progress(topic, *args, **kwargs)
  2779     ui.progress(topic, *args, **kwargs)
  2331 
  2780 
  2332 if getattr(exchange, '_pushdiscoveryobsmarkers', None) is not None:
  2781 @eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers')
  2333     @eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers')
  2782 def _pushdiscoveryobsmarkers(orig, pushop):
  2334     def _pushdiscoveryobsmarkers(orig, pushop):
  2783     if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
  2335         if (obsolete._enabled
  2784         and pushop.repo.obsstore
  2336             and pushop.repo.obsstore
  2785         and 'obsolete' in pushop.remote.listkeys('namespaces')):
  2337             and 'obsolete' in pushop.remote.listkeys('namespaces')):
  2786         repo = pushop.repo
  2338             repo = pushop.repo
  2787         obsexcmsg(repo.ui, "computing relevant nodes\n")
  2339             obsexcmsg(repo.ui, "computing relevant nodes\n")
  2788         revs = list(repo.revs('::%ln', pushop.futureheads))
  2340             revs = list(repo.revs('::%ln', pushop.futureheads))
  2789         unfi = repo.unfiltered()
  2341             unfi = repo.unfiltered()
  2790         cl = unfi.changelog
  2342             cl = unfi.changelog
  2791         if not pushop.remote.capable('_evoext_obshash_0'):
  2343             if not pushop.remote.capable('_evoext_obshash_0'):
  2792             # do not trust core yet
  2344                 # do not trust core yet
  2793             # return orig(pushop)
  2345                 # return orig(pushop)
       
  2346                 nodes = [cl.node(r) for r in revs]
       
  2347                 if nodes:
       
  2348                     obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
       
  2349                                        % len(nodes))
       
  2350                     pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
       
  2351                 else:
       
  2352                     obsexcmsg(repo.ui, "markers already in sync\n")
       
  2353                     pushop.outobsmarkers = []
       
  2354                     pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
       
  2355                 return
       
  2356 
       
  2357             common = []
       
  2358             obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
       
  2359                                % len(revs))
       
  2360             commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads))
       
  2361             common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs)
       
  2362 
       
  2363             revs = list(unfi.revs('%ld - (::%ln)', revs, common))
       
  2364             nodes = [cl.node(r) for r in revs]
  2794             nodes = [cl.node(r) for r in revs]
  2365             if nodes:
  2795             if nodes:
  2366                 obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
  2796                 obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
  2367                                    % len(nodes))
  2797                                    % len(nodes))
  2368                 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
  2798                 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
  2369             else:
  2799             else:
  2370                 obsexcmsg(repo.ui, "markers already in sync\n")
  2800                 obsexcmsg(repo.ui, "markers already in sync\n")
  2371                 pushop.outobsmarkers = []
  2801                 pushop.outobsmarkers = []
       
  2802                 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
       
  2803             return
       
  2804 
       
  2805         common = []
       
  2806         obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
       
  2807                            % len(revs))
       
  2808         commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads))
       
  2809         common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs)
       
  2810 
       
  2811         revs = list(unfi.revs('%ld - (::%ln)', revs, common))
       
  2812         nodes = [cl.node(r) for r in revs]
       
  2813         if nodes:
       
  2814             obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
       
  2815                                % len(nodes))
       
  2816             pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
       
  2817         else:
       
  2818             obsexcmsg(repo.ui, "markers already in sync\n")
       
  2819             pushop.outobsmarkers = []
  2372 
  2820 
  2373 @eh.wrapfunction(wireproto, 'capabilities')
  2821 @eh.wrapfunction(wireproto, 'capabilities')
  2374 def discocapabilities(orig, repo, proto):
  2822 def discocapabilities(orig, repo, proto):
  2375     """wrapper to advertise new capability"""
  2823     """wrapper to advertise new capability"""
  2376     caps = orig(repo, proto)
  2824     caps = orig(repo, proto)
  2377     if obsolete._enabled:
  2825     if obsolete.isenabled(repo, obsolete.exchangeopt):
  2378         caps += ' _evoext_obshash_0'
  2826         caps += ' _evoext_obshash_0'
  2379     return caps
  2827     return caps
  2380 
  2828 
  2381 @eh.extsetup
  2829 @eh.extsetup
  2382 def _installobsmarkersdiscovery(ui):
  2830 def _installobsmarkersdiscovery(ui):
  2532     if cgresult == 0:
  2980     if cgresult == 0:
  2533         return
  2981         return
  2534     pushop.ui.debug('try to push obsolete markers to remote\n')
  2982     pushop.ui.debug('try to push obsolete markers to remote\n')
  2535     repo = pushop.repo
  2983     repo = pushop.repo
  2536     remote = pushop.remote
  2984     remote = pushop.remote
  2537     if (obsolete._enabled  and repo.obsstore and
  2985     if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore and
  2538         'obsolete' in remote.listkeys('namespaces')):
  2986         'obsolete' in remote.listkeys('namespaces')):
  2539         markers = pushop.outobsmarkers
  2987         markers = pushop.outobsmarkers
  2540         if not markers:
  2988         if not markers:
  2541             obsexcmsg(repo.ui, "no marker to push\n")
  2989             obsexcmsg(repo.ui, "no marker to push\n")
  2542         elif remote.capable('_evoext_pushobsmarkers_0'):
  2990         elif remote.capable('_evoext_pushobsmarkers_0'):
  2607 def local_pushobsmarker_capabilities(orig, repo, caps):
  3055 def local_pushobsmarker_capabilities(orig, repo, caps):
  2608     caps = orig(repo, caps)
  3056     caps = orig(repo, caps)
  2609     caps.add('_evoext_pushobsmarkers_0')
  3057     caps.add('_evoext_pushobsmarkers_0')
  2610     return caps
  3058     return caps
  2611 
  3059 
  2612 @eh.addattr(localrepo.localpeer, 'evoext_pushobsmarkers_0')
  3060 def _pushobsmarkers(repo, data):
  2613 def local_pushobsmarkers(peer, obsfile):
       
  2614     data = obsfile.read()
       
  2615     tr = lock = None
       
  2616     try:
       
  2617         lock = peer._repo.lock()
       
  2618         tr = peer._repo.transaction('pushkey: obsolete markers')
       
  2619         new = peer._repo.obsstore.mergemarkers(tr, data)
       
  2620         if new is not None:
       
  2621             obsexcmsg(peer._repo.ui, "%i obsolescence markers added\n" % new, True)
       
  2622         tr.close()
       
  2623     finally:
       
  2624         lockmod.release(tr, lock)
       
  2625     peer._repo.hook('evolve_pushobsmarkers')
       
  2626 
       
  2627 def srv_pushobsmarkers(repo, proto):
       
  2628     """wireprotocol command"""
       
  2629     fp = StringIO()
       
  2630     proto.redirect()
       
  2631     proto.getfile(fp)
       
  2632     data = fp.getvalue()
       
  2633     fp.close()
       
  2634     tr = lock = None
  3061     tr = lock = None
  2635     try:
  3062     try:
  2636         lock = repo.lock()
  3063         lock = repo.lock()
  2637         tr = repo.transaction('pushkey: obsolete markers')
  3064         tr = repo.transaction('pushkey: obsolete markers')
  2638         new = repo.obsstore.mergemarkers(tr, data)
  3065         new = repo.obsstore.mergemarkers(tr, data)
  2640             obsexcmsg(repo.ui, "%i obsolescence markers added\n" % new, True)
  3067             obsexcmsg(repo.ui, "%i obsolescence markers added\n" % new, True)
  2641         tr.close()
  3068         tr.close()
  2642     finally:
  3069     finally:
  2643         lockmod.release(tr, lock)
  3070         lockmod.release(tr, lock)
  2644     repo.hook('evolve_pushobsmarkers')
  3071     repo.hook('evolve_pushobsmarkers')
       
  3072 
       
  3073 @eh.addattr(localrepo.localpeer, 'evoext_pushobsmarkers_0')
       
  3074 def local_pushobsmarkers(peer, obsfile):
       
  3075     data = obsfile.read()
       
  3076     _pushobsmarkers(peer._repo, data)
       
  3077 
       
  3078 def srv_pushobsmarkers(repo, proto):
       
  3079     """wireprotocol command"""
       
  3080     fp = StringIO()
       
  3081     proto.redirect()
       
  3082     proto.getfile(fp)
       
  3083     data = fp.getvalue()
       
  3084     fp.close()
       
  3085     _pushobsmarkers(repo, data)
  2645     return wireproto.pushres(0)
  3086     return wireproto.pushres(0)
  2646 
  3087 
  2647 def _buildpullobsmarkersboundaries(pullop):
  3088 def _buildpullobsmarkersboundaries(pullop):
  2648     """small funtion returning the argument for pull markers call
  3089     """small funtion returning the argument for pull markers call
  2649     may to contains 'heads' and 'common'. skip the key for None.
  3090     may to contains 'heads' and 'common'. skip the key for None.
  2672         common = boundaries['common']
  3113         common = boundaries['common']
  2673         if common != [nullid]:
  3114         if common != [nullid]:
  2674             kwargs['evo_obscommon'] = common
  3115             kwargs['evo_obscommon'] = common
  2675     return ret
  3116     return ret
  2676 
  3117 
  2677 if getattr(exchange, '_getbundleobsmarkerpart', None) is not None:
  3118 @eh.wrapfunction(exchange, '_getbundleobsmarkerpart')
  2678     @eh.wrapfunction(exchange, '_getbundleobsmarkerpart')
  3119 def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs):
  2679     def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs):
  3120     if 'evo_obscommon' not in kwargs:
  2680         if 'evo_obscommon' not in kwargs:
  3121         return orig(bundler, repo, source, **kwargs)
  2681             return orig(bundler, repo, source, **kwargs)
  3122 
  2682 
  3123     heads = kwargs.get('heads')
  2683         heads = kwargs.get('heads')
  3124     if kwargs.get('obsmarkers', False):
  2684         if kwargs.get('obsmarkers', False):
  3125         if heads is None:
  2685             if heads is None:
  3126             heads = repo.heads()
  2686                 heads = repo.heads()
  3127         obscommon = kwargs.get('evo_obscommon', ())
  2687             obscommon = kwargs.get('evo_obscommon', ())
  3128         assert obscommon
  2688             assert obscommon
  3129         obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon)
  2689             obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon)
  3130         subset = [c.node() for c in obsset]
  2690             subset = [c.node() for c in obsset]
  3131         markers = repo.obsstore.relevantmarkers(subset)
  2691             markers = repo.obsstore.relevantmarkers(subset)
  3132         exchange.buildobsmarkerspart(bundler, markers)
  2692             exchange.buildobsmarkerspart(bundler, markers)
  3133 
  2693 
  3134 @eh.uisetup
  2694     @eh.uisetup
  3135 def installgetbundlepartgen(ui):
  2695     def installgetbundlepartgen(ui):
  3136     origfunc = exchange.getbundle2partsmapping['obsmarkers']
  2696         origfunc = exchange.getbundle2partsmapping['obsmarkers']
  3137     def newfunc(*args, **kwargs):
  2697         def newfunc(*args, **kwargs):
  3138         return _getbundleobsmarkerpart(origfunc, *args, **kwargs)
  2698             return _getbundleobsmarkerpart(origfunc, *args, **kwargs)
  3139     exchange.getbundle2partsmapping['obsmarkers'] = newfunc
  2699         exchange.getbundle2partsmapping['obsmarkers'] = newfunc
       
  2700 
       
  2701 
  3140 
  2702 @eh.wrapfunction(exchange, '_pullobsolete')
  3141 @eh.wrapfunction(exchange, '_pullobsolete')
  2703 def _pullobsolete(orig, pullop):
  3142 def _pullobsolete(orig, pullop):
  2704     if not obsolete._enabled:
  3143     if not obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
  2705         return None
  3144         return None
  2706     if 'obsmarkers' not in getattr(pullop, 'todosteps', ['obsmarkers']):
  3145     if 'obsmarkers' not in getattr(pullop, 'todosteps', ['obsmarkers']):
  2707         return None
  3146         return None
  2708     if 'obsmarkers' in getattr(pullop, 'stepsdone', []):
  3147     if 'obsmarkers' in getattr(pullop, 'stepsdone', []):
  2709         return None
  3148         return None
  2869         ui.status('%s %s\n' % (node.hex(chg), node.hex(obs)))
  3308         ui.status('%s %s\n' % (node.hex(chg), node.hex(obs)))
  2870 
  3309 
  2871 _bestformat = max(obsolete.formats.keys())
  3310 _bestformat = max(obsolete.formats.keys())
  2872 
  3311 
  2873 
  3312 
  2874 if getattr(obsolete, '_checkinvalidmarkers', None) is not None:
  3313 @eh.wrapfunction(obsolete, '_checkinvalidmarkers')
  2875     @eh.wrapfunction(obsolete, '_checkinvalidmarkers')
  3314 def _checkinvalidmarkers(orig, markers):
  2876     def _checkinvalidmarkers(orig, markers):
  3315     """search for marker with invalid data and raise error if needed
  2877         """search for marker with invalid data and raise error if needed
  3316 
  2878 
  3317     Exist as a separated function to allow the evolve extension for a more
  2879         Exist as a separated function to allow the evolve extension for a more
  3318     subtle handling.
  2880         subtle handling.
  3319     """
  2881         """
  3320     if 'debugobsconvert' in sys.argv:
  2882         if 'debugobsconvert' in sys.argv:
  3321         return
  2883             return
  3322     for mark in markers:
  2884         for mark in markers:
  3323         if node.nullid in mark[1]:
  2885             if node.nullid in mark[1]:
  3324             raise util.Abort(_('bad obsolescence marker detected: '
  2886                 raise util.Abort(_('bad obsolescence marker detected: '
  3325                                'invalid successors nullid'),
  2887                                    'invalid successors nullid'),
  3326                              hint=_('You should run `hg debugobsconvert`'))
  2888                                  hint=_('You should run `hg debugobsconvert`'))
       
  2889 
  3327 
  2890 @command(
  3328 @command(
  2891     'debugobsconvert',
  3329     'debugobsconvert',
  2892     [('', 'new-format', _bestformat, _('Destination format for markers.'))],
  3330     [('', 'new-format', _bestformat, _('Destination format for markers.'))],
  2893     '')
  3331     '')
  2918 
  3356 
  2919 @eh.wrapfunction(wireproto, 'capabilities')
  3357 @eh.wrapfunction(wireproto, 'capabilities')
  2920 def capabilities(orig, repo, proto):
  3358 def capabilities(orig, repo, proto):
  2921     """wrapper to advertise new capability"""
  3359     """wrapper to advertise new capability"""
  2922     caps = orig(repo, proto)
  3360     caps = orig(repo, proto)
  2923     if obsolete._enabled:
  3361     if obsolete.isenabled(repo, obsolete.exchangeopt):
  2924         caps += ' _evoext_pushobsmarkers_0'
  3362         caps += ' _evoext_pushobsmarkers_0'
  2925         caps += ' _evoext_pullobsmarkers_0'
  3363         caps += ' _evoext_pullobsmarkers_0'
  2926         caps += ' _evoext_obshash_0'
  3364         caps += ' _evoext_obshash_0'
  2927         caps += ' _evoext_obshash_1'
  3365         caps += ' _evoext_obshash_1'
  2928         caps += ' _evoext_getbundle_obscommon'
  3366         caps += ' _evoext_getbundle_obscommon'
  2939     # wrap command content
  3377     # wrap command content
  2940     oldcap, args = wireproto.commands['capabilities']
  3378     oldcap, args = wireproto.commands['capabilities']
  2941     def newcap(repo, proto):
  3379     def newcap(repo, proto):
  2942         return capabilities(oldcap, repo, proto)
  3380         return capabilities(oldcap, repo, proto)
  2943     wireproto.commands['capabilities'] = (newcap, args)
  3381     wireproto.commands['capabilities'] = (newcap, args)
       
  3382 
       
  3383 def _helploader():
       
  3384     return help.gettext(evolutionhelptext)
       
  3385 
       
  3386 @eh.uisetup
       
  3387 def _setuphelp(ui):
       
  3388     for entry in help.helptable:
       
  3389         if entry[0] == "evolution":
       
  3390             break
       
  3391     else:
       
  3392         help.helptable.append((["evolution"], _("Safely Rewriting History"),
       
  3393                       _helploader))
       
  3394         help.helptable.sort()