changeset 291:d508e5dd0a11

merge with fix
author Pierre-Yves David <pierre-yves.david@logilab.fr>
date Wed, 20 Jun 2012 16:07:20 +0200
parents d64f8468aaf9 (diff) 9bb4217ddfce (current diff)
children 98c9c69c311d
files
diffstat 11 files changed, 727 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Jun 11 11:59:08 2012 +0200
+++ b/.hgignore	Wed Jun 20 16:07:20 2012 +0200
@@ -3,4 +3,8 @@
 ^docs/build/
 ^docs/html/
 ^html/
-.pyc$
+\.pyc$
+~$
+\.orig$
+\.rej$
+\.err$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgtags	Wed Jun 20 16:07:20 2012 +0200
@@ -0,0 +1,2 @@
+6c6bb7a23bb5125bf06da73265f039dd3447dafa 0.1.0
+d3f20770b86a31dba56ae7b252089e12b34702da 0.2.0
--- a/README	Mon Jun 11 11:59:08 2012 +0200
+++ b/README	Wed Jun 20 16:07:20 2012 +0200
@@ -20,9 +20,13 @@
 Contribute
 ==================
 
+The simplest way to contribute is to issue a pull request on bitbucket.
 
-This repository contains public immutable changeset. Developement happen here::
+However, some cutting edge change may be found in a mutable repository hosted
+by logilab before they are published.
 
     http://hg-lab.logilab.org/wip/mutable-history/
 
-Make sure to pull lastest draft changeset before submitting new changeset.
+Make sure to check lastest draft changeset before submitting new changeset.
+
+
--- a/hgext/evolve.py	Mon Jun 11 11:59:08 2012 +0200
+++ b/hgext/evolve.py	Wed Jun 20 16:07:20 2012 +0200
@@ -57,6 +57,11 @@
 #############################
 
 def rewrite(repo, old, updates, head, newbases, commitopts):
+    """Return (nodeid, created) where nodeid is the identifier of the
+    changeset generated by the rewrite process, and created is True if
+    nodeid was actually created. If created is False, nodeid
+    references a changeset existing before the rewrite call.
+    """
     if len(old.parents()) > 1: #XXX remove this unecessary limitation.
         raise error.Abort(_('cannot amend merge changesets'))
     base = old.p1()
@@ -112,6 +117,8 @@
 
         user = commitopts.get('user') or old.user()
         date = commitopts.get('date') or None # old.date()
+        extra = dict(commitopts.get('extra', {}))
+        extra['branch'] = head.branch()
 
         new = context.memctx(repo,
                              parents=newbases,
@@ -120,33 +127,39 @@
                              filectxfn=filectxfn,
                              user=user,
                              date=date,
-                             extra=commitopts.get('extra') or None)
+                             extra=extra)
 
         if commitopts.get('edit'):
             new._text = cmdutil.commitforceeditor(repo, new, [])
+        revcount = len(repo)
         newid = repo.commitctx(new)
         new = repo[newid]
-
-        # update the bookmark
-        if bm:
-            repo._bookmarks[bm] = newid
-            bookmarks.write(repo)
+        created = len(repo) != revcount
+        if created:
+            # update the bookmark
+            if bm:
+                repo._bookmarks[bm] = newid
+                bookmarks.write(repo)
 
-        # add evolution metadata
-        repo.addobsolete(new.node(), old.node())
-        for u in updates:
-            repo.addobsolete(u.node(), old.node())
-            repo.addobsolete(new.node(), u.node())
-        oldbookmarks = repo.nodebookmarks(old.node())
-        for book in oldbookmarks:
-            repo._bookmarks[book] = new.node()
-        if oldbookmarks:
-            bookmarks.write(repo)
-
+            # add evolution metadata
+            repo.addobsolete(new.node(), old.node())
+            for u in updates:
+                repo.addobsolete(u.node(), old.node())
+                repo.addobsolete(new.node(), u.node())
+            oldbookmarks = repo.nodebookmarks(old.node())
+            for book in oldbookmarks:
+                repo._bookmarks[book] = new.node()
+            if oldbookmarks:
+                bookmarks.write(repo)
+        else:
+            # newid is an existing revision. It could make sense to
+            # replace revisions with existing ones but probably not by
+            # default.
+            pass
     finally:
         wlock.release()
 
