Restoring Files From Subversion (or: Subversion is Retarded)

One of the great things about a version control system is that you can resurrect deleted files and revert changes. This is a fairly common task - well, not excessively common, but it definitely happens.

In this case, I want to resurrect a directory that, even though I reverted the delete, Subversion decided to commit anyway. (Apparently "svn revert" doesn't actually revert changes like deleting the file. It copies it back, and then deletes it on commit anyway. svn status offers no indication this is the case.)

So how do you resurrect a dead file? (And the term is "resurrect," not "restore" - looking up "restore" won't find you a damned thing, you must "resurrect" it.)

Well, you have two strategies. Merge the old version in with the new copy (see svn merge) or copy the old version to the working copy (see svn copy). Notice how neither of these commands really has anything to do with restoring or resurrecting.

Let's say you want to undelete "file.txt" in your current repository because an unfortunately svn delete command got it when it wasn't supposed to. How do you do this?

First, check the log. There's no way to pull up the log of the file you deleted, because it doesn't exist any more (?!)[1]. Instead you have to pull up the log of the directory it's in. And you'll want to use -v to get a verbose listing of what was added and deleted so you can find what revision number the file was deleted in.

You'd think this would be the kind of thing that a command line utility could help you with, but as the general thrust of this post is that Subversion is retarded, it doesn't offer anything to help.

So once you've scanned through the revisions and found the revision number where the file was deleted, you're ready to copy it back in. You'll want to copy the revision prior to the revision it was deleted in, so after quickly decrementing the revision number, you're finally ready to start the copy command.

Well, not really. Before you can do that, it's a quick svn info to find out what the URL is of the current directory. With that, you can write the copy command.

So the final command will be:

svn copy -r [revision] [url] [filename]

Let's assume that "file.txt" was deleted in revision 412, and svn info gives us the URL of "http://svn.scm.example.com/repository/trunk/doc/text" for the directory containing "file.txt". The final command will be:

svn copy -r 411 http://svn.scm.example.com/repository/trunk/doc/text/file.txt file.txt

Heaven help you if the file you're trying to revert has a space in its name.

[1] Actually, you can. It involves knowing the full URL, which is a huge and unnecessary pain. Subversion already knows the URL, but is retarded and instead forces you to find it (via svn info), copy it, and use the full path. Under Windows this can be even more painful if the URL is larger than a single line since Windows only copies blocks out of the console and not lines. So you get to copy multiple lines of the URL out.

Topics: 

Comments

I find the svn documentation to be quite obtuse in many respects, and it's surprisingly difficult to find information this good about it on the internet. "resurrecting" to an older revision should NOT be this difficult. It should be a matter of

svn resurrect -r <#> someFileName

that brings an old revision's file and replaces the current one. It's the only feature within svn that I've found to be completely non-intuitive. That's more of a compliment to the sw, since the rest has been exactly as I expect version control to work.

This article helps greatly. Thanks. I've been pulling my hair out all evening.

Maybe now I can get some actual work done.

We converted to Subversion here from CVS a few months back and it has been nothing but a continuous and very large head ache for me.

I am so spoiled with Perforce, I wish we'd switched to that instead :)

This article doesn't even address the pain around branching and merging. Yow!

Until now.

I think it flagged every single file as conflicted. Despite the fact that literally none of them had actually been changed since the branch was made.

The branch was made, and then we wound up not doing anything to it while continuing development on the trunk. So I tried to merge the trunk back in to the branch before making the changes the branch was created for.

Any file that had changed was flagged "conflicted" and the conflicted changes were, even better, completely wrong.

In the end I wound up just copying the new files over directly via the file system and skipping SVN entirely.

You write the following in your footnote:

> Actually, you can. It involves knowing the full URL, which is a huge and unnecessary pain.

Can you give an example of this?

It doesn't really help, since you already have to know the last revision that the item existed in. (Or at least, it does now - when I wrote this post, I'm fairly sure I tried an svn log on the URL and got something, but when I tried it this time it didn't work.) But anyway...

First, use svn info on the parent directory to get the URL of the parent directory. Once you've got the URL, you can "simply" add the last part of the path. (Remembering to escape any characters that need to be escaped in a URL.)

Anyway, now you can do an svn log on that URL.

...Except you need to know the last revision it was in, and add "@REV" to the end. So if the last revision the file was in was revision 42, you'd have to add "@42" to the end of the URL.

To make this even more fun, the "-r" flag does not work with URLs.

If you are dissatisfied with the Windows command line -- as everyone is -- use Cygwin.

It might be even more useful to point out that it's possible to tell the Windows command line window to be a small viewport on an absurdly large terminal. I tell it I want 999 columns. No more crazy wrapping.

Thanks for the hint on how to resurrect a revision.

svn up -r 411 file.txt
Also svn log -v works for deleted files, so it is quite easy to undelete the file if you exactly know what you are looking for.

Sure, you'll get the old version of the file, but SVN will have it marked as the old version. When you go back to commit it, it will see that it's the old version and complain. It was the first thing I tried because it seemed so easy.

You have to "copy" the old revision to the current revision to successfully "resurrect" the file.

svn up -r $OLD file.txt
emacs file.txt &
svn revert file.txt

This gets the file to rev HEAD, then just save in Emacs.
In other words, as always, just change the file /from the current revision/.

It is very smart that svn considers revision numbers as it does; nobody sane would want it differently. You would lose all the flexibility of mixed revisions.

Now, there are instances where svn could be improved, but judging them requires understanding why it works the way it does today. Criticism is more esteemed from those who demonstrate this understanding. Reading the svn book might prove a good step towards both this understanding and less frustration when using it. After all, you take driving lessons before complaining about car design.

I liked CVS, though it was not without flaws.

I like SVN, though it is not without flaws. I TOTALLY agree that some things which should be SIMPLE are not, in fact, simple at all.

Thanks for the process hints.

I find that a good repository browser can make up for some of SVNs deficiencies. For example, I just installed viewvc and I can set any revision number and view the repository at that point in time. I just found my file before deletion and copied the contents to my PC and added a new file. Not that way it SHOULD be, but at least I got it done.

That was a very nice explanation! At work they are now moving from cvs to svn, and this was a missing feature. I used your tutorial and wrote it as a bash function for unix users. You can find it here: http://pastebin.ca/1863165

If you add that piece of code to your ~/.bashrc file, then when you want to undelete a file, you simply write "svnressurect filename". If you don't know the precise filename you can list deleted files using the log ("svn log -v | grep D" is recommended to get a shorter output)