SvnRev: a utility for Subversion and CVS/RCS
Stamp your applications or components with revision numbers
SvnRev is a little program that writes the current revision number of project into a C/C++ header file or a Java package 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" that must be present in the source files. 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.
Downloads
The downloads below are for SvnRev version 1.7:
- Source code (4.5 kiB)
- The source code of the utility, which can be built with any ANSI C compiler.
- Win32 binary (35 kiB)
- A pre-built utility for Win32, including support for "wildcard characters" in the filename(s).
- Linux binary (6.2 kiB)
- A pre-built utility for Linux.
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 that all have the same version number 2.11.000. In a similar way, incompatible releases of CTL3D.DLL version 1.0 and COMDLG32.DLL version 4.00 are "out there".
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. File date/time stamps have a (well deserved) 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 add the RCS keywords to all your source files. You should do this in the header files as well as in the code files. A tag 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 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:
/* * Version: $Id: svnrev.c 3116 2005-03-18 16:26:29Z thiadmer $ */
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, state, and the login name of the user who checked-in the revision (if locked).
$Date:$- The date and time the revision was checked-in.
$Rev:$- The revision number assigned to the revision.
$Revision:$- Another name for
$Rev:$.
The next step is to ensure that the source file is in version control: the
Subversion or CVS/RCS system must expand the RCS keywords, so when a file is not
under version control the keywords will never be expanded. Furthermore, to
expand keywords, each file must have the property "svn:keywords",
and this property must have the value "Rev Date Id" (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. I will get back to this
later in this article.
In your makefile, or project file, you will now have to let the program run
over all source and header files. 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; more on this later.
SvnRev has the following options:
| Option | Description |
|---|---|
| -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 and "SvnRevision.java" for Java 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. |
| -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. |
| -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 or SvnRevision.java. |
| -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"). |
| -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.
SvnRev creates and updates the following macros:
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
-foption.
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 alsoSVN_MODIFIED, below. To find out which files have uncommitted changes, use the-voption. 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_MODIFIED- Set to one (1) if any of the source files contain modifications that are note (yet) committed to version control, and to zero if version control is up to date. When SvnRev is used with CVS or RCS, this value will always be zero. You can use this macro to conditionally print a warning message at the start of your program, for example.
An example of a generated file is:
/* This file was generated by the "svnrev" utility * (http://www.compuphase.com/svnrev.htm). * You should not modify it manually, as it may be re-generated. * * $Revision: 3116$ * $Date: 2005-03-18$ */ #define SVN_REV 3116 #define SVN_REVSTR "3116" #define SVN_REVDATE "2005-03-18" #define SVN_REVSTAMP 20050318L #define SVN_MODIFIED 0
An example Java package file is:
/* This file was generated by the "svnrev" utility
* (http://www.compuphase.com/svnrev.htm).
* You should not modify it manually, as it may be re-generated.
*
* $Revision: 3116$
* $Date: 2005-03-18$
*/
package com.compuphase;
public interface SvnRevision
{
public final static int SVN_REV = 3116;
public final static String SVN_REVSTR = "3116";
public final static String SVN_REVDATE = "2005-03-18"
public final static long SVN_REVSTAMP = 20050318L;
public final static int SVN_MODIFIED = 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 1.7." SVN_REVSTR "\n\n");
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
three screen grabs below illustrate setting the property to an appropriate value.
Click on the pictures for a detailed view.
The file properties dialog for TortoiseSVN |
The file properties dialog for eSvn |
The file properties dialog for RapidSVN |
|
To speed things up, TortoiseSVN allows you to set properties on a directory. The Subversion client will then assign these properties to all the files in the directory (the file inherit the settings of the directory). Note though that if you query the settings of the directory, the TortoiseSVN will show an empty property list --even though the properties are set on every file in that directory. With the command line client, you can use wildcards to achieve the same effect.
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
SvnRev is a simple utility consisting of a single C file. The source is build to be portable, and I have successfully compiled it under Windows using Borland C++ and OpenWatcom C/C++, and under Linux using GCC.
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 (a repository that holds all version-control information on the files in a directory) and SvnRev gets these numbers from keywords in the source files. SubWCRev has the advantages that you do not need to add keywords to the source files and that it also gets 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). This is no longer important for me, as we have switched to SubVersion internally and only use CVS in projects for clients that use CVS themselves.
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++ or Java 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".
Acknowledgements
The support for .java package files is contributed by Tom McCann (version 1.4). The option for prefixing and/or suffixing the build number (in the string constant) was suggested by Robert Nitzel.
History
- Version 1.7, 31 March 2009
- Option to add a prefix and/or a suffix to the build number (in the string constant).
- Version 1.6, 30 December 2008
- Option to list the names of files that were modified since last commit.
- Version 1.5, 22 November 2007
- Verification for modifications in the input files.
- Version 1.4, 5 March 2007
- Support for Java package files, contributed by Tom McCann.
- Version 1.3, 9 August 2006
- Minor corrections for using SvnRev with CVS.
- Version 1.2, 8 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.1, 24 July 2005
- Support for CVS and RCS.
- Version 1.0, 6 June 2005
- Initial release.
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; also the home of the SubWCRev utility.
- svnwcrev
- A port of SubWCRev to Linux.
- eSvn
- A multi-platform graphical client for Subversion; now on Sourceforge.
- RapidSVN
- A multi-platform graphical client for Subversion.



