Как потребовать сообщения о фиксации на сервере VisualSVN?

StackOverflow https://stackoverflow.com/questions/247888

Вопрос

У нас настроен сервер VisualSVN в качестве сервера Subversion в Windows, и мы используем Ankhsvn + TortoiseSVN в качестве клиентов на наших рабочих станциях.

Как настроить сервер так, чтобы сообщения о фиксации были непустыми?

Это было полезно?

Решение

VisualSVN Server 3.9 предоставляет VisualSVNServerHooks.exe check-logmessage перехватчик предварительной фиксации, который помогает отклонять фиксации с пустыми или короткими сообщениями журнала.Посмотреть статью КБ140:Проверка сообщений журнала фиксации на сервере VisualSVN для инструкций.

Помимо встроенного VisualSVNServerHooks.exe, VisualSVN Server и SVN в целом используют количество крючков для выполнения подобных задач.

  • start-commit — запускается до начала транзакции фиксации, может использоваться для проверки специальных разрешений
  • pre-commit — запуск в конце транзакции, но перед фиксацией.Часто используется для проверки таких вещей, как сообщение журнала ненулевой длины.
  • post-commit — запускается после фиксации транзакции.Может использоваться для отправки электронных писем или резервного копирования хранилища.
  • pre-revprop-change — выполняется перед изменением свойства ревизии.Может использоваться для проверки разрешений.
  • post-revprop-change — запускается после изменения свойства ревизии.Может использоваться для отправки по электронной почте или резервного копирования этих изменений.

Вам необходимо использовать pre-commit крюк.Вы можете написать его самостоятельно практически на любом языке, поддерживаемом вашей платформой, но в сети есть множество скриптов.Погуглив «перехват svn precommit для запроса комментариев», я нашел пару, которая выглядела так, как будто они отвечают всем требованиям:

Другие советы

Я рад, что вы задали этот вопрос. Это наш скрипт перехвата перед фиксацией, написанный на простом Пакете Windows . Он запрещает фиксацию, если сообщение журнала меньше 6 символов. Просто поместите pre-commit.bat в каталог хуков.

предварительно 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

Технические ответы на ваш вопрос уже были даны.Я хотел бы добавить социальный ответ, а именно:«Установив стандарты сообщений о коммитах со своей командой и заставив их согласовать (или принять) причины, по которым это может понадобиться выразительный зафиксировать сообщения"

Я видел так много сообщений о коммитах, в которых говорилось «исправить», «опечатка», «исправить» или что-то подобное, что я потерял счет.

Действительно - объясните всем, зачем они вам нужны.

Примеры причин:

  • Сгенерированные изменения (ну, на самом деле это может стать хорошим автоматическим инструментом для обеспечения хороших сообщений, если я знаю, что они будут (с моим именем) публично видны - хотя бы для команды)
  • Проблемы с лицензией:Возможно, вам понадобится узнать происхождение кода позже, например.если вы захотите изменить лицензию на свой код (в некоторых организациях даже есть стандарты форматирования сообщений о фиксации — ну, вы можете автоматизировать проверку для этого, но вы не обязательно получите хороший зафиксировать сообщения с этим)
  • Совместимость с другими инструментами, напримерсистемы отслеживания ошибок/проблем, которые взаимодействуют с вашим контролем версий и извлекают информацию из сообщений о фиксации.

Надеюсь, это поможет, в дополнение к техническим ответам о перехватчиках предварительной фиксации.

Вот пример из двух частей Batch + PowerShell , выполняющий предварительную фиксацию, запрещающий фиксацию сообщения журнала длиной менее 25 символов.

Поместите pre-commit.bat и pre-commit.ps1 в свой репозиторий Папка hooks , например <Код> C: \ Хранилища \ репозиториев \ Крючки \

предварительно 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 {

Вот пример из двух частей Batch + PowerShell , выполняющий предварительную фиксацию, запрещающий фиксацию сообщения журнала длиной менее 25 символов.

Поместите pre-commit.bat и pre-commit.ps1 в свой репозиторий Папка hooks , например <Код> C: \ Хранилища \ репозиториев \ Крючки \

предварительно 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%

предварительно commit.bat

<*>

Примечание 1: pre-commit.bat - единственный, который может быть вызван VisualSVN, а затем pre-commit.ps1 - тот, который вызывается < код> предварительно commit.bat .

Примечание 2: pre-commit.bat также может называться pre-commit.cmd .

Примечание 3: Если вы экспериментируете с проблемами кодирования с некоторыми акцентированными символами и выводом [Console] :: Error.WriteLine , добавьте, например, chcp 1252 в pre-commit.bat , следующая строка после @echo off .

.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

<*>

Примечание 1: pre-commit.bat - единственный, который может быть вызван VisualSVN, а затем pre-commit.ps1 - тот, который вызывается < код> предварительно 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, который должен быть зафиксирован. Также взгляните на файл pre-commit.tmpl в каталоге hooks вашего хранилища.

Мы используем отличный 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) {
  }
}

Вот JScript оболочки Windows, который вы можете использовать, указав хук следующим образом:

%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 и использует утилиту командной строки 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 - > Свойства имя свойства - добавьте / установите tsvn: logminsize соответственно.

Это, конечно, не является гарантией на сервере, так как клиенты / пользователи могут не делать этого, но вы можете распространять файлы svnprops, если хотите. Таким образом, пользователям не нужно устанавливать свои собственные значения - вы можете предоставить их всем пользователям.

Это также работает для таких вещей, как bugtraq: настройки для связи материалов отслеживания проблем в журналах.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top