Tuesday, January 23, 2007

How to Undo a Commit in Subversion

A frequent question that is asked on the Subversion and Subclipse mailing lists is how to undo a change once it has been committed. A variant of the same request is how to get back a file that has been deleted.

If you need to undo a change completely, as in remove all traces it ever happened, there is really only one way to do it. You need to dump the repository up to the revision you want to remove, and then recreate and load the repository from the dump file. I will not be covering that option in any detail in this article. This article will just be about reversing the effects of a commit, as in restoring a deleted file/folder or some change to a file or set of files. Depending on the circumstances, there might be several ways you can accomplish this. For example, the svn copy command can be used to restore something that is deleted. The technique I am going to focus on in this post is called a "reverse merge". Essentially, you use the svn merge command with the revisions flipped around so that the merge command undoes the changes in the selected revisions. For command-line users, the process to do this is explained well in the Subversion book. See this section on undoing changes.

When using a GUI tool like Subclipse, you could just use the Merge option and fill out the dialog using similar values to what you would do from the command line. However, Subclipse offers a built-in technique that makes it very easy to undo a change.

To give proper credit where it is due, the technique that is described in this article first appeared in the TortoiseSVN product. The Subclipse feature described below is based on the TortoiseSVN feature.

Open the History View
The first step is to show the history of the item containing the change you want to restore. If you just want to restore the changes to one file, then select just that file. Otherwise, you can select your project or a folder so that you can undo changes to multiple files.

If you want to restore something that is deleted, then you need to show the history of the item's parent, so that the history will show you the deletion.

Select the Revision(s)
Now, just select the revision, or contiguous range of revisions, that contain the changes you want to undo. Right-click and choose the option to revert the changes from those revisions:




Review and Commit
When the option runs, it will invoke the merge command, supplying the proper parameters to do a reverse merge. Review the results of the merge and commit the changes to finish the process.

Partial Undo?
It would not be uncommon to only want to reverse part of the changes from a commit or series of commits. There are a couple of ways to deal with this:
  1. Select the correct context. If you only want to undo the changes to files in a specific folder, then select that folder when you show the history. Then the merge will only undo the changes in the context of that folder and other changes in the commit will be skipped by the merge process.
  2. Revert what you do not want. Suppose you delete five files from the same folder and want to restore one of them. You have to select the parent folder when you take the option, and this will restore all five files. When the command completes, just use the Revert option to undo any changes you do not want, and then commit the rest. The merge process only restores the change in your working copy. Nothing is forcing you to commit the results of the merge without editing it first.
Conclusion
Undoing the effects of a change in Subversion is a fairly easy process once you understand what it is that you have to do. The Subclipse and TortoiseSVN UI further simplifies the process and saves you from having to lookup and enter the revisions correctly in the merge dialog.

7 comments:

Anonymous said...

Mark, I had a coworker delete a main subfolder that has a lot of history. Not good. Anyway, I want to bring it back WITH the history. Is this possible? Thanks.

Mark Phippard said...

Sure. In that scenario, the best approach is to use the copy command. Suppose the folder URL was:

url://server/repos/project/folder

And it was deleted in r100. Then to get it back, run this command:

svn copy -r99 -m "Restore folder" url://server/repos/project/folder url://server/repos/project/folder

If you get an error, add "@99" to the end of the first URL.

Mark

Anonymous said...

In case you use subversive, its easy to replicate that subclipse functionality using merge. Just select the revision where the commit was a the start revision and the revision before as the end revision.

Brendan said...

Thanks Mark :-) We had that problem, somebody deleted trunk. And now its back with no problem. Yay. YAY.

board tc said...

Thanks for the post, it really helped me reverting some unnecessary updates. Note I found that if you revert 1 file you need to commit before moving onto the next file.

There seems to no way to remove a file completely without rebuilding the repository which is unfortunate.

Anonymous said...

Thanks Mark, this was quite a time saver. In my case I had to delete the empty dir first and modify your command to this:
svn copy -r99 -m "Restore folder" url://server/repos/project/folder url://server/repos/project

This prevents restoring the directory as url://server/repos/project/folder/folder

Chris Welch said...

Confirming that @99 is required on the first URL for this to work.
Otherwise you'll get a file not found error as it no longer exists in the head revision.