rewrite revert command. fix issues 93, 123, 147.
authorVadim Gelfer <vadim.gelfer@gmail.com>
Fri, 31 Mar 2006 10:37:25 -0800
changeset 2029 d436b21b20dc
parent 2028 1f1fc418a96c
child 2030 8d9ec30d58bc
rewrite revert command. fix issues 93, 123, 147. new version does these things: - saves backup copies of modified files (issue 147) - prints output like other commands, and errors when files not found (issue 123) - marks files added/removed (issue 93)
mercurial/commands.py
mercurial/localrepo.py
tests/test-confused-revert.out
tests/test-merge-revert.out
tests/test-merge-revert2
tests/test-merge-revert2.out
tests/test-revert
tests/test-revert-unknown.out
tests/test-revert.out
tests/test-tag.out
--- a/mercurial/commands.py	Fri Mar 31 03:25:35 2006 -0600
+++ b/mercurial/commands.py	Fri Mar 31 10:37:25 2006 -0800
@@ -43,16 +43,17 @@
     return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
                            opts.get('exclude'), head)
 
-def makewalk(repo, pats, opts, node=None, head=''):
+def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
     files, matchfn, anypats = matchpats(repo, pats, opts, head)
     exact = dict(zip(files, files))
     def walk():
-        for src, fn in repo.walk(node=node, files=files, match=matchfn):
+        for src, fn in repo.walk(node=node, files=files, match=matchfn,
+                                 badmatch=None):
             yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
     return files, matchfn, walk()
 
-def walk(repo, pats, opts, node=None, head=''):
-    files, matchfn, results = makewalk(repo, pats, opts, node, head)
+def walk(repo, pats, opts, node=None, head='', badmatch=None):
+    files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
     for r in results:
         yield r
 
@@ -2003,7 +2004,7 @@
     performed before any further updates are allowed.
     """
     return update(ui, repo, node=node, merge=True, **opts)
-    
+
 def outgoing(ui, repo, dest="default-push", **opts):
     """show changesets not found in destination
 
@@ -2088,7 +2089,7 @@
         ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
     else:
         ui.status(_("(run 'hg update' to get a working copy)\n"))
-    
+
 def pull(ui, repo, source="default", **opts):
     """pull changes from the specified source
 
@@ -2286,6 +2287,10 @@
     to the named files or directories.  This restores the contents of
     the affected files to an unmodified state.
 
+    Modified files have backup copies saved before revert.  To disable
+    backups, use --no-backup.  To change the name of backup files, use
+    --backup to give a format string.
+
     Using the -r option, it reverts the given files or directories to
     their state as of an earlier revision.  This can be helpful to "roll
     back" some or all of a change that should not have been committed.
@@ -2300,15 +2305,92 @@
 
     If no arguments are given, all files in the repository are reverted.
     """
