diff -r fd77b7ee4aac mercurial/commands.py
--- a/mercurial/commands.py	Fri Apr 21 16:09:43 2006 -0700
+++ b/mercurial/commands.py	Wed Apr 26 09:21:04 2006 -0700
@@ -18,6 +18,11 @@ class UnknownCommand(Exception):
     """Exception raised if command is not in the command table."""
 class AmbiguousCommand(Exception):
     """Exception raised if command shortcut matches more than one command."""
+
+def bail_if_changed(repo):
+    modified, added, removed, deleted, unknown = repo.changes()
+    if modified or added or removed or deleted:
+        raise util.Abort(_("outstanding uncommitted changes"))
 
 def filterfiles(filters, files):
     l = [x for x in files if x in filters]
@@ -929,6 +934,40 @@ def archive(ui, repo, dest, **opts):
     _, matchfn, _ = matchpats(repo, [], opts)
     archival.archive(repo, dest, node, opts.get('type') or 'files',
                     not opts['no_decode'], matchfn, prefix)
+
+def backout(ui, repo, rev, **opts):
+    '''reverse effect of earlier changeset
+
+    Commit the backed out changes as a new changeset.  With the -m
+    option, merge the backed out changeset with the previous parent of
+    the working directory.'''
+
+    bail_if_changed(repo)
+    op1, op2 = repo.dirstate.parents()
+    if op2 != nullid:
+        raise util.Abort(_('outstanding uncommitted merge'))
+    node = repo.lookup(rev)
+    parent, p2 = repo.changelog.parents(node)
+    if parent == nullid:
+        raise util.Abort(_('cannot back out a change with no parents'))
+    if p2 != nullid:
+        raise util.Abort(_('cannot back out a merge'))
+    repo.update(node, force=True)
+    revert_opts = opts.copy()
+    revert_opts['rev'] = hex(parent)
+    revert(ui, repo, **revert_opts)
+    commit_opts = opts.copy()
+    commit_opts['addremove'] = False
+    if not commit_opts['message']:
+        commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
+    commit(ui, repo, **commit_opts)
+    def nice(node):
+        return '%d:%s' % (repo.changelog.rev(node), short(node))
+    ui.status(_('changeset %s backs out changeset %s\n') %
+              (nice(repo.changelog.tip()), nice(node)))
+    if opts['merge'] and op1 != parent:
+        ui.status(_('merging with changeset %s\n') % nice(op1))
+        update(ui, repo, hex(op1), **opts)
 
 def bundle(ui, repo, fname, dest="default-push", **opts):
     """create a changegroup file
@@ -1792,9 +1831,7 @@ def import_(ui, repo, patch1, *patches, 
     patches = (patch1,) + patches
 
     if not opts['force']:
-        modified, added, removed, deleted, unknown = repo.changes()
-        if modified or added or removed or deleted:
-            raise util.Abort(_("outstanding uncommitted changes"))
+        bail_if_changed(repo)
 
     d = opts["base"]
     strip = opts["strip"]
@@ -2888,6 +2925,17 @@ table = {
           ('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
          _('hg archive [OPTION]... DEST')),
+    'backout':
+        (backout,
+         [('', 'message', '', _('use <text> as commit message')),
+          ('', 'merge', None, _('merge backout with old dirstate parent')),
+          ('l', 'logfile', '', _('read commit message from <file>')),
+          ('d', 'date', '', _('record datecode as commit date')),
+          ('u', 'user', '', _('record user as committer')),
+          ('I', 'include', [], _('include names matching the given patterns')),
+          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         _('hg backout [OPTION]... [FILE]...')),
+
     "bundle":
         (bundle,
          [('f', 'force', None,
