merge bumped rename
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Tue, 23 Oct 2012 17:43:22 +0200
changeset 594 7f89b31fcb26
parent 592 35c46a780dd6 (diff)
parent 593 26f76b38f879 (current diff)
child 596 5527e4fe4418
merge bumped rename (drop several part of the code because bumped is now in core)
hgext/evolve.py
tests/test-obsolete.t
--- a/hgext/evolve.py	Tue Oct 23 17:32:22 2012 +0200
+++ b/hgext/evolve.py	Tue Oct 23 17:43:22 2012 +0200
@@ -19,7 +19,7 @@
     - improves some aspect of the early implementation in 2.3
 '''
 
-testedwith = '2.3 2.3.1 2.3.2'
+testedwith = '2.4-rc'
 buglink = 'https://bitbucket.org/marmoute/mutable-history/issues'
 
 
@@ -29,18 +29,11 @@
 
 try:
     from mercurial import obsolete
+    getattr(obsolete, 'getrevs') # 2.4 specific
     if not obsolete._enabled:
         obsolete._enabled = True
-except ImportError:
-    raise util.Abort('Evolve extension requires Mercurial 2.3 (or later)')
-
-try:
-    getattr(obsolete, 'getrevs') # 2.4 specific
-    raise util.Abort('Your version of Mercurial is too recent for this '
-                     'version of evolve',
-                     hint="upgrade your evolve")
-except AttributeError:
-    pass
+except (ImportError, AttributeError):
+    raise util.Abort('Evolve extension requires Mercurial 2.4 (or later)')
 
 
 from mercurial import bookmarks
@@ -326,184 +319,10 @@
         yield marker
 
 
-#####################################################################
-### Obsolescence Caching Logic                                    ###
-#####################################################################
-
-# IN CORE fb72eec7efd8
-
-# Obsolescence related logic can be very slow if we don't have efficient cache.
-#
-# This section implements a cache mechanism that did not make it into core for
-# time reason. It stores meaningful set of revisions related to obsolescence
-# (obsolete, unstable, etc.)
-#
-# Here is:
-#
-# - Computation of meaningful sets
-# - Cache access logic,
-# - Cache invalidation logic,
-# - revset and ctx using this cache.
-#
-
-
-### Computation of meaningful set
-#
-# Most set can be computed with "simple" revset.
-
-#: { set name -> function to compute this set } mapping
-#:   function take a single "repo" argument.
-#:
-#: Use the `cachefor` decorator to register new cache function
-try:
-    cachefuncs = obsolete.cachefuncs
-    cachefor = obsolete.cachefor
-    getobscache = obsolete.getobscache
-    clearobscaches = obsolete.clearobscaches
-except AttributeError:
-    cachefuncs = {}
-
-    def cachefor(name):
-        """Decorator to register a function as computing the cache for a set"""
-        def decorator(func):
-            assert name not in cachefuncs
-            cachefuncs[name] = func
-            return func
-        return decorator
-
-    @cachefor('obsolete')
-    def _computeobsoleteset(repo):
-        """the set of obsolete revisions"""
-        obs = set()
-        nm = repo.changelog.nodemap
-        for prec in repo.obsstore.precursors:
-            rev = nm.get(prec)
-            if rev is not None:
-                obs.add(rev)
-        return set(repo.revs('%ld - public()', obs))
-
-    @cachefor('unstable')
-    def _computeunstableset(repo):
-        """the set of non obsolete revisions with obsolete parents"""
-        return set(repo.revs('(obsolete()::) - obsolete()'))
-
-    @cachefor('suspended')
-    def _computesuspendedset(repo):
-        """the set of obsolete parents with non obsolete descendants"""
-        return set(repo.revs('obsolete() and obsolete()::unstable()'))
-
-    @cachefor('extinct')
-    def _computeextinctset(repo):
-        """the set of obsolete parents without non obsolete descendants"""
-        return set(repo.revs('obsolete() - obsolete()::unstable()'))
-
-    @eh.wrapfunction(obsolete.obsstore, '__init__')
-    def _initobsstorecache(orig, obsstore, *args, **kwargs):
-        """add a cache attribute to obsstore"""
-        obsstore.caches = {}
-        return orig(obsstore, *args, **kwargs)
-
-### Cache access
-
-    def getobscache(repo, name):
-        """Return the set of revision that belong to the <name> set
-
-        Such access may compute the set and cache it for future use"""
-        if not repo.obsstore:
-            return ()
-        if name not in repo.obsstore.caches:
-            repo.obsstore.caches[name] = cachefuncs[name](repo)
-        return repo.obsstore.caches[name]
-
-### Cache clean up
-#
-# To be simple we need to invalidate obsolescence cache when:
-#
-# - new changeset is added:
-# - public phase is changed
-# - obsolescence marker are added
-# - strip is used a repo
-
-
-    def clearobscaches(repo):
-        """Remove all obsolescence related cache from a repo
-
-        This remove all cache in obsstore is the obsstore already exist on the
-        repo.
-
-        (We could be smarter here)"""
-        if 'obsstore' in repo._filecache:
-            repo.obsstore.caches.clear()
-
-    @eh.wrapfunction(localrepo.localrepository, 'addchangegroup')  # new changeset
-    @eh.wrapfunction(phases, 'retractboundary')  # phase movement
-    @eh.wrapfunction(phases, 'advanceboundary')  # phase movement
-    @eh.wrapfunction(localrepo.localrepository, 'destroyed')  # strip
-    def wrapclearcache(orig, repo, *args, **kwargs):
-        try:
-            return orig(repo, *args, **kwargs)
-        finally:
-            # we are a bit wide here
-            # we could restrict to:
-            # advanceboundary + phase==public
-            # retractboundary + phase==draft
-            clearobscaches(repo)
-
-    @eh.wrapfunction(obsolete.obsstore, 'add')  # new marker
-    def clearonadd(orig, obsstore, *args, **kwargs):
-        try:
-            return orig(obsstore, *args, **kwargs)
-        finally:
-            obsstore.caches.clear()
-
-### Use the case
-# Function in core that could benefic from the cache are overwritten by cache using version
-
-# changectx method
-
-    @eh.addattr(context.changectx, 'unstable')
-    def unstable(ctx):
-        """is the changeset unstable (have obsolete ancestor)"""
-        if ctx.node() is None:
-            return False
-        return ctx.rev() in getobscache(ctx._repo, 'unstable')
-
-
-    @eh.addattr(context.changectx, 'extinct')
-    def extinct(ctx):
-        """is the changeset extinct by other"""
-        if ctx.node() is None:
-            return False
-        return ctx.rev() in getobscache(ctx._repo, 'extinct')
-
-# revset
-
-    @eh.revset('obsolete')
-    def revsetobsolete(repo, subset, x):
-        """``obsolete()``
-        Changeset is obsolete.
-        """
-        args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
-        obsoletes = getobscache(repo, 'obsolete')
-        return [r for r in subset if r in obsoletes]
-
-    @eh.revset('unstable')
-    def revsetunstable(repo, subset, x):
-        """``unstable()``
-        Unstable changesets are non-obsolete with obsolete ancestors.
-        """
-        args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
-        unstables = getobscache(repo, 'unstable')
-        return [r for r in subset if r in unstables]
-
-    @eh.revset('extinct')
-    def revsetextinct(repo, subset, x):
-        """``extinct()``
-        Obsolete changesets with obsolete descendants only.
-        """
-        args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
-        extincts = getobscache(repo, 'extinct')
-        return [r for r in subset if r in extincts]
+cachefuncs = obsolete.cachefuncs
+cachefor = obsolete.cachefor
+getrevs = obsolete.getrevs
+clearobscaches = obsolete.clearobscaches
 
 #####################################################################
 ### Complete troubles computation logic                           ###
@@ -522,13 +341,6 @@
 ### Cache computation
 latediff = 1  # flag to prevent taking late comer fix into account
 
-@cachefor('bumped')
-def _computebumpedset(repo):
-    """the set of rev trying to obsolete public revision"""
-    candidates = _allsuccessors(repo, repo.revs('public()'),
-                                                haltonflags=latediff)
-    query = '%ld - obsolete() - public()'
-    return set(repo.revs(query, candidates))
 
 @cachefor('divergent')
 def _computedivergentset(repo):
@@ -537,7 +349,7 @@
     obsstore = repo.obsstore
     newermap = {}
     for ctx in repo.set('(not public()) - obsolete()'):
-        mark = obsstore.successors.get(ctx.node(), ())
+        mark = obsstore.precursors.get(ctx.node(), ())
         toprocess = set(mark)
         while toprocess:
             prec = toprocess.pop()[0]
@@ -547,18 +359,15 @@
             if len(newer) > 1:
                 divergent.add(ctx.rev())
                 break
-            toprocess.update(obsstore.successors.get(prec, ()))
+            toprocess.update(obsstore.precursors.get(prec, ()))
     return divergent
 
 ### changectx method
 
 @eh.addattr(context.changectx, 'latecomer')
-@eh.addattr(context.changectx, 'bumped')
-def bumped(ctx):
+def latecomer(ctx):
     """is the changeset bumped (Try to succeed to public change)"""
-    if ctx.node() is None:
-        return False
-    return ctx.rev() in getobscache(ctx._repo, 'bumped')
+    return ctx.bumped()
 
 @eh.addattr(context.changectx, 'conflicting')
 @eh.addattr(context.changectx, 'divergent')
@@ -566,19 +375,11 @@
     """is the changeset divergent (Try to succeed to public change)"""
     if ctx.node() is None:
         return False
-    return ctx.rev() in getobscache(ctx._repo, 'divergent')
+    return ctx.rev() in getrevs(ctx._repo, 'divergent')
 
 ### revset symbol
 
-@eh.revset('latecomer')
-@eh.revset('bumped')
-def revsetbumped(repo, subset, x):
-    """``bumped()``
-    Changesets marked as successors of public changesets.
-    """
-    args = revset.getargs(x, 0, 0, 'bumped takes no arguments')
-    lates = getobscache(repo, 'bumped')
-    return [r for r in subset if r in lates]
+eh.revset('latecomer')(revset.symbols['bumped'])
 
 @eh.revset('conflicting')
 @eh.revset('divergent')
@@ -587,7 +388,7 @@
     Changesets marked as successors of a same changeset.
     """
     args = revset.getargs(x, 0, 0, 'divergent takes no arguments')
-    conf = getobscache(repo, 'divergent')
+    conf = getrevs(repo, 'divergent')
     return [r for r in subset if r in conf]
 
 
@@ -598,16 +399,13 @@
 def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
     """wrap mercurial.discovery.checkheads
 
-    * prevent bumped and unstable to be pushed
+    * prevent divergent to be pushed
     """
     # do not push instability
     for h in outgoing.missingheads:
         # Checking heads is enough, obsolete descendants are either
         # obsolete or unstable.
         ctx = repo[h]
-        if ctx.bumped():
-            raise util.Abort(_("push includes a bumped changeset: %s!")
-                             % ctx)
         if ctx.divergent():
             raise util.Abort(_("push includes a divergent changeset: %s!")
                              % ctx)
@@ -659,44 +457,7 @@
 # - function to travel throught the obsolescence graph
 # - function to find useful changeset to stabilize
 
-### Marker Create
-# NOW IN CORE f85816af6294
-try:
-    createmarkers = obsolete.createmarkers
-except AttributeError:
-    def createmarkers(repo, relations, metadata=None, flag=0):
-        """Add obsolete markers between changeset in a repo
-
-        <relations> must be an iterable of (<old>, (<new>, ...)) tuple.
-        `old` and `news` are changectx.
-
-        Current user and date are used except if specified otherwise in the
-        metadata attribute.
-
-        /!\ assume the repo have been locked by the user /!\
-        """
-        # prepare metadata
-        if metadata is None:
-            metadata = {}
-        if 'date' not in metadata:
-            metadata['date'] = '%i %i' % util.makedate()
-        if 'user' not in metadata:
-            metadata['user'] = repo.ui.username()
-        # check future marker
-        tr = repo.transaction('add-obsolescence-marker')
-        try:
-            for prec, sucs in relations:
-                if not prec.mutable():
-                    raise util.Abort("cannot obsolete immutable changeset: %s" % prec)
-                nprec = prec.node()
-                nsucs = tuple(s.node() for s in sucs)
-                if nprec in nsucs:
-                    raise util.Abort("changeset %s cannot obsolete himself" % prec)
-                repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
-                clearobscaches(repo)
-            tr.close()
-        finally:
-            tr.release()
+createmarkers = obsolete.createmarkers
 
 
 ### Useful alias
@@ -732,7 +493,7 @@
 ### Troubled revset symbol
 
 @eh.revset('troubled')
-def revsetbumped(repo, subset, x):
+def revsettroubled(repo, subset, x):
     """``troubled()``
     Changesets with troubles.
     """
@@ -749,7 +510,7 @@
     """Precursor of a changeset"""
     cs = set()
     nm = repo.changelog.nodemap
-    markerbysubj = repo.obsstore.successors
+    markerbysubj = repo.obsstore.precursors
     for r in s:
         for p in markerbysubj.get(repo[r].node(), ()):
             pr = nm.get(p[0])
@@ -761,7 +522,7 @@
     """transitive precursors of a subset"""
     toproceed = [repo[r].node() for r in s]
     seen = set()
-    allsubjects = repo.obsstore.successors
+    allsubjects = repo.obsstore.precursors
     while toproceed:
         nc = toproceed.pop()
         for mark in allsubjects.get(nc, ()):
@@ -781,7 +542,7 @@
     """Successors of a changeset"""
     cs = set()
     nm = repo.changelog.nodemap
-    markerbyobj = repo.obsstore.precursors
+    markerbyobj = repo.obsstore.successors
     for r in s:
         for p in markerbyobj.get(repo[r].node(), ()):
             for sub in p[1]:
@@ -797,7 +558,7 @@
     marker.  """
     toproceed = [repo[r].node() for r in s]
     seen = set()
-    allobjects = repo.obsstore.precursors
+    allobjects = repo.obsstore.successors
     while toproceed:
         nc = toproceed.pop()
         for mark in allobjects.get(nc, ()):
@@ -822,7 +583,7 @@
     """Return the newer version of an obsolete changeset"""
 
     # prec -> markers mapping
-    markersfor = repo.obsstore.precursors
+    markersfor = repo.obsstore.successors
 
     # Stack of node need to know the last successors set
     toproceed = [initialnode]
@@ -944,16 +705,6 @@
 # they are subject to changes
 
 
-if 'hidden' not in revset.symbols:
-    # in 2.3+
-    @eh.revset('hidden')
-    def revsethidden(repo, subset, x):
-        """``hidden()``
-        Changeset is hidden.
-        """
-        args = revset.getargs(x, 0, 0, 'hidden takes no argument')
-        return [r for r in subset if r in repo.hiddenrevs]
-
 ### XXX I'm not sure this revset is useful
 @eh.revset('suspended')
 def revsetsuspended(repo, subset, x):
@@ -961,7 +712,7 @@
     Obsolete changesets with non-obsolete descendants.
     """
     args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
-    suspended = getobscache(repo, 'suspended')
+    suspended = getrevs(repo, 'suspended')
     return [r for r in subset if r in suspended]
 
 
@@ -1098,9 +849,9 @@
             ui.note(s)
 
     ret = orig(ui, repo, *args, **kwargs)
-    nbunstable = len(getobscache(repo, 'unstable'))
-    nbbumped = len(getobscache(repo, 'bumped'))
-    nbdivergent = len(getobscache(repo, 'unstable'))
+    nbunstable = len(getrevs(repo, 'unstable'))
+    nbbumped = len(getrevs(repo, 'bumped'))
+    nbdivergent = len(getrevs(repo, 'unstable'))
     write('unstable: %i changesets\n', nbunstable)
     write('bumped: %i changesets\n', nbbumped)
     write('divergent: %i changesets\n', nbdivergent)
@@ -1111,134 +862,6 @@
 ### Core Other extension compat                                   ###
 #####################################################################
 
-# This section make official history rewritter create obsolete marker
-
-
-### commit --amend
-# make commit --amend create obsolete marker
-#
-# The precursor is still strip from the repository.
-
-# IN CORE 63e45aee46d4
-
-if getattr(cmdutil, 'obsolete', None) is None:
-    @eh.wrapfunction(cmdutil, 'amend')
-    def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
-        oldnode = old.node()
-        new = orig(ui, repo, commitfunc, old, *args, **kwargs)
-        if new != oldnode:
-            lock = repo.lock()
-            try:
-                tr = repo.transaction('post-amend-obst')
-                try:
-                    meta = {
-                        'date':  '%i %i' % util.makedate(),
-                        'user': ui.username(),
-                        }
-                    repo.obsstore.create(tr, oldnode, [new], 0, meta)
-                    tr.close()
-                    clearobscaches(repo)
-                finally:
-                    tr.release()
-            finally:
-                lock.release()
-        return new
-
-### rebase
-#
-# - ignore obsolete changeset
-# - create obsolete marker *instead of* striping
-
-def buildstate(orig, repo, dest, rebaseset, *ags, **kws):
-    """wrapper for rebase 's buildstate that exclude obsolete changeset"""
-
-    rebaseset = repo.revs('%ld - extinct()', rebaseset)
-    if not rebaseset:
-        repo.ui.warn(_('whole rebase set is extinct and ignored.\n'))
-        return {}
-    root = min(rebaseset)
-    if (not getattr(repo, '_rebasekeep', False)
-        and not repo[root].mutable()):
-        raise util.Abort(_("can't rebase immutable changeset %s") % repo[root],
-                         hint=_('see hg help phases for details'))
-    return orig(repo, dest, rebaseset, *ags, **kws)
-
-def defineparents(orig, repo, rev, target, state, *args, **kwargs):
-    rebasestate = getattr(repo, '_rebasestate', None)
-    if rebasestate is not None:
-        repo._rebasestate = dict(state)
-        repo._rebasetarget = target
-    return orig(repo, rev, target, state, *args, **kwargs)
-
-def concludenode(orig, repo, rev, p1, *args, **kwargs):
-    """wrapper for rebase 's concludenode that set obsolete relation"""
-    newrev = orig(repo, rev, p1, *args, **kwargs)
-    rebasestate = getattr(repo, '_rebasestate', None)
-    if rebasestate is not None:
-        if newrev is not None:
-            nrev = repo[newrev].rev()
-        else:
-            nrev = p1
-        repo._rebasestate[rev] = nrev
-    return newrev
-
-def cmdrebase(orig, ui, repo, *args, **kwargs):
-
-    reallykeep = kwargs.get('keep', False)
-    kwargs = dict(kwargs)
-    kwargs['keep'] = True
-    repo._rebasekeep = reallykeep
-
-    # We want to mark rebased revision as obsolete and set their
-    # replacements if any. Doing it in concludenode() prevents
-    # aborting the rebase, and is not called with all relevant
-    # revisions in --collapse case. Instead, we try to track the
-    # rebase state structure by sampling/updating it in
-    # defineparents() and concludenode(). The obsolete markers are
-    # added from this state after a successful call.
-    repo._rebasestate = {}
-    repo._rebasetarget = None
-    try:
-        l = repo.lock()
-        try:
-            res = orig(ui, repo, *args, **kwargs)
-            if not reallykeep:
-                # Filter nullmerge or unrebased entries
-                repo._rebasestate = dict(p for p in repo._rebasestate.iteritems()
-                                         if p[1] >= 0)
-                if not res and not kwargs.get('abort') and repo._rebasestate:
-                    # Rebased revisions are assumed to be descendants of
-                    # targetrev. If a source revision is mapped to targetrev
-                    # or to another rebased revision, it must have been
-                    # removed.
-                    markers = []
-                    if kwargs.get('collapse'):
-                        # collapse assume revision disapear because they are all
-                        # in the created revision
-                        newrevs = set(repo._rebasestate.values())
-                        newrevs.remove(repo._rebasetarget)
-                        if newrevs:
-                            # we create new revision.
-                            # A single one by --collapse design
-                            assert len(newrevs) == 1
-                            new = tuple(repo[n] for n in newrevs)
-                        else:
-                            # every body died. no new changeset created
-                            new = (repo[repo._rebasetarget],)
-                        for rev, newrev in sorted(repo._rebasestate.items()):
-                            markers.append((repo[rev], new))
-                    else:
-                        # no collapse assume revision disapear because they are
-                        # contained in parent
-                        for rev, newrev in sorted(repo._rebasestate.items()):
-                            markers.append((repo[rev], (repo[newrev],)))
-                    createmarkers(repo, markers)
-            return res
-        finally:
-            l.release()
-    finally:
-        delattr(repo, '_rebasestate')
-        delattr(repo, '_rebasetarget')
 
 @eh.extsetup
 def _rebasewrapping(ui):
@@ -1246,14 +869,7 @@
     try:
         rebase = extensions.find('rebase')
         if rebase:
-            incore = getattr(rebase, 'obsolete', None) is not None
-            if not incore:
-                extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
             extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
-            if not incore:
-                extensions.wrapfunction(rebase, 'buildstate', buildstate)
-                extensions.wrapfunction(rebase, 'defineparents', defineparents)
-                extensions.wrapfunction(rebase, 'concludenode', concludenode)
     except KeyError:
         pass  # rebase not found
 
--- a/tests/test-obsolete.t	Tue Oct 23 17:32:22 2012 +0200
+++ b/tests/test-obsolete.t	Tue Oct 23 17:43:22 2012 +0200
@@ -164,7 +164,7 @@
   $ hg push ../other-new
   pushing to ../other-new
   searching for changes
-  abort: push includes an unstable changeset: a7a6f2b5d8a5!
+  abort: push includes unstable changeset: a7a6f2b5d8a5!
   (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]
   $ hg push -f ../other-new
@@ -217,7 +217,7 @@
   $ hg push ../other-new
   pushing to ../other-new
   searching for changes
-  abort: push includes an unstable changeset: 95de7fc6918d!
+  abort: push includes unstable changeset: 95de7fc6918d!
   (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]
   $ hg push ../other-new -f # use f because there is unstability
@@ -562,7 +562,7 @@
   $ hg push ../other-new/
   pushing to ../other-new/
   searching for changes
-  abort: push includes a bumped changeset: 6db5e282cb91!
+  abort: push includes bumped changeset: 6db5e282cb91!
   (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]
 
@@ -575,7 +575,7 @@
   $ echo 42 >> f
   $ hg commit --amend --traceback --quiet
   $ hg log -G
-  @  changeset:   1[35]:3734a65252e6 (re)
+  @  changeset:   15:705ab2a6b72e
   |  tag:         tip
   |  parent:      10:2033b4e49474
   |  user:        test
@@ -626,7 +626,7 @@
   0d3f46688ccc6e756c7e96cf64c391c411309597 2033b4e494742365851fac84d276640cbf52833e 0 {'date': '* *', 'user': 'test'} (glob)
   159dfc9fa5d334d7e03a0aecfc7f7ab4c3431fea 9468a5f5d8b2c5d91e17474e95ae4791e9718fdf 0 {'date': '* *', 'user': 'test'} (glob)
   9468a5f5d8b2c5d91e17474e95ae4791e9718fdf 6db5e282cb91df5c43ff1f1287c119ff83230d42 0 {'date': '', 'user': 'test'} (glob)
-  0b1b6dd009c037985363e2290a0b579819f659db 3734a65252e69ddcced85901647a4f335d40de1e 0 {'date': '* *', 'user': 'test'} (glob)
+  0b1b6dd009c037985363e2290a0b579819f659db 705ab2a6b72e2cd86edb799ebe15f2695f86143e 0 {'date': '* *', 'user': 'test'} (glob)
 #no produced by 2.3
 33d458d86621f3186c40bfccd77652f4a122743e 3734a65252e69ddcced85901647a4f335d40de1e 0 {'date': '* *', 'user': 'test'} (glob)
 
@@ -650,7 +650,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     add obsol_d'''
   
-  changeset:   14:50f11e5e3a63
+  changeset:   16:50f11e5e3a63
   tag:         tip
   parent:      11:9468a5f5d8b2
   user:        test
--- a/tests/test-tutorial.t	Tue Oct 23 17:32:22 2012 +0200
+++ b/tests/test-tutorial.t	Tue Oct 23 17:43:22 2012 +0200
@@ -612,7 +612,7 @@
   $ hg push other
   pushing to $TESTTMP/other
   searching for changes
-  abort: push includes an unstable changeset: 9ac5d0e790a2!
+  abort: push includes unstable changeset: 9ac5d0e790a2!
   (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]