Subversion primer

NOTE: in addition to this, you should read http://people.freebsd.org/~peter/svn_notes.txt, which documents some of the gorier and more obscure details.

NOTE: The "subversion book" is an excellent reference. See the first few chapters of http://svnbook.red-bean.com/. At the time of writing, you want the 'nightly' build for svn-1.5.

Overview

The FreeBSD src repo switched from CVS to Subversion on 2008-06-03.

There are mechanisms in place to automatically merge changes back from the Subversion repository to the CVS repository, so regular users shouldn't notice any difference, but developers most definitely will.

Subversion is not so different from CVS in daily use, but there are differences. In addition, Subversion has a number of features that should make developers' lives easier. The most important advantage to Subversion (and the reason why we switched) is that it handles branches and merging much better than CVS does. Some of the principal differences are:

Getting started

There are basically three ways to get a working copy from Subversion.

Direct checkout

The first is to check out directly from the main repo:

$ svn checkout svn+ssh://svn.freebsd.org/base/head /usr/src

This will check out a CURRENT source tree as /usr/src. Replace /usr/src with whatever you want. If you omit the final argument, the working copy will be named head, but you can safely rename it to whatever you want.

svn+ssh means the SVN protocol tunneled over ssh; svn.freebsd.org is the name of the server, base is the path to the repo, head is the subdirectory within the repo.

This is the simplest method, but it's hard to tell just yet how much load it will place on the repo. Subversion is much faster than CVS (significantly less disk I/O), so it might not be a problem.

Note that svn diff - probably the most frequently used command - does not require access to the server, as SVN stores a reference copy of every file in your working copy (which unfortunately means that SVN working copies are very big)

Checkout from a mirror

You can check out a working copy from a mirror by simply substituting the mirror's URL for svn+ssh://svn.freebsd.org/base. This can be an official mirror, or a mirror you maintain yourself using svnsync or similar.

There is a serious disadvantage to this method: every time you want to commit, you have to svn switch --relocate to the master repo, then svn switch back to the mirror after you've committed. Also, since svn switch only works between repos that have the same UUID, you have to hack the UUID of your local repo before you can start using it.

Unlike with cvs and cvsup, you probably don't want the hassle of a local svnsync mirror unless your network connectivity situation or other considerations demand it. See section on setting up a mirror at the end.

Checkout from a local mirror using SVK

The third alternative is to use SVK to maintain a local mirror. Svk is a version control system built on top of the Subversion storage engine. It is identical to SVN in most respects, except that it allows you to set up parts of your repo as mirrors of other repos, and to keep local branches which you can merge back to the upstream repo. There are extensions that allows SVK to mirror CVS and Perforce repos in addition to Subversion repos.

There are disadvantages to SVK as well - one of them being that local revision numbers will not match upstream revision numbers, which makes it difficult to svk log or svk diff or svk update to an arbitrary upstream revision.

To set up a mirror of the FreeBSD repo:

$ svk mirror svn+ssh://svn.freebsd.org/base //freebsd/base

Your local SVK repo will be stored in ~/.svk/local; you can move it to wherever you like, but you have to update ~/.svk/config manually to reflect the move.

You can use any path you want instead of //freebsd/base. One common pattern is to place mirrors under //mirror (e.g. //mirror/freebsd/base) and local branches under //local.

Then you have to pull down the contents:

$ svk sync //freebsd/base

Note that svk sync will take a very long time (up to several days depending on your network connection). Peter Wemm has a tarball (freefall:/home/peter/dot_svk_r179527.tbz2) you can use to jumpstart your SVK mirror, but only if you don't already have one...

$ cd ~
$ scp freefall:/home/peter/dot_svk_r179646.tbz2 .
$ tar xf dot_svk_r179646.tbz2

Then edit ~/.svk/config and replace /scratch/tmp/peter/.svk/local with the expanded equivalent of ~/.svk/local (e.g. /home/jarjar/.svk/local)

You can check out directly from your mirror:

$ svk checkout //freebsd/base/head /usr/src

Unlike SVN, SVK does not store metadata or reference copies in the working copy. All metadata is recorded in ~/.svk/config; reference copies aren't used at all since SVK always operates on a local repo.

