SVN - subversion version control system

Beware that a lot of the online documentation covers older versions of subversion. I am now running 1.6.6 and a lot of the online documentation covers 1.4 or older. This is not a terrible situation since none of the basics have changed, but newer features are not documented and limitations have been removed. For example the move command now moves multiple files, a command svnsync has been added, and things like that, which are hard to find out about.

First of all, take a look at:

Beware that a lot of the online documentation covers older versions of subversion.

Second, get a copy of the Subversion book, and/or view it online at online subversion book.

Long ago I tried using RCS, but then CVS came along and that really saved the day. Now subversion promises (and people say delivers) all that CVS did, and fixes all of its major shortcomings, so here we go!

If you must, you could take a look at the random collection of junk I accumulated on how to use CVS.

First of all you have to decide how you want to handle your repositories, if you want more than one, etc. etc. Of course setting this up right is vitally important, but you don't have a clue when you are starting out. Nonetheless, it is the first thing you need to do to get started. What to do?

Making a project repository

What I do is have one repository per project (totally unlike how I handled CVS) and keep all these repositories in some directory (typically~/SVN). I already had some toy project from the first time I began playing with subversion, so I renamed this repository to be ~/SVN/bogus. Then I made a new repository for the myproject, via the command:

svnadmin create ~/SVN/myproject

Now, somewhere outside the repository, and in this case on an entirely different machine, I create a template directory holding what I plan to import:

cd myproject
mkdir branches tags trunk
cd trunk
tar xzvf myproject.tgz .
cd ..

Now we do the import. This project has a lot of binary stuff and hidden files so we can really see how this is going to work. The binary files are mostly images as .gif files. To do this to a server on a different machine:

cd myproject
svn import . svn+ssh://myserver.com/home/sam/SVN/myproject -m "initial import"

To do this on the same machine:

cd myproject
svn import . file:///home/sam/SVN/myproject -m "initial import"

This being done, I delete the whole template and then check out a working copy via:

rm -rf branches tags trunk
svn checkout svn+ssh://myserver.com/home/sam/SVN/myproject/trunk src

Now the trunk business (see the book) has vanished and the only visible (or not so visible) artifact of going in and out of the repository is the .svn directory, the files themselves are unchanged.

Note that you are by no means required to do the trunk, branches, tags thing. In fact if you never intend to use branches (and you never should intend to) and you never use tags, it will just get in your way.

To look at a repository, you can use:

svnlook tree /path_to_repos

Day to day use of svn

As with cvs, the bulk of your use is svn commite, update, add, and delete.
Note that cvs includes copy and move.

