UPL Subversion Tutorial

Spring 2009

Introduction

Subversion (SVN) is currently one of the most popular version control systems. Version control systems make it easier for people to manage projects, helping you and your partner(s) make changes that do not conflict with each other and letting you keep older versions of your project around in case anything goes wrong. SVN stores all files in a central repository, from which you will check out and check in ('commit') changes. The repository will contain all of the versions of your project in a compact way, only storing the changes between files. It also keeps track of who made what change, so you know who to talk to if something goes wrong. This tutorial will cover the basics of using SVN.

Initial Setup

Create a directory ~/svndemo to hold the files that will be made over the course of the demo.

Creating a Repository

cd ~/svndemo
svnadmin create repos
This will create the repository in ~/svndemo/repos. You can look inside to see what it contains but you shouldn't modify anything inside by hand. Now to give your partner permissions to edit what's in the repository, do
afs_rseta repos -acl PARTNER write
where PARTNER is your partner's login name.

Importing Files into the Repository

Suppose we already have some code that we want to import into the repository. Create a directory ~/svndemo/proj, and make a file called Main.java in it that contains the following:
public class Main
{
/* Nothing here yet */
}
Then to import the code, use
svn import proj file://$HOME/svndemo/repos/proj/trunk
where $HOME is your home directory.

This will copy your code into the proj/trunk subdirectory within the repository. trunk/ will hold the main line of development for your project--it's just called that by convention. The name will become more apparent when we get to branching.

Most commands that affect the repository as opposed to the working copy will require URLs like the above.

You will probably get tired of typing all that every time, so create an environmental variable that will contain your project inside the repository. The code for this depends on what shell you're running.

For bash:
export RPPROJ=file://$HOME/svndemo/repos/proj
For tcsh:
setenv RPPROJ file://$HOME/svndemo/repos/proj
Now you can access things by using that variable instead. For example, the above import statement could be rewritten as:
svn import proj $RPPROJ/trunk
Now that we have put our project into the repository, we no longer need the previous project dir, so delete it:
cd ~/svndemo ; rm -rf proj
And now we check out a working copy:
svn checkout $RPPROJ/trunk wc
wc will now contain a copy of the current revision of the project. Take a look:
cd wc
ls -A
Note the new directory, .svn/ which contains some metadata used by Subversion.

Adding and Removing Files

Create a file called Other.java in your working copy that contains:
public class Other
{
void method1()
{
}

void method2()
{
}

void method3()
{
}
}
Also create a file called Extra.java -- its contents are unimportant.
Now add those two files to the repository:
svn add Other.java Extra.java
Look at the status:
svn status
This shows you the status relative to what you checked out (the BASE revision). You can see that those two files have been added (or, actually, will be added as of next commit). To actually commit the changes to the repository, type:
svn commit
This will actually commit the changes to the repository.
To update your working copy to the newest version in the repository (the HEAD revision), type:
svn update
To remove a file, use svn delete:
svn delete Extra.java
Check out what's happening again, and then commit:
svn status
svn commit

Committing a file will prompt you for a log message--this is merely a textual description of the changes you have made. Try to make these log messages brief but meaningful. You can view the log messages with the svn log command:

svn log

You can also restrict log information to a specific file. By default, log will only show the first line of each log message--this is the reason why you should be brief. If you pass the -v flag, however, the entire log message will be shown, along with the files added/modified/deleted lines that Subversion generates.

A Second Working Copy and Automatic Conflict Resolution

You and your partner will have separate working copies. What happens if you both make changes at the same time?

To demonstrate, first checkout a second working copy:
cd ~/svndemo
svn checkout $RPPROJ/trunk second_wc
And then change something in the first working copy. Add something to the first method of Other.java and commit the change. Now, look at the second working copy.
cd second_wc
cat Other.java
Naturally, this will not contain the changes you just committed. Add something to the third method of Other.java, and attempt to commit.
svn commit
which displays:
svn: Commit failed (details follow):
svn: Out of date: '/proj/trunk/Other.java' in transaction '4-1'

Your working copy is out of date. Update to get the newest (svn update).

Notice the G by Other.java. That indicates that there were conflicts but they were automatically merged. To verify this, look at Other.java. You can see that both sets of changes are in there. Now commit the merged version back to the repository (svn commit), go to your first working copy (~/svndemo/wc) and update.

Manual Conflict Resolution

What if both you and your partner modify the same piece of code? In that case, Subversion cannot automatically merge the files and you will have to do that yourself. To demonstrate, add a line to Main.java in your first working copy and commit. Then add a different line to Main.java in your second working copy and update. You will see a C by Main.java which indicates a conflict Subversion wasn't able to resolve.

If you do an ls, you will notice a couple of new files: one representing Main.java as it was in the BASE version (such as Main.java.r4); one representing Main.java as it is in the HEAD (current) version (such as Main.java.r6), and one representing the Main.java that was in your working copy (Main.java.mine). Main.java itself will become a merge file that will look something like this:

