changeset 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 1e82f2337498
children 5c34b98ad6b1
files mercurial/commands.py tests/test-backout tests/test-backout.out tests/test-help.out
diffstat 4 files changed, 127 insertions(+), 3 deletions(-) [+]
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,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-backout	Sat Apr 29 20:56:46 2006 -0700
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+echo '# basic operation'
+hg init basic
+cd basic
+echo a > a
+hg commit -d '0 0' -A -m a
+echo b >> a
+hg commit -d '1 0' -m b
+
+hg backout -d '2 0' tip
+cat a
+
+echo '# file that was removed is recreated'
+cd ..
+hg init remove
+cd remove
+
+echo content > a
+hg commit -d '0 0' -A -m a
+
+hg rm a
+hg commit -d '1 0' -m b
+
+hg backout -d '2 0' --merge tip
+cat a
+
+echo '# backout of backout is as if nothing happened'
+
+hg backout -d '3 0' --merge tip
+cat a
+
+echo '# backout with merge'
+cd ..
+hg init merge
+cd merge
+
+echo line 1 > a
+hg commit -d '0 0' -A -m a
+
+echo line 2 >> a
+hg commit -d '1 0' -m b
+
+echo line 3 >> a
+hg commit -d '2 0' -m c
+
+hg backout --merge -d '3 0' 1
+hg commit -d '4 0' -m d
+cat a
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-backout.out	Sat Apr 29 20:56:46 2006 -0700
@@ -0,0 +1,19 @@
+# basic operation
+adding a
+changeset 2:b38a34ddfd9f backs out changeset 1:a820f4f40a57
+a
+# file that was removed is recreated
+adding a
+adding a
+changeset 2:44cd84c7349a backs out changeset 1:76862dcce372
+content
+# backout of backout is as if nothing happened
+removing a
+changeset 3:0dd8a0ed5e99 backs out changeset 2:44cd84c7349a
+cat: a: No such file or directory
+# backout with merge
+adding a
+changeset 3:6c77ecc28460 backs out changeset 1:314f55b1bf23
+merging with changeset 2:b66ea5b77abb
+merging a
+line 1
--- a/tests/test-help.out	Sat Apr 29 20:39:28 2006 -0700
+++ b/tests/test-help.out	Sat Apr 29 20:56:46 2006 -0700
@@ -42,6 +42,7 @@
  addremove   add all new files, delete all missing files
  annotate    show changeset information per file line
  archive     create unversioned archive of a repository revision
+ backout     reverse effect of earlier changeset
  bundle      create a changegroup file
  cat         output the latest or given revisions of files
  clone       make a copy of an existing repository
@@ -84,6 +85,7 @@
  addremove   add all new files, delete all missing files
  annotate    show changeset information per file line
  archive     create unversioned archive of a repository revision
+ backout     reverse effect of earlier changeset
  bundle      create a changegroup file
  cat         output the latest or given revisions of files
  clone       make a copy of an existing repository