When you commit from this working copy, SVK will commit directly to the upstream repo, then sync the mirror.

However, the "killer app" for SVK is the ability to work without a network connection. To do that, you have to set up a local branch:

$ svk mkdir //local/freebsd
$ svk copy //freebsd/base/head //local/freebsd/head

Again, you can use any path you like instead of //local/freebsd/head.

You have to synchronize your local branch before you can use it:

$ svk pull //local/freebsd/head

Now you can check out from your local branch:

$ svk checkout //local/freebsd/head /usr/src

The point of this exercise is that you can commit work-in-progress to your local branch, and only push it to the upstream repo when you're done. The easy way to push is with svk push, but there is a serious disadvantage to it: it will push every single commit you made to your local branch incrementally instead of lumping them all into a single commit. Therefore, you should use svk smerge instead. For instance, if you've been working on the bge(4) driver:

$ cd /usr/src
$ svk smerge -f sys/dev/bge

You'll know what to do from there.

RELENG_* branches and general layout

Given svn+ssh://svn.freebsd.org/base, base refers to the src tree. There will presumably be others. Expect things like /ports, /scratch, /proj, etc. These are separate repositories with their own change number sequences, access controls and commit mail.

For the "base" repository, /head refers to the -current tree. /head/bin/ls is what would go in /usr/src/bin/ls in a release.

The other key locations are:

Daily use

This section will show you how to perform common day-to-day operations with Subversion. There should be no difference between SVN and SVK in daily use, except for the revision renumbering mentioned earlier.

Note that SVN / SVK commands that have direct CVS equivalents usually have the same name, including abbreviations (for instance, checkout and co', update and up', commit and ci).

Help

Both SVN and SVK have built-in documentation:

$ svn help
$ svn help checkout

Checkout

As seen earlier:

$ svn checkout svn+ssh://svn.freebsd.org/base/head /usr/src

At some point, you will probably need more than just head - for instance, you may want to merge changes to stable/7. Therefore, it may be useful to have a partial checkout of the complete tree (a full checkout would be very painful).

First, check out the root of the repo:

$ svn checkout --depth=immediates svn+ssh://svn.freebsd.org/base

This will give you base with all the files it contains (currently just ROADMAP.txt) and empty subdirectories (head, stable, vendor etc.)

You can expand your working copy by changing the depth of the various subdirectories:

$ svn up --set-depth=infinity base/head
$ svn up --set-depth=immediates base/release base/releng base/stable

This will give you a full copy of head, and empty copies of every release tag and every releng and stable branch.

If, at a later point, you need to merge something to the 7-STABLE, you can expand your working copy again:

$ svn up --set-depth=infinity base/stable/7

Note that you don't have to expand a complete subtree - for instance, you can expand only stable/7/sys, then later expand the rest of stable/7:

$ svn up --set-depth=infinity base/stable/7/sys
$ svn up --set-depth=infinity base/stable/7

You can svn update the root of your working copy without fear that it will pull down the entire tree. It will only update what you previously asked for - in this case, head and stable/7.

The flip side is that you can only increase the depth of a working copy, never decrease it.

Update

Update a working copy to the latest revision, or to a specific revision:

$ svn update
$ svn update -r12345

Status

See what local changes you have in your working copy:

$ svn status

This has no direct equivalent in CVS: the cvs up -N command will not only show local changes, but also indicate files that are out-of-date. This corresponds to the following SVN command:

$ svn status --show-updates

Edit and commit

Like CVS (and unlike Perforce), SVN and SVK don't need to be told in advance that you intend to edit a file.

The svn commit command works pretty much just like its CVS equivalent.

Commit all changes in the current directory and all subdirectories:

$ svn commit

Commit all changes in the lib/libfetch and usr.bin/fetch directories in a single operation:

$ svn commit lib/libfetch usr.bin/fetch

Add and remove

NOTE: Before adding files, get a copy of http://people.freebsd.org/~peter/auto-props.txt and edit it into your ~/.subversion/config file according to the instructions in the file. If you added something before you've read this, you may use svn rm --keep-local for just added files, fix your config file and re-add them again. The initial config file is created when you first run a svn command, even something as simple as: svn help

