views:

942

answers:

3

Related to http://stackoverflow.com/questions/1078881/mercurial-merging-one-file-between-branches-in-one-repo , I'm trying to perform a backout operation on a single file, even though that file was one of many participants in the revision being backed out.

HG being the changeset-oriented tool that it is, it doesn't want to operate on files.

Closest I could find was to use hg export to create a diff, hand-edit the diff, and then hg import to patch the file in reverse order.

..but then I hit this annoying situation where http://hgbook.red-bean.com/read/finding-and-fixing-mistakes.html claims that there is a --reverse option to hg patch when there is not.

So the closest thing I can think of is to generate a hand-edited patch as above, and then using vanilla patch -R to apply a reverse patch.

The hg backout command would seem to be useful here, but is actually a red herring.

There has GOT to be a better way, no?

+3  A: 

Here's what I would do: Use a fresh clone of the tip revision.

hg backout --merge -r revision_where_the_change_happened

to merge the reversed changes into the working copy.

Now copy the file in question to your regular working copy and commit

hg commit -m "Reversed the changes to file.h made in revision bla"

and throw away the clone you created above.

This way, mercurial doesn't know that there is a connection between revision_where_the_change_happened and this commit. If you want mercurial to remember this, instead do a

hg revert {all files except the one in question}

after merging the backout commit into the working copy and before commiting. For the second way, you don't need to work on a clone, because you want to keep the backout commit.

I would guess that the choice of which way you use depends on how big a part of the changeset the particular file change was.

balpha
+5  A: 

You can do it using just the -I (include names matching the given patterns) argument for backout with a single line:

hg backout --merge -I thefiletorevert -m 'message' OFFENDINGREVISIONID

Example Script:

hg init testrepo
cd testrepo
echo -e "line1\n\nline3" > file1
echo -e "line1\n\nline3" > file2
hg commit -A -m 'changes to two files'
perl -pi -e 's/line1/line 1/' file1
perl -pi -e 's/line1/line 1/' file2
hg commit -m 'put spaces in line1'
perl -pi -e 's/line3/line 3/' file1
perl -pi -e 's/line3/line 3/' file2
hg commit -m 'put spaces in line3'
hg backout --merge -I file1 -m 'remove spaces from line1' 1

Sample output:

adding file1
adding file2
reverting file1
created new head
changeset 3:6d354f1ad4c5 backs out changeset 1:906bbeaca6a3
merging with changeset 3:6d354f1ad4c5
merging file1
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

Resulting File Contents:

file1:line1
file1:line 3
file2:line 1
file2:line 3

notice that file1 is missing it's space in line one after the backout of the middle changeset, and the verbose log shows only one file changed in the backout:

$ hg log -v -r tip
changeset:   3:6d354f1ad4c5
tag:         tip
parent:      1:906bbeaca6a3
user:        Ry4an Brase <ry4an@mini>
date:        Mon Sep 14 12:17:23 2009 -0500
files:       file1
description:
remove spaces from line1
Ry4an
I think I understand this solution, but this case seems simpler than mine, since here you're backing out (one of the files in) the tip change, whereas balpha deals with my more general case of backing out a historical change.
djsadinoff
Naw, this would work fine for a non-tip changeset, though it would require you to 'hg merge' at the end. However, you'd still end up with a history graph that accurately reflects what happened and changeset parentage, unlike with balpha's copy-it-over, which would yield a linear changelog.
Ry4an
Cool. If you were to post an example with backing out a buried change, I'd mark it correct.
djsadinoff
backing out middle changeset
Ry4an
A: 

Use the revert command.

hg revert -r1 file

This should revert the contents of file to the version in revision 1. You can then further edit it and commit as normal.

hwiechers
It is correct that you can reset a file back to an earlier version like that. It would even work here if the file has not been changed since the bad version. But if more edits have been made, then a simple revert wont work. Instead you would have to update to the bad revision, revert the file to a good version, make a commit and merge the two heads. I believe this is basically what backout does for you.
Martin Geisler