How to require commit messages on a VisualSVN server?

We have a VisualSVN Server configured as our Subversion server on Windows, and we use Ankhsvn + TortoiseSVN as clients on our workstations.

How to configure the server to require that commit messages are not empty?

+48
svn hook visualsvn-server
Oct 29 '08 at 18:30
source share
11 answers

SVN uses the number of hooks to perform such tasks.

  • start-commit - start before a commit transaction begins, can be used to check for special permissions
  • pre-commit - executed at the end of the transaction, but before committing. Often used to check things like a zero-length log message.
  • post-commit - executed after the transaction. It can be used to send email or backup storage.
  • pre-revprop-change - performed before changing the revision property. Can be used to check permissions.
  • post-revprop-change - run after changing the revision property. Can be used to email or back up these changes.

You need to use pre-commit hook. You can write it yourself in any language supported by the platform, but there are several scenarios on the Internet. Googling "svn precommit hook to request a comment" I found a pair that looked as if it would fit the bill:

+37
Oct 29 '08 at 18:36
source share

I'm glad you asked this question. This is our pre-commit hook script co-written by the Windows Package . It rejects the commit if the log message is less than 6 characters. Just put pre-commit.bat in your hooks directory.

pre commit.bat

 setlocal enabledelayedexpansion set REPOS=%1 set TXN=%2 set SVNLOOK="%VISUALSVN_SERVER%\bin\svnlook.exe" SET M= REM Concatenate all the lines in the commit message FOR /F "usebackq delims==" %%g IN (`%SVNLOOK% log -t %TXN% %REPOS%`) DO SET M=!M!%%g REM Make sure M is defined SET M=0%M% REM Here the 6 is the length we require IF NOT "%M:~6,1%"=="" goto NORMAL_EXIT :ERROR_TOO_SHORT echo "Commit note must be at least 6 letters" >&2 goto ERROR_EXIT :ERROR_EXIT exit /b 1 REM All checks passed, so allow the commit. :NORMAL_EXIT exit 0 
+64
Aug 12 '09 at 14:18
source share

Technical answers to your question have already been given. I would like to add a social answer: "By setting commit messages with your team and receiving them to agree (or accept) the reasons why expressive commit messages are needed"

I saw so many commit messages that said “patch,” “typo,” “fix,” or the like, that I lost track of.

In fact - make it clear to everyone why you will need them.

Examples of causes are:

  • Generated Changenotes (well - this is a really good tool for enforcing good messages if I know that they will be (with my name) public - if only for the team)
  • License Issues . You may need to find out the origin of the code later, for example. if you want to change the license to your code (some organizations even have standards for formatting commit messages - well, you can automate verification for this, but you don’t necessarily get good messages about doing this)
  • Interaction with other tools , for example. bugtrackers / release control systems that interact with your version control and extract information from commit messages.

Hope this helps, as well as technical answers to pre-war hooks.

+17
Oct 29 '08 at 18:46
source share

Here is an example with two parts of Batch + PowerShell that prohibits capturing a log message less than 25 characters long.

Put hooks and pre-commit.bat and pre-commit.ps1 in the repository folder, for example. C:\Repositories\repository\hooks\

pre commit.ps1

 # Store hook arguments into variables with mnemonic names $repos = $args[0] $txn = $args[1] # Build path to svnlook.exe $svnlook = "$env:VISUALSVN_SERVER\bin\svnlook.exe" # Get the commit log message $log = (&"$svnlook" log -t $txn $repos) # Check the log message contains non-empty string $datalines = ($log | where {$_.trim() -ne ""}) if ($datalines.length -lt 25) { # Log message is empty. Show the error. [Console]::Error.WriteLine("Commit with empty log message is prohibited.") exit 3 } exit 0 

pre commit.bat

 @echo off set PWSH=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe %PWSH% -command $input ^| %1\hooks\pre-commit.ps1 %1 %2 if errorlevel 1 exit %errorlevel% 

Note 1: pre-commit.bat is the only one that can be called by VisualSVN, and then pre-commit.ps1 is the one that is called by pre-commit.bat .

Note 2: pre-commit.bat may also be called pre-commit.cmd .

Note 3: If you are experimenting with problems with some accented characters and the output of [Console]::Error.WriteLine , add, for example, chcp 1252 to pre-commit.bat , the next line after @echo off .

+5
Jul 19 '12 at 12:08
source share

What VisualSVN offers you to introduce, since hooks are “Windows NT command scripts,” which are mostly batch files.

Writing if-then-else in batch files is very ugly and probably very difficult to debug.

It will look something like this (search for pre-commit.bat) (not verified):

 SVNLOOK.exe log -t "%2" "%1" | grep.exe "[a-zA-Z0-9]" > nul || GOTO ERROR GOTO OK :ERROR ECHO "Please enter comment and then retry commit!" exit 1 :OK exit 0 

You need grep.exe on the path,% 1 is the path to this repository,% 2 is the name txn, which should be fixed. Also check out pre-commit.tmpl in the hooks directory of your repository.

+4
Oct 30 '08 at 9:01
source share

We use the excellent CS-Script tool for our prefixes so that we can write scripts in the language we are doing this. Here is an example that provides a commit message longer than 10 characters and ensures that the .suo and .user files are not checked. You can also test the indentation of the table / space or force the small code standards to be executed on but be careful when doing your script too much, since you don't want to slow down the commit.

 // run from pre-commit.cmd like so: // css.exe /nl /c C:\SVN\Scripts\PreCommit.cs %1 %2 using System; using System.Diagnostics; using System.Text; using System.Text.RegularExpressions; using System.Linq; class PreCommitCS { /// <summary>Controls the procedure flow of this script</summary> public static int Main(string[] args) { if (args.Length < 2) { Console.WriteLine("usage: PreCommit.cs repository-path svn-transaction"); Environment.Exit(2); } try { var proc = new PreCommitCS(args[0], args[1]); proc.RunChecks(); if (proc.MessageBuffer.ToString().Length > 0) { throw new CommitException(String.Format("Pre-commit hook violation\r\n{0}", proc.MessageBuffer.ToString())); } } catch (CommitException ex) { Console.WriteLine(ex.Message); Console.Error.WriteLine(ex.Message); throw ex; } catch (Exception ex) { var message = String.Format("SCRIPT ERROR! : {1}{0}{2}", "\r\n", ex.Message, ex.StackTrace.ToString()); Console.WriteLine(message); Console.Error.WriteLine(message); throw ex; } // return success if we didn't throw return 0; } public string RepoPath { get; set; } public string SvnTx { get; set; } public StringBuilder MessageBuffer { get; set; } /// <summary>Constructor</summary> public PreCommitCS(string repoPath, string svnTx) { this.RepoPath = repoPath; this.SvnTx = svnTx; this.MessageBuffer = new StringBuilder(); } /// <summary>Main logic controller</summary> public void RunChecks() { CheckCommitMessageLength(10); // Uncomment for indent checks /* string[] changedFiles = GetCommitFiles( new string[] { "A", "U" }, new string[] { "*.cs", "*.vb", "*.xml", "*.config", "*.vbhtml", "*.cshtml", "*.as?x" }, new string[] { "*.designer.*", "*.generated.*" } ); EnsureTabIndents(changedFiles); */ CheckForIllegalFileCommits(new string[] {"*.suo", "*.user"}); } private void CheckForIllegalFileCommits(string[] filesToExclude) { string[] illegalFiles = GetCommitFiles( new string[] { "A", "U" }, filesToExclude, new string[] {} ); if (illegalFiles.Length > 0) { Echo(String.Format("You cannot commit the following files: {0}", String.Join(",", illegalFiles))); } } private void EnsureTabIndents(string[] filesToCheck) { foreach (string fileName in filesToCheck) { string contents = GetFileContents(fileName); string[] lines = contents.Replace("\r\n", "\n").Replace("\r", "\n").Split(new string[] { "\n" }, StringSplitOptions.None); var linesWithSpaceIndents = Enumerable.Range(0, lines.Length) .Where(i => lines[i].StartsWith(" ")) .Select(i => i + 1) .Take(11) .ToList(); if (linesWithSpaceIndents.Count > 0) { var message = String.Format("{0} has spaces for indents on line(s): {1}", fileName, String.Join(",", linesWithSpaceIndents)); if (linesWithSpaceIndents.Count > 10) message += "..."; Echo(message); } } } private string GetFileContents(string fileName) { string args = GetSvnLookCommandArgs("cat") + " \"" + fileName + "\""; string svnlookResults = ExecCmd("svnlook", args); return svnlookResults; } private void CheckCommitMessageLength(int minLength) { string args = GetSvnLookCommandArgs("log"); string svnlookResults = ExecCmd("svnlook", args); svnlookResults = (svnlookResults ?? "").Trim(); if (svnlookResults.Length < minLength) { if (svnlookResults.Length > 0) { Echo("Your commit message was too short."); } Echo("Please describe the changes you've made in a commit message in order to successfully commit. Include support ticket number if relevant."); } } private string[] GetCommitFiles(string[] changedIds, string[] includedFiles, string[] exclusions) { string args = GetSvnLookCommandArgs("changed"); string svnlookResults = ExecCmd("svnlook", args); string[] lines = svnlookResults.Split(new string[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); var includedPatterns = (from a in includedFiles select ConvertWildcardPatternToRegex(a)).ToArray(); var excludedPatterns = (from a in exclusions select ConvertWildcardPatternToRegex(a)).ToArray(); var opts = RegexOptions.IgnoreCase; var results = from line in lines let fileName = line.Substring(1).Trim() let changeId = line.Substring(0, 1).ToUpper() where changedIds.Any(x => x.ToUpper() == changeId) && includedPatterns.Any(x => Regex.IsMatch(fileName, x, opts)) && !excludedPatterns.Any(x => Regex.IsMatch(fileName, x, opts)) select fileName; return results.ToArray(); } private string GetSvnLookCommandArgs(string cmdType) { string args = String.Format("{0} -t {1} \"{2}\"", cmdType, this.SvnTx, this.RepoPath); return args; } /// <summary> /// Executes a command line call and returns the output from stdout. /// Raises an error is stderr has any output. /// </summary> private string ExecCmd(string command, string args) { Process proc = new Process(); proc.StartInfo.FileName = command; proc.StartInfo.Arguments = args; proc.StartInfo.UseShellExecute = false; proc.StartInfo.CreateNoWindow = true; proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.RedirectStandardError = true; proc.Start(); var stdOut = proc.StandardOutput.ReadToEnd(); var stdErr = proc.StandardError.ReadToEnd(); proc.WaitForExit(); // Do after ReadToEnd() call per: http://chrfalch.blogspot.com/2008/08/processwaitforexit-never-completes.html if (!string.IsNullOrWhiteSpace(stdErr)) { throw new Exception(string.Format("Error: {0}", stdErr)); } return stdOut; } /// <summary> /// Writes the string provided to the Message Buffer - this fails /// the commit and this message is presented to the comitter. /// </summary> private void Echo(object s) { this.MessageBuffer.AppendLine((s == null ? "" : s.ToString())); } /// <summary> /// Takes a wildcard pattern (like *.bat) and converts it to the equivalent RegEx pattern /// </summary> /// <param name="wildcardPattern">The wildcard pattern to convert. Syntax similar to VB Like operator with the addition of pipe ("|") delimited patterns.</param> /// <returns>A regex pattern that is equivalent to the wildcard pattern supplied</returns> private string ConvertWildcardPatternToRegex(string wildcardPattern) { if (string.IsNullOrEmpty(wildcardPattern)) return ""; // Split on pipe string[] patternParts = wildcardPattern.Split('|'); // Turn into regex pattern that will match the whole string with ^$ StringBuilder patternBuilder = new StringBuilder(); bool firstPass = true; patternBuilder.Append("^"); foreach (string part in patternParts) { string rePattern = Regex.Escape(part); // add support for ?, #, *, [...], and [!...] rePattern = rePattern.Replace("\\[!", "[^"); rePattern = rePattern.Replace("\\[", "["); rePattern = rePattern.Replace("\\]", "]"); rePattern = rePattern.Replace("\\?", "."); rePattern = rePattern.Replace("\\*", ".*"); rePattern = rePattern.Replace("\\#", "\\d"); if (firstPass) { firstPass = false; } else { patternBuilder.Append("|"); } patternBuilder.Append("("); patternBuilder.Append(rePattern); patternBuilder.Append(")"); } patternBuilder.Append("$"); string result = patternBuilder.ToString(); if (!IsValidRegexPattern(result)) { throw new ArgumentException(string.Format("Invalid pattern: {0}", wildcardPattern)); } return result; } private bool IsValidRegexPattern(string pattern) { bool result = true; try { new Regex(pattern); } catch { result = false; } return result; } } public class CommitException : Exception { public CommitException(string message) : base(message) { } } 
+4
11 Oct '12 at 21:32
source share

Use this pre-commit binding on Windows. It is written in the Windows Batch package and uses the grep command line utility to check the length of the commit.

 svnlook log -t "%2" "%1" | c:\tools\grep -c "[a-zA-z0-9]" > nul if %ERRORLEVEL% NEQ 1 exit 0 echo Please enter a check-in comment 1>&2 exit 1 

Remember that you will need a copy of grep, I recommend the version of gnu tools .

+3
Feb 20 '09 at 18:42
source share

It uses JScript Windows Shell, which can be used by specifying the hook as:

 %SystemRoot%\System32\CScript.exe //nologo <..path..to..script> %1 %2 

This is pretty easy to read, so keep the experiment going.

By the way, the reason for this in JScript is that it does not rely on any other tools (Perl, CygWin, etc.) that will be installed.

 if (WScript.Arguments.Length < 2) { WScript.StdErr.WriteLine("Repository Hook Error: Missing parameters. Should be REPOS_PATH then TXN_NAME, eg %1 %2 in pre-commit hook"); WScript.Quit(-1); } var oShell = new ActiveXObject("WScript.Shell"); var oFSO = new ActiveXObject("Scripting.FileSystemObject"); var preCommitStdOut = oShell.ExpandEnvironmentStrings("%TEMP%\\PRE-COMMIT." + WScript.Arguments(1) + ".stdout"); var preCommitStdErr = oShell.ExpandEnvironmentStrings("%TEMP%\\PRE-COMMIT." + WScript.Arguments(1) + ".stderr"); var commandLine = "%COMSPEC% /C \"C:\\Program Files\\VisualSVN Server\\bin\\SVNLook.exe\" log -t "; commandLine += WScript.Arguments(1); commandLine += " "; commandLine += WScript.Arguments(0); commandLine += "> " + preCommitStdOut + " 2> " + preCommitStdErr; // Run Synchronously, don't show a window // WScript.Echo("About to run: " + commandLine); var exitCode = oShell.Run(commandLine, 0, true); var fsOUT = oFSO.GetFile(preCommitStdOut).OpenAsTextStream(1); var fsERR = oFSO.GetFile(preCommitStdErr).OpenAsTextStream(1); var stdout = fsOUT && !fsOUT.AtEndOfStream ? fsOUT.ReadAll() : ""; var stderr = fsERR && !fsERR.AtEndOfStream ? fsERR.ReadAll() : ""; if (stderr.length > 0) { WScript.StdErr.WriteLine("Error with SVNLook: " + stderr); WScript.Quit(-2); } // To catch naught commiters who write 'blah' as their commit message if (stdout.length < 5) { WScript.StdErr.WriteLine("Please provide a commit message that describes why you've made these changes."); WScript.Quit(-3); } WScript.Quit(0); 
+3
Aug 04 '09 at 15:15
source share

Note. This only applies to TortoiseSVN

Just right-click the top level of your repository. From the context menu, select TortoiseSVN, then "Properties" to see this dialog box:

enter image description here

Click the Create button in the lower right corner and select Log Dimensions. Enter the number of characters you want to use for Commit and Lock (10 in the example below).

enter image description here

Make Commit from the just modified top-level directory. Now your repository requires all users to comment before committing changes.

+3
Apr 27 '14 at 2:39 on
source share

I believe that you will need to install a pre-commit hook that will check the message.

Indeed, only by the first result did I get a pre-commit perl script to do exactly what you intended.

Perl interception example (untested)

+2
Oct 29 '08 at 18:31
source share

Before adding commit bindings to my server, I just passed svnprops to TortoiseSVN clients.

So, as an alternative:

In TortoiseSVN -> Properties of the name property - add / set tsvn:logminsize accordingly.

This, of course, is not a guarantee on the server, as clients / users can refuse it, but you can distribute svnprops files if you want. Thus, users do not need to set their own values ​​- you can provide them to all users.

This also works for things like bugtraq: settings for linking log tracking material.

+2
Dec 19 '08 at 14:20
source share



All Articles