Files are added with svn add, just like with CVS:

$ touch foo
$ svn add foo

and removed with svn remove:

$ svn remove foo

Unlike CVS, Subversion does not require you to rm the file before you svn rm it; in fact, it will complain if you do, but you can shut it up with --force.

Directories can also be added with svn add:

$ mkdir bar
$ svn add bar

However, if the directory does not already exist, it is simpler to just use svn mkdir:

$ svn mkdir bar

In CVS, the directory is immediately created in the repository when you cvs add it; this is not the case in Subversion. Furthermore, unlike CVS, Subversion allows you to remove directories using svn rm (there is no separate svn rmdir command):

$ svn rm bar

Copy and move

The following (obviously) creates a copy of foo.c named bar.c:

$ svn copy foo.c bar.c

This has no equivalent in CVS.

Moving or renaming a file is also supported:

$ svn move foo.c bar.c

This is actually the exact equivalent of

$ svn copy foo.c bar.c
$ svn remove foo.c

Log and annotate

The svn log command works pretty much like cvs log. The most important difference is that svn log on a directory will show all revisions affecting the directory or files in it, in reverse chronological order, while cvs log will show the complete log for each file in the directory, including duplicate entries for revisions that affect multiple files.

The svn annotate command (or, depending on your mood svn blame or svn praise) is equivalent to cvs annotate, with some differences in output format.

Diff

As in CVS, the svn diff command displays changes to your working copy. The main differences are that it uses the unified diff format by default (similar to cvs diff -u), and it always shows diffs for added files (similar to cvs diff -N).

Like cvs diff, svn diff can show the changes between two revisions of the same file:

$ svn diff -r179453:179454 ROADMAP.txt

It can also show all changes for a specific changeset. The following will show what changes were made to the current directory and all subdirectories in changeset 179454:

$ svn diff -c179454 .

Revert

Local changes (including additions and deletions) can be reverted using svn revert. Unlike cvs up -C, it does not update out-of-date files - it just replaces them with pristine copies of the version you already have.

Conflicts

If an svn update resulted in a merge conflict, Subversion will remember which files have conflicts and refuse to commit any changes to those files until you've explicitly told it that the conflicts have been resolved. The simple (but deprecated) procedure is the following:

$ svn resolved foo

The preferred procedure is to use svn resolve (no d) and specify the correct action:

$ svn resolve --accept=working foo

This is equivalent to the previous example. Possible values for --accept are:

Advanced use

Sparse checkouts

The equivalent to cvs checkout -l (to check out a directory without its subdirectories) is svn checkout -N. However, unlike CVS, SVN remembers the -N, so you can't just svn update later to get the subdirectories. In Subversion 1.5 and newer, -N has been deprecated in favor of the --depth option, which allows precise control of this feature:

$ svn checkout -N svn+ssh://svn.freebsd.org/base ~/freebsd

is equivalent to

$ svn checkout --depth=empty svn+ssh://svn.freebsd.org/base ~/freebsd

Valid arguments to --depth are:

The --depth option applies to many other commands as well, including svn commit, svn revert and svn diff.

Since --depth is sticky, there is a --set-depth option for the update command that will change the selected depth. Thus, given the working copy produced by the previous example:

$ cd ~/freebsd
$ svn update --set-depth=immediates .

You will now have ROADMAP.txt and a bunch of empty subdirectories, but if you svn update any of these, nothing will happen. However,

$ svn update --set-depth=infinity head

will set the depth for head to infinity, and fully populate it.

Direct operation

Certain operations can be performed directly on the repo, without touching your working copy. Specifically, this applies to any operation that does not require editing a file, including:

The most important consequence is that branching is very fast. The following command:

$ svn copy svn+ssh://svn.freebsd.org/base/head svn+ssh://svn.freebsd.org/base/stable/8

would be used to branch what RELENG_8, and is equivalent to the following sequence:

$ svn checkout --depth=immediates svn+ssh://svn.freebsd.org/base
$ cd base
$ svn update --depth=infinity head
$ svn copy head stable/8
$ svn commit stable/8