-    node = opts['rev'] and repo.lookup(opts['rev']) or \
-           repo.dirstate.parents()[0]
-
-    files, choose, anypats = matchpats(repo, pats, opts)
-    modified, added, removed, deleted, unknown = repo.changes(match=choose)
-    repo.forget(added)
-    repo.undelete(removed)
-
-    return repo.update(node, False, True, choose, False)
+    parent = repo.dirstate.parents()[0]
+    node = opts['rev'] and repo.lookup(opts['rev']) or parent
+    mf = repo.manifest.read(repo.changelog.read(node)[0])
+
+    def backup(name, exact):
+        bakname = make_filename(repo, repo.changelog,
+                                opts['backup_name'] or '%p.orig',
+                                node=parent, pathname=name)
+        if os.path.exists(name):
+            # if backup already exists and is same as backup we want
+            # to make, do nothing
+            if os.path.exists(bakname):
+                if repo.wread(name) == repo.wread(bakname):
+                    return
+                raise util.Abort(_('cannot save current version of %s - '
+                                   '%s exists and differs') %
+                                 (name, bakname))
+            ui.status(('saving current version of %s as %s\n') %
+                      (name, bakname))
+            shutil.copyfile(name, bakname)
+            shutil.copymode(name, bakname)
+
+    wlock = repo.wlock()
+
+    entries = []
+    names = {}
+    for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
+        names[abs] = True
+        entries.append((abs, rel, exact))
+
+    changes = repo.changes(match=names.has_key, wlock=wlock)
+    modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
+
+    revert = ([], _('reverting %s\n'))
+    add = ([], _('adding %s\n'))
+    remove = ([], _('removing %s\n'))
+    forget = ([], _('forgetting %s\n'))
+    undelete = ([], _('undeleting %s\n'))
+    update = {}
+
+    disptable = (
+        # dispatch table:
+        #   file state
+        #   action if in target manifest
+        #   action if not in target manifest
+        #   make backup if in target manifest
+        #   make backup if not in target manifest
+        (modified, revert, remove, True, True),
+        (added, revert, forget, True, True),
+        (removed, undelete, None, False, False),
+        (deleted, revert, remove, False, False),
+        (unknown, add, None, True, False),
+        )
+
+    for abs, rel, exact in entries:
+        def handle(xlist, dobackup):
+            xlist[0].append(abs)
+            if dobackup and not opts['no_backup']:
+                backup(rel, exact)
+            if ui.verbose or not exact:
+                ui.status(xlist[1] % rel)
+        for table, hitlist, misslist, backuphit, backupmiss in disptable:
+            if abs not in table: continue
+            # file has changed in dirstate
+            if abs in mf:
+                handle(hitlist, backuphit)
+            elif misslist is not None:
+                handle(misslist, backupmiss)
+            else:
+                if exact: ui.warn(_('file not managed: %s\n' % rel))
+            break
+        else:
+            # file has not changed in dirstate
+            if node == parent:
+                if exact: ui.warn(_('no changes needed to %s\n' % rel))
+                continue
+            if abs not in mf:
+                remove[0].append(abs)
+        update[abs] = True
+
+    repo.dirstate.forget(forget[0])
+    r = repo.update(node, False, True, update.has_key, False, wlock=wlock)
+    repo.dirstate.update(add[0], 'a')
+    repo.dirstate.update(undelete[0], 'n')
+    repo.dirstate.update(remove[0], 'r')
+    return r
 
 def root(ui, repo):
     """print the root (top) of the current working dir
@@ -2929,8 +3011,10 @@
     "^revert":
         (revert,
          [('r', 'rev', '', _('revision to revert to')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+          ('', 'backup-name', '', _('save backup with formatted name')),
+          ('', 'no-backup', None, _('do not save backup copies of files')),
+          ('I', 'include', [], _('include names matching given patterns')),
+          ('X', 'exclude', [], _('exclude names matching given patterns'))],
          _('hg revert [-r REV] [NAME]...')),
     "root": (root, [], _('hg root')),
     "^serve":
--- a/mercurial/localrepo.py	Fri Mar 31 03:25:35 2006 -0600
+++ b/mercurial/localrepo.py	Fri Mar 31 10:37:25 2006 -0800
@@ -483,7 +483,7 @@
         self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
         return n
 
-    def walk(self, node=None, files=[], match=util.always):
+    def walk(self, node=None, files=[], match=util.always, badmatch=None):
         if node:
             fdict = dict.fromkeys(files)
             for fn in self.manifest.read(self.changelog.read(node)[0]):
@@ -491,8 +491,12 @@
                 if match(fn):
                     yield 'm', fn
             for fn in fdict:
-                self.ui.warn(_('%s: No such file in rev %s\n') % (
-                    util.pathto(self.getcwd(), fn), short(node)))
+                if badmatch and badmatch(fn):
+                    if match(fn):
+                        yield 'b', fn
+                else:
+                    self.ui.warn(_('%s: No such file in rev %s\n') % (
+                        util.pathto(self.getcwd(), fn), short(node)))
         else:
             for src, fn in self.dirstate.walk(files, match):
                 yield src, fn
--- a/tests/test-confused-revert.out	Fri Mar 31 03:25:35 2006 -0600
+++ b/tests/test-confused-revert.out	Fri Mar 31 10:37:25 2006 -0800
@@ -2,16 +2,24 @@
 A b
 R a
 reverting...
+saving current version of b as b.bak
+forgetting b
+undeleting a
 %%% should show b unknown and a back to normal
 ? b
+? b.bak
 merging a
 %%% should show foo-b
 foo-b
 %%% should show a removed and b added
 A b
 R a
+? b.bak
 reverting...
+forgetting b
+undeleting a
 %%% should show b unknown and a marked modified (merged)
 ? b
+? b.bak
 %%% should show foo-b
 foo-b
--- a/tests/test-merge-revert.out	Fri Mar 31 03:25:35 2006 -0600
+++ b/tests/test-merge-revert.out	Fri Mar 31 10:37:25 2006 -0800
@@ -3,10 +3,18 @@
 016807e6fdaf tip
 eb43f19ff115
 eb43f19ff115+
+saving current version of file1 as file1.bak
+reverting file1
+? file1.bak
 eb43f19ff115
+? file1.bak
 016807e6fdaf tip
 merging file1
+? file1.bak
 016807e6fdaf tip
+? file1.bak
 016807e6fdaf tip
+? file1.bak
 016807e6fdaf tip
+? file1.bak
 016807e6fdaf tip
--- a/tests/test-merge-revert2	Fri Mar 31 03:25:35 2006 -0600
+++ b/tests/test-merge-revert2	Fri Mar 31 10:37:25 2006 -0800
@@ -16,7 +16,7 @@
 hg id
 echo "changed file1" >> file1
 hg id
-hg revert
+hg revert --no-backup
 hg diff
 hg status
 hg id
@@ -31,11 +31,11 @@
               -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" -e "s/\(>>>>>>>\) .*/\1/"
 hg status
 hg id
-hg revert
+hg revert --no-backup
 hg diff
 hg status
 hg id
-hg revert -r tip
+hg revert -r tip --no-backup
 hg diff
 hg status
 hg id
--- a/tests/test-merge-revert2.out	Fri Mar 31 03:25:35 2006 -0600
+++ b/tests/test-merge-revert2.out	Fri Mar 31 10:37:25 2006 -0800
@@ -3,6 +3,7 @@
 f248da0d4c3e tip
 9eca13a34789
 9eca13a34789+
+reverting file1
 9eca13a34789
 f248da0d4c3e tip
 merge: warning: conflicts during merge
@@ -21,6 +22,7 @@
 +>>>>>>>
 M file1
 f248da0d4c3e+ tip
+reverting file1
 f248da0d4c3e tip
 f248da0d4c3e tip
 f248da0d4c3e tip
--- a/tests/test-revert	Fri Mar 31 03:25:35 2006 -0600
+++ b/tests/test-revert	Fri Mar 31 10:37:25 2006 -0800
@@ -3,8 +3,9 @@
 hg init
 echo 123 > a
 echo 123 > c
-hg add a c
-hg commit -m "first" -d "1000000 0" a c
+echo 123 > e
+hg add a c e
+hg commit -m "first" -d "1000000 0" a c e
 echo 123 > b
 echo %% should show b unknown
 hg status
@@ -18,15 +19,25 @@
 echo %% should show a removed, b added and c modified
 hg status
 hg revert a
-echo %% should show b added and c modified
+echo %% should show b added, copy saved, and c modified
 hg status
 hg revert b
-echo %% should show b unknown and c modified
+echo %% should show b unknown, b.bak unknown, and c modified
+hg status
+hg revert --no-backup c
+echo %% should show unknown: b b.bak
 hg status
-hg revert c
-echo %% should show b unknown
-hg status
-echo %% should show a b and c
+echo %% should show a b b.bak c e
 ls
+echo %% should save backup to e.0
+echo z > e
+hg revert --backup='%p.%R'
+echo %% should say no changes needed
+hg revert a
+echo %% should say file not managed
+echo q > q
+hg revert q
+echo %% should say file not found
+hg revert notfound
 
 true
--- a/tests/test-revert-unknown.out	Fri Mar 31 03:25:35 2006 -0600
+++ b/tests/test-revert-unknown.out	Fri Mar 31 10:37:25 2006 -0800
@@ -1,7 +1,7 @@
 %% Should show unknown
 ? unknown
 %% Should show unknown and b removed
-! b
+R b
 ? unknown
 %% Should show a and unknown
 a
--- a/tests/test-revert.out	Fri Mar 31 03:25:35 2006 -0600
+++ b/tests/test-revert.out	Fri Mar 31 10:37:25 2006 -0800
@@ -10,15 +10,29 @@
 M c
 A b
 R a
-%% should show b added and c modified
+%% should show b added, copy saved, and c modified
 M c
 A b
-%% should show b unknown and c modified
+saving current version of b as b.bak
+%% should show b unknown, b.bak unknown, and c modified
 M c
 ? b
-%% should show b unknown
+? b.bak
+%% should show unknown: b b.bak
 ? b
-%% should show a b and c
+? b.bak
+%% should show a b b.bak c e
 a
 b
+b.bak
 c
+e
+%% should save backup to e.0
+saving current version of e as e.0
+reverting e
+%% should say no changes needed
+no changes needed to a
+%% should say file not managed
+file not managed: q
+%% should say file not found
+notfound: No such file or directory
--- a/tests/test-tag.out	Fri Mar 31 03:25:35 2006 -0600
+++ b/tests/test-tag.out	Fri Mar 31 10:37:25 2006 -0800
@@ -21,6 +21,7 @@
 use of 'hg tag NAME [REV]' is deprecated, please use 'hg tag [-r REV] NAME' instead
 abort: use only one form to specify the revision
 failed
+saving current version of .hgtags as .hgtags.bak
 use of 'hg tag NAME [REV]' is deprecated, please use 'hg tag [-r REV] NAME' instead
 0acdaf8983679e0aac16e811534eb49d7ee1f2b4 bleah
 0acdaf8983679e0aac16e811534eb49d7ee1f2b4 bleah0