diff --git a/hgext/keyword.py b/hgext/keyword.py
index de496752d9364cbc55e14900ccffa851c20d8b41_aGdleHQva2V5d29yZC5weQ==..59e8bc22506ec40f74ce192b6f0c36fb9e42d2c6_aGdleHQva2V5d29yZC5weQ== 100644
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -590,8 +590,8 @@
                 kwt.restrict = restrict
             return n
 
-        def rollback(self, dryrun=False):
+        def rollback(self, dryrun=False, force=False):
             wlock = self.wlock()
             try:
                 if not dryrun:
                     changed = self['.'].files()
@@ -594,8 +594,8 @@
             wlock = self.wlock()
             try:
                 if not dryrun:
                     changed = self['.'].files()
-                ret = super(kwrepo, self).rollback(dryrun)
+                ret = super(kwrepo, self).rollback(dryrun, force)
                 if not dryrun:
                     ctx = self['.']
                     modified, added = _preselect(self[None].status(), changed)
diff --git a/mercurial/commands.py b/mercurial/commands.py
index de496752d9364cbc55e14900ccffa851c20d8b41_bWVyY3VyaWFsL2NvbW1hbmRzLnB5..59e8bc22506ec40f74ce192b6f0c36fb9e42d2c6_bWVyY3VyaWFsL2NvbW1hbmRzLnB5 100644
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4612,7 +4612,8 @@
     finally:
         wlock.release()
 
-@command('rollback', dryrunopts)
+@command('rollback', dryrunopts +
+         [('f', 'force', False, _('ignore safety measures'))])
 def rollback(ui, repo, **opts):
     """roll back the last transaction (dangerous)
 
@@ -4633,6 +4634,12 @@
     - push (with this repository as the destination)
     - unbundle
 
+    It's possible to lose data with rollback: commit, update back to
+    an older changeset, and then rollback. The update removes the
+    changes you committed from the working directory, and rollback
+    removes them from history. To avoid data loss, you must pass
+    --force in this case.
+
     This command is not intended for use on public repositories. Once
     changes are visible for pull by other users, rolling a transaction
     back locally is ineffective (someone else may already have pulled
@@ -4642,7 +4649,8 @@
 
     Returns 0 on success, 1 if no rollback data is available.
     """
-    return repo.rollback(opts.get('dry_run'))
+    return repo.rollback(dryrun=opts.get('dry_run'),
+                         force=opts.get('force'))
 
 @command('root', [])
 def root(ui, repo):
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
index de496752d9364cbc55e14900ccffa851c20d8b41_bWVyY3VyaWFsL2xvY2FscmVwby5weQ==..59e8bc22506ec40f74ce192b6f0c36fb9e42d2c6_bWVyY3VyaWFsL2xvY2FscmVwby5weQ== 100644
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -754,9 +754,9 @@
         finally:
             lock.release()
 
-    def rollback(self, dryrun=False):
+    def rollback(self, dryrun=False, force=False):
         wlock = lock = None
         try:
             wlock = self.wlock()
             lock = self.lock()
             if os.path.exists(self.sjoin("undo")):
@@ -758,12 +758,12 @@
         wlock = lock = None
         try:
             wlock = self.wlock()
             lock = self.lock()
             if os.path.exists(self.sjoin("undo")):
-                return self._rollback(dryrun)
+                return self._rollback(dryrun, force)
             else:
                 self.ui.warn(_("no rollback information available\n"))
                 return 1
         finally:
             release(lock, wlock)
 
@@ -764,10 +764,10 @@
             else:
                 self.ui.warn(_("no rollback information available\n"))
                 return 1
         finally:
             release(lock, wlock)
 
-    def _rollback(self, dryrun):
+    def _rollback(self, dryrun, force):
         ui = self.ui
         try:
             args = self.opener.read('undo.desc').splitlines()
@@ -786,6 +786,13 @@
                        % (oldtip, desc))
         except IOError:
             msg = _('rolling back unknown transaction\n')
+            desc = None
+
+        if not force and self['.'] != self['tip'] and desc == 'commit':
+            raise util.Abort(
+                _('rollback of last commit while not checked out '
+                  'may lose data (use -f to force)'))
+
         ui.status(msg)
         if dryrun:
             return 0
