SVN

I've written about the differences between SVN and CVS and the usage of the plugins in Eclipse, see also 2006-02-01.

Importing your project

I found the svn import syntax a bit funny, but that might just be me. If you're importing from the commandline, do it like this:

  $ cd myproject
  $ svn import http://machinename/subversion/myproject

I'm assuming here that you use the http protocol to access the subversion protocol; most do it that way. However it's also possible to create a file-based repository. If the admin likes that better, then import as follows. (I'm assuming here the admin has given you the path /var/www/svn/repos but that might be different!)

  $ cd myproject
  $ svn import file:///var/www/svn/repos/myproject

Your source is NOT versioned yet. So, continue with something like:

  $ cd ..
  $ mv myproject myproject.backup
  $ svn checkout http://machinename/subversion/myproject
  or
  $ svn checkout file:///var/www/svn/repos/myproject

Check the result:

  $ cd myproject
  $ svnversion .

SVN should answer "I'm sorry Dave, I'm afraid I can't do that", or if it's in a better mood, "Revision 1".

Moving a directory from one repository to another

Private projects can grow bigger than expected. Such a project may be sitting in a private repository and to keep access rights simple, it's useful to move a growing project to its own repository. I assume a new repository has been created.

First export the directory from the old repository:

  $ svn export http://localhost/svn/my_private_repository/libs/lib_tool

What results is a directory called 'lib_tool', stripped of all evidence that it once resided in SVN.

Now import that same directory into the new repository called 'lib_tool'.

  $ cd lib_tool
  $ svn import http://localhost/svn/lib_tool

If the import was successful, delete the directory you just imported, and check out the fresh first version:

  $ cd ..
  $ rm -rf lib_tool
  $ svn checkout http://localhost/svn/lib_tool
  Checked out revision 1.

It's probably best to do an 'svn delete' in the old repository:

  $ svn delete -m "Moved to its own repository" \
       http://localhost/svn/my_private_repository/libs/lib_tool

Properties

There's a couple things that are easily done via Subversion's properties.

Execute bit

Store the executable bit of shell scripts or Python scripts or what have you:

 $ svn propset svn:executable ON somescript

Ignore files

Some files are generated and you don't want these in your status lists:

  $ svn stat
  ?      test/leon3
  ?      eqm/leon3

SVN does ignore files its own way, through a property. To ignore certain files, do:

  $ cd test
  $ svn propedit svn:ignore .

(An editor pops up, type in the filenames you want to have ignored, separated by enter)

  $ cd ../eqm
  $ svn propedit svn:ignore .
  $ cd ..
  $ svn commit -m "Added Eclipse project files to ignorelist" .

Note that propedit has a -R switch to make the property recursively in all subdirectories.

Put the revision/author/et cetera in a file

SVN also uses properties for keyword substitution. Enter any of $Date$, $Revision$, $Author$, $HeadURL$ or $Id$ in your file and then do:

  $ svn propset svn:keywords "Revision" filename
  $ svn commit -m "Added property for keywords" filename

When you want to specify, or add, additional keywords, separate them with a space:

  $ svn propset svn:keywords "Date Author" filename

To remove:

  $ svn propdel svn:keywords filename
  property 'svn:keywords' deleted from 'filename'.

From CVS to SVN

If you want to move from CVS to SVN, there are all sorts of fancy tools. What I did for small projects was the following:

  $ for i in `find . -type d -name CVS`; do svn delete $i; done
  $ svn commit -m "Deleted superfluous CVS directories"

Undeleting (resurrecting) a file or directory

Undeleting before committing

If you deleted a file with "svn rm", but haven't committed yet, then just use the "revert" command.

  $ svn stat
  D       instrumentstateview.py
  D       instrumentstate.py
  A  +    parameterpresetsview.py
  $ svn revert instrumentstate.py
 Reverted 'instrumentstate.py'

Voila...

Undeleting after committing

To undelete (or as the documentation says, "resurrect") a file or directory, use the copy command, together with the exact revision.

In the example below, I had to get back the directory "shamroc" from the trunk. First, I needed to find it:

  $ svn log -v http://subversion.example.org/svn/ed_software/trunk | less

Searching for the directory name, I found I deleted it in revision 3128. So I need the copy of that directory in revision 3127. First, I check out the current copy of trunk:

  $ svn co http://subversion.example.org/svn/ed_software/trunk

Then I change into the directory, and copy the deleted directory into the current directory. Then it's possible to commit the change:

  $ svn copy ^/trunk/shamroc@3127 ./shamroc
  .....
  .....
  A    shamroc/scripts/adc_styx/linearitybuf.pl
  A    shamroc/scripts/adc_styx/noise1.pl
  A    shamroc/scripts/adc_styx/steps.pl
  A    shamroc/scripts/adc_styx/dacadctest.pl
  A    shamroc/scripts/adc_styx/boardpower.pl
  Checked out revision 3127.
  A         shamroc
  $ svn ci -m "Resurrected the shamroc directory"
  Adding         shamroc
  Committed revision 3783.
  $

Rolling back changes

Suppose you inadvertently made changes in some files some time back. You can examine revisions of files with

 $ svn log ccsds.h

You think that the correct revision was 205. So you diff it:

 $ svn diff -r 205 ccsds.h

You then decide that yes, you want to roll back the file to that revision to the current revision 270 and then commit it. You can specify the current revision as a number (270 in my case), or just type HEAD (yes, all capitals), which SVN will understand:

 $ svn merge -r HEAD:205 ccsds.h
 U  ccsds.h
 $ svn commit -m "Rolled back inadvertent changes"
 Transmitting file data ....
 Sending       ccsds.h
 Committed revision 271.

Done!

Note: if you just want the previous version back, you can use the keyword PREV instead of the specific revision; i.e.

 $ svn merge -rHEAD:PREV ccsds.h
 --- Reverse-merging r271 through r205 into 'ccsds.h':
 U    ccsds.h

For some reason, I've had situations where it doesn't work, and instead SVN would say "so-and-so.h is not under version control"; I'd then revert the whole directory if the action was just deleting of files, but first do a dry run:

$ svn merge --dry-run -rHEAD:PREV .

And then if the output looks good, leave out the --dry-run option.

Rolling back an inadvertent commit

Darn, you accidentally committed some changes! Your last command looks as follows:

 $ svn ci -m "Checking in one file"
 Sending        gui/src/file1.cpp
 Sending        python/mylib/file2.py
 Sending        python/test_scripts/file3.py
 Sending        python/test_scripts/file4.py
 Transmitting file data .......
 Committed revision 3250

You only wanted to commit file1.cpp. To revert the others, issue the following command for each file:

 $ svn merge -r HEAD:PREV python/mylib/file2.py
 $ svn merge -r HEAD:PREV python/test_scripts/file3.py
 $ svn merge -r HEAD:PREV python/test_scripts/file4.py
 $ svn ci -m "Reverting inadvertently commited files"
 Sending        python/mylib/file2.py
 Sending        python/test_scripts/file3.py
 Sending        python/test_scripts/file4.py
 Transmitting file data .......
 Committed revision 3251

If you want to continue working on the files you had, just merge them again from PREV to HEAD:

 $ svn merge -rHEAD:PREV python/mylib/file2.py
 $ svn merge -rHEAD:PREV python/test_scripts/file3.py
 $ svn merge -rHEAD:PREV python/test_scripts/file4.py

...and continue hacking away without bothering anyone. The situation is now as it was before. Note that a simple "svn stat" will display the status "Modified" correctly, like when you did before the first (erroneous) commit.

Current revision

If you are in a source tree and you want to know what revision is there, just type:

 $ svnversion .

To incorporate this into your source files, check out this FAQ.

Creating a tag

Sometimes, you do a release of a particular directory in your repository, and you want to re-create that release later. For instance, you have the repository my_department, in which you have the subdirectory trunk/embedded_software/superwidget.

You don't want to branch the whole trunk but rather, you want to mark that subdirectory so you can always refer to it later. This is called tagging. Technically, it's the same as branching in SVN, but we use it somewhat differently.

Move into your repository directory. A possible layout could be:

 $ cd my_department
 $ ls
 trunk/
 branch-version-1.0
 branch-version-1.1

Then create a tags subdirectory:

 $ mkdir tags

Then tag a subdirectory deep in your trunk:

 $ svn copy trunk/embedded_software/superwidget \ 
            tags/superwidget-release-client-CompanyInc
 A   tags/superwidget-release-client-CompanyInc
 $ svn ci -m "Added tag for specific release for client Company Inc."
 Adding tags/superwidget-release-client-CompanyInc
 Committed version 12355.
 $

You don't actually need to be in the repository directory for the copy action; it can work with URLs as well. For more info, check the SVN manual on svn copy.

Creating a new branch afterwards

Sometimes, you start working on a revision to find out later that your work is huge and others have been committing all the time. In hindsight, creating a new branch would've been a good idea. To correct this, first you'll want to create a patch of all your changes:

  $ cd your/current/project/directory
  $ svn diff > ~/hackwork.patch

Then find out what revision you are hacking:

  $ svnversion .
  590M

Now create a separate branch of the original version:

  $ svn copy http://subversion.company.com/svn/telis/tuce \
    http://subversion.company.com/svn/telis/tuce-branch-gron \
    -m "Creating separate branch for work outside integration efforts"
  Committed revision 606.

Go to another directory, in my case ~/workspace

  $ cd ~/workspace
  $ svn co http://subversion/svn/telis/tuce-branch-gron
  $ cd tuce-branch-gron

And now integrate your changes again, and commit them in the branch:

  $ patch -p 0 < ~/gron.patch
  patching file tdb/mysql-telis.sql
  patching file client/python/plotffo.py
  ... lines removed ...
  $ svn commit -m "Fixes made on colors in FFO plot, conversion housekeeping \
  macro different, conversion FFO plot corrected"

Updating a branch with the latest changes to the trunk

This happens when you're done hacking away on your branch, and now you want to test your changes together with the latest work on trunk.

First, in your branch its working directory, find out the revision that you started your branch:

 $ svn log --verbose --stop-on-copy | tail -20
 [ removed some stuff here ]
 ------------------------------------------------------------------------
  r4559 | yourusername | 2014-01-10 10:34:24 +0100 (Fri, 10 Jan 2014) | 2 lines
 Changed paths:
    A /branches/branch_lorem (from /trunk:4558)Branch for working on lorem ipsum.
------------------------------------------------------------------------

OK, it's revision 4559 that we branched. Then, see what would happen if you merged your trunk back into your branch:

 $ svn merge --dry-run -r4559:HEAD https://your_repository/svn/trunk/

A list of changed/deleted files and directories will follow. If you're satisfied, repeat the above command without the --dry-run option, compile and do your tests.

Merging a branch back into the trunk

Go to your branch directory and find out the last revision:

  $ svn log --verbose --stop-on-copy | tail -20
  ...
  ...
  r608 | bartvk | 2007-02-02 12:20:55 -0100 (Fri, 02 Feb 2007) | 1 line
  ..
  ..

It should also show the comment that you entered when you created a branch then committed.

Go to the trunk directory and make sure everything's committed. Then find out the current revision with

  $ svnversion .
  668

Then see what would happen with a dry-run merge:

  $ svn merge --dry-run -r 608:668 http://subversion/svn/mybranch/

You'll see a whole list of modifications, additions, deletions and probably lots of conflicts (beginning the line with a capital C).

Now merge the whole branch into the trunk:

  $ svn merge -r 608:668 http://subversion/svn/mybranch/

Check what files are conflicted:

  $ svn status | grep "^C"

For each file:

When all conflicts have been merged, you can compile and test your code. If everything's well, you can do a commit, remove the old branch and commit again:

  $ svn commit -m "Merged branch xxx into trunk with changes x, y and z"
  $ svn delete mybranch
  $ svn commit -m "Removed branch xxx"

Seeing which files changed between revisions

Sometimes you need to revisit a particular change. So you type 'svn log' to see which revision you made the change. You spot that you made the change in revision 45. To see what files changed from revision 44 to 45, do:

  $ svn diff -r44:45 --summarize
  M      themes/Outlet/style/style.css
  M      includes/meta.php
  M      modules/Outlet/language/lang-dutch.php
  M      modules/Outlet/index.php
  M      modules/Outlet/functions.php
  A      issues/keywords_issue.sql

Creating a new project

To create a new project, there are two options:

  1. You access SVN via the svn+ssh option, or purely locally (on a multi-user server). You have local accounts that have access to the SVN repository. In this method, you'll need to set the UNIX rights for all developers.
  2. All access goes through Apache. Developers do not have local accounts, instead they authenticate via HTTP authentication. You need to take care of rights via the dav_svn.conf file, located in the /etc/apache2 (or /etc/httpd) directory.

Access SVN via local accounts

I'm assuming your SVN directory is /var/www/svn. This directory should have owner apache, group dev (or some other common development group), and rights set to rwxrwsr-x (octal 4775).

Anyone in the development group can then create a project with:

 $ svnadmin create /var/www/svn/newprojectname

Depending on the umask of the creating user, the directory will probably have to be made writeable for the group:

 $ chmod g+w /var/www/svn/newprojectname

After this, other developers can check out and update the new project:

 $ svn co file:///var/www/svn/newprojectname
 Checked out revision 0.

Access via HTTP

Check out which file is used to specify username/password, and the list of projects (with their associated usernames). This can be found in the Apache configuration file. For Debian, this is /etc/apache2/mods-enabled/dav_svn.conf. The option AuthUserFile specifies the username/password file. The option AuthzSVNAccessFile specifies who has access to which project.

Then the Subversion repository can be created with:

 $ svnadmin create /var/www/svn/newprojectname

And Apache needs read/write access. For Debian, this is done with a quick:

 $ chmod -R www-data:www-data /var/www/svn/newprojectname

Adding usernames to SVN

Subversion has a couple of authentication backends, personally I use accounts separate from the normal UNIX accounts. Thus, the authentication is handled via the webserver Apache. This involves a htpasswd file somewhere outside of the SVN directory, such as /var/www/private. The file can be edited with the htpasswd utility. Apache has to be configured as such, on CentOS this would mean creating a file below /etc/httpd/conf.d with the following contents:

 <Location /svn>
    DAV svn
    SVNParentPath /var/www/svn
   #SSLRequireSSL
   AuthType Basic
   AuthName "My Very Special Subversion"
   AuthUserFile /var/www/private/htpasswd
   Require valid-user
 </Location>

Code review on a branch

When you're asked to do a code review on a branch, basically you'll want to check out the branch, then see all changes since the branch:

After checking out the branch, find the first revision since branching:

 $ svn log --verbose --stop-on-copy | tail

For an overview file-wise:

 $ svn diff --summarize -r3391:HEAD  | less

Then, review each file with:

 $ svn diff -r3391:HEAD path/to/the/filename.c

Or, as one big diff:

 $ svn diff -r3391:HEAD | less

Switching server or connection method

If the Subversion repository is moved from one server to another, you can easily switch to the new one without doing a complete download, or a checkin/checkout first. Type "svn info" in your project directory to find the old URL. The new URL should be supplied by your friendly neighborhood sysadmin. Then type:

 $ svn switch --relocate http://oldmachine/svn/projectname/trunk \
    http://newmachine/svn/projectname/trunk

The same command can be used to switch the way you connect to the server. To start using SSH, do something like:

 $ svn switch --relocate http://domainname/svn/projectname/trunk  \
    svn+ssh://domainname/var/www/svn/projectname/trunk

Of course, this assumes you have an SSH account on the box.