# HG changeset patch # User Vadim Gelfer # Date 1143830245 28800 # Node ID d436b21b20dc0d14220fcad07b154994f5e55e27 # Parent 1f1fc418a96c9fdd10a3d87aa2b3a3f934d0ef8f 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) diff -r 1f1fc418a96c -r d436b21b20dc mercurial/commands.py --- 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": diff -r 1f1fc418a96c -r d436b21b20dc mercurial/localrepo.py --- 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 diff -r 1f1fc418a96c -r d436b21b20dc tests/test-confused-revert.out --- 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 diff -r 1f1fc418a96c -r d436b21b20dc tests/test-merge-revert.out --- 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 diff -r 1f1fc418a96c -r d436b21b20dc tests/test-merge-revert2 --- 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 diff -r 1f1fc418a96c -r d436b21b20dc tests/test-merge-revert2.out --- 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 diff -r 1f1fc418a96c -r d436b21b20dc tests/test-revert --- 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 diff -r 1f1fc418a96c -r d436b21b20dc tests/test-revert-unknown.out --- 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 diff -r 1f1fc418a96c -r d436b21b20dc tests/test-revert.out --- 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 diff -r 1f1fc418a96c -r d436b21b20dc tests/test-tag.out --- 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