Best Practices
**************

Tips to use Subversion more effectively.

In this chapter, we'll focus on how to avoid some pitfalls of version control systems in general and Subversion specifically.

Source code formatting
======================

Subversion diffs and merges text files work on a line-by-line basis. They don't understand the syntax of programming languages or even know when you've just reflowed text to a different line width.

Given this design, it's important to avoid unnecessary reformatting. It creates unnecessary conflicts when merging branches, updating working copies, and applying patches. It also can drown you in noise when viewing differences between revisions.

You can avoid these problems by following clearly-defined formatting rules. The Subversion project's own HACKING (http://svn.collab.net/repos/svn/trunk/HACKING) document and the Code Conventions for the Java Programming Language (http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html) are good examples.

Tabs are particularly important. Some projects, like Subversion, do not use tabs at all in the source tree. Others always use them and define a particular tab size.

It can be very helpful to have an editor smart enough to help adhere to these rules. For example, vim can do this on a per-project basis with .vimrc commands like the following:

autocmd BufRead,BufNewFile */rapidsvn/*.{cpp,h}
    setlocal ts=4 noexpandtab
autocmd BufRead,BufNewFile */subversion/*.[ch]
    setlocal sw=2 expandtab cinoptions=>2sn-s{s^-s:s

Check your favorite editor's documentation for more information.

When you have to reformat
-------------------------

In the real world, we're not always so perfect. Formatting preferences may change over time, or we may just make mistakes. There are things you can do to minimize the problems of reformatting.

These are good guidelines to follow:

  * If you're making a sweeping reformatting change, do it in a single commit with no semantic changes. Give precise directions on duplicating formatting changes.

  * If you've made semantic changes to some area of code and see inconsistent formatting in the immediate context, it's okay to reformat. Causing conflicts is not as great a concern because your semantic changes are likely to do that anyway.

Here's an example of a sweeping reformat:

     $ svn co file:///repo/path/trunk indent_wc
     $ indent -gnu indent_wc/src/*.[ch]
     $ svn commit -m 'Ran indent -gnu src/*.[ch]' indent_wc

This follows all rules: there were no semantic changes mixed in (no files were changed other than through indent). The indent commandline was given, so the changes can be very easily duplicated. All the reformatting was done in a single revision.

Let's say these changes occurred to the trunk at revision 26. The head revision is now 42. You created a branch at revision 13 and now want to merge it back into the trunk. Ordinarily you'd do this:

     $ svn co file://repo/path/trunk merge_wc
     $ svn merge -r 13:head file://repo/path/branches/mybranch merge_wc
     ... # resolve conflicts
     $ svn commit -m 'Merged branch'

But with the reformatting changes, there will be many, many conflicts. If you follow these rules, you can merge more easily:

     $ svn co -r 25 file://repo/path/trunk merge_wc
     $ svn merge -r 13:head file://repo/path/branches/mybranch merge_wc
     ... # resolve conflicts
     $ indent -gnu src/*.[ch]
     $ svn up
     ... # resolve conflicts
     $ svn commit -m 'Merged branch'

In English, the procedure is:

  * Check out a pre-reformatting trunk working copy.

  * Merge all branch changes. Fix conflicts.

  * Reformat in the same manner.

  * Update to the head revision. Fix conflicts.

  * Check in the merged working copy.

Ignoring whitespace differences
-------------------------------

When viewing differences between revisions, you can customize svn diff output to hide whitespace changes. The -x argument passes arguments through to GNU diff. Here are some useful arguments:

-b
     Ignore differences in whitespace only.

-B
     Ignore added/removed blank lines.

-i
     Ignore changes in case.

-t
     Expand tabs to spaces to preserve alignment.

-T
     Output a tab rather than a space at the beginning of each line to start on a tab stop.

The commit emails always show whitespace-only changes. commit-email.pl uses svnlook diff to get differences, which doesn't support the -x option.

Line endings
------------

Different platforms (Unix, Windows, MacOS) have different conventions for marking the line endings of text files. Simple editors may rewrite line endings, causing problems with diff and merge. This is a subset of the formatting problems.

Subversion has built-in support for normalizing line endings. To enable it, set the svn:eol-style property to ``native''. See Properties.

When you commit
===============

It pays to take some time before you commit to review your changes and create an appropriate log message. You are publishing the newly changed project anew every time you commit. This is true in two senses:

  * When you commit, you are potentially destabilizing the head revision. Many projects have a policy that the head revision is ``stable'' --- it should always parse/compile, it should always pass unit tests, etc. If you don't get something right, you may be inconveniencing an arbitrary number of people until someone commits a fix.

  * You can not easily remove revisions. (There is no equivalent to cvs admin -o.) If you might not want something to be in the repository, make sure it is not included in your commit.  Check for sensitive information, autogenerated files, and unnecessary large files.

If you later don't like your log message, it is possible to change it. The svnadmin setlog command will do this locally. You can set up the tweak-log.cgi (http://svn.collab.net/repos/svn/trunk/tools/cgi/tweak-log.cgi) script to allow the same thing remotely. All the same, creating a good log message beforehand helps clarify your thoughts and avoid committing a mistake.

You should run a svn diff before each commit and ask yourself:

  * do these changes belong together? It's best that each revision is a single logical change. It's very easy to forget that you've started another change.

  * do I have a log entry for these changes?

Defining a log entry policy is also helpful --- the Subversion HACKING (http://svn.collab.net/repos/svn/trunk/HACKING) document is a good model. If you always embed filenames, function names, etc. then you can easily search through the logs with search-svnlog.pl (http://svn.collab.net/repos/svn/trunk/tools/client-side/search-svnlog.pl).

You may want to write the log entry as you go. It's common to create a file changes with your log entry in progress. When you commit, use svn ci -F changes.

If you do not write log entries as you go, you can generate an initial log entry file using the output of svn status which contains a list of all modified files and directories and write a comment for each one.

Binary files
------------

Subversion does not have any way to merge or view differences of binary files, so it's critical that these have accurate log messages. Since you can't review your changes with svn diff immediately before committing, it's a particularly good idea to write the log entry as you go.