public class Main
{
<<<<<<< .mine
/*Line 2*/
=======
/*Line 1*/
>>>>>>> .r6
}

The lines between <<<<<<< .mine and ======== were in the .mine version (what was in your working copy) that are no longer in the .r6 version. The lines between ======= and >>>>>>> .r6 are lines that are in the .r6 version that were not in the .mine version. You will have to merge the changes yourself.

Once you have done that, you must tell Subversion that you have resolved the conflict before it lets you commit. This is a safety feature, so you won't accidentally commit a merge file thinking it's just a regular file.

svn resolved Main.java

And then commit.

Other Commands

Some other useful commands are svn copy, svn move and svn mkdir. The syntax is

These work much like their Unix equivalents, except for two things: they automatically add/remove the files from the repository and keep track of the changes (i.e. you'll be able to tell Main2.java was copied from Main.java). Also, they can work directly on the repository if you give them repository URLs instead of local paths. In that case, they will make the change and automatically commit it (much like svn import did). For example, type:

svn mkdir $RPPROJ/branches

and it will automatically create the directory and ask you for a log message. We'll be using this directory further down.

If you make a mistake in your working copy and want a file restored to the state it was when you checked it out, use svn revert. The syntax is

for reverting individual files or entire subtrees. This does not actually contact the repository; the original versions are restored from the .svn/ directories in your working copy.

svn diff will show the difference between two files in two different revisions. There are multiple ways of invoking it:

The first will compare the file in that URL in the revision you give against the most current revision (the HEAD revision). The second will compare the file in the first revision you give against the same file in the second revision you give. The third will compare two different files, and the fourth will do the same thing in  a different revision. You can also diff directory trees.

svn ls will show you the contents of a subdirectory in the repository. svn cat will show the specified file in the repository. You can pass a revision argument (-r REVISION) to these as well to show earlier versions. The syntax is

To see a line-by-line breakdown of a file annotated with what version it came from and who made that change, you can use svn blame FILE. If that sounds too negative, you can also use svn praise FILE to do the same thing. It will look somewhat like this:

    27     abrown         {
30 matyas off_t recover_offset; // the offset in the recovered data from
47 abrown // which the read data starts
30 matyas recover_offset = readStart - badBlockStart;
30 matyas size_t bytesToRecover = ((recover_offset + bytesRead) > BLOCK_SIZE ?
47 abrown BLOCK_SIZE - recover_offset
47 abrown : bytesRead);
30 matyas strncpy((char*)buf, &recoveredData[recover_offset], bytesToRecover);
27 abrown }

You can see that the first column shows what revision that line came from and the second column shows who made that revision.

Revision Specifiers

So far we have been specifying what revision to use just by revision number. That is fine for now, but as a project gets larger and older, our revision numbers will grow to be in the hundreds or thousands, and you won't be able to remember what change occurred in what revision. There are other ways of specifying revisions: pre-defined keywords and by date.

HEAD
The most current version in the repository
BASE
The revision that your working directory is based on. If you last updated your working copy to revision 3 (or just checked out revision 3), BASE will be 3.
PREV
The previous revision.
COMMITTED
The last revision you committed from your working copy.

You can also specify a revision by putting a date/time between brackets, like so: -r {2009-04-06 10:30}. Subversion accepts many variations on this--if you do not specify a date, it will assume the current day. If you do not specify a time, it will assume midnight. This will give you the latest revision before the given date. For example, if I had committed revision 3 at 10:25 on 04/06 and revision 4 at 10:31 on 04/06, then the above date will give me revision 3.

Finally, you can specify revision ranged by separating revisions with a colon, as in -r 5:7. Since you often want to see what the latest changes were, you can use -c as a shorthand for -r PREV:HEAD.

Branching

In general, it is good practice for the main line of development of a project, i.e. what's in the proj/trunk/ subtree to be as stable and complete as possible. But what if you want to make some major changes that, until complete, would pretty much break the program, but still want your changes under version control? The solution is to create a copy of the project that has your changes only. This copy is called a branch--there is the explanation for why the main line is called the trunk, and why we created the branches directory a few pages up.

To create a branch, we merely use the svn copy command. For example:

svn copy $RPPROJ/trunk $RPPROJ/branches/custom

Be sure to enter in your log message that this was a branch.

Then we check out a new working directory based on the contents of the custom branch:

svn checkout $RPPROJ/branches/custom custom_wc

The files in your branch share a common history with the files in your trunk (at least up until you created the branch), and svn log will show this. If you only want to see the changes you've made in this branch, pass svn log the --stop-on-copy flag.

Now any changes you make to the branch will not mess up what's in the trunk.

As a side note, svn copy does not literally copy files, it just makes a note that new files were made based off of the old ones. If you are familiar with Unix hard links, the idea is pretty much the same. SVN only tracks changes after all. So you don't need to worry about using up disk space when you make a branch.

You can also make tags, which are similar to branches but are static copies. By convention, these are kept in the proj/tags/ subtree. You make a tag when you want to mark a specific revision as important. For example, if you decide to call revision 109 your 'version 1.0' that you release to the public, you would do

svn copy $RPPROJ/trunk -r 109 $RPPROJ/tags/version-1.0

so you would always know which revision 'version-1.0' refers to. Mechanics-wise, this is exactly like a branch, except you aren't supposed to modify it. SVN does not actually distinguish the two.

Merging

Suppose you are busy making changes to your custom branch, while your partner is doing bugfixes in the trunk, and you would like to incorporate your partner's changes into your version. You would use the svn merge command to do this. This is a fairly confusing part of SVN, so you must be careful (and be ready with the revert command). Suppose your partner's bugfixes in the trunk occurred between revision 35 and revision 39. To incorporate these changes into your branch, go to your branch's working copy and do

svn merge $RPPROJ/trunk -r 35:39 .

This will add the changes to your working copy, as local modifications. This is important: SVN has no way of distinguishing between you using merge and you making the changes by hand, so you must be sure to mention that in your log entries!

Perhaps a more accurate name for merge is diff-and-apply: it does a diff (see svn diff above) of the two revisions specified and applies the change to your working copy.

Once you are done with your branch and you want to incorporate your changes into the trunk, check out a working copy for the trunk, and run:

svn merge $RPPROJ/branches/custom $RPPROJ/trunk .

Resolve any conflicts that may have caused and commit the changes. Again, be sure to note in your log message what you did!

Resurrecting Deleted Files

To resurrect files that were deleted in an earlier revision, you can check out the earlier revision using svn checkout -r REVISION, as we demonstrated above. You can't make changes to that revision and check them back in, however, but you can simply copy those files to an updated working copy (or merge them with an updated working copy) and add them back in. You can also give a revision specifier to svn copy. For example, if you are in a current working copy, do this:

svn copy -r 2 $RPPROJ/trunk/Extra.java Extra.java

and you will bring back Extra.java.

SVN on Windows

So far we have been working on Unix systems. Now, we will briefly go over using SVN on Windows. The best way is to install the tool TortoiseSVN from tortoisesvn.net.

TortoiseSVN is an Explorer extension, and it will add commands to your right-click menu. To create a repository, first create the directory it will reside in. Then, open up that directory, right-click and choose TortoiseSVN -> Create a repository here. 

To import a folder, right-click on it and choose TortoiseSVN -> Import. It will ask you for the URL of the repository to import into, and a log message.

To checkout from a repository, right-click and choose SVN Checkout. Enter the URL of the repository and where you want the working copy placed.

A working copy in TortoiseSVN looks like this:

The checkmark on file1.txt indicates that it is up-to-date. The question mark on file2.txt indicates that it's unknown. To add it to the repository, right-click on it and hit TortoiseSVN -> Add... which will schedule the file for addition on next commit. Note that the question mark has changed to a plus sign.

Right-click and choose SVN Commit to commit the changes. It will ask you for a log message again.


If a file has been modified, the overlay will turn into an exclamation mark to indicate that.

The interface is really self-explanatory and most of the commands work the same way as they did in the command line. A couple of things to note:
If you do an update and there is a conflict, your working copy will end up looking like this:

If you right-click on the conflicted file and choose TortoiseSVN -> Edit conflicts, you will get a nice GUI editor where you can resolve everything easily.

Then right-click and choose TortoiseSVN -> Resolved... to mark the changes as resolved. It will automatically delete the .r* files that update made.
Choosing TortoiseSVN -> Repo-browser will bring up a nice repository browser that can show current and past states of the repository.

Accessing Repositories Remotely

So far all our work has been on local file systems. However, you can access SVN repositories remotely via many ways as well. SVN has its own server program, or you can use an Apache module, or use SSH, that last one needing the least setup. To use SVN via SSH, you change the URL that you use for the repository so that it uses the svn+ssh protocol instead of the file protocol. You also need to give it a username and machine name to SSH into. For example, if I had a repository at ~/svndemo/repos, to get to it I would use the URL svn+ssh://matyas@mumble-01.cs.wisc.edu/u/m/a/matyas/svndemo/repos. It will ask you for your password--probably multiple times if you're using it with CSL machines--that is a known problem.

Useful Websites

http://svn.tigris.org
This is the SVN website. Here you will find downloads, plus links to plugins that will allow you to use SVN more easily with KDE, Eclipse, and other environments.
http://svnbook.red-book.com
This is the SVN documentation. Note that at the time of this writing, we have one of the 1.4 versions installed at the CSL, so you might want to take a look at older docs.
http://tortoisesvn.net
The homepage for TortoiseSVN.
http://www.upl.cs.wisc.edu/tutorials
Where you are probably reading this tutorial from.