SvnRev: A Utility for Subversion and CVS/RCS
SvnRev is a little program that writes the current revision number of a project into a C/C++ header file, a Java package file, a C# class file, or an Ant property file. This revision number is stored in constants (macros in the case of C/C++), both as a number and as a string. It gets the revision number from the "RCS keywords" in the source files, or (for recent versions of Subversion) from the "working copy" database. SvnRev is specifically designed for the Subversion version control system, but it can also be used with CVS and RCS.
SvnRev is a self-contained utility that does not rely on a particular IDE. SvnRev is a portable utility and should run on every environment on which a conforming C compiler is available. Our aim was to use it from a "makefile" and to attach to a version control system, and specifically to the Subversion system.
For full functionality, SvnRev must be compiled with SQLite. The source code for SQLite must be downloaded separately. The pre-compiled executables, for Microsoft Windows and Linux, are built with SQLite.
Why use SvnRev?
Computer programs have versions. Each component (DLL, ActiveX object, OLE server, embedded firmware, etc.) may have its own version. If the world were perfect, there should not exist two different components with the same version number. In practice, "stealth" upgrades happen, especially during the beta period. There are numerous different releases of MSVCRT20.DLL version 2.11.000, CTL3D.DLL version 1.0 and COMDLG32.DLL version 4.00 "out there". (And it would be foolish to think that this happens only to Microsoft.)
SvnRev comes of age. At the time SvnRev was first developed, the three files cited above could be found on many systems. Stealth upgrades still happen these days, though.
What is needed to distinguish the various components from each other is to attach a "revision number" to the version string of the component. When such a scheme is set up (and the revision number is part of the version number), developers no longer accidentally release an updated component with exactly the same version stamp as the previous release. When the version number is present in the "About" box and/or in the version resource, the user can quickly verify exactly what version he or she has, and when this is communicated to the developers, bugs might be quite a bit easier to reproduce.
In more popular terms: if you have ever asked a customer or a colleague "of what date is that component?", you should consider using SvnRev. Date and time stamps have built a reputation of being unreliable.
There are several utilities to maintain an automatically increasing build number for every compile. However, this has the disadvantage that there is no direct link between the build stamp and the version control. To get such a link, you would need to check in the file that holds the current build number into version control. In multi-developer groups, this machine-generated, frequently changing file, will become a nuisance for the version control system. Actually, you do typically not put machine-generated files in version control.
SvnRev uses a different approach: it queries the build number from the keywords that a version control system maintains in a source file. More specifically, SvnRev uses the "$Id:$
" keyword (and optionally two others). Subversion has the convenient property that it uses only a single revision number for an entire project, instead of a separate revision number per file. This is not the case with CVS and RCS; see the section "Using SvnRev with CVS & RCS" for details on using SvnRev with CVS/RCS.
SvnRev does assume that you commit your changes to version control before you send a product/update to someone. If SvnRev detects a difference, it adds a "modified flag" to two of the macros/constants that it generates. The reasoning behind this is that if your local copy of the source code contains changes that are not yet in version control, the revision number of the application/component that you sent is linked to the wrong revision number in Subversion. The number is still likely to be different from any earlier application/component that went out of the door (unless you use version control badly, or send out updates far too quickly), but finding the correct revision back may be hard or even impossible. Therefore: commit your changes first, then build the final release that you will deploy.
How to use SvnRev?
The first step is to ensure that the source file is in version control: obviously, a file that is not known in Subversion or CVS/RCS system will never get assigned a "revision numbers" by that system.
You need to adjust your makefile or project (or "solution") so that SvnRev runs over all source (and header) files, as part of the build process. This is easy to do in Unix or Linux, because of the powerful command shells and because of the versatile GNU make utility. Running svnrev *.c *.cpp *.h lets the shell expand the wildcards and have svnrev
run over all files. To get the same functionality under Windows, you must link the program with an additional (compiler-supplied) object file; see the build instructions.
SvnRev has the following options (grouped by purpose):
Option |
Description |
-jpackage |
The option -j writes a Java package file instead of a C/C++ header file. The name of the package must follow the option (the default package name is "com.compuphase"). |
-cnamespace.class |
The option -c writes a C# class file instead of a C/C++ header file. The name of the namespace and class must follow the option; namespace and class names are separated by a period (the default namespace/class string is "compuphase.svnrev"). |
-Oname |
The option -O writes an Oracle this option creates a Oracle package file instead of a C/C++ header file. The name of the package must follow the option (the default package name is "svnrev.sql"). |
-P |
The option -P writes a "property file" for Apache Ant instead of a C/C++ header file. The name file is "svnrev.property". |
-pfilename |
The option -p causes the fields AssemblyVersion and AssemblyFileVersion to be updated in the Assembly Property file (usually "AssemblyInfo.cs"). The -c should be set as well. |
-ofilename |
The option -o allows you to specify the name of the generated include file. The default name is "svnrev.h" for C/C++ mode, "SvnRevision.java" for Java mode and "SvnRevision.cs " for C# mode. When you add "-omydefs.h " to the command line (without the quotes), the utility creates the file "mydefs.h" instead. When you say -o without gluing a filename directly behind the option, the results get written to stdout . |
-i |
The option -i is for incremental running. This switch makes SvnRev scan the "svnrev.h" generated from a previous run, in addition to the (partial) set of source files. This switch may be important when using SvnRev in a makefile, see below. If you use the -o in addition to -i , SvnRev will look for the filename specified with the -o option instead of svnrev.h, SvnRevision.java or SvnRevision.cs. |
-fpattern |
The option -f adds a prefix or a suffix (or both) to the build number in the string constant SVN_REVSTR . The format for pattern is "prefix#suffix" (without the quotes) where the # symbol gets replaced by the build number and prefix and suffix are arbitrary strings. |
-mname |
The option -m changes the base name (or prefix) of the generated constants. By default all generated constants start with SVN_REV , but this can be changed with this option. |
-n |
The option -n makes SvnRev ignore line endings (CR versus LF versus CR+LF) when comparing the working copy to the base version. |
-v |
The option -v causes SvnRev to list the names of files that were modified after the last commit to version control on stderr (console output). |
In a makefile, you need to add a new target that runs over all the files. The generated include file needs to be updated only if a file changes. Most make utilities provide the automatic variable "$^
" that results in the list of prerequisite source files (also called "dependencies"), separated by space characters.
svnrev.h : main.cpp gui.cpp gui.h storage.cpp storage.h
svnrev $^
The GNU make utility and several others provide an automatic variable "$?
" that holds the names of all the prerequisites that are newer than the target, with spaces between them. By using this variable, svnrev
runs only over the modified files, which is, of course, quicker. In this situation, you will also need to tell SvnRev to run incrementally (the -i
option).
svnrev.h : main.cpp gui.cpp gui.h storage.cpp storage.h
svnrev -i $?
Next, include svnrev.h in any C/C++ source file and/or the .RC resource file and use the macros that SvnRev has generated. For Java and C#, use the corresponding output files (instead of svnrev.h).
SvnRev creates and updates the following macros/constants:
SVN_REV
- The number of the current revision.
SVN_REVSTR
- The same number as above, but encoded as a string, and possibly decorated with a prefix and/or suffix set with the
-f
option. When the letter "M" appears behind the number, one or more of the source files have uncommitted changes (meaning that the build number may not be a good representation of the current build); see also SVN_REVMODIFIED
, below. SVN_REVDATE
- The date of the revision, encoded as a string in the format YYYY-MM-DD.
SVN_REVSTAMP
- The date of the revision, encoded as an integer in the format YYYYMMDD. This format may be convenient when "using SvnRev with CVS & RCS".
SVN_REVMODIFIED
- Set to zero (0) if all source files are up-to-date in version control, and to a non-zero value if one or more of the source files have uncommitted changes. If there are non-committed changes, the value of the
SVN_REVMODIFIED
constant is incremented every time that SvnRev runs; it functions akin to a "build" number. As soon as all sources are committed to version control, the value is reset to zero. You can use this macro to conditionally print a warning message at the start of your program, for example. To find out which files have uncommitted changes, use the -v
option. When SvnRev is used with CVS or RCS, this value will always be zero.
The names of the constants may be different if the
-m
option is used on the command line of SvnRev. For example, with the option
-mAPP
, the constant with the revision date will be
APPDATE
instead of the default
SVN_REVDATE
.
An example of a generated file is:
#ifndef SVNREV_H
#define SVNREV_H
#define SVN_REV 4224
#define SVN_REVSTR "4224"
#define SVN_REVDATE "2011-02-22"
#define SVN_REVSTAMP 20110222L
#define SVN_REVMODIFIED 0
#endif
An example Java package file is:
package com.compuphase;
public interface SvnRevision
{
public final static int SVN_REV = 4224;
public final static String SVN_REVSTR = "4224";
public final static String SVN_REVDATE = "2011-02-22";
public final static long SVN_REVSTAMP = 20110222L;
public final static int SVN_REVMODIFIED = 0;
}
In your source files, you can now use these macros/constants where you would otherwise type a build or revision number. SvnRev defines both a numeric macro and a string macro so that you can take advantage of the string concatenation feature of the ANSI C preprocessor (also used in C++ and resource files).
Somewhere in your program, you will now use one or more of the macros to display the revision number. If you make a simple console program, you could, for example, print it in the usage information, like in the example below (which comes from the source code of SvnRev):
printf("svnrev 2.0." SVN_REVSTR "\n\n");
RCS Keywords
For "working copies" created by clients compiled against the Subversion libraries version 1.6 and earlier, there is another requirement: the source files should all contain "RCS keywords" and have the property "svn:keywords
". For "working copies" created by Subversion 1.7 clients, this step is optional.
How do you know whether a working copy is in the "1.7 format" or an earlier format? Go to the root folder of a project on your local hard disk. There will be a
.svn directory in it. Look inside the
.svn for a file called
wc.db; if it is present, this is the "1.7" format. For a summary of the changes, see the
Subversion 1.7 Release notes.
The RCS keywords should be present in all source files: header files as well the code files. A keyword looks like:
$Id$
The version control system will expand this to something like the line below:
$Id: svnrev.c 3116 2005-03-18 16:26:29Z thiadmer $
A C/C++ compiler does not like lines like the above in the source code, so typically a keyword is expanded in a comment, or in a string. As a more complete example, the code below is from the svnrev.c source file:
The following keywords are supported by SvnRev. Note that the "$Id:$
" keyword contains all the required information, so this is often the one that you will want to use in the comment that you have at the top of each source file.
$Id:$
- A standard header containing the RCS file name, revision number, date and time, author, status, and the login name of the user who committed the revision (or who has locked the file).
$Date:$
- The date and time the revision was checked-in.
$Rev:$
- The revision number assigned to the revision.
$Revision:$
- Another name for
$Rev:$
.
Furthermore, to expand keywords, each file must have the property "svn:keywords
", and this property must have the value "Date Id Rev"
(you may use "Revision" instead of "Rev"). You can add these to the files explicitly, or configure Subversion on your client to add them automatically; see below.
Configure Subversion for Automatic Keyword Properties
As stated before, each source file must have a property called svn:keywords
. Subversion clients allow you to set the properties of each file individually. The two screen grabs below illustrate setting the property to an appropriate value.
The file properties dialog for TortoiseSVN
The file properties dialog for eSvn
Apart from setting these fields explicitly for every file that you add, you may have Subversion set them automatically. This behaviour must be enabled in the Subversion "config" file on the client. You will find this file in the "Subversion" folder below the "Application Data" folder, in the user's profile. The file is called "config", without extension. If you are using TortoiseSVN, you can edit this file from TortoiseSVN's "Settings" dialog; for other clients, you may need to locate the file and load it into Notepad or another suitable editor. In the configuration file, you will be looking for the enable-auto-props
keyword and the [auto-props]
section (which are commented out by default). You need to remove the comment markers (the "#" character) and add the property svn:keywords=Rev Date Id
to selected file extensions. Below is a snippet from a config file after this adjustment.
[miscellany]
### Set enable-auto-props to 'yes' to enable automatic properties
### for 'svn add' and 'svn import', it defaults to 'no'.
### Automatic properties are defined in the section 'auto-props'.
enable-auto-props = yes
### Section for configuring automatic properties.
### The format of the entries is:
### file-name-pattern = propname[=value][;propname[=value]...]
### The file-name-pattern can contain wildcards (such as '*' and
### '?'). All entries which match will be applied to the file.
### Note that auto-props functionality must be enabled, which
### is typically done by setting the 'enable-auto-props' option.
[auto-props]
*.c = svn:eol-style=native;svn:keywords=Author Rev Id Date
*.cpp = svn:eol-style=native;svn:keywords=Author Rev Id Date
*.h = svn:eol-style=native;svn:keywords=Author Rev Id Date
*.dsp = svn:eol-style=CRLF
*.dsw = svn:eol-style=CRLF
*.sh = svn:eol-style=native;svn:executable
*.txt = svn:eol-style=native
*.png = svn:mime-type=image/png
*.jpg = svn:mime-type=image/jpeg
Using SvnRev with CVS & RCS
When you use CVS or RCS, SvnRev can extract the revision number and the date from the keywords as well. The CVS and RCS system use the same keywords as Subversion, except that they often use $Revision:$ instead of $Rev:$. There is no need to enable keyword expansion (per file) with CVS/RCS. In contrast to Subversion, keyword expansion is always done on a check-in (or "commit") --there is no need to configure file properties.
An important feature of Subversion is that it uses a single revision number for a complete project. CVS and RCS have a revision number per file. The SvnRev utility now has to make up a unique number that represents the revision for the complete set of files. SvnRev does this in the same way as the cvs2svn.py script: every commit of a file counts as a new revision of a project. In brief, this means that the revision numbers of individual files are accumulated. The total is the "project revision" number.
Unfortunately, this scheme does not allow a simple lookup of all revisions of the source files that are part of a particular project revision. That is, if a customer reports a bug related to revision "4351" of the complete application, this application consists of several source files that each have their own revision number --but none of these files will have revision "4351".
One option is to check in the generated svnrev.h file at selected milestones. Alternatively, you can use the revision date as the criterion for retrieving an older revision. The macro SVN_REVDATE
holds the date of the latest modification in a string format; the macro SVN_REVSTAMP
also has the date encoded in an integer, in YYYYMMDD format.
Did I not write earlier that date/time stamps are unreliable, whereas I am now suggesting that you rely on them? Well, yes, but... before I was talking about the date/time stamp of a
file. These are unreliable. The date stamp in the
SVN_REVDATE
is that of the commit, which is stored in the CVS repository and which can be relied upon.
Building SvnRev
The source is build to be portable, and I have successfully compiled it under Microsoft Windows using Borland C++ and OpenWatcom C/C++, and under Linux using GCC.
For Subversion version 1.7 clients, SvnRev has a dependency on SQLite. The "working copy" format of Subversion has changed significantly in version 1.7: and the meta-data for a working copy is now stored in a SQLite database, which means that SvnRev has to be able to read SQLite databases, too. You can optionally build SvnRev without SQLite, thereby supporting only working copies of Subversion clients up to 1.6. To compile without SQLite, add the definition NOSQL
on the command line when building SvnRev.
The shells under Unix and Linux expand wildcards in filenames to a list of all matching files. The command line shell of Windows is bare-bones when compared with BASH (and cousins), and it does not provide features like expanding the filenames with wildcards on the command line. Several Windows compilers provide an object file that you can link with your program that does the automatic command line expansion before calling your main()
function. This object file is setargv.obj for Microsoft Visual C/C++ and wildargs.obj for Borland C++. Other compilers may come with such a file too.
What about SubWCRev?
SubWCRev is a utility with a similar goal and operation as SvnRev. The "official" release SubWCRev runs only on Microsoft Windows, but there now exists a port of it for Linux: see svnwcrev.
The main difference is that SubWCRev gets the revision numbers from the "working copy" of the project, for all versions of Subversion. SvnRev has this capability only for version 1.7 (and later) of Subversion. (The "working copy" is a repository that holds all version-control information on the files in a directory). The advantages for using the working copy are that you do not need to add keywords to the source files and that you can also query the revision numbers of binary files, which do not contain keywords.
SubWCRev only returns the revision number of a single file or of a directory. The first option, returning the revision number of one file, is not very useful. However, the second option, returning the top revision number of a directory, has limitations as well. Many of our products can be built in multiple configurations, where each configuration adds a few specific source files. A change in one of the configuration-specific source files should not affect the build numbers of the other configurations. SvnRev gives you the (committed) build number of the exact set of files that you pass in. Therefore, different configurations of a product each have their own relevant revision number.
Another difference is that SvnRev also works with CVS/RCS (with limitations).
What about svnversion?
The "svnversion" utility is part of the standard Subversion distribution. Like SubWCRev, svnversion gets the version information from the working copy, so you do not need to add the RCS keywords. In addition, and unlike SubWCRev, svnversion is portable.
By default, svnversion returns the version number of the entire Subversion repository, which is not what you want if you have multiple projects in a single repository, but it can be told to give you the "committed" version number of a working copy. This value is comparable to what SvnRev gives you.
Similar to SubWCRev, svnversion only returns the version number of a directory, not that of a set of files. As was explained above, this is a significant disadvantage in projects that can be built in multiple configurations, where each configuration adds a few specific source files (a change in one of the configuration-specific source files should not affect the build numbers of the other configurations). Again, SvnRev gives you the (committed) build number of the exact set of files that you pass in.
Convenience of use in a build environment is another difference: svnversion cannot be told to write the version in a format that is immediately usable from a C/C++, Java or C# program. If you use svnversion, you will always need to write a script wrapper around it. SvnRev, on the other hand, is designed to be integrated in the "build chain".
References
- Subversion
The site for the Subversion version control system, with many links to server and client software, as well as books. (There are also tools and scripts to migrate from RCS/CVS or Visual SourceSafe to Subversion.)
- TortoiseSVN
A client for Microsoft Windows that integrates with Windows Explorer.
- eSvn
A multi-platform graphical client for Subversion; apparently no longer maintained.
- RapidSVN
A multi-platform graphical client for Subversion.
History
- Version 1.0, 6th June 2005 - Initial release
- Version 1.1, 24th July, 2005 - Support for CVS and RCS
- Version 1.2, 9th December, 2005 - Fix the use of SvnRev in a Makefile where only part of all files in the project are re-built (and therefore re-scanned by SvnRev). Expanded and corrected the explanation of Subversion keywords
- Version 1.3, 9th August, 2006 - Minor corrections for using SvnRev with CVS
- Version 1.4, 5th March, 2007 - Support for Java package files, contributed by Tom McCann
- Version 1.5, 22th November, 2007 - Verification for modifications in the input files
- Version 1.6, 30th December, 2008 - Option to list the names of files that were modified since last commit
- Version 1.7, 31th March, 2009 - Option to add a prefix and/or a suffix to the build number (in the string constant)
- Version 1.8, 2nd April, 2010 - Support for C#, plus the ability to change the names of the generated macros
- Version 1.9, 21st May, 2010 - Bug fix release
- Version 1.10, 15th July, 2010 - The constant
SVN_REVMODIFIED
now increments on every run, to function as a build number for source code that is not checked in
- Version 1.11, 23rd November, 2010 - Differences in line endings (Unix/Linux versus Microsoft Windows versus Apple) do not count as a "modification" when the new option
-n
is used
- Version 1.12, 30th April, 2011 - Support for output files for Oracle and Apache Ant
- Version 2.0, 30th October, 2011 - Support for the new "working copy" of Subversion 1.7; relaxed requirement for RCS keywords in source files (on Subversion 1.7 working copies)
- Version 2.0A, 15th November, 2011 - Better error checking; (re-)write svnrev.h only when version number changes