--- a/README Wed Jun 20 18:04:50 2012 +0200
+++ b/README Wed Jun 27 15:12:19 2012 +0200
@@ -15,6 +15,11 @@
**These extensions are experimental and are not meant for production.**
+You can quicky enable them using::
+
+ ./enable.sh >> ~/.hgrc
+
+But it's recommended to look at the doc in the first place.
See doc/ directory for details.
@@ -35,3 +40,26 @@
Make sure to check lastest draft changeset before submitting new changeset.
+Changelog
+==================
+
+0.3.0 --
+
+- obsolete: Add "latecomer" error detection (stabilize does not handle resolution yet)
+- evolve: Introduce a new `uncommit` command to remove change from a changeset
+- rebase: allow the use of --keep again
+- commit: --amend option create obsolete marker (but still strip)
+- obsolete: fewer marker are created when collapsing revision.
+- revset: add successors, allsuccessors, precursors, all precursors and hidden
+- evolve: add `prune` alias to `kill`.
+
+0.2.0 -- 2012-06-20
+
+- stabilize: improve choice of the next changeset to stabilize
+- stabilize: improve resolution of several corner case
+- rebase: handle removing empty changesets
+- rebase: handle --collapse
+- evolve: add `obsolete` alias to `kill`
+- evolve: add `evolve` alias to `stabilize`
+
+
--- a/docs/evolve-faq.rst Wed Jun 20 18:04:50 2012 +0200
+++ b/docs/evolve-faq.rst Wed Jun 27 15:12:19 2012 +0200
@@ -93,11 +93,30 @@
you can use amend -c to collapse multiple changeset in a single one.
+Getting changes out of a commit
+------------------------------------------------------------
+
+the ``hg uncommit`` commands allow you to rewrite the current commit to not
+include change for some file. The content of target files are not altered on
+disk and back as "modified"::
+
+ $ hg st
+ M babar
+ M celestine
+ $ hg commit babar celestine
+ $ hg st
+ $ hg uncommit celestine
+ $ hg status
+ M celestine
+
Split a changeset
-----------------------
-There is no official command to split a changeset. However is it easily achieved
-by manual operation::
+I you just want to split whole file, you can just use the ``uncommit`` command.
+
+
+If you need fine grained split, there is no official command for that yet.
+However is it easily achieved by manual operation::
### you want to split changeset A: 42
# update to A parent
@@ -169,12 +188,7 @@
Export to mq: ``synchronize``
------------------------------------------------------------
-Another extension allows to export.
-
-View change to your file
-------------------------------------------------------------
-
-Another extension allows to export.
+Another extension allows to export. you changes to mq
View diff from the last amend
------------------------------------------------------------
@@ -183,7 +197,7 @@
::
[alias]
- odiff = diff --rev 'limit(obsparents(.),1)' --rev .
+ odiff = diff --rev 'limit(precursors(.),1)' --rev .
View obsolete markers
------------------------------------------------------------
@@ -193,6 +207,12 @@
$ hg clone http://hg-dev.octopoid.net/hgwebdir.cgi/hgview/
+You can also use a debug command
+
+ $ hg debugsuccessors
+ 5eb72dbe0cb4 e8db4aa611f6
+ c4cbebac3751 4f1c269eab68
+
Important Note
@@ -203,7 +223,8 @@
Extinct changesets are hidden using the *hidden* feature of mercurial.
-Only ``hg log`` and ``hgview`` support it. ``hg glog`` or other visual viewer don't.
+Only ``hg log`` and ``hgview`` support it. ``hg glog`` Only support that since
+2.2. Other visual viewer don't.
--- a/docs/from-mq.rst Wed Jun 20 18:04:50 2012 +0200
+++ b/docs/from-mq.rst Wed Jun 27 15:12:19 2012 +0200
@@ -82,6 +82,14 @@
note: refresh is an alias for amend
+hg qref -X
+````````````
+
+To remove change from you current commit use::
+
+ $ hg uncommit not-ready.txt
+
+
hg qpop
`````````
--- a/docs/obs-implementation.rst Wed Jun 20 18:04:50 2012 +0200
+++ b/docs/obs-implementation.rst Wed Jun 27 15:12:19 2012 +0200
@@ -200,7 +200,7 @@
* Use secret phase to remove from discovery obsolete and unstable changeset (to
be improved soon)
-* alter rebase to use obsolete marker instead of stripping. (XXX break --keep for now)
+* alter rebase to use obsolete marker instead of stripping.
* Have an experimental mq-like extension to rewrite history (more on that later)
--- a/enable.sh Wed Jun 20 18:04:50 2012 +0200
+++ b/enable.sh Wed Jun 27 15:12:19 2012 +0200
@@ -42,7 +42,7 @@
pstatus=status --rev .^
# diff with the previous amend
-odiff=diff --rev 'limit(obsparents(.),1)' --rev .
+odiff=diff --rev 'limit(precursors(.),1)' --rev .
EOF
cat << EOF >&2
--- a/hgext/evolve.py Wed Jun 20 18:04:50 2012 +0200
+++ b/hgext/evolve.py Wed Jun 27 15:12:19 2012 +0200
@@ -27,31 +27,29 @@
### util function
#############################
+
def noderange(repo, revsets):
"""The same as revrange but return node"""
return map(repo.changelog.node,
scmutil.revrange(repo, revsets))
-
-
-def warnunstable(orig, ui, repo, *args, **kwargs):
+def warnobserrors(orig, ui, repo, *args, **kwargs):
"""display warning is the command resulted in more instable changeset"""
priorunstables = len(repo.revs('unstable()'))
+ priorlatecomers = len(repo.revs('latecomer()'))
#print orig, priorunstables
#print len(repo.revs('secret() - obsolete()'))
try:
return orig(ui, repo, *args, **kwargs)
finally:
newunstables = len(repo.revs('unstable()')) - priorunstables
+ newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
#print orig, newunstables
#print len(repo.revs('secret() - obsolete()'))
if newunstables > 0:
ui.warn(_('%i new unstables changesets\n') % newunstables)
-
-
-### extension check
-#############################
-
+ if newlatecomers > 0:
+ ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
### changeset rewriting logic
#############################
@@ -65,7 +63,7 @@
if len(old.parents()) > 1: #XXX remove this unecessary limitation.
raise error.Abort(_('cannot amend merge changesets'))
base = old.p1()
- bm = bookmarks.readcurrent(repo)
+ updatebookmarks = _bookmarksupdater(repo, old.node())
wlock = repo.wlock()
try:
@@ -136,21 +134,10 @@
new = repo[newid]
created = len(repo) != revcount
if created:
- # update the bookmark
- if bm:
- repo._bookmarks[bm] = newid
- bookmarks.write(repo)
-
+ updatebookmarks(newid)
# 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)
+ collapsed = set([u.node() for u in updates] + [old.node()])
+ repo.addcollapsedobsolete(collapsed, new.node())
else:
# newid is an existing revision. It could make sense to
# replace revisions with existing ones but probably not by
@@ -179,7 +166,14 @@
else:
rebase.rebasenode(repo, orig.node(), dest.node(),
{node.nullrev: node.nullrev})
- nodenew = rebase.concludenode(repo, orig.node(), dest.node(), node.nullid)
+ try:
+ nodenew = rebase.concludenode(repo, orig.node(), dest.node(),
+ node.nullid)
+ except util.Abort:
+ repo.ui.write_err(_('/!\\ stabilize failed /!\\\n'))
+ repo.ui.write_err(_('/!\\ Their is no "hg stabilize --continue" /!\\\n'))
+ repo.ui.write_err(_('/!\\ use "hg up -C . ; hg stabilize --dry-run" /!\\\n'))
+ raise
oldbookmarks = repo.nodebookmarks(nodesrc)
if nodenew is not None:
phases.retractboundary(repo, destphase, [nodenew])
@@ -200,7 +194,6 @@
repo.dirstate.invalidate()
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
@@ -220,17 +213,34 @@
return unstables[0]
return None
+def _bookmarksupdater(repo, oldid):
+ """Return a callable update(newid) updating the current bookmark
+ and bookmarks bound to oldid to newid.
+ """
+ bm = bookmarks.readcurrent(repo)
+ def updatebookmarks(newid):
+ dirty = False
+ if bm:
+ repo._bookmarks[bm] = newid
+ dirty = True
+ oldbookmarks = repo.nodebookmarks(oldid)
+ if oldbookmarks:
+ for b in oldbookmarks:
+ repo._bookmarks[b] = newid
+ dirty = True
+ if dirty:
+ bookmarks.write(repo)
+ return updatebookmarks
+
### new command
#############################
cmdtable = {}
command = cmdutil.command(cmdtable)
@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'),
- ],
- '')
+ [('n', 'dry-run', False, 'do not perform actions, print what to be done'),
+ ('A', 'any', False, 'stabilize any unstable changeset'),],
+ _('[OPTIONS]...'))
def stabilize(ui, repo, **opts):
"""rebase an unstable changeset to make it stable again
@@ -298,10 +308,10 @@
shorttemplate = '[{rev}] {desc|firstline}\n'
@command('^gdown',
- [],
- 'update to working directory parent and display summary lines')
+ [],
+ '')
def cmdgdown(ui, repo):
- """update to working directory parent an display summary lines"""
+ """update to parent an display summary lines"""
wkctx = repo[None]
wparents = wkctx.parents()
if len(wparents) != 1:
@@ -321,10 +331,10 @@
return 1
@command('^gup',
- [],
- 'update to working directory children and display summary lines')
+ [],
+ '')
def cmdup(ui, repo):
- """update to working directory children an display summary lines"""
+ """update to child an display summary lines"""
wkctx = repo[None]
wparents = wkctx.parents()
if len(wparents) != 1:
@@ -346,12 +356,9 @@
ui.warn(_('Multiple non-obsolete children, explicitly update to one\n'))
return 1
-
-@command('^kill|obsolete',
- [
- ('n', 'new', [], _("New changeset that justify this one to be killed"))
- ],
- '<revs>')
+@command('^kill|obsolete|prune',
+ [('n', 'new', [], _("successor changeset"))],
+ _('[OPTION] REV...'))
def kill(ui, repo, *revs, **opts):
"""mark a changeset as obsolete
@@ -389,15 +396,11 @@
@command('^amend|refresh',
[('A', 'addremove', None,
_('mark new/missing files as added/removed before committing')),
- ('n', 'note', '',
- _('use text as commit message for this update')),
- ('c', 'change', '',
- _('specifies the changeset to amend'), _('REV')),
- ('e', 'edit', False,
- _('edit commit message.'), _('')),
+ ('n', 'note', '', _('use text as commit message for this update')),
+ ('c', 'change', '', _('specifies the changesets to amend'), _('REV')),
+ ('e', 'edit', False, _('invoke editor on commit messages')),
] + walkopts + commitopts + commitopts2,
_('[OPTION]... [FILE]...'))
-
def amend(ui, repo, *pats, **opts):
"""combine a changeset with updates and replace it with a new one
@@ -408,11 +411,9 @@
If you don't specify -m, the parent's message will be reused.
- If you specify --change, amend additionally considers all changesets between
- the indicated changeset and the working copy parent as updates to be subsumed.
- This allows you to commit updates manually first. As a special shorthand you
- can say `--amend .` instead of '--amend p1(p1())', which subsumes your latest
- commit as an update of its parent.
+ If you specify --change, amend additionally considers all
+ changesets between the indicated changeset and the working copy
+ parent as updates to be subsumed.
Behind the scenes, Mercurial first commits the update as a regular child
of the current parent. Then it creates a new commit on the parent's parents
@@ -424,17 +425,15 @@
"""
# determine updates to subsume
- change = opts.get('change', '.')
- if change == '.':
- change = 'p1(p1())'
- old = scmutil.revsingle(repo, change)
+ old = scmutil.revsingle(repo, opts.get('change') or '.')
lock = repo.lock()
try:
wlock = repo.wlock()
try:
- if not old.phase():
- raise util.Abort(_("can not rewrite immutable changeset %s") % old)
+ if old.phase() == phases.public:
+ raise util.Abort(_("can not rewrite immutable changeset %s")
+ % old)
oldphase = old.phase()
# commit current changes as update
# code copied from commands.commit to avoid noisy messages
@@ -444,8 +443,8 @@
ciopts['message'] = opts.get('note') or ('amends %s' % old.hex())
e = cmdutil.commiteditor
def commitfunc(ui, repo, message, match, opts):
- return repo.commit(message, opts.get('user'), opts.get('date'), match,
- editor=e)
+ return repo.commit(message, opts.get('user'), opts.get('date'),
+ match, editor=e)
revcount = len(repo)
tempid = cmdutil.commit(ui, repo, commitfunc, pats, ciopts)
if len(repo) == revcount:
@@ -466,8 +465,6 @@
raise error.Abort(_('no updates found'))
updates = [repo[n] for n in updatenodes]
-
-
# perform amend
if opts.get('edit'):
opts['force_editor'] = True
@@ -490,8 +487,142 @@
finally:
lock.release()
+def _commitfiltered(repo, ctx, match):
+ """Recommit ctx with changed files not in match. Return the new
+ node identifier, or None if nothing changed.
+ """
+ base = ctx.p1()
+ m, a, r = repo.status(base, ctx)[:3]
+ allfiles = set(m + a + r)
+ files = set(f for f in allfiles if not match(f))
+ if files == allfiles:
+ return None
+ # Filter copies
+ copied = copies.pathcopies(base, ctx)
+ copied = dict((src, dst) for src, dst in copied.iteritems()
+ if dst in files)
+ def filectxfn(repo, memctx, path):
+ if path not in ctx:
+ raise IOError()
+ fctx = ctx[path]
+ flags = fctx.flags()
+ mctx = context.memfilectx(fctx.path(), fctx.data(),
+ islink='l' in flags,
+ isexec='x' in flags,
+ copied=copied.get(path))
+ return mctx
+ new = context.memctx(repo,
+ parents=[base.node(), node.nullid],
+ text=ctx.description(),
+ files=files,
+ filectxfn=filectxfn,
+ user=ctx.user(),
+ date=ctx.date(),
+ extra=ctx.extra())
+ # commitctx always create a new revision, no need to check
+ newid = repo.commitctx(new)
+ return newid
+
+def _uncommitdirstate(repo, oldctx, match):
+ """Fix the dirstate after switching the working directory from
+ oldctx to a copy of oldctx not containing changed files matched by
+ match.
+ """
+ ctx = repo['.']
+ ds = repo.dirstate
+ copies = dict(ds.copies())
+ m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
+ for f in m:
+ if ds[f] == 'r':
+ # modified + removed -> removed
+ continue
+ ds.normallookup(f)
+
+ for f in a:
+ if ds[f] == 'r':
+ # added + removed -> unknown
+ ds.drop(f)
+ elif ds[f] != 'a':
+ ds.add(f)
+
+ for f in r:
+ if ds[f] == 'a':
+ # removed + added -> normal
+ ds.normallookup(f)
+ elif ds[f] != 'r':
+ ds.remove(f)
+
+ # Merge old parent and old working dir copies
+ oldcopies = {}
+ for f in (m + a):
+ src = oldctx[f].renamed()
+ if src:
+ oldcopies[f] = src[0]
+ oldcopies.update(copies)
+ copies = dict((dst, oldcopies.get(src, src))
+ for dst, src in oldcopies.iteritems())
+ # Adjust the dirstate copies
+ for dst, src in copies.iteritems():
+ if (src not in ctx or dst in ctx or ds[dst] != 'a'):
+ src = None
+ ds.copy(src, dst)
+
+@command('^uncommit',
+ [('a', 'all', None, _('uncommit all changes when no arguments given')),
+ ] + commands.walkopts,
+ _('[OPTION]... [NAME]'))
+def uncommit(ui, repo, *pats, **opts):
+ """move changes from parent revision to working directory
+
+ Changes to selected files in parent revision appear again as
+ uncommitted changed in the working directory. A new revision
+ without selected changes is created, becomes the new parent and
+ obsoletes the previous one.
+
+ The --include option specify pattern to uncommit
+ The --exclude option specify pattern to keep in the commit
+
+ Return 0 if changed files are uncommitted.
+ """
+ lock = repo.lock()
+ try:
+ wlock = repo.wlock()
+ try:
+ wctx = repo[None]
+ if len(wctx.parents()) <= 0:
+ raise util.Abort(_("cannot uncommit null changeset"))
+ if len(wctx.parents()) > 1:
+ raise util.Abort(_("cannot uncommit while merging"))
+ old = repo['.']
+ if old.phase() == phases.public:
+ raise util.Abort(_("cannot rewrite immutable changeset"))
+ if len(old.parents()) > 1:
+ raise util.Abort(_("cannot uncommit merge changeset"))
+ oldphase = old.phase()
+ updatebookmarks = _bookmarksupdater(repo, old.node())
+ # Recommit the filtered changeset
+ newid = None
+ if (pats or opts.get('include') or opts.get('exclude')
+ or opts.get('all')):
+ match = scmutil.match(old, pats, opts)
+ newid = _commitfiltered(repo, old, match)
+ if newid is None:
+ raise util.Abort(_('nothing to uncommit'))
+ # Move local changes on filtered changeset
+ repo.addobsolete(newid, old.node())
+ phases.retractboundary(repo, oldphase, [newid])
+ repo.dirstate.setparents(newid, node.nullid)
+ _uncommitdirstate(repo, old, match)
+ updatebookmarks(newid)
+ if not repo[newid].files():
+ ui.warn(_("new changeset is empty\n"))
+ ui.status(_('(use "hg kill ." to remove it)\n'))
+ finally:
+ wlock.release()
+ finally:
+ lock.release()
def commitwrapper(orig, ui, repo, *arg, **kwargs):
lock = repo.lock()
@@ -547,16 +678,19 @@
raise error.Abort(_('evolution extension require rebase extension.'))
entry = extensions.wrapcommand(commands.table, 'commit', commitwrapper)
- entry[1].append(('o', 'obsolete', [], _("this commit obsolet this revision")))
+ entry[1].append(('o', 'obsolete', [],
+ _("make commit obsolete this revision")))
entry = extensions.wrapcommand(commands.table, 'graft', graftwrapper)
- entry[1].append(('o', 'obsolete', [], _("this graft obsolet this revision")))
- entry[1].append(('O', 'old-obsolete', False, _("graft result obsolete graft source")))
+ entry[1].append(('o', 'obsolete', [],
+ _("make graft obsoletes this revision")))
+ entry[1].append(('O', 'old-obsolete', False,
+ _("make graft obsoletes its source")))
# warning about more obsolete
- for cmd in ['commit', 'push', 'pull', 'graft']:
- entry = extensions.wrapcommand(commands.table, cmd, warnunstable)
- for cmd in ['kill', 'amend']:
- entry = extensions.wrapcommand(cmdtable, cmd, warnunstable)
+ for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']:
+ entry = extensions.wrapcommand(commands.table, cmd, warnobserrors)
+ for cmd in ['amend', 'kill', 'uncommit']:
+ entry = extensions.wrapcommand(cmdtable, cmd, warnobserrors)
if rebase is not None:
- entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnunstable)
+ entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
--- a/hgext/obsolete.py Wed Jun 20 18:04:50 2012 +0200
+++ b/hgext/obsolete.py Wed Jun 27 15:12:19 2012 +0200
@@ -101,6 +101,7 @@
from mercurial.lock import release
from mercurial import localrepo
from mercurial import cmdutil
+from mercurial import templatekw
try:
from mercurial.localrepo import storecache
@@ -137,10 +138,22 @@
context.changectx.extinct = extinct
+def latecomer(ctx):
+ """is the changeset latecomer (Try to succeed to public change)"""
+ if ctx.node() is None:
+ return False
+ return ctx.rev() in ctx._repo._latecomerset
+
+context.changectx.latecomer = latecomer
+
### revset
#############################
+def revsethidden(repo, subset, x):
+ """hidden changesets"""
+ args = revset.getargs(x, 0, 0, 'hidden takes no argument')
+ return [r for r in subset if r in repo.changelog.hiddenrevs]
def revsetobsolete(repo, subset, x):
"""obsolete changesets"""
@@ -161,17 +174,21 @@
def revsetsuspended(repo, subset, x):
"""obsolete changesets with non obsolete descendants"""
- args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
+ args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
return [r for r in subset if r in repo._suspendedset]
def revsetextinct(repo, subset, x):
"""obsolete changesets without obsolete descendants"""
- args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
+ args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
return [r for r in subset if r in repo._extinctset]
+def revsetlatecomer(repo, subset, x):
+ """latecomer, Try to succeed to public change"""
+ args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
+ return [r for r in subset if r in repo._latecomerset]
-def _obsparents(repo, s):
- """obsolete parents of a subset"""
+def _precursors(repo, s):
+ """Precursor of a changeset"""
cs = set()
nm = repo.changelog.nodemap
markerbysubj = repo.obsoletestore.subjects
@@ -182,14 +199,14 @@
cs.add(pr)
return cs
-def revsetobsparents(repo, subset, x):
- """obsolete parents"""
+def revsetprecursors(repo, subset, x):
+ """precursors of a subset"""
s = revset.getset(repo, range(len(repo)), x)
- cs = _obsparents(repo, s)
+ cs = _precursors(repo, s)
return [r for r in subset if r in cs]
-def _obsancestors(repo, s):
- """obsolete ancestors of a subset"""
+def _allprecursors(repo, s): # XXX we need a better naming
+ """transitive precursors of a subset"""
toproceed = [repo[r].node() for r in s]
seen = set()
allsubjects = repo.obsoletestore.subjects
@@ -208,13 +225,73 @@
cs.add(pr)
return cs
-def revsetobsancestors(repo, subset, x):
+def revsetallprecursors(repo, subset, x):
"""obsolete parents"""
s = revset.getset(repo, range(len(repo)), x)
- cs = _obsancestors(repo, s)
+ cs = _allprecursors(repo, s)
+ return [r for r in subset if r in cs]
+
+def _successors(repo, s):
+ """Successors of a changeset"""
+ cs = set()
+ nm = repo.changelog.nodemap
+ markerbyobj = repo.obsoletestore.objects
+ for r in s:
+ for p in markerbyobj.get(repo[r].node(), ()):
+ for sub in p['subjects']:
+ sr = nm.get(sub)
+ if sr is not None:
+ cs.add(sr)
+ return cs
+
+def revsetsuccessors(repo, subset, x):
+ """successors of a subset"""
+ s = revset.getset(repo, range(len(repo)), x)
+ cs = _successors(repo, s)
+ return [r for r in subset if r in cs]
+
+def _allsuccessors(repo, s): # XXX we need a better naming
+ """transitive successors of a subset"""
+ toproceed = [repo[r].node() for r in s]
+ seen = set()
+ allobjects = repo.obsoletestore.objects
+ while toproceed:
+ nc = toproceed.pop()
+ for mark in allobjects.get(nc, ()):
+ for sub in mark['subjects']:
+ if sub not in seen:
+ seen.add(sub)
+ toproceed.append(sub)
+ nm = repo.changelog.nodemap
+ cs = set()
+ for s in seen:
+ sr = nm.get(s)
+ if sr is not None:
+ cs.add(sr)
+ return cs
+
+def revsetallsuccessors(repo, subset, x):
+ """obsolete parents"""
+ s = revset.getset(repo, range(len(repo)), x)
+ cs = _allsuccessors(repo, s)
return [r for r in subset if r in cs]
+### template keywords
+#####################
+
+def obsoletekw(repo, ctx, templ, **args):
+ """:obsolete: String. The obsolescence level of the node, could be
+ ``stable``, ``unstable``, ``suspended`` or ``extinct``.
+ """
+ rev = ctx.rev()
+ if rev in repo._extinctset:
+ return 'extinct'
+ if rev in repo._suspendedset:
+ return 'suspended'
+ if rev in repo._unstableset:
+ return 'unstable'
+ return 'stable'
### Other Extension compat
############################
@@ -245,9 +322,8 @@
return newrev
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'"))
+
+ reallykeep = kwargs.get('keep', False)
kwargs = dict(kwargs)
kwargs['keep'] = True
@@ -260,33 +336,41 @@
# 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)
+ 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.
+ targetrev = repo[repo._rebasetarget].rev()
+ newrevs = set([targetrev])
+ replacements = {}
+ for rev, newrev in sorted(repo._rebasestate.items()):
+ oldnode = repo[rev].node()
+ if newrev not in newrevs:
+ newnode = repo[newrev].node()
+ newrevs.add(newrev)
+ else:
+ newnode = nullid
+ replacements[oldnode] = newnode
+
+ if kwargs.get('collapse'):
+ newnodes = set(n for n in replacements.values() if n != nullid)
+ if newnodes:
+ # Collapsing into more than one revision?
+ assert len(newnodes) == 1, newnodes
+ newnode = newnodes.pop()
+ else:
+ newnode = nullid
+ repo.addcollapsedobsolete(replacements, newnode)
else:
- newnode = emptynode
- repo.addobsolete(newnode, oldnode)
+ for oldnode, newnode in replacements.iteritems():
+ repo.addobsolete(newnode, oldnode)
return res
finally:
delattr(repo, '_rebasestate')
@@ -295,13 +379,20 @@
def extsetup(ui):
+ revset.symbols["hidden"] = revsethidden
revset.symbols["obsolete"] = revsetobsolete
revset.symbols["unstable"] = revsetunstable
revset.symbols["suspended"] = revsetsuspended
revset.symbols["extinct"] = revsetextinct
- revset.symbols["obsparents"] = revsetobsparents
- revset.symbols["obsancestors"] = revsetobsancestors
+ revset.symbols["latecomer"] = revsetlatecomer
+ revset.symbols["obsparents"] = revsetprecursors # DEPR
+ revset.symbols["precursors"] = revsetprecursors
+ revset.symbols["obsancestors"] = revsetallprecursors # DEPR
+ revset.symbols["allprecursors"] = revsetallprecursors # bad name
+ revset.symbols["successors"] = revsetsuccessors
+ revset.symbols["allsuccessors"] = revsetallsuccessors # bad name
+ templatekw.keywords['obsolete'] = obsoletekw
try:
rebase = extensions.find('rebase')
@@ -311,7 +402,7 @@
extensions.wrapfunction(rebase, 'concludenode', concludenode)
extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
except KeyError:
- pass # rebase not found
+ pass # rebase not found
# Pushkey mechanism for mutable
#########################################
@@ -382,6 +473,9 @@
if ctx.obsolete():
raise util.Abort(_("Trying to push obsolete changeset: %s!") % ctx,
hint=hint)
+ if ctx.latecomer():
+ raise util.Abort(_("Trying to push latecomer changeset: %s!") % ctx,
+ hint=hint)
### patch remote branch map
# do not read it this burn eyes
try:
@@ -436,6 +530,12 @@
else:
return None # break recursion
+def wrapclearcache(orig, repo, *args, **kwargs):
+ try:
+ return orig(repo, *args, **kwargs)
+ finally:
+ repo._clearobsoletecache()
+
### New commands
#############################
@@ -527,12 +627,35 @@
repo._turn_extinct_secret()
return orig(repo)
+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:
+ newmarker = {
+ 'subjects': [new],
+ 'object': oldnode,
+ 'date': util.makedate(),
+ 'user': ui.username(),
+ 'reason': 'commit --amend',
+ }
+ repo.obsoletestore.new(newmarker)
+ repo._clearobsoletecache()
+ repo._turn_extinct_secret()
+ finally:
+ lock.release()
+ return new
+
def uisetup(ui):
extensions.wrapcommand(commands.table, "update", wrapmayobsoletewc)
extensions.wrapcommand(commands.table, "pull", wrapmayobsoletewc)
+ if util.safehasattr(cmdutil, 'amend'):
+ extensions.wrapfunction(cmdutil, 'amend', wrapcmdutilamend)
extensions.wrapfunction(discovery, 'findcommonoutgoing', wrapfindcommonoutgoing)
extensions.wrapfunction(discovery, 'checkheads', wrapcheckheads)
extensions.wrapfunction(phases, 'visibleheads', noextinctsvisibleheads)
+ extensions.wrapfunction(phases, 'advanceboundary', wrapclearcache)
if util.safehasattr(phases, 'visiblebranchmap'):
extensions.wrapfunction(phases, 'visiblebranchmap', wrapvisiblebranchmap)
@@ -767,6 +890,11 @@
"""the set of obsolete parent without non obsolete descendant"""
return set(self.revs('obsolete() - obsolete()::unstable()'))
+ @util.propertycache
+ def _latecomerset(self):
+ """the set of rev trying to obsolete public revision"""
+ return set(self.revs('allsuccessors(public()) - obsolete()'))
+
def _clearobsoletecache(self):
if '_obsoleteset' in vars(self):
del self._obsoleteset
@@ -783,6 +911,8 @@
del self._suspendedset
if '_extinctset' in vars(self):
del self._extinctset
+ if '_latecomerset' in vars(self):
+ del self._latecomerset
def addobsolete(self, sub, obj):
"""Add a relation marking that node <sub> is a new version of <obj>"""
@@ -812,12 +942,22 @@
finally:
lock.release()
+ def addcollapsedobsolete(self, oldnodes, newnode):
+ """Mark oldnodes as collapsed into newnode."""
+ # Assume oldnodes are all descendants of a single rev
+ rootrevs = self.revs('roots(%ln)', oldnodes)
+ assert len(rootrevs) == 1, rootrevs
+ rootnode = self[rootrevs[0]].node()
+ for n in oldnodes:
+ self.addobsolete(newnode, n)
+
def _turn_extinct_secret(self):
"""ensure all extinct changeset are secret"""
self._clearobsoletecache()
# this is mainly for safety purpose
# both pull and push
- expobs = [c.node() for c in repo.set('extinct() - secret()')]
+ query = '(obsolete() - obsolete()::(unstable() - secret())) - secret()'
+ expobs = [c.node() for c in repo.set(query)]
phases.retractboundary(repo, 2, expobs)
### Disk IO
@@ -954,13 +1094,3 @@
return c
repo.__class__ = obsoletingrepo
-
- if False:
- expobs = [c.node() for c in repo.set('extinct() - secret()')]
- if expobs: # do not lock in nothing move. locking for peanut make hgview reload on any command
- lock = repo.lock()
- try:
- expobs = [c.node() for c in repo.set('extinct() - secret()')]
- phases.retractboundary(repo, 2, expobs)
- finally:
- lock.release()
--- a/tests/test-amend.t Wed Jun 20 18:04:50 2012 +0200
+++ b/tests/test-amend.t Wed Jun 27 15:12:19 2012 +0200
@@ -26,7 +26,6 @@
$ hg amend
$ hg debugsuccessors
07f494440405 a34b93d251e4
- 07f494440405 bd19cbe78fbf
bd19cbe78fbf a34b93d251e4
$ hg branch
foo
@@ -65,7 +64,6 @@
[255]
$ hg debugsuccessors
07f494440405 a34b93d251e4
- 07f494440405 bd19cbe78fbf
bd19cbe78fbf a34b93d251e4
$ hg phase 2
2: draft
@@ -92,7 +90,6 @@
[255]
$ hg debugsuccessors
07f494440405 a34b93d251e4
- 07f494440405 bd19cbe78fbf
7384bbcba36f 000000000000
bd19cbe78fbf a34b93d251e4
$ glog
--- a/tests/test-evolve.t Wed Jun 20 18:04:50 2012 +0200
+++ b/tests/test-evolve.t Wed Jun 27 15:12:19 2012 +0200
@@ -23,6 +23,10 @@
> hg ci -m "add $1"
> }
+ $ glog() {
+ > hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
+ > }
+
various init
$ hg init local
@@ -52,7 +56,9 @@
test simple kill
- $ hg kill 5
+ $ hg id -n
+ 5
+ $ hg kill .
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
working directory now at fbb94e3a0ecf
$ hg qlog
@@ -71,6 +77,18 @@
2 - 4538525df7e2 add c (draft)
1 - 7c3bad9141dc add b (public)
0 - 1f0dee641bb7 add a (public)
+
+test kill with dirty changes
+
+ $ hg up 2
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo 4 > g
+ $ hg add g
+ $ hg kill .
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ working directory now at 7c3bad9141dc
+ $ hg st
+ A g
$ cd ..
##########################
@@ -209,8 +227,27 @@
4 feature-B: another feature - test
1 : a nifty feature - test
0 : base - test
- $ hg up -q 1
- Working directory parent is obsolete
+ $ hg up -q 0
+ $ glog --hidden
+ o 6:23409eba69a0@default(draft) a nifty feature
+ |
+ | o 5:e416e48b2742@default(secret) french looks better
+ | |
+ | | o 4:f8111a076f09@default(draft) another feature
+ | |/
+ | | o 3:524e478d4811@default(secret) fix spelling of Zwei
+ | | |
+ | | o 2:7b36850622b2@default(secret) another feature
+ | |/
+ | o 1:568a468b60fc@default(draft) a nifty feature
+ |/
+ @ 0:e55e0562ee93@default(draft) base
+
+ $ hg debugsuccessors
+ 524e478d4811 f8111a076f09
+ 568a468b60fc 23409eba69a0
+ 7b36850622b2 f8111a076f09
+ e416e48b2742 23409eba69a0
$ hg stabilize
move:[4] another feature
atop:[6] a nifty feature
@@ -234,6 +271,12 @@
8 feature-B: another feature that rox - test
6 feature-A: a nifty feature - test
0 : base - test
+
+phase change turning obsolete changeset public issue a latecomer warning
+
+ $ hg phase --public 7
+ 1 new latecomers changesets
+
$ cd ..
enable general delta
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-push.t Wed Jun 27 15:12:19 2012 +0200
@@ -0,0 +1,48 @@
+ $ 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
+
+ $ template='{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n'
+ $ glog() {
+ > hg glog --template "$template" "$@"
+ > }
+
+Test outgoing, common A is suspended, B unstable and C secret, remote
+has A and B, neither A or C should be in outgoing.
+
+ $ hg init source
+ $ cd source
+ $ echo a > a
+ $ hg ci -qAm A a
+ $ echo b > b
+ $ hg ci -qAm B b
+ $ hg up 0
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ echo c > c
+ $ hg ci -qAm C c
+ $ hg phase --secret --force .
+ $ hg kill 0 1
+ 1 new unstables changesets
+ $ glog --hidden
+ @ 2:244232c2222a@default(unstable/secret) C
+ |
+ | o 1:6c81ed0049f8@default(extinct/secret) B
+ |/
+ o 0:1994f17a630e@default(suspended/secret) A
+
+ $ hg init ../clone
+ $ cat > ../clone/.hg/hgrc <<EOF
+ > [phases]
+ > publish = false
+ > EOF
+ $ hg outgoing ../clone --template "$template"
+ comparing with ../clone
+ searching for changes
+ no changes found (ignored 2 secret changesets)
+ [1]
--- a/tests/test-obsolete-rebase.t Wed Jun 20 18:04:50 2012 +0200
+++ b/tests/test-obsolete-rebase.t Wed Jun 27 15:12:19 2012 +0200
@@ -30,11 +30,43 @@
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
+ $ hg rebase -d 1 -r 3 --detach --keep
+ $ glog
+ @ 4:9c5494949763@default(draft) adde
+ |
+ | o 3:98e4a024635e@default(draft) adde
+ | |
+ | o 2:102a90ea7b4a@default(draft) addb
+ | |
+ o | 1:540395c44225@default(draft) changea
+ |/
+ o 0:07f494440405@default(draft) adda
+
+ $ glog --hidden
+ @ 4:9c5494949763@default(draft) adde
+ |
+ | o 3:98e4a024635e@default(draft) adde
+ | |
+ | o 2:102a90ea7b4a@default(draft) addb
+ | |
+ o | 1:540395c44225@default(draft) changea
+ |/
+ o 0:07f494440405@default(draft) adda
+
+ $ hg debugsuccessors
+ $ hg --config extensions.hgext.mq= strip tip
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9c5494949763-backup.hg
+ $ hg rebase -d 1 -r 3 --detach
+ $ glog
+ @ 4:9c5494949763@default(draft) adde
+ |
+ | o 2:102a90ea7b4a@default(draft) addb
+ | |
+ o | 1:540395c44225@default(draft) changea
+ |/
+ o 0:07f494440405@default(draft) adda
+
$ glog --hidden
@ 4:9c5494949763@default(draft) adde
|
--- a/tests/test-obsolete.t Wed Jun 20 18:04:50 2012 +0200
+++ b/tests/test-obsolete.t Wed Jun 27 15:12:19 2012 +0200
@@ -5,7 +5,7 @@
> [phases]
> publish=False
> [alias]
- > odiff=diff --rev 'limit(obsparents(.),1)' --rev .
+ > odiff=diff --rev 'limit(precursors(.),1)' --rev .
> [extensions]
> hgext.graphlog=
> EOF
@@ -54,7 +54,7 @@
Test that obsolete parent a properly computed
- $ qlog -r 'obsparents(.)' --hidden
+ $ qlog -r 'precursors(.)' --hidden
2
- 4538525df7e2
$ qlog -r .
@@ -72,6 +72,12 @@
@@ -0,0 +1,1 @@
+obsol_c
+Test that obsolete successors a properly computed
+
+ $ qlog -r 'successors(2)' --hidden
+ 3
+ - 0d3f46688ccc
+
test obsolete changeset with no-obsolete descendant
$ hg up 1 -q
$ mkcommit "obsol_c'" # 4 (on 1)
@@ -89,11 +95,16 @@
- 4538525df7e2
3
- 0d3f46688ccc
- $ qlog -r 'obsancestors(4)' --hidden
+ $ qlog -r 'allprecursors(4)' --hidden
2
- 4538525df7e2
3
- 0d3f46688ccc
+ $ qlog -r 'allsuccessors(2)' --hidden
+ 3
+ - 0d3f46688ccc
+ 4
+ - 725c380fe99b
$ hg up 3 -q
Working directory parent is obsolete
$ mkcommit d # 5 (on 3)
@@ -111,6 +122,23 @@
5
- a7a6f2b5d8a5
+Test obsolete keyword
+
+ $ hg glog --template '{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n' \
+ > --hidden
+ @ 5:a7a6f2b5d8a5@default(unstable/secret) add d
+ |
+ | o 4:725c380fe99b@default(stable/draft) add obsol_c'
+ | |
+ o | 3:0d3f46688ccc@default(suspended/secret) add obsol_c
+ |/
+ | o 2:4538525df7e2@default(extinct/secret) add c
+ |/
+ o 1:7c3bad9141dc@default(stable/draft) add b
+ |
+ o 0:1f0dee641bb7@default(stable/public) add a
+
+
Test communication of obsolete relation with a compatible client
$ hg init ../other-new
@@ -454,3 +482,99 @@
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
$ cd ..
+
+check latecomer detection
+(make an obsolete changeset public)
+
+ $ cd local
+ $ hg phase --public 11
+ $ hg --config extensions.graphlog=glog glog --template='{rev} - ({phase}) {node|short} {desc}\n'
+ @ 12 - (draft) 6db5e282cb91 add obsol_d'''
+ |
+ | o 11 - (public) 9468a5f5d8b2 add obsol_d''
+ |/
+ o 10 - (public) 2033b4e49474 add obsol_c
+ |
+ o 4 - (public) 725c380fe99b add obsol_c'
+ |
+ o 1 - (public) 7c3bad9141dc add b
+ |
+ o 0 - (public) 1f0dee641bb7 add a
+
+ $ hg log -r 'latecomer()'
+ changeset: 12:6db5e282cb91
+ tag: tip
+ parent: 10:2033b4e49474
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: add obsol_d'''
+
+ $ hg push ../other-new/
+ pushing to ../other-new/
+ searching for changes
+ abort: Trying to push latecomer changeset: 6db5e282cb91!
+ (use 'hg stabilize' to get a stable history (or --force to proceed))
+ [255]
+
+Check hg commit --amend compat
+
+ $ hg up 'desc(obsol_c)'
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ mkcommit f
+ created new head
+ $ echo 42 >> f
+ $ hg commit --amend --traceback
+ saved backup bundle to $TESTTMP/local/.hg/strip-backup/0b1b6dd009c0-amend-backup.hg
+ $ hg glog
+ @ changeset: 13:3734a65252e6
+ | tag: tip
+ | parent: 10:2033b4e49474
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: add f
+ |
+ | o changeset: 12:6db5e282cb91
+ |/ parent: 10:2033b4e49474
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: add obsol_d'''
+ |
+ | o changeset: 11:9468a5f5d8b2
+ |/ user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: add obsol_d''
+ |
+ o changeset: 10:2033b4e49474
+ | parent: 4:725c380fe99b
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: add obsol_c
+ |
+ o changeset: 4:725c380fe99b
+ | parent: 1:7c3bad9141dc
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: add obsol_c'
+ |
+ o changeset: 1:7c3bad9141dc
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: add b
+ |
+ o changeset: 0:1f0dee641bb7
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: add a
+
+ $ hg debugsuccessors
+ 0b1b6dd009c0 3734a65252e6
+ 0d3f46688ccc 2033b4e49474
+ 0d3f46688ccc 725c380fe99b
+ 159dfc9fa5d3 9468a5f5d8b2
+ 1f0dee641bb7 83b5778897ad
+ 4538525df7e2 0d3f46688ccc
+ 83b5778897ad 000000000000
+ 909a0fb57e5d 159dfc9fa5d3
+ 9468a5f5d8b2 6db5e282cb91
+ 95de7fc6918d 909a0fb57e5d
+ a7a6f2b5d8a5 95de7fc6918d
--- a/tests/test-stabilize-order.t Wed Jun 20 18:04:50 2012 +0200
+++ b/tests/test-stabilize-order.t Wed Jun 27 15:12:19 2012 +0200
@@ -106,9 +106,9 @@
3a4a591493f8 f5ff10856e5a
3ca0ded0dc50 ab8cbb6d87ff
+7a7552255fb5 5e819fbb0d27
- 93418d2c0979 3a4a591493f8
93418d2c0979 f5ff10856e5a
ab8cbb6d87ff 6bf44048e43f
+ ef23d6ef94d6 ab8cbb6d87ff
[1]
$ glog
@ 9:5e819fbb0d27@default(draft) addc
--- a/tests/test-stabilize-result.t Wed Jun 20 18:04:50 2012 +0200
+++ b/tests/test-stabilize-result.t Wed Jun 27 15:12:19 2012 +0200
@@ -47,6 +47,5 @@
$ hg debugsuccessors
102a90ea7b4a 1447e1c4828d
- 102a90ea7b4a 41ad4fe8c795
41ad4fe8c795 1447e1c4828d
cce2c55b8965 000000000000
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-uncommit.t Wed Jun 27 15:12:19 2012 +0200
@@ -0,0 +1,334 @@
+ $ cat >> $HGRCPATH <<EOF
+ > [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}({obsolete}/{phase}) {desc|firstline}\n' "$@"
+ > }
+
+ $ hg init repo
+ $ cd repo
+
+Cannot uncommit null changeset
+
+ $ hg uncommit
+ abort: cannot rewrite immutable changeset
+ [255]
+
+Cannot uncommit public changeset
+
+ $ echo a > a
+ $ hg ci -Am adda a
+ $ hg phase --public .
+ $ hg uncommit
+ abort: cannot rewrite immutable changeset
+ [255]
+ $ hg phase --force --draft .
+
+Cannot uncommit merge
+
+ $ hg up -q null
+ $ echo b > b
+ $ echo c > c
+ $ echo d > d
+ $ echo f > f
+ $ echo g > g
+ $ echo j > j
+ $ echo m > m
+ $ echo n > n
+ $ echo o > o
+ $ hg ci -Am addmore
+ adding b
+ adding c
+ adding d
+ adding f
+ adding g
+ adding j
+ adding m
+ adding n
+ adding o
+ created new head
+ $ hg merge
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ hg uncommit
+ abort: cannot uncommit while merging
+ [255]
+ $ hg ci -m merge
+ $ hg uncommit
+ abort: cannot uncommit merge changeset
+ [255]
+
+Prepare complicated changeset
+
+ $ hg branch bar
+ marked working directory as branch bar
+ (branches are permanent and global, did you want a bookmark?)
+ $ hg cp a aa
+ $ echo b >> b
+ $ hg rm c
+ $ echo d >> d
+ $ echo e > e
+ $ hg mv f ff
+ $ hg mv g h
+ $ echo j >> j
+ $ echo k > k
+ $ echo l > l
+ $ hg rm m
+ $ hg rm n
+ $ echo o >> o
+ $ hg ci -Am touncommit
+ adding e
+ adding k
+ adding l
+ $ hg st --copies --change .
+ M b
+ M d
+ M j
+ M o
+ A aa
+ a
+ A e
+ A ff
+ f
+ A h
+ g
+ A k
+ A l
+ R c
+ R f
+ R g
+ R m
+ R n
+ $ hg man -r .
+ a
+ aa
+ b
+ d
+ e
+ ff
+ h
+ j
+ k
+ l
+ o
+
+Add a couple of bookmarks
+
+ $ glog --hidden
+ @ 3:5eb72dbe0cb4@bar(stable/draft) touncommit
+ |
+ o 2:f63b90038565@default(stable/draft) merge
+ |\
+ | o 1:f15c744d48e8@default(stable/draft) addmore
+ |
+ o 0:07f494440405@default(stable/draft) adda
+
+ $ hg bookmark -r 2 unrelated
+ $ hg bookmark touncommit-bm
+ $ hg bookmark --inactive touncommit-bm-inactive
+ $ hg bookmarks
+ * touncommit-bm 3:5eb72dbe0cb4
+ touncommit-bm-inactive 3:5eb72dbe0cb4
+ unrelated 2:f63b90038565
+
+Prepare complicated working directory
+
+ $ hg branch foo
+ marked working directory as branch foo
+ (branches are permanent and global, did you want a bookmark?)
+ $ hg mv ff f
+ $ hg mv h i
+ $ hg rm j
+ $ hg rm k
+ $ echo l >> l
+ $ echo m > m
+ $ echo o > o
+
+Test uncommit without argument, should be a no-op
+
+ $ hg uncommit
+ abort: nothing to uncommit
+ [255]
+ $ hg bookmarks
+ * touncommit-bm 3:5eb72dbe0cb4
+ touncommit-bm-inactive 3:5eb72dbe0cb4
+ unrelated 2:f63b90038565
+
+Test no matches
+
+ $ hg uncommit --include nothere
+ abort: nothing to uncommit
+ [255]
+
+Enjoy uncommit
+
+ $ hg uncommit aa b c f ff g h j k l m o
+ $ hg branch
+ foo
+ $ hg st --copies
+ M b
+ A aa
+ a
+ A i
+ g
+ A l
+ R c
+ R g
+ R j
+ R m
+ $ cat aa
+ a
+ $ cat b
+ b
+ b
+ $ cat l
+ l
+ l
+ $ cat m
+ m
+ $ test -f c && echo 'error: c was removed!'
+ [1]
+ $ test -f j && echo 'error: j was removed!'
+ [1]
+ $ test -f k && echo 'error: k was removed!'
+ [1]
+ $ hg st --copies --change .
+ M d
+ A e
+ R n
+ $ hg man -r .
+ a
+ b
+ c
+ d
+ e
+ f
+ g
+ j
+ m
+ o
+ $ hg cat -r . d
+ d
+ d
+ $ hg cat -r . e
+ e
+ $ glog --hidden
+ @ 4:e8db4aa611f6@bar(stable/draft) touncommit
+ |
+ | o 3:5eb72dbe0cb4@bar(extinct/secret) touncommit
+ |/
+ o 2:f63b90038565@default(stable/draft) merge
+ |\
+ | o 1:f15c744d48e8@default(stable/draft) addmore
+ |
+ o 0:07f494440405@default(stable/draft) adda
+
+ $ hg bookmarks
+ * touncommit-bm 4:e8db4aa611f6
+ touncommit-bm-inactive 4:e8db4aa611f6
+ unrelated 2:f63b90038565
+ $ hg debugsuccessors
+ 5eb72dbe0cb4 e8db4aa611f6
+
+Test phase is preserved, no local changes
+
+ $ hg up -C 3
+ 8 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ Working directory parent is obsolete
+ $ hg --config extensions.purge= purge
+ $ hg uncommit -I 'set:added() and e'
+ $ hg st --copies
+ A e
+ $ hg st --copies --change .
+ M b
+ M d
+ M j
+ M o
+ A aa
+ A ff
+ f
+ A h
+ g
+ A k
+ A l
+ R c
+ R f
+ R g
+ R m
+ R n
+ $ glog --hidden
+ @ 5:c706fe2c12f8@bar(stable/secret) touncommit
+ |
+ | o 4:e8db4aa611f6@bar(stable/draft) touncommit
+ |/
+ | o 3:5eb72dbe0cb4@bar(extinct/secret) touncommit
+ |/
+ o 2:f63b90038565@default(stable/draft) merge
+ |\
+ | o 1:f15c744d48e8@default(stable/draft) addmore
+ |
+ o 0:07f494440405@default(stable/draft) adda
+
+ $ hg debugsuccessors
+ 5eb72dbe0cb4 c706fe2c12f8
+ 5eb72dbe0cb4 e8db4aa611f6
+
+Test --all
+
+ $ hg up -C 3
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ Working directory parent is obsolete
+ $ hg --config extensions.purge= purge
+ $ hg uncommit --all -X e
+ $ hg st --copies
+ M b
+ M d
+ M j
+ M o
+ A aa
+ a
+ A ff
+ f
+ A h
+ g
+ A k
+ A l
+ R c
+ R f
+ R g
+ R m
+ R n
+ $ hg st --copies --change .
+ A e
+
+ $ hg debugsuccessors
+ 5eb72dbe0cb4 c4cbebac3751
+ 5eb72dbe0cb4 c706fe2c12f8
+ 5eb72dbe0cb4 e8db4aa611f6
+
+Display a warning if nothing left
+
+ $ hg uncommit e
+ new changeset is empty
+ (use "hg kill ." to remove it)
+ $ hg debugsuccessors
+ 5eb72dbe0cb4 c4cbebac3751
+ 5eb72dbe0cb4 c706fe2c12f8
+ 5eb72dbe0cb4 e8db4aa611f6
+ c4cbebac3751 4f1c269eab68
+
+Test instability warning
+
+ $ hg ci -m touncommit
+ $ echo unrelated > unrelated
+ $ hg ci -Am addunrelated unrelated
+ $ hg gdown
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ [8] touncommit
+ $ hg uncommit aa
+ 1 new unstables changesets