# HG changeset patch # User Vadim Gelfer # Date 1146369406 25200 # Node ID ec96c451823664b9d1b9cb3eb91aa7387cbfe83b # Parent 1e82f2337498c767316fe7c819497749c2382e8b add backout command. command undoes effect of an earlier commit, commits new changeset as result. diff -r 1e82f2337498 -r ec96c4518236 mercurial/commands.py --- 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 as commit message')), + ('', 'merge', None, _('merge with old dirstate parent after backout')), + ('l', 'logfile', '', _('read commit message from ')), + ('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, diff -r 1e82f2337498 -r ec96c4518236 tests/test-backout --- /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 diff -r 1e82f2337498 -r ec96c4518236 tests/test-backout.out --- /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 diff -r 1e82f2337498 -r ec96c4518236 tests/test-help.out --- 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