-    return newid
+    return newid, created
 
 def relocate(repo, orig, dest):
     """rewrite <rev> on dest"""
@@ -167,11 +180,17 @@
             rebase.rebasenode(repo, orig.node(), dest.node(),
                               {node.nullrev: node.nullrev})
         nodenew = rebase.concludenode(repo, orig.node(), dest.node(), node.nullid)
-        phases.retractboundary(repo, destphase, [nodenew])
-        repo.addobsolete(nodenew, nodesrc)
         oldbookmarks = repo.nodebookmarks(nodesrc)
-        for book in oldbookmarks:
-            repo._bookmarks[book] = nodenew
+        if nodenew is not None:
+            phases.retractboundary(repo, destphase, [nodenew])
+            repo.addobsolete(nodenew, nodesrc)
+            for book in oldbookmarks:
+                repo._bookmarks[book] = nodenew
+        else:
+            repo.addobsolete(node.nullid, nodesrc)
+            # Behave like rebase, move bookmarks to dest
+            for book in oldbookmarks:
+                repo._bookmarks[book] = dest.node()
         for book in destbookmarks: # restore bookmark that rebase move
             repo._bookmarks[book] = dest.node()
         if oldbookmarks or destbookmarks:
@@ -182,43 +201,65 @@
         raise
 
 
+def stabilizableunstable(repo, pctx):
+    """Return a changectx for an unstable changeset which can be
+    stabilized on top of pctx or one of its descendants. None if none
+    can be found.
+    """
+    def selfanddescendants(repo, pctx):
+        yield pctx
+        for ctx in pctx.descendants():
+            yield ctx
+
+    # Look for an unstable which can be stabilized as a child of
+    # node. The unstable must be a child of one of node predecessors.
+    for ctx in selfanddescendants(repo, pctx):
+        unstables = list(repo.set('unstable() and children(obsancestors(%d))',
+                                  ctx.rev()))
+        if unstables:
+            return unstables[0]
+    return None
 
 ### new command
 #############################
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 
-@command('^stabilize',
+@command('^stabilize|evolve',
     [
      ('n', 'dry-run', False, 'Do nothing but printing what should be done'),
      ('A', 'any', False, 'Stabilize unstable change on any topological branch'),
     ],
     '')
 def stabilize(ui, repo, **opts):
-    """move changeset out of the unstable state
+    """rebase an unstable changeset to make it stable again
 
-    By default only works on changeset that will be rebase on ancestors of the
-    current working directory parent (included)"""
+    By default, take the first unstable changeset which could be
+    rebased as child of the working directory parent revision or one
+    of its descendants and rebase it.
+
+    With --any, stabilize any unstable changeset.
+
+    The working directory is updated to the rebased revision.
+    """
 
     obsolete = extensions.find('obsolete')
 
-    if opts['any']:
-        rvstargets = 'unstable()'
-    else:
-        rvstargets = 'unstable() and ((suspended() and obsancestors(::.))::)'
-
-    unstable = list(repo.set(rvstargets))
-    if not unstable:
-        unstable = opts['any'] and () or list(repo.set('unstable()'))
-        if unstable:
+    node = None
+    if not opts['any']:
+        node = stabilizableunstable(repo, repo['.'])
+    if node is None:
+        unstables = list(repo.set('unstable()'))
+        if unstables and not opts['any']:
             ui.write_err(_('nothing to stabilize here\n'))
             ui.status(_('(%i unstable changesets, do you want --any ?)\n')
-                      % len(unstable))
+                      % len(unstables))
             return 2
-        else:
+        elif not unstables:
             ui.write_err(_('no unstable changeset\n'))
             return 1
-    node = unstable[0]
+        node = unstables[0]
+
     obs = node.parents()[0]
     if not obs.obsolete():
         obs = node.parents()[1]
