Wednesday, October 19, 2011

Reading the revision number of a local copy of a SVN repository

The SVN client (TortoiseSVN) has a handy SubWcRev.exe application which is commonly used for obtaining the revision number of a local copy of given SVN repository and (optionally) rewriting text files and replace the $WCREV$ token with the retrieved version number.

This post is to document the way we mimic SubWcRev.exe for our internal purposes.

TortoiseSVN 1.6 and earlier

When TortoiseSVN 1.6 (or earlier) is used, each folder in the repository tree contains a .svn subfolder. In there, the file named entries contains some information about the local repository. The root svn subfolder can be used as the source of the revision number - the entries file located there always contains the revision number in the third line:

public class SvnManager
{
    public string FolderName { get; private set; }
 
    public SvnManager( string FolderName )
    {
        this.FolderName = FolderName;
    }
 
    const string ENTRIES = "entries";
    private string RevisionNumber
    {
        get
        {
            string SvnSubfolder = FolderName + "\\.svn";
 
            if ( Directory.Exists( SvnSubfolder ) )
            {
                string EntriesFile = Directory.GetFiles( SvnSubfolder, ENTRIES ).FirstOrDefault();
 
                if ( !string.IsNullOrEmpty( EntriesFile ) )
                {
                    string[] Lines = File.ReadAllLines( EntriesFile );
                    if ( Lines.Length > 3 )
                        return Lines[3];
                }
            }
 
            return string.Empty;
        }
    }
}

The client code would be:

SvnManager manager = new SvnManager( "local repository root path" );
string revisionNumber = manager.RevisionNumber;

Tortoise 1.7

Tortoise 1.7 no longer creates .svn subfolders in each single folder of the local copy of your repository. Instead, a SQLite database, wc.db, is created in the .svn folder at the root level of the local copy. The database is a binary file, however it seems that the revision number is stored as string value which makes it possible to match the binary data for a regular expression, /!svn/ver/[0-9]*/:

public class SvnManager
 {
     public string FolderName { get; private set; }
 
     public SvnManager( string FolderName )
     {
         this.FolderName = FolderName;
     }
     
     const string DB      = "wc.db";
     const string PATTERN = "/!svn/ver/(?'version'[0-9]*)/";
     private string RevisionNumber
     {
         get
         {
             string SvnSubfolder = FolderName + "\\.svn";
 
             if ( Directory.Exists( SvnSubfolder ) )
             {
                 int maxVer = int.MinValue;
                 string EntriesFile = Directory.GetFiles( SvnSubfolder, DB ).FirstOrDefault();
 
                 if ( !string.IsNullOrEmpty( EntriesFile ) )
                 {
                     byte[] fileData = File.ReadAllBytes( EntriesFile );
                     string fileDataString = Encoding.Default.GetString( fileData );
 
                     Regex regex = new Regex( PATTERN );
 
                     foreach ( Match match in regex.Matches( fileDataString ) )
                     {
                         string version = match.Groups["version"].Value;
 
                         int curVer;
                         if ( int.TryParse( version, out curVer ) )
                             if ( curVer > maxVer )
                                 maxVer = curVer;
                     }
 
                     if ( maxVer > int.MinValue )
                         return maxVer.ToString();
                 }
             }
 
             return string.Empty;
         }
     }     
 }

6 comments:

Anonymous said...

Nice post!.

Beauty of Darkness said...

Oh great!
Thank you very much (-:

This is exactly what I need.

I wanted to write a version grabber, too. Then I found out, that it's still done by you.
Also I like so see that you use regular expressions. It's one of my favorite "tools".

Greetings from Germany
Beauty

Beauty of Darkness said...

It would be useful to add the needed namespaces to the code:

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;
using System.IO;

Beauty of Darkness said...

One extra note:

You need to change the property from:
PRIVATE string RevisionNumber

To:
PUBLIC string RevisionNumber


Otherwise you can't get the number from the class (-;

Beauty of Darkness said...

Improved Version

Based on your code I wrote an improved class.

Important differences:
* Improved error handling
* No class instance needed
* Method to update the file "ApplicationInfo.cs". Then the SVN revision number becomes part of the application version number.

Usage:

SvnManager.GetRevisionNumber("myPath")

SvnManager.UpdateAssemblyInfo("myPath", "otherPath")


... Oh, I can't publish my code here, because it's too long for a comment.
Ok, now I published it somewhere else:

Download here
http://www.mycsharp.de/wbb2/thread.php?threadid=101541

Marco Born said...

Hello,
I'm not very firm with C# and could need some help to use it in my VB.NET project. How I can include the code? I always get an error message that the class is not declared. What do I need to get it running?

Thanks a lot,
Marco