如何在VisualSVN服务器中要求提交消息?

我们在Windows上设置了VisualSVN Server作为我们的Subversion服务器,我们在工作站上使用Ankhsvn + TortoiseSVN作为客户端。

如何configuration服务器来要求提交消息非空?

SVN使用一些钩子来完成这样的任务。

  • start-commit – 在提交事务开始之前运行,可以用来做特殊权限检查
  • pre-commit – 在事务结束时运行,但在提交之前运行。 通常用于validation诸如非零长度日志消息的事情。
  • post-commit – 在事务post-commit后运行。 可用于发送电子邮件或备份存储库。
  • pre-revprop-change – 在修改属性更改之前运行。 可以用来检查权限。
  • 在修订版本属性更改后运行。 可用于发送电子邮件或备份这些更改。

你需要使用pre-commit钩子。 你可以用你的平台支持的任何语言自己编写,但是网上有很多脚本。 使用Googlesearch“svn预先提交挂钩要求评论”我发现一对夫妇,看起来他们会适合该法案:

  • Perl脚本
  • Python脚本

我很高兴你问这个问题。 这是我们用普通Windows Batch编写的预先提交钩子脚本。 如果日志消息less于6个字符,则拒绝提交。 只要把pre-commit.bat放到你的hooks目录下即可。

预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 

你的问题的技术答案已经给出。 我想添加一个社交答案,即:“通过与团队build立提交消息标准,并让他们同意(或接受)为什么需要expression提交消息的原因”

我见过这么多的提交信息,说“补丁”,“错字”,“修复”或类似的,我已经失去了重要。

真的 – 让大家清楚为什么你需要他们。

例子的原因是:

  • 生成的注释 (好吧,这实际上是一个很好的自动工具来执行好的消息,如果我知道他们将(以我的名字)公开可见 – 如果只是为了团队)
  • 许可证问题 :以后可能需要知道代码的来源,例如,是否要将许可证更改为代码(有些组织甚至具有提交消息格式的标准 – 也可以自动检查这一点,但是您可以不一定会得到好的提交消息)
  • 与其他工具的互操作性 ,例如,与您的版本控制接口并从提交消息中提取信息的bug tracker /问题pipe理系统。

希望有助于,另外还有关于预先提交钩子的技术答案。

什么VisualSVN提供你作为钩子input是“Windows NT命令脚本”,这基本上是batch file。

在batch file中编写if-then-else是非常难看的,可能很难debugging。

它将如下所示(searchpre-commit.bat)(未testing):

 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 

您需要在path上有一个grep.exe,%1是此存储库的path,%2是将要提交的txn的名称。 还可以查看存储库的hooks目录中的pre-commit.tmpl。

我们使用优秀的CS-Script工具进行预提交,以便我们可以使用我们正在开发的语言编写脚本。下面是一个例子,确保提交的消息超过10个字符,并确保.suo和.user文件未被签入。您还可以testing制表符/空格缩进,或者在签入时执行小代码标准执行,但要小心使脚本执行太多,因为您不希望减慢提交速度。

 // 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's 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) { } } 

以下是PowerShell预先提交的示例钩子,它拒绝提交less于25个字符的日志消息。

pre-commit.batpre-commit.ps1放到你的仓库'hooks'文件夹中,例如C:\Repositories\repository\hooks\

预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 

预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% 

这里是一个Windows Shell JScript,你可以通过指定钩子来使用:

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

这是相当容易阅读,所以继续一个实验。

顺便说一句,在JScript中这样做的原因是它不依赖任何其他工具(Perl,CygWin等)来安装。

 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); 

在Windows上使用这个预先提交钩子。 它是用Windows Batch编写的,使用grep命令行实用工具来检查提交长度。

 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 

记住你需要一个grep的副本,我推荐gnu工具版本 。

注意:这只适用于TortoiseSVN

只需右键单击您的存储库的顶层。 在上下文菜单中selectTortoiseSVN,然后select属性,看到这个对话框:

在这里输入图像说明

点击右下angular附近的新buildbutton,然后select日志大小。 input提交和locking所需的字符数(在下面的例子中为10)。

在这里输入图像说明

从刚刚修改的顶级目录执行提交。 现在您的存储库要求所有用户在提交更改之前进行注释。

我相信你将不得不设置一个pre-commit钩子来检查消息。

事实上,通过使用Googlesearch得到的第一个结果是perl pre-commit脚本来完成你想要的。

Perl预提交钩子示例(未经testing)

在将提交钩子添加到我的服务器之前,我只是将svnprops分发给TortoiseSVN客户端。

所以,作为替代:

在TortoiseSVN – >属性属性名称 – 添加/设置tsvn:logminsize适当。

这当然不能保证服务器上的客户/用户可以select不这样做,但你可以分发svnprops文件,如果你喜欢。 这样,用户不必设置自己的值 – 您可以将其提供给所有用户。

这也适用于像bugtraq:设置链接日志中的问题跟踪的东西。