@@ -306,7 +347,7 @@
         return 1
 
 
-@command('^kill',
+@command('^kill|obsolete',
     [
     ('n', 'new', [], _("New changeset that justify this one to be killed"))
     ],
@@ -352,8 +393,6 @@
      _('use text as commit message for this update')),
     ('c', 'change', '',
      _('specifies the changeset to amend'), _('REV')),
-    ('b', 'branch', '',
-     _('specifies a branch for the new.'), _('REV')),
     ('e', 'edit', False,
      _('edit commit message.'), _('')),
     ] + walkopts + commitopts + commitopts2,
@@ -385,16 +424,10 @@
     """
 
     # determine updates to subsume
-    change = opts.get('change')
+    change = opts.get('change', '.')
     if change == '.':
         change = 'p1(p1())'
     old = scmutil.revsingle(repo, change)
-    branch = opts.get('branch')
-    if branch:
-        opts.setdefault('extra', {})['branch'] = branch
-    else:
-        if old.branch() != 'default':
-            opts.setdefault('extra', {})['branch'] = old.branch()
 
     lock = repo.lock()
     try:
@@ -413,15 +446,18 @@
             def commitfunc(ui, repo, message, match, opts):
                 return repo.commit(message, opts.get('user'), opts.get('date'), match,
                                    editor=e)
-            cmdutil.commit(ui, repo, commitfunc, pats, ciopts)
+            revcount = len(repo)
+            tempid = cmdutil.commit(ui, repo, commitfunc, pats, ciopts)
+            if len(repo) == revcount:
+                # No revision created
+                tempid = None
 
             # find all changesets to be considered updates
-            cl = repo.changelog
             head = repo['.']
-            updatenodes = set(cl.nodesbetween(roots=[old.node()],
-                                              heads=[head.node()])[0])
+            updatenodes = set(repo.changelog.nodesbetween(
+                    roots=[old.node()], heads=[head.node()])[0])
             updatenodes.remove(old.node())
-            okoptions = ['message', 'logfile', 'edit', 'user', 'branch']
+            okoptions = ['message', 'logfile', 'edit', 'user']
             if not updatenodes:
                 for o in okoptions:
                     if opts.get(o):
@@ -435,13 +471,20 @@
             # perform amend
             if opts.get('edit'):
                 opts['force_editor'] = True
-            newid = rewrite(repo, old, updates, head,
-                            [old.p1().node(), old.p2().node()], opts)
-
-            # reroute the working copy parent to the new changeset
-            phases.retractboundary(repo, oldphase, [newid])
-            repo.dirstate.setparents(newid, node.nullid)
-
+            newid, created = rewrite(repo, old, updates, head,
+                                     [old.p1().node(), old.p2().node()], opts)
+            if created:
+                # reroute the working copy parent to the new changeset
+                phases.retractboundary(repo, oldphase, [newid])
+                repo.dirstate.setparents(newid, node.nullid)
+            else:
+                # rewrite() recreated an existing revision, discard
+                # the intermediate revision if any. No need to update
+                # phases or parents.
+                if tempid is not None:
+                    repo.addobsolete(node.nullid, tempid)
+                # XXX: need another message in collapse case.
+                raise error.Abort(_('no updates found'))
         finally:
             wlock.release()
     finally:
--- a/hgext/obsolete.py	Mon Jun 11 11:59:08 2012 +0200
+++ b/hgext/obsolete.py	Wed Jun 20 16:07:20 2012 +0200
@@ -42,8 +42,11 @@
 New commands
 ------------
 
-A ``debugobsolete`` command was added. It adds an obsolete relation between two
-nodes.
+Note that rebased changesets are not marked obsolete rather than being stripped
+In this experimental extensions, this is done forcing the --keep option. Trying
+to use the --keep option of rebase with this extensionn this experimental
+extension will cause such a call to abort. Until better releasen please use
+graft command to rebase and copy changesets.
 
 Context object
 --------------
@@ -97,6 +100,7 @@
 from mercurial.node import hex, bin, short, nullid
 from mercurial.lock import release
 from mercurial import localrepo
+from mercurial import cmdutil
 
 try:
     from mercurial.localrepo import storecache
@@ -221,22 +225,72 @@
     rebaseset = repo.revs('%ld - extinct()', rebaseset)
     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, *args, **kwargs):
+def concludenode(orig, repo, rev, p1, *args, **kwargs):
     """wrapper for rebase 's concludenode that set obsolete relation"""