diff --git a/tests/test-debugcomplete.t b/tests/test-debugcomplete.t
index de496752d9364cbc55e14900ccffa851c20d8b41_dGVzdHMvdGVzdC1kZWJ1Z2NvbXBsZXRlLnQ=..59e8bc22506ec40f74ce192b6f0c36fb9e42d2c6_dGVzdHMvdGVzdC1kZWJ1Z2NvbXBsZXRlLnQ= 100644
--- a/tests/test-debugcomplete.t
+++ b/tests/test-debugcomplete.t
@@ -257,7 +257,7 @@
   rename: after, force, include, exclude, dry-run
   resolve: all, list, mark, unmark, no-status, tool, include, exclude
   revert: all, date, rev, no-backup, include, exclude, dry-run
-  rollback: dry-run
+  rollback: dry-run, force
   root: 
   showconfig: untrusted
   tag: force, local, rev, remove, edit, message, date, user
diff --git a/tests/test-import-bypass.t b/tests/test-import-bypass.t
index de496752d9364cbc55e14900ccffa851c20d8b41_dGVzdHMvdGVzdC1pbXBvcnQtYnlwYXNzLnQ=..59e8bc22506ec40f74ce192b6f0c36fb9e42d2c6_dGVzdHMvdGVzdC1pbXBvcnQtYnlwYXNzLnQ= 100644
--- a/tests/test-import-bypass.t
+++ b/tests/test-import-bypass.t
@@ -60,7 +60,7 @@
   |/
   @  0:07f494440405 test 0 0 - default - adda
   
-  $ hg rollback
+  $ hg rollback -f
   repository tip rolled back to revision 1 (undo commit)
 
 Test --import-branch
@@ -72,7 +72,7 @@
   |
   @  0:07f494440405 test 0 0 - default - adda
   
-  $ hg rollback
+  $ hg rollback -f
   repository tip rolled back to revision 1 (undo commit)
 
 Test --strip
@@ -94,7 +94,7 @@
   > +a
   > EOF
   applying patch from stdin
-  $ hg rollback
+  $ hg rollback -f
   repository tip rolled back to revision 1 (undo commit)
 
 Test unsupported combinations
diff --git a/tests/test-rollback.t b/tests/test-rollback.t
index de496752d9364cbc55e14900ccffa851c20d8b41_dGVzdHMvdGVzdC1yb2xsYmFjay50..59e8bc22506ec40f74ce192b6f0c36fb9e42d2c6_dGVzdHMvdGVzdC1yb2xsYmFjay50 100644
--- a/tests/test-rollback.t
+++ b/tests/test-rollback.t
@@ -83,7 +83,7 @@
   $ hg bookmark bar
   $ cat .hg/undo.branch ; echo
   test
-  $ hg rollback
+  $ hg rollback -f
   repository tip rolled back to revision 1 (undo commit)
   $ hg id -n
   0
@@ -146,3 +146,37 @@
   working directory now based on revision 0
   $ hg id default
   791dd2169706
+
+update to older changeset and then refuse rollback, because
+that would lose data (issue2998)
+  $ cd ../t
+  $ hg -q update
+  $ rm `hg status -un`
+  $ template='{rev}:{node|short}  [{branch}]  {desc|firstline}\n'
+  $ echo 'valuable new file' > b
+  $ echo 'valuable modification' >> a
+  $ hg commit -A -m'a valuable change'
+  adding b
+  $ hg update 0
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg rollback
+  abort: rollback of last commit while not checked out may lose data (use -f to force)
+  [255]
+  $ hg tip -q
+  2:4d9cd3795eea
+  $ hg rollback -f
+  repository tip rolled back to revision 1 (undo commit)
+  $ hg status
+  $ hg log --removed b   # yep, it's gone
+
+same again, but emulate an old client that doesn't write undo.desc
+  $ hg -q update
+  $ echo 'valuable modification redux' >> a
+  $ hg commit -m'a valuable change redux'
+  $ rm .hg/undo.desc
+  $ hg update 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg rollback
+  rolling back unknown transaction
+  $ cat a
+  a