Subversion notes

I like to keep personal files in my home directory under source control. Over the years, I've used SCCS, RCS, CVS, and now Subversion. (I've used Visual SourceSafe and ClearCase strictly for work.) Anything that requires occasional editing deserves source control, such as login profiles, utility scripts, code, personal website, and any research documents or notes.

The best reference is this Creative-Commons book, also available from O'Reilly: http://svnbook.red-bean.com/

For personal files, I now increasingly prefer Mercurial [ Mercurial_notes.html ] or Git [ Git_notes.html ] .

§    Why not stay with CVS?

SVN is essentially a complete rewrite of CVS, which is now unsupported and obsolete. For some time now, former CVS developers have put their effort into enhancing SVN. The 1.0 version of SVN had 100% of the functionality of CVS.

Most run the SVN service as a secure Apache plugin, rather than as a special purpose server. This has allowed much more web-based enhancements than were ever available for CVS. Here are examples of reports: http://mpy-svn-stats.berlios.de/zope-stats/

Apache is the most popular webserver on the web, and the most heavily audited for security vulnerabilities.

Whenever possible, SVN commands have the same name as for CVS, such as "add," "remove," "checkout," and "update." New names were introduced when simpler and more obvious, such as "move," "copy," "mkdir," and "revert." Unlike CVS, all commands seem to work as expected without any flags at all. I no longer need aliases or scripts for common chores. As a former CVS user, you can start with "svn help" and quickly figure out the rest.

Changing the names of directories and files is now trivial and causes no duplication in the repository. Copies retain the history of their sources. CVS always discouraged refactoring by remembering changes only for a certain file in a certain directory. Now directories are versioned as well.

You no longer need to distinguish binary and text files: all are compressed and differenced with a binary algorithm. Yet, you can specify that certain text files will receive an extra carriage-return on Windows(TM).

The complicated branching and tagging in CVS is now handled in SVN by a simple intuitive copy of an entire directory, without any actual duplication in the repository. Merging is also much more intuitive and less error-prone because you can see multiple branches at once.

Working copies of CVS require constant access to a repository simply to see what local changes have been made to the code. SVN allows you to see those changes and undo them without any contact with the repository.

Here are other substantial improvements over CVS: http://svnbook.red-bean.com/en/1.1/ch01s03.html All files can contain useful metadata. Symbolic links are handled automatically. All commits are atomic and cannot result in inconsistent state.

§    New repository

I think it is best to keep unrelated base directories as entirely separate SVN repositories.

First I create a top-level directory $HOME/mysvn/ to contain all the individual repositories.

Here's how to create a new repository from a directory outside of source control.
  SVNHOME=$HOME/mysvn
  mkdir $SVNHOME

  project=foo
  svnadmin create --fs-type fsfs $SVNHOME/$project

  SVNURL="file://$SVNHOME"
  svn mkdir $SVNURL/$project/branches -m "first version"
  svn mkdir $SVNURL/$project/tags -m "first version"
  svn mkdir $SVNURL/$project/trunk -m "first version"

  cd $HOME/$project
  svn import . $SVNURL/$project/trunk 

I strongly recommend using the new "fsfs" file system rather than the older Berkely database. BDB requires a local filesystem with full POSIX locking and memory mapping. NFS disks will be unusable. There are many other drawbacks and no significant advantages to BDB.

§    Converting from CVS

Here's how I convert a preexisting CVS project to SVN.
  SVNHOME=$HOME/mysvn
  mkdir $SVNHOME

  project=foo
  svnadmin create --fs-type fsfs $SVNHOME/$project

  cd /tmp/
  CVSROOT=$HOME/mycvsroot
  cvs2svn --use-cvs --existing-svnrepos --trunk-only --keywords-off \
   --no-default-eol -s $SVNHOME/$project $CVSROOT/$project

The --use-cvs flag is much slower, but avoids possible rcs format errors.

I turn off keyword expansion and eol changes to avoid corrupting binary files that were not properly checked into CVS as binaries.

If you have no binary files you may omit the --no-default-eol flag. If you are editing files on both windows and unix, then you will want to add this property later with something like
find . -name '*.txt' | grep -v '\.svn' | 
  xargs svn propset svn:eol-style native

§    Making a working directory

Once the project is checked in, I replace the existing one.
  SVNHOME=$HOME/mysvn
  SVNURL="file://$SVNHOME"
  project=foo

  cd $HOME
  mv $project $project.original

  svn checkout $SVNURL/$project/trunk $project
