Renaming files and directories in CVS

One of the difficulties one runs into in working with CVS is that all of its tracking of revisions is by file, and it tracks a particular file by its name. That means that when you rename a file that CVS knows about, it doesn't cope very well. This is a known and very standard problem with CVS, and while there have been a few long-term solutions for it proposed (mostly revolving around maintaining a separate database of files and their names), no fix in CVS itself appears to be forthcoming in the short term.

There are a few standard workarounds, some of which are somewhat "ugly" in that they involve direct manipulation of the CVS repository. I'll present them in order of how likely I am to use them.

The method I usually use is to find the path to the file in the repository and rename the RCS file corresponding to the file that I want to rename. A subsequent cvs update will then delete the file under its old name and check out a new copy of the file under its new name. (*Be careful not to do this when you have uncommitted modifications to the file, because you'll lose them.* Do a cvs update first to be sure.)

The CVS repository is actually just a tree of RCS files, and CVS uses the names of those files to know what names the files should have. The RCS files have the same name as their checked out versions, but with an extra ",v" appended to the end. The full path to the repository directory corresonding to a checked out directory is stored in CVS/Repository.

Here's an example of how to do this, using a file named baz.

    windlord:~/dvl/tmp> ls
    CVS/  baz   blah
    windlord:~/dvl/tmp> cvs update .
    cvs update: Updating .
    windlord:~/dvl/tmp> cat CVS/Repository 
    /home/eagle/cvs/tmp
    windlord:~/dvl/tmp> cd /home/eagle/cvs/tmp
    windlord:~/cvs/tmp> mv baz,v foo,v
    windlord:~/cvs/tmp> cd -
    windlord:~/dvl/tmp> cvs update .
    cvs update: Updating .
    cvs update: baz is no longer in the repository
    U foo
    windlord:~/dvl/tmp> ls
    CVS/  blah  foo

When I renamed baz,v to foo,v in the repository, I renamed the file as far as CVS was concerned, and then the next cvs update deleted baz (since its RCS file was no longer in the repository) and checked out a new copy of foo. Renaming complete. Even better, foo now has all of baz's revision history still intact.

The problem with this approach is that if you check out an older version of the renamed file, it will still be checked out under its new name. So if you have a whole tree of files that are interrelated (such as a bundle file and a bunch of files it installs), and you're reverting to an older version of the whole mess, you may end up with an inconsistent tree since the other files will be referring to the old name of the file but it will be checked out under its new name.

One way to avoid that problem is to use CVS to delete the file under its old name and create a new file under the new name, and use that as the mechanism for renaming files. In other words:

    mv baz foo
    cvs remove baz
    cvs add -m'Brief description.' foo
    cvs commit baz foo

This approach lets CVS keep track of what you're doing a lot better, but has the problem that you've now hidden the revision history of foo back when it was named baz. cvs log foo will only show you revisions since the renaming, and you'd have to go look through the log of the old deleted baz to find older entries.

A modification of this approach is to go into the repository and rather than rename the RCS file for baz to foo, instead copy it. Then use cvs remove to mark baz deleted. This way, CVS can understand what's going on a little better and you still have the revision history; however, if you check out older versions of the tree, CVS will check out both baz and foo, and you have two copies of the revision history floating around. So this can be confusing.

In general, I nearly always just rename the RCS file in the repository, since I rarely need to use tags or other methods for marking an entire snapshot of a tree, and have had little need for reverting a whole tree of files to an older version. In cases where I do need to do that, I usually just cvs remove the old name and cvs add the new name.

Renaming directories involves many of the same issues as renaming files, but with some additional complications. One is that CVS can't cope at all well with entire directories present in a checked out copy suddenly disappearing from the repository. This means that before renaming a directory in the repository, unless you intend to go through the files in the CVS directories of the checked out copy and fix them by hand, you want to cvs release -d all of your checked out copies.

Once that's been done, though, you can just rename the directory in the repository and check out the tree again and everything works fairly well, with the caveat that again retrieving old versions of files may result in inconsistencies. There are the same alternate choices too; you can create a new directory, copy the files (and optionally the RCS files in the repository into the new directory), and delete all the files out of the old directory. The drawback here is that there's no way of deleting an entire directory out of the CVS repository, and in fact the directory has to stay in the repository to store the old revision logs of the files that used to be in it.

The best solution there is that if you regularly use CVS trees that contain old deleted directories that have only deleted files in them, you can use cvs checkout -P rather than cvs checkout (or put "checkout -P" in your ~/.cvsrc file). That tells cvs update to "prune" the directory structure, and any directories that contain only deleted files won't be created in the checked out copy.

While we're on the subject of things you may want in ~/.cvsrc, cvs update by default won't pick up any new directories, only update the ones you already have. To make it pick up new directories, give it the -d flag. But that will pick up empty directories, so you may also want the -P flag, or to save yourself time, just put "update -dP" in your ~/.cvsrc.

But remember: If you have "checkout -P" and "update -dP" in your ~/.cvsrc, you'll never check out empty directories. Even if you wanted them. This has bitten me at least once, so it's good to also know about the -f flag to all CVS commands that tells CVS to ignore ~/.cvsrc.

    cvs -f update

will do a normal, flagless update, even if you have "update -dP" in your ~/.cvsrc.

Last modified and spun 2014-08-17