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.
~/svndemo to hold the
files that will be made over
the course of the demo.cd ~/svndemoThis will create the repository in
svnadmin create repos
~/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, doafs_rseta repos -acl PARTNER writewhere PARTNER is your partner's login name.
~/svndemo/proj,
and make a file called Main.java in it that
contains the following:public class MainThen to import the code, use
{
/* Nothing here yet */
}
svn import proj file://$HOME/svndemo/repos/proj/trunkwhere
$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/projFor tcsh:
setenv RPPROJ file://$HOME/svndemo/repos/projNow you can access things by using that variable instead. For example, the above import statement could be rewritten as:
svn import proj $RPPROJ/trunkNow that we have put our project into the repository, we no longer need the previous project dir, so delete it:
cd ~/svndemo ; rm -rf projAnd now we check out a working copy:
svn checkout $RPPROJ/trunk wcwc will now contain a copy of the current revision of the project. Take a look:
cd wcNote the new directory,
ls -A
.svn/ which contains
some metadata used by Subversion.Other.java in your
working copy that contains:
public class OtherAlso create a file called
{
void method1()
{
}
void method2()
{
}
void method3()
{
}
}
Extra.java -- its
contents are unimportant.svn add Other.java Extra.javaLook at the status:
svn statusThis 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 commitThis will actually commit the changes to the repository.
svn updateTo remove a file, use
svn delete:svn delete Extra.javaCheck 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.
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 ~/svndemoAnd 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.
svn checkout $RPPROJ/trunk second_wc
cd second_wcNaturally, this will not contain the changes you just committed. Add something to the third method of Other.java, and attempt to commit.
cat Other.java
svn commitwhich 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.
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.
Some other useful commands are svn copy,
svn move and svn mkdir.
The syntax is
svn copy OLDFILE NEWFILEsvn move OLDFILE NEWFILEsvn mkdir DIRThese 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
svn revert FILENAMEsvn revert -R DIRNAMEfor 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:
svn diff URL -r REVISIONsvn diff URL -r REVISION1:REVISION2svn diff URL1 URL2svn diff URL1 URL2 -r REVISIONThe 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
svn ls URLsvn cat URLsvn ls URL -r REVISIONsvn cat URL -r REVISIONTo 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.
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.
HEADBASEPREVCOMMITTEDYou 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.
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.
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!
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.
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.











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.