[or]
  SVNREMOTE="svn+ssh://$HOSTNAME/$SVNHOME/"
  svn checkout $SVNREMOTE/$project/trunk $project

§    Breaking apart a repository

You may want to break subtrees of a repository into multiple repositories. Or you may want to reclaim space by deleting a part of your tree permanently.

You'll need need to dump the repository with svn dump, filter through svndumpfilter, then load into a new repository. See the RedBean book for details.


Merging

You will find it necessary at times to create a release branch. You'll make changes in the trunk and want to merge them selectively into the release branch.

§    Merging with mergeinfo

Try to convince everyone on your team to merge from the trunk directly into the top level of the branch. A mergeinfo property will keep track of merges and help keep you out of trouble.

Get a working copy of the branch and cd into the top level directory, corresponding to the top level of the trunk.
cd branch_DS_5000_6_0_0
svn diff

Make sure you have no edits or local modifications. Compare the differences with the trunk:
svn diff ^/trunk ^/branches/branch_DS_5000_6_0_0

Find the revisions you want applied to the branch by listing all revisions on the entire repository, including trunk and branches:
svn log -v ^/  | less

Write down the range of revisions you want merged. Let's say you want to merge only one revision 38417.

Then you merge this change as follows:
svn merge -r38416:38417 ^/trunk/ .
svn diff

If you have any conflicts, then revert all changes, and look again for which earlier revisions are also necessary.
svn revert -R .
svn log -v ^/  | less

Otherwise, you are probably good. Test a complete build with tests, of course, before checking anything in.

You will notice that the merge created or modified an existing svn:merginfo property:
$ svn diff --depth empty .
Property changes on: .
___________________________________________________________________
Added: svn:mergeinfo
...

Check it all in, and include the merge command in your checkin message:
svn ci -m "Merged revisions from trunk: svn merge -r38416:38417 ^/trunk/ ." .

You can examine the value of the merge
$ svn propget svn:mergeinfo
/trunk:38442-38459,38417

Here we see a record of a previous merge, plus our latest single revision. (Are you sure you don't want those missing revisions as well?)

§    Messy merging

If your mergeinfo properties are a mess, and no one is honoring them, then here is an alternative:

Find the revisions you want applied to the branch by listing all revisions on the entire repository, including trunk and branches:
$ svn log -v ^/  | less

Write down the range of revisions you want merged. Let's say you want to merge only one revision 38417. Then save the range in a shell variable. (We will use several variables to avoid typos.)
$ R="-r38416:38417"

Cd to the directory in your working copy that contains all files you want updated. Save this location as the partial path below the trunk.
$ cd project
$ D=src/com/foo/stuff
$ cd $D

See that you are already in the trunk, and get the full URL for the directory that you are in
$ svn info . | grep URL
URL: http://server.foo.com/svn/project/trunk/src/com/foo/stuff

Save this location, using the ^/ shorthand for the repository root:
$ T=^/trunk/src/com/foo/stuff
$ svn ls $T
...

Get the name of the branch you want:
$ svn ls ^/branches/
...
branch_5000/
...

Substitute the name of the branch for the trunk in your URL and confirm it exists:
$ B=^/branches/branch_5000/$D
$ svn ls $B
...

Now switch your subdirectory. Make sure you are still actually in that directory in your working copy.
$ cd project/$D
$ svn switch $B .
U  file
...
$ svn info . | grep URL
URL: http://server.foo.com/svn/project/branches/branch_5000/src/com/foo/stuff
$ svn diff

You should see files update to the older branch versions, and you should see nothing from the svn diff

Now merge the revision range you saved earlier, from the trunk to the branch.
$ svn merge $R $T .
--- Merging r38417 into '.':
U    filename
$ svn diff
...
$ svn status
 M    .
...

Now you should see differences from svn diff and from svn status

You may not want to save mergeinfo properties:
$ svn diff --depth empty .
Property changes on: .
___________________________________________________________________
Added: svn:mergeinfo
...

$ svn propdel svn:mergeinfo .
property 'svn:mergeinfo' deleted from '.'.

Check in with a comment showing your merge command.
$ svn ci -m "Merged revisions from trunk: svn merge $R $T ." .

Finally, switch the directory back to the trunk:
$ cd $D
$ svn switch $T .

Bill Harlan, 2005-2010


Return to parent directory.