Mercurial > public > mercurial-scm > hg
comparison mercurial/localrepo.py @ 15183:59e8bc22506e
rollback: avoid unsafe rollback when not at tip (issue2998)
You can get into trouble if you commit, update back to an older
changeset, and then rollback. The update removes your valuable changes
from the working dir, then rollback removes them history. Oops: you've
just irretrievably lost data running nothing but core Mercurial
commands. (More subtly: rollback from a shared clone that was already
at an older changeset -- no update required, just rollback from the
wrong directory.)
The fix assumes that only "commit" transactions have irreplaceable
data, and allows rolling back non-commit transactions as always. But
when rolling back a commit, check that the working dir is checked out
to tip, i.e. the changeset we're about to destroy. If not, abort. You
can get back the old (dangerous) behaviour with --force.
author | Greg Ward <greg@gerg.ca> |
---|---|
date | Fri, 30 Sep 2011 21:58:54 -0400 |
parents | 7c26ce9edbd2 |
children | 0292f88d3b86 |
comparison
equal
deleted
inserted
replaced
15182:de496752d936 | 15183:59e8bc22506e |
---|---|
752 self.ui.warn(_("no interrupted transaction available\n")) | 752 self.ui.warn(_("no interrupted transaction available\n")) |
753 return False | 753 return False |
754 finally: | 754 finally: |
755 lock.release() | 755 lock.release() |
756 | 756 |
757 def rollback(self, dryrun=False): | 757 def rollback(self, dryrun=False, force=False): |
758 wlock = lock = None | 758 wlock = lock = None |
759 try: | 759 try: |
760 wlock = self.wlock() | 760 wlock = self.wlock() |
761 lock = self.lock() | 761 lock = self.lock() |
762 if os.path.exists(self.sjoin("undo")): | 762 if os.path.exists(self.sjoin("undo")): |
763 return self._rollback(dryrun) | 763 return self._rollback(dryrun, force) |
764 else: | 764 else: |
765 self.ui.warn(_("no rollback information available\n")) | 765 self.ui.warn(_("no rollback information available\n")) |
766 return 1 | 766 return 1 |
767 finally: | 767 finally: |
768 release(lock, wlock) | 768 release(lock, wlock) |
769 | 769 |
770 def _rollback(self, dryrun): | 770 def _rollback(self, dryrun, force): |
771 ui = self.ui | 771 ui = self.ui |
772 try: | 772 try: |
773 args = self.opener.read('undo.desc').splitlines() | 773 args = self.opener.read('undo.desc').splitlines() |
774 (oldlen, desc, detail) = (int(args[0]), args[1], None) | 774 (oldlen, desc, detail) = (int(args[0]), args[1], None) |
775 if len(args) >= 3: | 775 if len(args) >= 3: |
784 msg = (_('repository tip rolled back to revision %s' | 784 msg = (_('repository tip rolled back to revision %s' |
785 ' (undo %s)\n') | 785 ' (undo %s)\n') |
786 % (oldtip, desc)) | 786 % (oldtip, desc)) |
787 except IOError: | 787 except IOError: |
788 msg = _('rolling back unknown transaction\n') | 788 msg = _('rolling back unknown transaction\n') |
789 desc = None | |
790 | |
791 if not force and self['.'] != self['tip'] and desc == 'commit': | |
792 raise util.Abort( | |
793 _('rollback of last commit while not checked out ' | |
794 'may lose data (use -f to force)')) | |
795 | |
789 ui.status(msg) | 796 ui.status(msg) |
790 if dryrun: | 797 if dryrun: |
791 return 0 | 798 return 0 |
792 | 799 |
793 parents = self.dirstate.parents() | 800 parents = self.dirstate.parents() |