changeset 1058:00bc31523074

uncommit: add a --rev argument The uncommit command now taks a --rev argument. This lets restore the file content in the current commit to another revision (instead of just is parent content). This still does not touch the working directory content.\ This allow the longly awaited: hg uncommit --hidden --rev 'precursors(.)'
author Pierre-Yves David <pierre-yves.david@fb.com>
date Fri, 15 Aug 2014 00:46:23 -0700
parents edfb9a0ad085
children d1baf69b935c
files README hgext/evolve.py tests/test-tutorial.t tests/test-uncommit.t
diffstat 4 files changed, 71 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/README	Thu Aug 14 15:26:55 2014 -0700
+++ b/README	Fri Aug 15 00:46:23 2014 -0700
@@ -57,6 +57,10 @@
 Changelog
 =========
 
+4.2.0 --
+
+- uncommit: add a --rev argument
+
 4.1.0 -- 2014-08-08
 
 - amend: add -D/--current-date option
--- a/hgext/evolve.py	Thu Aug 14 15:26:55 2014 -0700
+++ b/hgext/evolve.py	Fri Aug 15 00:46:23 2014 -0700
@@ -1859,25 +1859,45 @@
     _alias, commitcmd = cmdutil.findcmd('commit', commands.table)
     return commitcmd[0](ui, repo, *pats, **opts)
 
-def _commitfiltered(repo, ctx, match):
+
+def _touchedbetween(repo, source, dest, match=None):
+    touched = set()
+    for files in repo.status(source, dest, match=match)[:3]:
+        touched.update(files)
+    return touched
+
+def _commitfiltered(repo, ctx, match, target=None):
     """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:
+    if target is None:
+        target = base
+    # ctx
+    initialfiles = _touchedbetween(repo, base, ctx)
+    if base == target:
+        affected = set(f for f in initialfiles if match(f))
+        newcontent = set()
+    else:
+        affected = _touchedbetween(repo, target, ctx, match=match)
+        newcontent = _touchedbetween(repo, target, base, match=match)
+    # The commit touchs all existing files
+    # + all file that needs a new content
+    # - the file affected bny uncommit with the same content than base.
+    files = (initialfiles - affected) | newcontent
+    if not newcontent and files == initialfiles:
         return None
 
     # Filter copies
-    copied = copies.pathcopies(base, ctx)
+    copied = copies.pathcopies(target, 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:
+    def filectxfn(repo, memctx, path, contentctx=ctx, redirect=newcontent):
+        if path in redirect:
+            return filectxfn(repo, memctx, path, contentctx=target, redirect=())
+        if path not in contentctx:
             raise IOError()
-        fctx = ctx[path]
+        fctx = contentctx[path]
         flags = fctx.flags()
         mctx = memfilectx(repo, fctx.path(), fctx.data(),
                           islink='l' in flags,
@@ -1943,6 +1963,7 @@
 
 @command('^uncommit',
     [('a', 'all', None, _('uncommit all changes when no arguments given')),
+     ('r', 'rev', '', _('revert commit content to REV instead')),
      ] + commands.walkopts,
     _('[OPTION]... [NAME]'))
 def uncommit(ui, repo, *pats, **opts):
@@ -1956,6 +1977,10 @@
     The --include option specifies patterns to uncommit.
     The --exclude option specifies patterns to keep in the commit.
 
+    The --rev argument let you change the commit file to a content of another
+    revision. It still does not change the content of your file in the working
+    directory.
+
     Return 0 if changed files are uncommitted.
     """
 
@@ -1976,13 +2001,18 @@
         oldphase = old.phase()
         updatebookmarks = _bookmarksupdater(repo, old.node())
 
+
+        rev = None
+        if opts.get('rev'):
+            rev = scmutil.revsingle(repo, opts.get('rev'))
+
         # Recommit the filtered changeset
         tr = repo.transaction('uncommit')
         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)
+            newid = _commitfiltered(repo, old, match, target=rev)
         if newid is None:
             raise util.Abort(_('nothing to uncommit'),
                              hint=_("use --all to uncommit all files"))
--- a/tests/test-tutorial.t	Thu Aug 14 15:26:55 2014 -0700
+++ b/tests/test-tutorial.t	Fri Aug 15 00:46:23 2014 -0700
@@ -446,11 +446,16 @@
       The --include option specifies patterns to uncommit. The --exclude option
       specifies patterns to keep in the commit.
   
+      The --rev argument let you change the commit file to a content of another
+      revision. It still does not change the content of your file in the working
+      directory.
+  
       Return 0 if changed files are uncommitted.
   
   options:
   
    -a --all                 uncommit all changes when no arguments given
+   -r --rev VALUE           revert commit content to REV instead
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
   
--- a/tests/test-uncommit.t	Thu Aug 14 15:26:55 2014 -0700
+++ b/tests/test-uncommit.t	Fri Aug 15 00:46:23 2014 -0700
@@ -336,3 +336,25 @@
   [8] touncommit
   $ hg uncommit aa
   1 new unstable changesets
+
+Test uncommiting agains a different base
+
+  $ hg cat b --rev .
+  b
+  b
+  $ hg cat b --rev .^
+  b
+  $ hg cat b --rev 0
+  b: no such file in rev 07f494440405
+  [1]
+  $ hg uncommit --rev 0 b
+  $ hg cat b --rev .
+  b: no such file in rev 5b27f6b17da2
+  [1]
+
+Test uncommiting precursors
+
+  $ hg uncommit --hidden --rev 'precursors(.)' b
+  $ hg cat b --rev .
+  b
+  b