comparison mercurial/commands.py @ 16304:a740fa28d718

revert: move bulk of revert command from commands to cmdutil This revision has no functionality change. The code on the original commands.revert() function has been split. The first part of the original code, which checks that the command inputs are correct remains in commands.revert(). The rest of the function, which performs the actual revert operation has been moved into cmdutil.revert(). The purpose of this change is to make it easier to perform a revert operation, from other parts of the code. This may be used to implement reverting of subrepos.
author Angel Ezquerra <angel.ezquerra@gmail.com>
date Wed, 28 Mar 2012 11:42:17 +0200
parents 5d75eb8568d1
children 329887a7074c
comparison
equal deleted inserted replaced
16295:ba42eb722bb3 16304:a740fa28d718
4777 # revert after merge is a trap for new users (issue2915) 4777 # revert after merge is a trap for new users (issue2915)
4778 raise util.Abort(_('uncommitted merge with no revision specified'), 4778 raise util.Abort(_('uncommitted merge with no revision specified'),
4779 hint=_('use "hg update" or see "hg help revert"')) 4779 hint=_('use "hg update" or see "hg help revert"'))
4780 4780
4781 ctx = scmutil.revsingle(repo, opts.get('rev')) 4781 ctx = scmutil.revsingle(repo, opts.get('rev'))
4782 node = ctx.node()
4783 4782
4784 if not pats and not opts.get('all'): 4783 if not pats and not opts.get('all'):
4785 msg = _("no files or directories specified") 4784 msg = _("no files or directories specified")
4786 if p2 != nullid: 4785 if p2 != nullid:
4787 hint = _("uncommitted merge, use --all to discard all changes," 4786 hint = _("uncommitted merge, use --all to discard all changes,"
4788 " or 'hg update -C .' to abort the merge") 4787 " or 'hg update -C .' to abort the merge")
4789 raise util.Abort(msg, hint=hint) 4788 raise util.Abort(msg, hint=hint)
4790 dirty = util.any(repo.status()) 4789 dirty = util.any(repo.status())
4790 node = ctx.node()
4791 if node != parent: 4791 if node != parent:
4792 if dirty: 4792 if dirty:
4793 hint = _("uncommitted changes, use --all to discard all" 4793 hint = _("uncommitted changes, use --all to discard all"
4794 " changes, or 'hg update %s' to update") % ctx.rev() 4794 " changes, or 'hg update %s' to update") % ctx.rev()
4795 else: 4795 else:
4799 hint = _("uncommitted changes, use --all to discard all changes") 4799 hint = _("uncommitted changes, use --all to discard all changes")
4800 else: 4800 else:
4801 hint = _("use --all to revert all files") 4801 hint = _("use --all to revert all files")
4802 raise util.Abort(msg, hint=hint) 4802 raise util.Abort(msg, hint=hint)
4803 4803
4804 mf = ctx.manifest() 4804 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4805 if node == parent:
4806 pmf = mf
4807 else:
4808 pmf = None
4809
4810 # need all matching names in dirstate and manifest of target rev,
4811 # so have to walk both. do not print errors if files exist in one
4812 # but not other.
4813
4814 names = {}
4815
4816 wlock = repo.wlock()
4817 try:
4818 # walk dirstate.
4819
4820 m = scmutil.match(repo[None], pats, opts)
4821 m.bad = lambda x, y: False
4822 for abs in repo.walk(m):
4823 names[abs] = m.rel(abs), m.exact(abs)
4824
4825 # walk target manifest.
4826
4827 def badfn(path, msg):
4828 if path in names:
4829 return
4830 if path in repo[node].substate:
4831 ui.warn("%s: %s\n" % (m.rel(path),
4832 'reverting subrepos is unsupported'))
4833 return
4834 path_ = path + '/'
4835 for f in names:
4836 if f.startswith(path_):
4837 return
4838 ui.warn("%s: %s\n" % (m.rel(path), msg))
4839
4840 m = scmutil.match(repo[node], pats, opts)
4841 m.bad = badfn
4842 for abs in repo[node].walk(m):
4843 if abs not in names:
4844 names[abs] = m.rel(abs), m.exact(abs)
4845
4846 m = scmutil.matchfiles(repo, names)
4847 changes = repo.status(match=m)[:4]
4848 modified, added, removed, deleted = map(set, changes)
4849
4850 # if f is a rename, also revert the source
4851 cwd = repo.getcwd()
4852 for f in added:
4853 src = repo.dirstate.copied(f)
4854 if src and src not in names and repo.dirstate[src] == 'r':
4855 removed.add(src)
4856 names[src] = (repo.pathto(src, cwd), True)
4857
4858 def removeforget(abs):
4859 if repo.dirstate[abs] == 'a':
4860 return _('forgetting %s\n')
4861 return _('removing %s\n')
4862
4863 revert = ([], _('reverting %s\n'))
4864 add = ([], _('adding %s\n'))
4865 remove = ([], removeforget)
4866 undelete = ([], _('undeleting %s\n'))
4867
4868 disptable = (
4869 # dispatch table:
4870 # file state
4871 # action if in target manifest
4872 # action if not in target manifest
4873 # make backup if in target manifest
4874 # make backup if not in target manifest
4875 (modified, revert, remove, True, True),
4876 (added, revert, remove, True, False),
4877 (removed, undelete, None, False, False),
4878 (deleted, revert, remove, False, False),
4879 )
4880
4881 for abs, (rel, exact) in sorted(names.items()):
4882 mfentry = mf.get(abs)
4883 target = repo.wjoin(abs)
4884 def handle(xlist, dobackup):
4885 xlist[0].append(abs)
4886 if (dobackup and not opts.get('no_backup') and
4887 os.path.lexists(target)):
4888 bakname = "%s.orig" % rel
4889 ui.note(_('saving current version of %s as %s\n') %
4890 (rel, bakname))
4891 if not opts.get('dry_run'):
4892 util.rename(target, bakname)
4893 if ui.verbose or not exact:
4894 msg = xlist[1]
4895 if not isinstance(msg, basestring):
4896 msg = msg(abs)
4897 ui.status(msg % rel)
4898 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4899 if abs not in table:
4900 continue
4901 # file has changed in dirstate
4902 if mfentry:
4903 handle(hitlist, backuphit)
4904 elif misslist is not None:
4905 handle(misslist, backupmiss)
4906 break
4907 else:
4908 if abs not in repo.dirstate:
4909 if mfentry:
4910 handle(add, True)
4911 elif exact:
4912 ui.warn(_('file not managed: %s\n') % rel)
4913 continue
4914 # file has not changed in dirstate
4915 if node == parent:
4916 if exact:
4917 ui.warn(_('no changes needed to %s\n') % rel)
4918 continue
4919 if pmf is None:
4920 # only need parent manifest in this unlikely case,
4921 # so do not read by default
4922 pmf = repo[parent].manifest()
4923 if abs in pmf and mfentry:
4924 # if version of file is same in parent and target
4925 # manifests, do nothing
4926 if (pmf[abs] != mfentry or
4927 pmf.flags(abs) != mf.flags(abs)):
4928 handle(revert, False)
4929 else:
4930 handle(remove, False)
4931
4932 if not opts.get('dry_run'):
4933 def checkout(f):
4934 fc = ctx[f]
4935 repo.wwrite(f, fc.data(), fc.flags())
4936
4937 audit_path = scmutil.pathauditor(repo.root)
4938 for f in remove[0]:
4939 if repo.dirstate[f] == 'a':
4940 repo.dirstate.drop(f)
4941 continue
4942 audit_path(f)
4943 try:
4944 util.unlinkpath(repo.wjoin(f))
4945 except OSError:
4946 pass
4947 repo.dirstate.remove(f)
4948
4949 normal = None
4950 if node == parent:
4951 # We're reverting to our parent. If possible, we'd like status
4952 # to report the file as clean. We have to use normallookup for
4953 # merges to avoid losing information about merged/dirty files.
4954 if p2 != nullid:
4955 normal = repo.dirstate.normallookup
4956 else:
4957 normal = repo.dirstate.normal
4958 for f in revert[0]:
4959 checkout(f)
4960 if normal:
4961 normal(f)
4962
4963 for f in add[0]:
4964 checkout(f)
4965 repo.dirstate.add(f)
4966
4967 normal = repo.dirstate.normallookup
4968 if node == parent and p2 == nullid:
4969 normal = repo.dirstate.normal
4970 for f in undelete[0]:
4971 checkout(f)
4972 normal(f)
4973
4974 finally:
4975 wlock.release()
4976 4805
4977 @command('rollback', dryrunopts + 4806 @command('rollback', dryrunopts +
4978 [('f', 'force', False, _('ignore safety measures'))]) 4807 [('f', 'force', False, _('ignore safety measures'))])
4979 def rollback(ui, repo, **opts): 4808 def rollback(ui, repo, **opts):
4980 """roll back the last transaction (dangerous) 4809 """roll back the last transaction (dangerous)