--- a/hgext/evolve.py Tue Jun 26 14:35:09 2012 +0200
+++ b/hgext/evolve.py Tue Jun 26 18:12:31 2012 +0200
@@ -471,6 +471,136 @@
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',
+ [] + 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()
+ # Recommit the filtered changeset
+ newid = None
+ if pats or opts.get('include') or opts.get('exclude'):
+ 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)
+ finally:
+ wlock.release()
+ finally:
+ lock.release()
+
def commitwrapper(orig, ui, repo, *arg, **kwargs):
lock = repo.lock()
try: