Occasionally we will either have files in SVN we don’t want updated or files we don’t want committed to source control. In the latter case you might ignore those files but equally they might be part of a csproj (for example) but you don’t want them updated after the initial check in.
A great example of this is a configuration file, checked in initially with a placeholder for a private key. Thus on your CI/build box this file is included in the build but the private key is kept out of the repo.
I’m using TortoiseSvn and whilst this may well be a standard feature of SVN I’ll be discussing it from the TortoiseSvn point of view.
A pre-commit hook can be set-up on your local machine. Obviously in some cases it’d be preferable to have the server handle such tasks, but in situations where that’s not possible we can create our own code and hook into TortoiseSvn’s pre-commit hook.
We’re going to write this in .NET (although it’s easy to implement in any language) as ultimately we’ll just be creating a pretty standard console application.
Configuring the hook in TortoiseSVN
In the Settings | Hook Scripts section we can add a hook, select Pre-Commit Hook from the Hook Type dropdown and the Working Copy Path is set to our local copy of the repository this hook should apply to. Hence this is not a hook against all projects’s or repositories you might have checked out but is on a per checkout basis. Next up, the Command Line To Execute should be the full path (including the EXE or script) to the application which should be executed prior to a commit.
Writing our pre-commit application
We can write the application in any language or scripting if we are able to run the interpreter for the script (for example the TortoiseSVN dialog settings in the previously listed link, show WScript running a JavaScript file).
For our purposes we’re going to create a Console application.
When executed, our application will be passed arguments (via the Main method’s arguments). There are four arguments sent to your application in the following order
- The path and filename of a .tmp file which contains a newline delimited list of the files to be committed
- The depth of commit/update
- The path and filename of a .tmp file which contains the message that is to be saved as part of the commit
- The current working directory
Note: different hooks send different arguments, see https://tortoisesvn.net/docs/release/TortoiseSVN_en/tsvn-dug-settings.html but listed here also for completness
- Start-commit
Arguments – PATH, MESSAGEFILE, CWD
- Manual Pre-commit
Arguments – PATH, MESSAGEFILE, CWD
- Pre-commit
Arguments – PATH DEPTH MESSAGEFILE CWD
- Post-commit
Arguments – PATH, DEPTH, MESSAGEFILE, REVISION, ERROR, CWD
- Start-update
Arguments – PATH, CWD
- Pre-update
Arguments – PATH, DEPTH, REVISION, CWD
- Post-update
Arguments – PATH, DEPTH, REVISION, ERROR, CWD, RESULTPATH
- Pre-connect
Arguments – no parameters are passed to this script. You can pass a custom parameter by appending it to the script path.
The meaning of each argument is as follows
Your application, or script should return 0 for success anything other than 0 for failure. We can also write to the console error stream to return a message for TortoiseSVN to display.
Here’s a stupid little example which just stops any more check-ins
class Program
{
static int Main(string[] args)
{
Console.Error.WriteLine("Stop checking in");
return 1;
}
}
Sample
The following code stops any check-ins of a file named keys.txt that has any data within it
public static class KeysFileCheck
{
public static int CheckFiles(string fileList)
{
return CheckFiles(
File.ReadAllLines(fileList)
.Where(path => Path.GetFileName(path) == "keys.txt")
.ToArray());
}
private static int CheckFiles(string[] keyFiles)
{
var result = 0;
foreach (var keyFile in keyFiles)
{
using (var fs = new FileStream(keyFile, FileMode.Open, FileAccess.Read))
{
result |= CheckKeyFile(keyFile, fs);
}
}
return result;
}
private static int CheckKeyFile(string filename, Stream stream)
{
using (var sr = new StreamReader(stream))
{
var data = sr.ReadToEnd();
if (!String.IsNullOrEmpty(data))
{
Console.Error.WriteLine(
"The keys file {0} cannot be saved with data in it as the build server will fail to build the solution",
filename);
return 1;
}
}
return 0;
}
}
Or Console application’s main method would simply be
class Program
{
static int Main(string[] args)
{
return KeysFileCheck.CheckFiles(args[0]);
}
}