diff mercurial/commands.py @ 2158:ec96c4518236

add backout command. command undoes effect of an earlier commit, commits new changeset as result.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Sat, 29 Apr 2006 20:56:46 -0700
parents 635653cd73ab
children 5c34b98ad6b1
line wrap: on
line diff
--- a/mercurial/commands.py	Sat Apr 29 20:39:28 2006 -0700
+++ b/mercurial/commands.py	Sat Apr 29 20:56:46 2006 -0700
@@ -19,6 +19,11 @@
 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]
 
@@ -930,6 +935,44 @@
     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.
+
+    If you back out a changeset other than the tip, a new head is
+    created.  The --merge option remembers the parent of the working
+    directory before starting the backout, then merges the new head
+    with it afterwards, to save you from doing this by hand.  The
+    result of this merge is not committed, as for a normal merge.'''
+
+    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 != node:
+        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
 
@@ -1797,9 +1840,7 @@
     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"]
@@ -2899,6 +2940,17 @@
           ('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 with old dirstate parent after backout')),
+          ('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,