VisualSVNサーバーでコミットメッセージを要求する方法は?
-
05-07-2019 - |
質問
VisualSVNサーバーをWindowsのSubversionサーバーとして設定し、Ankhsvn + TortoiseSVNをワークステーションのクライアントとして使用しています。
コミットメッセージが空でないことを要求するようにサーバーを構成するにはどうすればよいですか?
解決
VisualSVN Server 3.9には、 VisualSVNServerHooks.exe check-logmessage
事前コミットフックが用意されており、空または短いログメッセージによるコミットを拒否できます。手順については、 KB140:VisualSVN Serverのコミットログメッセージの検証の記事を参照してください。
組み込みの VisualSVNServerHooks.exe
のほかに、VisualSVNサーバーとSVNは一般にフックの数は、このようなタスクを実行します。
-
start-commit
—コミットトランザクションの開始前に実行し、特別な権限チェックに使用できます -
事前コミット
—トランザクションの最後に、ただしコミットの前に実行します。多くの場合、ゼロ以外の長さのログメッセージなどの検証に使用されます。 -
post-commit
—トランザクションがコミットされた後に実行されます。電子メールの送信、またはリポジトリのバックアップに使用できます。 -
pre-revprop-change
—リビジョンプロパティが変更される前に実行されます。権限の確認に使用できます。 -
post-revprop-change
—リビジョンプロパティの変更後に実行されます。これらの変更をメールまたはバックアップするために使用できます。
pre-commit
フックを使用する必要があります。プラットフォームでサポートされているほぼすべての言語で作成できますが、Webには多くのスクリプトがあります。グーグル「コメントを必要とするsvn precommitフック」法案に合うように見えるカップルを見つけました:
他のヒント
この質問をしてくれてうれしいです。これは、共通の Windows Batch で記述されたコミット前のフックスクリプトです。ログメッセージが6文字未満の場合、コミットを拒否します。フックディレクトリに pre-commit.bat を置くだけです。
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
あなたの質問に対する技術的な答えはすでに与えられています。社会的な答えを追加したいと思います:"あなたのチームとコミットメッセージの標準を確立し、表現力のあるコミットメッセージが必要な理由を同意(または承認)させる
" patch"、" typo"、" fix"と言った多くのコミットメッセージを見てきました。または私が数を失った同様の。
本当に-なぜあなたがそれらを必要とするのかを皆に明確にしてください。
理由の例は次のとおりです。
- 生成されたチェンジノート(まあ-これは、チームの場合のみ、(私の名前で)パブリックに表示されることがわかっている場合、良い自動メッセージを強制するための素晴らしい自動ツールになります)
- ライセンスの問題:後でコードの起源を知る必要がある場合があります。ライセンスをコードに変更する必要があります(一部の組織はコミットメッセージの書式設定の基準さえ持っています-これをチェックすることは自動化できますが、これでコミットメッセージが good になるとは限りません)
- 他のツールとの相互運用性。バージョン管理と連動し、コミットメッセージから情報を抽出するバグトラッカー/問題管理システム。
さらに、プリコミットフックに関する技術的な回答に役立つ希望。
これは、25文字未満のログメッセージのコミットを拒否する2部構成のサンプルの Batch + PowerShell プリコミットフックです。
pre-commit.bat
と pre-commit.ps1
の両方をリポジトリに挿入します hooks
フォルダ、たとえば 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 {
。これは、25文字未満のログメッセージのコミットを拒否する2部構成のサンプルの Batch + PowerShell プリコミットフックです。
pre-commit.bat
とpre-commit.ps1
の両方をリポジトリに挿入しますhooks
フォルダ、たとえばC:\ Repositories \ repository \ hooks \
pre-commit.ps1
@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%
pre-commit.bat
<*>注1:
pre-commit.bat
のみがVisualSVNから呼び出すことができ、pre-commit.ps1
は< code> pre-commit.bat注2:
pre-commit.bat
にはpre-commit.cmd
という名前を付けることもできます。注3:アクセント付き文字と
.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[Console] :: Error.WriteLine
出力でエンコードの問題を実験する場合は、たとえばchcp 1252
をpre-commit.bat
、@echo off
の次の行。
pre-commit.bat
<*>注1: pre-commit.bat
のみがVisualSVNから呼び出すことができ、 pre-commit.ps1
は< code> pre-commit.bat 。
注2: pre-commit.bat
には pre-commit.cmd
という名前を付けることもできます。
注3:アクセント付き文字と [Console] :: Error.WriteLine
出力でエンコードの問題を実験する場合は、たとえば chcp 1252
を pre-commit.bat
、 @echo off
の次の行。
VisualSVNがフックとして入力できるものは、「Windows NTコマンドスクリプト」です。これは基本的にバッチファイルです。
バッチファイルでif-then-elseを記述するのは非常に見苦しく、おそらくデバッグが非常に困難です。
次のようになります(pre-commit.batを検索)(テストされていません):
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
パスにgrep.exeが必要です。%1はこのリポジトリへのパス、%2はコミットしようとしているtxnの名前です。 リポジトリのhooksディレクトリにあるpre-commit.tmplもご覧ください。
プリコミットフックに優れた CS-Script ツールを使用して、スクリプトを記述できるようにします。開発中の言語。10文字以上のコミットメッセージがあり、.suoファイルと.userファイルがチェックインされないことを確認する例を次に示します。タブ/スペースのインデントをテストすることもできます。チェックイン時に小さなコード標準が適用されますが、コミットを遅くしたくないため、スクリプトの実行が多すぎるように注意してください。
// 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("<*>quot;);
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) {
}
}
次のようにフックを指定することで使用できる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, e.g. %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にのみ適用されます
単にリポジトリの最上位を右クリックします。コンテキストメニューで、TortoiseSVN、[プロパティ]の順に選択して、このダイアログを表示します。
右下にある[新規]ボタンをクリックし、[ログサイズ]を選択します。コミットとロックに必要な文字数を入力します(以下の例では10)。
変更したばかりの最上位ディレクトリからコミットを実行します。これで、リポジトリでは、すべてのユーザーが変更をコミットする前にコメントする必要があります。
コミットフックをサーバーに追加する前に、svnpropsをTortoiseSVNクライアントに配布しました。
したがって、代替として:
TortoiseSVNで-&gt;プロパティプロパティ名- tsvn:logminsize
を適切に追加/設定します。
もちろん、クライアント/ユーザーはそれを行わないことを選択できるため、サーバー上での保証はありませんが、必要に応じてsvnpropsファイルを配布できます。このように、ユーザーは独自の値を設定する必要はありません-すべてのユーザーに提供できます。
これは、bugtraq:ログ内の問題追跡項目をリンクする設定などにも使用できます。