A conceptual introduction to SVN for CVS users
This page contains a conceptual introduction of SVN tailored to old-time CVS users. Generically speaking, SVN was designed to be similar to CVS, in fact most SVN commands are similar to the CVS counterparts (but don't expect always an exact 1:1 mapping).
In SVN, the set of commands to work on a locally checked-out repository and to work on a remote repository is exactly the same. In other words, there is no cvs rdiff or cvs rlog using svn diff and svn log is enough, and you just need to provide a full URL to the repository if you want to work remotely. For instance:
$ svn log gcc/reload.c # display log of checked-out copy of reload.c $ svn log svn+ssh://gcc.gnu.org/svn/gcc/trunk/gcc/reload.c # display current log of reload.c $ svn diff --revision 340:500 svn+ssh://gcc.gnu.org/svn/gcc/trunk/gcc/reload.c # display difference between r340 and r500 $ svn diff --revision 340 gcc/reload.c # display difference between r340 and the current copy $ svn cat --revision=1234 svn://gcc.gnu.org/svn/gcc/trunk/gcc/expr.c # display contents of expr.c at revision 1234 $ svn cat svn://gcc.gnu.org/svn/gcc/trunk/gcc/expr.c@1234 # idem
Many commands allow both an URL or a path indifferently. You'll see that you can even modify the repository without checking it out.
If you want to find out the remote URL corresponding to a given file (e.g. if you have some confusion and you do not know anymore what a working copy is), you can use svn info For instance:
$ svn info gcc/java/parse-scan.y | grep "URL" URL: svn+ssh://firstname.lastname@example.org/svn/gcc/trunk/gcc/java/parse-scan.y
In the GCC repository, the remote URLs can use either the svn+ssh:// or the svn:// protocol. This is better explained in the configuration page.
The main difference in architecture is that there is *not a different revision history for each file*. Instead, the *whole tree* is versioned. When I say r23 I refer to revision 23 of the repository, which is basically the state of the *whole tree after 23 commits*. Each changeset (atomic commit of multiple files) is unequivocally identified by the revision number it brings the repository to. We can for instance say "Bug #123 was fixed by commit r345".
One obvious consequence of this is that *commits are really atomic*, so it's very easy to extract a full commit (affecting multiple files), by just diffing that revision. This means that things like regression hunting or patch reversion are much much easier.
Another consequence (which might be seen as a drawback at first) is that the changes in a specific file are not sequential. For instance, the (stripped) log of a random file might look like this:
$ svn log gcc/testsuite/gcc.dg/wint_t-1.c | grep "^r" r84717 | mrs | 2004-07-15 01:34:34 +0200 (Thu, 15 Jul 2004) | 2 lines r67176 | andreast | 2003-05-26 20:10:20 +0200 (Mon, 26 May 2003) | 5 lines r58562 | hp | 2002-10-26 16:03:12 +0200 (Sat, 26 Oct 2002) | 3 lines r52595 | hp | 2002-04-22 03:19:06 +0200 (Mon, 22 Apr 2002) | 9 lines r51804 | hp | 2002-04-03 14:08:47 +0200 (Wed, 03 Apr 2002) | 2 lines r51770 | hp | 2002-04-03 03:22:14 +0200 (Wed, 03 Apr 2002) | 3 lines r43216 | ro | 2001-06-11 22:36:56 +0200 (Mon, 11 Jun 2001) | 18 lines
You can see that the revision numbers for the file are not consecutive. In fact, the revision number identifies the atomic commit in which the file was changed. To see the whole commit as a diff, you can use svn diff from the toplevel (or specifying the URL), so to catch all changes in that commit:
$ svn diff -r52594:52595 svn://gcc.gnu.org/svn/gcc/trunk
If you were to work without a VCS, a branch would be just a copy of your project sitting in a different directory where you carry out some different work. In SVN, things are just like that: a branch is *just a different directory* in the repository. When you branch a tree, you just *copy the whole tree to a different directory* and commit it there. While this might sound crazy at first, it makes a lot of sense when you realize that a copy is a very cheap (constant-time) operation O(1) even for large trees), which preserves the whole history of the file. You may think of a subversion-copy of a tree or file like a hard-link (but when a copied file is modified, the hard link analogy is no longer accurate because both files will diverge).
When you look at the log of a copied/branched file, you'll see at some point copied from [path] but the history will not be interrupted.
Of course, copying the whole GCC tree does not need to be done locally. If you understood the paragraph above about URLs, you'll see how this can be done with a totally remote operation. For instance, the following command creates a new branch from the current HEAD:
$ svn cp svn://gcc.gnu.org/svn/gcc/trunk \ svn://gcc.gnu.org/svn/gcc/branches/reload-rewrite
When doing such a command, of course, there is no separate commit step, so SVN will prompt for a commit message (via the configured editor, or you can supply it on the command line).
By convention, all branches are stored in the repository directory /branches
Tags are *no different from branches*! Again, a tag is a just a copy of the whole tree at a given point in the history. It's just that nobody will modify that copy, so that it'll become an easily accessible snapshot of the tree. To tag the trunk (HEAD), you use svn cp exactly like you do to create branch. By convention, all tags are stored in the repository directory /tags
An example of tagging the current HEAD as release:
$ svn copy -m 'tagging release' \ svn+ssh://gcc.gnu.org/svn/gcc/trunk \ svn+ssh://gcc.gnu.org/svn/gcc/tags/release-gcc-4.1 Committed revision 63490.