-    newrev = orig(repo, rev, *args, **kwargs)
-    oldnode = repo[rev].node()
-    newnode = repo[newrev].node()
-    repo.addobsolete(newnode, oldnode)
+    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, repo, ui, *args, **kwargs):
-    oldkeep = kwargs.pop('keep', False)
-    if oldkeep:
-        ui.warn('WARNING --keep option ignored by experimental obsolete extension')
+def cmdrebase(orig, ui, repo, *args, **kwargs):
+    if kwargs.get('keep', False):
+        raise util.Abort(_('rebase --keep option is unsupported with obsolete '
+                           'extension'), hint=_("see 'hg help obsolete'"))
+    kwargs = dict(kwargs)
     kwargs['keep'] = True
-    return orig(repo, ui, *args, **kwargs)
 
+    # 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
+    maxrev = len(repo) - 1
+    try:
+        res = orig(ui, repo, *args, **kwargs)
+        if not res and not kwargs.get('abort') and repo._rebasetarget:
+            # We have to tell rewritten revisions from removed
+            # ones. When collapsing, removed revisions are considered
+            # to be collapsed onto the final one, while in the normal
+            # case their are marked obsolete without successor.
+            emptynode = nullid
+            if kwargs.get('collapse'):
+                emptynode = repo[max(repo._rebasestate.values())].node()
+            # 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.
+            targetrev = repo[repo._rebasetarget].rev()
+            newrevs = set([targetrev])
+            for rev, newrev in sorted(repo._rebasestate.items()):
+                if newrev == -2:  # nullmerge
+                    continue
+                oldnode = repo[rev].node()
+                if newrev not in newrevs and newrev >= 0:
+                    newnode = repo[newrev].node()
+                    newrevs.add(newrev)
+                else:
+                    newnode = emptynode
+                repo.addobsolete(newnode, oldnode)
+        return res
+    finally:
+        delattr(repo, '_rebasestate')
+        delattr(repo, '_rebasetarget')
 
 
 def extsetup(ui):
@@ -253,6 +307,7 @@
         rebase = extensions.find('rebase')
         if rebase:
             extensions.wrapfunction(rebase, 'buildstate', buildstate)
+            extensions.wrapfunction(rebase, 'defineparents', defineparents)
             extensions.wrapfunction(rebase, 'concludenode', concludenode)
             extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
     except KeyError:
@@ -385,11 +440,15 @@
 ### New commands
 #############################
 
+cmdtable = {}
+command = cmdutil.command(cmdtable)
 
+@command('debugobsolete', [], _('SUBJECT OBJECT'))
 def cmddebugobsolete(ui, repo, subject, object):
-    """Add an obsolete relation between a too node
+    """add an obsolete relation between two nodes
 
-    The subject is expected to be a newer version of the object"""
+    The subject is expected to be a newer version of the object.
+    """
     lock = repo.lock()
     try:
         sub = repo[subject]
@@ -399,7 +458,9 @@
         lock.release()
     return 0
 
+@command('debugconvertobsolete', [], '')
 def cmddebugconvertobsolete(ui, repo):
+    """import markers from an .hg/obsolete-relations file"""
     cnt = 0
     l = repo.lock()
     try:
@@ -433,10 +494,25 @@
         l.release()
     ui.status('%i obsolete marker converted\n' % cnt)
 