You can specify a given revision by number, or one of the following keywords. (These all refer to some version in the repository:

The following letter codes show you what happens on an update:

The command svn status is the quickest handiest way to see how your current copy relates to the repository.

Branches

Do not use SVN branches.
Use something like Git if you are even thinking of using branches.

Be warned! Branches have bitten me big time. Be careful about making branches and keep track of what you do when you create them or you are in for a world of hell when it comes time to merge them back into the trunk!! Making the branch is easy, deceptively easy. The pain and suffering comes when it is time to do the merge.

You make a branch by creating a copy within the repository, then checking out the copy. Something like this:

svn copy http://host/repo/project/trunk http://host/repo/project/branches/test -m "comment"
svn checkout http://host/repo/project/branches/test mytest
The copy knows that it has a history originating in the trunk, (which can continue on its own path). The trunk has no idea whatsoever than a branch exists. Making the copy increments the revision number. It is your job to keep track of the branch point, subversion does not do it for you!!. If you don't remember the revision at which the branch was created, do this:
svn log --verbose --stop-on-copy

merging a branch back onto the trunk

This is as clear as mud in the book and many online docs. Let us see if we can work up a foolproof process for doing this, as well as get some understanding of what is going on.
Check out a copy of the trunk (the merge target)
You probably already have a copy (if so, skip the checkout step), you do want the copy to be up to date with the repository.
svn checkout svn+ssh://host/repo/project/trunk trunk
cd trunk
svn update
Find out the initial and current revision numbers of the branch
The following command will yield r-split (at the end), the revision number at which the branch split off from the trunk
and r-branch (at the top) the most current revision number. (Assuming all changes have been committed).
svn log --stop-on-copy svn+ssh://host/repo/project/branches/yada

If you have an up to date working copy of the branch, the following will yield the current revision number. (which had better be fully committed to the repository).

cd branch
svn log
Do the merge!
cd trunk
svn merge -r r-split:r-branch svn+ssh://host/repo/project/branches/yada
The files in the current directory get updated by the merge. The trick in all of this is giving the correct revision number range to the merge command. The range is not at all what I would expect. You are saying to compare the branch now to the trunk at the time of branching (i.e. sublimate all the change activity done on the branch), then apply that set of diffs to the trunk. You do NOT compare the current trunk to the current branch (which is what I would have expected).

merging changes from the trunk onto a branch.

If you understood the above, this will be easy, as it is just the flip of the above.
cd branch
svn merge -r r-split:r-trunk svn+ssh://host/repo/project/trunk
Think of this as doing a diff, within the trunk itself, from the trunk at the time of the split to the trunk at the present time, then applying these diffs to the current directory (which is the branch). Again the whole trick is getting the version numbers. To get r-split, you will need something like:
svn log --stop-on-copy svn+ssh://host/repo/project/branches/yada
The lowest numbered revision (at the end) is the number you want.

You will also want r_trunk, which you can get from:

cd trunk
svn log

Resolving conflicts

When you do an update or a merge, you must deal with any files that get a C marker by hand. There will be 3 extra files to help you along, the original file will have merge markup in it. When you get everything sorted out, you can get rid of the 3 "bonus files" via:
svn resolved fixedfile.c

SVN and the outside world

svn+ssh

This scheme (described above) may be all you need.

svnserve

For this, see chapter 6 of "the book", under svnserve, a custom server. (I have never used this, but you may want to take a look).

Apache

If you really want to make your source tree available to the entire planet, you can use mod_dav_svn to allow Apache to dole out your repository over port 80. The apache+DAV thing has some nice features for browsing a big repository. If you are behind a firewall and want to share files with others without opening up extra ports, this is the way.

The deal here is to monkey with the file /etc/httpd/conf.d/subversion.conf and to restart apache, repeating this process until things work.

All in all this is not too painful and pretty soon http://myserver.com/svn/myproject will be giving a view of your repository. This would be frightening except that there are two layers of protection. User apache can read but not write these files and the directive in the configuration file limits access to GET and other innocuous uses.

TRAC

The cadillac way to do what mod_dav_svn does is to use TRAC. TRAC is a web based interface to a SVN repository with lots of features.

lighttpd and svn

For any number of reasons, I have given up on lighttpd. (I once used it when it seemed to be the best way to do RAILS, but now I use apache with a mongrel cluster, as do most people it would seem.)

But, what do you do if you are running lighttpd and also want to offer access to a svn repository? At this point in time (mid 2007) there is nothing like the mod_dav_svn thing, and there is not likely to be any time soon. See my subversion with lighttpd notes for full details. In short, what you do is to run apache on an alternate port (like 8080) and set up the mod_dav_svn business. Then what you do is have lighttpd use mod_proxy to proxy requests to apache on that port via something like the following:

$HTTP["host"] == "projects.example.com" {
    server.document-root = "/var/www/projects.example.com/httpdocs"
	proxy.server = (
	    "/svn/test" => (("host" => "127.0.0.1", "port" => 8080))
	)
}

Moving an SVN repository

A simple whole-hog move

If you want to move the repository and nothing more, this is actually quite simple. You dump the repository on the machine hosting it, move the dump file to the new machine and reconstitute it there. Note that the dump file can be quite large. The commands are like this:
svnadmin dump /path/to/repository > repository-name.dmp
scp repository-name.dmp new_host:
ssh new_host:
cd /path/to/new-repository
svnadmin create repository-name
svnadmin load repository-name< repository-name.dmp
After doing this, you will need to tell your working copies that the repository is in the new location. Do this via:
svn switch --relocate oldurl newurl
Alternately you could just check out a new working copy and ditch the old one.

After I moved my repository from a local file (actually a link to an NFS mounted directory) to another machine entirely, I used the following command, and it worked like a champ:

svn switch --relocate file://localhost/svn/myproject/trunk svn+ssh://new_server/mmt/tom/SVNarchive/myproject/trunk

More tricky - changing layout, then moving

In my case I have a collection of repositories (one per project) that I would like to convert to projects within a grand repository (which is also under TRAC) on another machine. This will require extra fussing.

The basic task is to modify the original single project repository so that instead of just having the 3 directories (trunk, branches, tags) at the base level, it instead has a directory with the project name (and containing trunk, branches, and tags) at the base level. After this is done, a dump can be generated and just loaded into the grand repository as described above.

I am (as of 1/2010) running Subversion version 1.6.6 - this makes this whole business easy. (In the old days (prior to version 1.5) you had to write scripts with loops external to subversion.)
With the nice modern subversion, I can do what I want via:

svn mkdir svn+ssh://server/home/svn/project/project
svn move svn+ssh://server/home/svn/project/tags svn+ssh://server/home/svn/project/project
svn move svn+ssh://server/home/svn/project/branches svn+ssh://server/home/svn/project/project
svn move svn+ssh://server/home/svn/project/trunk svn+ssh://server/home/svn/project/project
Then to clue in my working copy about what I have done, I need to do:
svn switch svn+ssh://server/home/svn/project/project/trunk
All of this seems to work fine, and sets the stage for moving this to the grand repository on a different server, as follows:
svnadmin dump /home/svn/project > project.dump
scp project.dump new_server:
ssh new_server
svnadmin load /home/repo < project.dump
After this, in an attempt to clue in my working copy, I do this:
svn switch --relocate svn+ssh://old_server/home/svn/project svn+ssh://new_server/home/repo
This yields an unpleasant looking error:
svn: The repository at 'svn+ssh://new_server/home/repo/project/trunk' has uuid '...cdac2', but the WC has '...0899d'
This means that the working copy has discovered that the repository has changed, and the generally recommended fix is to checkout a new working copy (which is what I do).

Not so tricky - moving part of a repository

Suppose you have two repositories (repoA and repoB). Suppose also that repoA contains some project "P" that you would like to move to repoB. Here is how to do it:

svnadmin dump --quiet /home/svn/repoA | svndumpfilter include P > P.dmp
svnadmin load --quiet /home/svn/repoB < P.dmp
svn remove /home/svn/repoA/P
Of course, the first two steps above could be done in one big pipeline if the repositories were on the same machine.

A cool trick

I have never tried this, but I am told that you can use netcat along with tar to recursively mirror the contents of two directories across machines as follows:
# On the destination "mirror" machine
cd NEW_REPOS
nc -l -p 2345 | tar xv
# On the source machine
tar c OLD_REPOS > /dev/tcp/DOTTED.IP.OF.MIRROR/2345
You pick a port number like 2345 for the task, then wonder what operating system supports the /dev/tcp scheme.

Look for svnsync on more recent versions of subversion.

SVN and SSH

If you have your ssh keys and authentication already set up, this will just work (which is always a good thing). What you do is use the svn+ssh:// schema instead of file://. Once you checkout a working directory you won't need to specifiy this again for updates and commits as the schema for access will be "remembered" (somewhere in the .svn directory.

Random ugly things

I got this error when I started using subversion on a new machine:
sh: line 1: svn-commit.tmp: command not found
svn: Commit failed (details follow):
svn: system(' svn-commit.tmp') returned 32512
This was a mistake in my .bashrc setting the EDITOR environment variable (which svn insists on). A typo caused it to be set to an empty string, and then svn was just blithely using that empty string as the name of an editor to run, yikes!