except that the former takes seconds, while the latter may take minutes to hours, depending on your network connection.

Merging

See SubversionPrimer/Merging

Vendor imports

See SubversionPrimer/VendorImports

Reverting a commit

Reverting to a previous revision is fairly easy:

$ svn merge -r179454:179453 ROADMAP.txt
$ svn commit

or, use change number syntax. Negative means a reverse change.

$ svn merge -c -179454 ROADMAP.txt
$ svn commit

This can also be done directly in the repo:

$ svn merge -r179454:179453 svn+ssh://svn.freebsd.org/base/ROADMAP.txt

Reverting the deletion of a file is slightly different. You need to copy a version of the file that predates the deletion (typically, to restore a file that was deleted in revision N, you would copy back revision N-1):

$ svn copy svn+ssh://svn.freebsd.org/base/ROADMAP.txt@179454 .
$ svn commit

or

$ svn copy svn+ssh://svn.freebsd.org/base/ROADMAP.txt@179454 svn+ssh://svn.freebsd.org/base

Do not simply recreate the file manually and svn add it - this will lose history.

Fixing mistakes

While we can do surgery in an emergency, do not plan on having mistakes fixed behind the scenes. Plan on mistakes remaining in the logs forever. Be sure to check the output of svn status and svn diff before committing.

Mistakes will happen. But, unlike CVS, they can generally be fixed without disruption.

Take a case of adding a file in the wrong location. The right thing to do is to 'svn mv' the file to the correct location and commit. This causes just a couple of lines of metadata in the repository journal. The logs are all linked up correctly.

The wrong thing to do is to delete the file and then 'svn add' an independent copy in the correct location. Instead of a couple of lines of text, the repository journal grows an entire new copy of the file. This is a waste.

Setting up a svnsync mirror

You probably do not want to do this unless there is a good reason for it. Such reasons might be to support many multiple local read-only client machines, or if your network bandwidth is limited. Starting a fresh mirror from empty would take a very long time. Expect a minimum of 10 hours for high speed connectivity. If you have international links, expect this to take 4 to 10 times longer.

A far better option is to grab a seed file. It is large (~1GB) but will consume less network traffic and take less time to fetch than a svnsync will.

$ rsync -va --partial --progress freefall:/home/peter/svnmirror-base-r179637.tbz2 .

or:

$ rsync -va --partial --progress rsync://repoman.freebsd.org:50873/svnseed/svnmirror-base-r179637.tbz2 .

or by ftp, presumably a local mirror:

$ fetch ftp://ftp.freebsd.org/pub/FreeBSD/development/subversion/svnmirror-base-r179637.tbz2

Once you have the file, extract it to somewhere like /home/svnmirror/base. You then update it so that it fetches the changes after rev 17963. In the example location above:

$ svnsync sync file:///home/svnmirror/base

You can then set that up to run from cron, do checkouts locally, set up a svnserve server for your local machines to talk to, etc.

The seed mirror is set to fetch from svn://svn.freebsd.org/base. The configuration for the mirror is stored in revprop 0 on the local mirror. To see the configuration, try:

$ svn proplist -v --revprop -r 0 file:///home/svnmirror/base

Use propset to change things.

Tips

$ svn co --depth=empty svn+ssh://svn.freebsd.org/base fbsvn
$ cd fbsvn
$ svn up --depth=empty stable
$ svn up head
$ cd stable
$ cp -r ../head/ 7
$ cd 7
$ svn switch svn+ssh://svn.freebsd.org/base/stable/7
$ cd ..
$ cp -r 7/ 6
$ cd 6
$ svn switch svn+ssh://svn.freebsd.org/base/stable/6

What this bit of evil does is check out head, stable/7 and stable/6. We create the empty checkout directories under svn's control. In svn, subtrees are self identifying, like in cvs. We check out head and clone it as stable/7. Except we don't want the head version so we "switch" it to the 7.x tree location. Svn downloads diffs to convert the "head" files to "stable/7" instead of doing a fresh checkout. The same goes for stable/6. This definitely counts as "abuse" of the working copy client code so there is no warranty. :)

SubversionPrimer (last edited 2008-09-03 21:16:39 by MatteoRiondato)