+@command('debugsuccessors', [], '')
+def cmddebugsuccessors(ui, repo):
+    """dump obsolete changesets and their successors
 
-cmdtable = {'debugobsolete': (cmddebugobsolete, [], '<subject> <object>'),
-            'debugconvertobsolete': (cmddebugconvertobsolete, [], ''),
-           }
+    Each line matches an existing marker, the first identifier is the
+    obsolete changeset identifier, followed by it successors.
+    """
+    lock = repo.lock()
+    try:
+        allsuccessors = repo.obsoletestore.objects
+        for old in sorted(allsuccessors):
+            successors = [sorted(m['subjects']) for m in allsuccessors[old]]
+            for i, group in enumerate(sorted(successors)):
+                ui.write('%s' % short(old))
+                for new in group:
+                    ui.write(' %s' % short(new))
+                ui.write('\n')
+    finally:
+        lock.release()
 
 ### Altering existing command
 #############################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-amend.t	Wed Jun 20 16:07:20 2012 +0200
@@ -0,0 +1,108 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > hgext.rebase=
+  > hgext.graphlog=
+  > EOF
+  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+  $ glog() {
+  >   hg glog --template '{rev}@{branch}({phase}) {desc|firstline}\n' "$@"
+  > }
+
+  $ hg init repo
+  $ cd repo
+  $ echo a > a
+  $ hg ci -Am adda
+  adding a
+
+Test amend captures branches
+
+  $ hg branch foo
+  marked working directory as branch foo
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg amend
+  $ hg debugsuccessors
+  07f494440405 a34b93d251e4
+  07f494440405 bd19cbe78fbf
+  bd19cbe78fbf a34b93d251e4
+  $ hg branch
+  foo
+  $ hg branches
+  foo                            2:a34b93d251e4
+  default                        0:07f494440405 (inactive)
+  $ glog
+  @  2@foo(draft) adda
+  
+Test no-op
+
+  $ hg amend
+  abort: no updates found
+  [255]
+  $ glog
+  @  2@foo(draft) adda
+  
+
+Test forcing the message to the same value, no intermediate revision.
+
+  $ hg amend -m 'adda'
+  abort: no updates found
+  [255]
+  $ glog
+  @  2@foo(draft) adda
+  
+
+Test collapsing into an existing revision, no intermediate revision.
+
+  $ echo a >> a
+  $ hg ci -m changea
+  $ echo a > a
+  $ hg ci -m reseta
+  $ hg amend --change 2
+  abort: no updates found
+  [255]
+  $ hg debugsuccessors
+  07f494440405 a34b93d251e4
+  07f494440405 bd19cbe78fbf
+  bd19cbe78fbf a34b93d251e4
+  $ hg phase 2
+  2: draft
+  $ glog
+  @  4@foo(draft) reseta
+  |
+  o  3@foo(draft) changea
+  |
+  o  2@foo(draft) adda
+  
+
+Test collapsing into an existing rev, with an intermediate revision.
+
+  $ hg branch --force default
+  marked working directory as branch default
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -m resetbranch
+  created new head
+  $ hg branch --force foo
+  marked working directory as branch foo
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg amend --change 2
+  abort: no updates found
+  [255]
+  $ hg debugsuccessors
+  07f494440405 a34b93d251e4
+  07f494440405 bd19cbe78fbf
+  7384bbcba36f 000000000000
+  bd19cbe78fbf a34b93d251e4
+  $ glog
+  @  6@foo(secret) amends a34b93d251e49c93d5685ebacad785c73a7e8605
+  |
+  o  5@default(draft) resetbranch
+  |
+  o  4@foo(draft) reseta
+  |
+  o  3@foo(draft) changea
+  |
+  o  2@foo(draft) adda
+  
--- a/tests/test-evolve.t	Mon Jun 11 11:59:08 2012 +0200
+++ b/tests/test-evolve.t	Wed Jun 20 16:07:20 2012 +0200
@@ -211,19 +211,6 @@
   0	: base - test
   $ hg up -q 1
   Working directory parent is obsolete
-  $ hg stabilize -n
-  nothing to stabilize here
-  (1 unstable changesets, do you want --any ?)
-  [2]
-  $ hg stabilize -n --any
-  move:[4] another feature
-  atop:[6] a nifty feature
-  hg rebase -Dr f8111a076f09 -d 23409eba69a0
-  $ hg up -q 6
-  $ hg stabilize -n
-  move:[4] another feature
-  atop:[6] a nifty feature
-  hg rebase -Dr f8111a076f09 -d 23409eba69a0
   $ hg stabilize
   move:[4] another feature
   atop:[6] a nifty feature
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-rebase.t	Wed Jun 20 16:07:20 2012 +0200
@@ -0,0 +1,186 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > hgext.rebase=
+  > hgext.graphlog=
+  > EOF
+  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+
+  $ glog() {
+  >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n'\
+  >     "$@"
+  > }
+
+  $ hg init repo
+  $ cd repo
+  $ echo a > a
+  $ hg ci -Am adda
+  adding a
+  $ echo a >> a
+  $ hg ci -m changea
+
+Test regular rebase
+
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo b > b
+  $ hg ci -Am addb
+  adding b
+  created new head
+  $ echo e > e
+  $ hg ci -Am adde e
+  $ hg rebase -d 1 -r . --detach --keep  
+  abort: rebase --keep option is unsupported with obsolete extension
+  (see 'hg help obsolete')
+  [255]
+  $ hg rebase -d 1 -r . --detach
+  $ glog --hidden
+  @  4:9c5494949763@default(draft) adde
+  |
+  | o  3:98e4a024635e@default(secret) adde
+  | |
+  | o  2:102a90ea7b4a@default(draft) addb
+  | |
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors
+  98e4a024635e 9c5494949763
+
+Test rebase with deleted empty revision
+
+  $ hg up 0
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg branch foo
+  marked working directory as branch foo
+  (branches are permanent and global, did you want a bookmark?)
+  $ echo a >> a
+  $ hg ci -m changea
+  $ hg rebase -d 1
+  $ glog --hidden
+  o  5:4e322f7ce8e3@foo(secret) changea
+  |
+  | o  4:9c5494949763@default(draft) adde
+  | |
+  | | o  3:98e4a024635e@default(secret) adde
+  | | |
+  +---o  2:102a90ea7b4a@default(draft) addb
+  | |
+  | @  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors
+  4e322f7ce8e3 000000000000
+  98e4a024635e 9c5494949763
+
+Test rebase --collapse
+
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo c > c
+  $ hg ci -Am addc
+  adding c
+  created new head
+  $ echo c >> c
+  $ hg ci -m changec
+  $ hg rebase --collapse -d 1
+  merging c
+  $ glog --hidden
+  @  8:a7773ffa7edc@default(draft) Collapsed revision
+  |
+  | o  7:03f31481307a@default(secret) changec
+  | |
+  | o  6:076e9b2ffbe1@default(secret) addc
+  | |
+  | | o  5:4e322f7ce8e3@foo(secret) changea
+  | |/
+  +---o  4:9c5494949763@default(draft) adde
+  | |
+  | | o  3:98e4a024635e@default(secret) adde
+  | | |
+  | | o  2:102a90ea7b4a@default(draft) addb
+  | |/
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors
+  03f31481307a a7773ffa7edc
+  076e9b2ffbe1 a7773ffa7edc
+  4e322f7ce8e3 000000000000
+  98e4a024635e 9c5494949763
+
+Test rebase --abort
+
+  $ hg debugsuccessors > ../successors.old
+  $ hg up 0
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo d > d
+  $ hg ci -Am addd d
+  created new head
+  $ echo b >> a
+  $ hg ci -m appendab
+  $ hg rebase -d 1
+  merging a
+  warning: conflicts during merge.
+  merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+  abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [255]
+  $ hg rebase --abort
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/03f165c84ea8-backup.hg
+  rebase aborted
+  $ hg debugsuccessors > ../successors.new
+  $ diff -u ../successors.old ../successors.new
+
+Test rebase --continue
+
+  $ hg rebase -d 1
+  merging a
+  warning: conflicts during merge.
+  merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+  abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [255]
+  $ hg resolve --tool internal:other a
+  $ hg rebase --continue
+  $ glog --hidden
+  @  12:1951ead97108@default(draft) appendab
+  |
+  o  11:03f165c84ea8@default(draft) addd
+  |
+  | o  10:4b9d80f48523@default(secret) appendab
+  | |
+  | o  9:a31943eabc43@default(secret) addd
+  | |
+  +---o  8:a7773ffa7edc@default(draft) Collapsed revision
+  | |
+  | | o  7:03f31481307a@default(secret) changec
+  | | |
+  | | o  6:076e9b2ffbe1@default(secret) addc
+  | |/
+  | | o  5:4e322f7ce8e3@foo(secret) changea
+  | |/
+  +---o  4:9c5494949763@default(draft) adde
+  | |
+  | | o  3:98e4a024635e@default(secret) adde
+  | | |
+  | | o  2:102a90ea7b4a@default(draft) addb
+  | |/
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors > ../successors.new
+  $ diff -u ../successors.old ../successors.new
+  --- ../successors.old* (glob)
+  +++ ../successors.new* (glob)
+  @@ -1,4 +1,6 @@
+   03f31481307a a7773ffa7edc
+   076e9b2ffbe1 a7773ffa7edc
+  +4b9d80f48523 1951ead97108
+   4e322f7ce8e3 000000000000
+   98e4a024635e 9c5494949763
+  +a31943eabc43 03f165c84ea8
+  [1]
--- a/tests/test-obsolete.t	Mon Jun 11 11:59:08 2012 +0200
+++ b/tests/test-obsolete.t	Wed Jun 20 16:07:20 2012 +0200
@@ -453,3 +453,4 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-stabilize-order.t	Wed Jun 20 16:07:20 2012 +0200
@@ -0,0 +1,171 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > hgext.rebase=
+  > hgext.graphlog=
+  > EOF
+  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+  $ glog() {
+  >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
+  > }
+
+  $ hg init repo
+  $ cd repo
+  $ echo root > root
+  $ hg ci -Am addroot
+  adding root
+  $ echo a > a
+  $ hg ci -Am adda
+  adding a
+  $ echo b > b
+  $ hg ci -Am addb
+  adding b
+  $ echo c > c
+  $ hg ci -Am addc
+  adding c
+  $ glog
+  @  3:7a7552255fb5@default(draft) addc
+  |
+  o  2:ef23d6ef94d6@default(draft) addb
+  |
+  o  1:93418d2c0979@default(draft) adda
+  |
+  o  0:c471ef929e6a@default(draft) addroot
+  
+  $ hg gdown
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [2] addb
+  $ echo b >> b
+  $ hg amend
+  1 new unstables changesets
+  $ hg gdown
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [1] adda
+  $ echo a >> a
+  $ hg amend
+  1 new unstables changesets
+  $ glog
+  @  7:f5ff10856e5a@default(draft) adda
+  |
+  | o  5:ab8cbb6d87ff@default(draft) addb
+  | |
+  | | o  3:7a7552255fb5@default(draft) addc
+  | | |
+  | | o  2:ef23d6ef94d6@default(draft) addb
+  | |/
+  | o  1:93418d2c0979@default(draft) adda
+  |/
+  o  0:c471ef929e6a@default(draft) addroot
+  
+
+Test stabilizing a predecessor child
+
+  $ hg stabilize -v
+  move:[5] addb
+  atop:[7] adda
+  hg rebase -Dr ab8cbb6d87ff -d f5ff10856e5a
+  resolving manifests
+  getting b
+  b
+  $ glog
+  @  8:6bf44048e43f@default(draft) addb
+  |
+  o  7:f5ff10856e5a@default(draft) adda
+  |
+  | o  3:7a7552255fb5@default(draft) addc
+  | |
+  | o  2:ef23d6ef94d6@default(draft) addb
+  | |
+  | o  1:93418d2c0979@default(draft) adda
+  |/
+  o  0:c471ef929e6a@default(draft) addroot
+  
+
+Test stabilizing a descendant predecessors child
+
+  $ hg up 7
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg debugsuccessors > successors.old
+  $ hg stabilize -v
+  move:[3] addc
+  atop:[8] addb
+  hg rebase -Dr 7a7552255fb5 -d 6bf44048e43f
+  resolving manifests
+  getting b
+  resolving manifests
+  getting c
+  c
+  $ hg debugsuccessors > successors.new
+  $ diff -u successors.old successors.new
+  --- successors.old* (glob)
+  +++ successors.new* (glob)
+  @@ -1,5 +1,6 @@
+   3a4a591493f8 f5ff10856e5a
+   3ca0ded0dc50 ab8cbb6d87ff
+  +7a7552255fb5 5e819fbb0d27
+   93418d2c0979 3a4a591493f8
+   93418d2c0979 f5ff10856e5a
+   ab8cbb6d87ff 6bf44048e43f
+  [1]
+  $ glog
+  @  9:5e819fbb0d27@default(draft) addc
+  |
+  o  8:6bf44048e43f@default(draft) addb
+  |
+  o  7:f5ff10856e5a@default(draft) adda
+  |
+  o  0:c471ef929e6a@default(draft) addroot
+  
+  $ hg stabilize -v
+  no unstable changeset
+  [1]
+
+Test behaviour with --any
+
+  $ hg up 8
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo b >> b
+  $ hg amend
+  1 new unstables changesets
+  $ glog
+  @  11:4e7cec6b4afe@default(draft) addb
+  |
+  | o  9:5e819fbb0d27@default(draft) addc
+  | |
+  | o  8:6bf44048e43f@default(draft) addb
+  |/
+  o  7:f5ff10856e5a@default(draft) adda
+  |
+  o  0:c471ef929e6a@default(draft) addroot
+  
+  $ hg up 9
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg stabilize -v
+  nothing to stabilize here
+  (1 unstable changesets, do you want --any ?)
+  [2]
+  $ hg stabilize --any -v
+  move:[9] addc
+  atop:[11] addb
+  hg rebase -Dr 5e819fbb0d27 -d 4e7cec6b4afe
+  resolving manifests
+  removing c
+  getting b
+  resolving manifests
+  getting c
+  c
+  $ glog
+  @  12:24f95816bb21@default(draft) addc
+  |
+  o  11:4e7cec6b4afe@default(draft) addb
+  |
+  o  7:f5ff10856e5a@default(draft) adda
+  |
+  o  0:c471ef929e6a@default(draft) addroot
+  
+  $ hg stabilize --any -v
+  no unstable changeset
+  [1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-stabilize-result.t	Wed Jun 20 16:07:20 2012 +0200
@@ -0,0 +1,52 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > hgext.rebase=
+  > hgext.graphlog=
+  > EOF
+  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+  $ glog() {
+  >   hg glog --template \
+  >     '{rev}:{node|short}@{branch}({phase}) bk:[{bookmarks}] {desc|firstline}\n' "$@"
+  > }
+
+Test stabilize removing the changeset being stabilized
+
+  $ hg init empty
+  $ cd empty
+  $ echo a > a
+  $ hg ci -Am adda a
+  $ echo b > b
+  $ hg ci -Am addb b
+  $ echo a >> a
+  $ hg ci -m changea
+  $ hg bookmark changea
+  $ hg up 1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo a >> a
+  $ hg amend -m changea
+  1 new unstables changesets
+  $ hg stabilize -v
+  move:[2] changea
+  atop:[4] changea
+  hg rebase -Dr cce2c55b8965 -d 1447e1c4828d
+  resolving manifests
+  $ glog --hidden
+  @  4:1447e1c4828d@default(draft) bk:[changea] changea
+  |
+  | o  3:41ad4fe8c795@default(secret) bk:[] amends 102a90ea7b4a3361e4082ed620918c261189a36a
+  | |
+  | | o  2:cce2c55b8965@default(secret) bk:[] changea
+  | |/
+  | o  1:102a90ea7b4a@default(secret) bk:[] addb
+  |/
+  o  0:07f494440405@default(draft) bk:[] adda
+  
+  $ hg debugsuccessors
+  102a90ea7b4a 1447e1c4828d
+  102a90ea7b4a 41ad4fe8c795
+  41ad4fe8c795 1447e1c4828d
+  cce2c55b8965 000000000000