web-dev-qa-db-fra.com

Comment exiger des messages de validation dans VisualSvn Server?

Nous avons des serveurs VisualSvn configurés comme notre serveur Subversion sous Windows et nous utilisons Ankhsvn + Tortoisesvn en tant que clients sur nos postes de travail.

Comment pouvez-vous configurer le serveur pour exiger de commettre des messages non vides?

49
Ben Collins

VisualSvn Server 3.9 fournit le VisualSVNServerHooks.exe check-logmessage Crochet de pré-validation qui vous aide à rejeter les commits avec des messages de journal vides ou courts. Voir l'article KB140: Validation des messages de journal de validation dans VisualSvn Server pour les instructions.

Outre le intégré VisualSVNServerHooks.exe, VisualSvn Server et SVN en général utilise un nombre de crochets pour accomplir des tâches telles que celles-ci.

  • start-commit - courir avant que COMMIT La transaction commence, peut être utilisé pour effectuer une vérification spéciale de la permission
  • pre-commit - courir à la fin de la transaction, mais avant de commettre. Souvent utilisé pour valider des éléments tels qu'un message de journal de longueur non nulle.
  • post-commit - fonctionne après que la transaction a été commise. Peut être utilisé pour envoyer des courriels ou sauvegarder le référentiel.
  • pre-revprop-change - fonctionne avant un changement de propriété de révision. Peut être utilisé pour vérifier les autorisations.
  • post-revprop-change - fonctionne après un changement de propriété de révision. Peut être utilisé pour envoyer un courriel ou une sauvegarde de ces modifications.

Vous devez utiliser le pre-commit accrocher. Vous pouvez vous écrire vous-même dans une langue à peu près n'importe quelle langue de votre plate-forme, mais il existe un certain nombre de scripts sur le Web. Googling "SVN Precommit Hook pour avoir besoin de commentaire" J'ai trouvé un couple qui ressemblait à ce qu'ils correspondaient à la facture:

37
Jason Jackson

Je suis content que vous ayez posé cette question. Ceci est notre script de crochet de pré-validation écrit en commun Windows Batch . Il nie commettre si le message de journal est inférieur à 6 caractères. Il suffit de mettre le pré-commis.bat dans votre répertoire de crochets.

Pré-commis.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
65
sylvanaar

Les réponses techniques à votre question ont déjà été données. J'aimerais ajouter la réponse sociale, qui est: "En établissant des normes de commit des messages avec votre équipe et de les faire accepter (ou acceptez) les raisons pour lesquelles on aurait besoin expressif commettre des messages "

J'ai vu tant de messages de validation qui ont dit "patch", "typo", "correction" ou similaire que j'ai perdu compté.

Vraiment - définissez clairement à tout le monde pourquoi vous auriez besoin d'eux.

Des exemples pour des raisons sont:

  • Changénotes générés (Eh bien - cela fait un bon outil automatique pour appliquer de bons messages si je sais qu'ils seront (avec mon nom) publiquement visible - si seulement pour l'équipe)
  • Problèmes de licence: Vous devrez peut-être connaître l'origine du code plus tard, par exemple. Si vous souhaitez modifier la licence de votre code (certaines organisations ont même des normes pour le formatage de message commettent - eh bien, vous pouvez automatiser la vérification de cela, mais vous n'obtenez pas nécessairement bien commettre des messages avec cela)
  • Interopérabilité avec d'autres outils, E.G. BugTrackers/Systèmes de gestion des émissions qui s'interfèrent avec votre contrôle de version et extrayez les informations des messages de validation.

J'espère que cela aide, en plus des réponses techniques sur les crochets préalables.

17
Olaf Kock

Voici un échantillon de deux parties Batch + PowerShell Crochet de pré-validation qui nie commettre un message de journal avec moins de 25 caractères.

Mettre les deux pre-commit.bat et pre-commit.ps1 Dans votre référentiel hooks Dossier, par exemple. C:\Repositories\repository\hooks\

( pré-commis.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

(( pré-commis.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 est le seul qui peut être appelé par VisualSvn, puis pre-commit.ps1 est celui qui est appelé par pre-commit.bat.

Note 2 : pre-commit.bat peut aussi être nommé pre-commit.cmd.

NOTE 3: Si vous expérimentez des problèmes de codage avec certains caractères accentués et le [Console]::Error.WriteLine Sortie, puis ajouter par exemple chcp 1252 dans pre-commit.bat, ligne suivante après @echo off.

6
bahrep

Ce que VisualSvn vous propose d'entrer en tant que crochets sont "Scripts de commande Windows NT", qui sont essentiellement des fichiers par lots.

Écrire si, alors, d'autre dans les fichiers de lots est très laid et probablement très difficile à déboguer.

Il ressemblera à quelque chose comme ce qui suit (recherche de pré-commis.bat) (non testé):

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 

Vous avez besoin d'un grep.exe sur le chemin,% 1 est le chemin de ce référentiel,% 2 le nom du TXN sur le point de s'engager. Regardez également le pré-commit.tmpl dans le répertoire Hooks de votre référentiel.

4
Ansgar

Nous utilisons l'outil excellent cs-script pour nos crochets de pré-validation afin que nous puissions écrire des scripts dans la langue que nous effectuons le développement. Voici un exemple qui garantit un message de validation de plus de 10 caractères et assure que .suo et .Suo Les fichiers ne sont pas enregistrés. Vous pouvez également tester des tirettes d'onglets/spatiaux, ou faire une petite application des normes de code à l'enregistrement, mais faites attention à faire de votre script trop que vous n'avez pas. t veulent ralentir un 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'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) {
  }
}
4
mattmc3

Voici une shell Windows Jscript que vous pouvez utiliser en spécifiant le crochet comme suit:

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

C'est assez facile à lire, alors allez-y une expérience.

BTW, la raison de le faire dans JScript est qu'il ne repose pas sur aucun autre outil (Perl, Cygwin, etc.) à installer.

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

NOTE: Ceci s'applique uniquement à TortoiseSVN

Cliquez simplement avec le bouton droit de la souris sur le niveau supérieur de votre référentiel. Dans le menu contextuel, sélectionnez Tortoisesvn, puis propriétés, pour voir cette boîte de dialogue:

enter image description here

Cliquez sur le bouton Nouveau en bas à droite et sélectionnez Tailles de journal. Entrez le nombre de caractères que vous souhaitez demander pour commit et verrouillage (10 dans l'exemple ci-dessous).

enter image description here

Faites une validation du répertoire de niveau supérieur que vous venez de modifier. Maintenant, votre référentiel exige que tous les utilisateurs commencent avant de commettre des modifications.

3
Mr. B

Avant d'ajouter des crochets de validation à mon serveur, je viens de distribuer SVNProps aux clients TortoiseSVN.

Donc, comme une alternative:

À Tortoisesvn -> Propriétés Nom de la propriété - Ajouter/Set tsvn:logminsize de manière appropriée.

Ceci n'est bien sûr aucune garantie sur le serveur, car les clients/utilisateurs ne peuvent pas choisir de ne pas le faire, mais vous pouvez distribuer des fichiers SVNProps si vous le souhaitez. De cette façon, les utilisateurs n'ont pas à définir leurs propres valeurs - vous pouvez les fournir à tous les utilisateurs.

Cela fonctionne également pour des choses comme Bugtraq: Paramètres de relier les trucs de suivi des émissions dans les journaux.

2
Tim

Je pense que vous devrez configurer un crochet de pré-validation qui vérifiera le message.

En effet, juste en googling le premier résultat que j'ai eu était un script de pré-validation Perl pour faire exactement ce que vous avez voulu.

exemple de crochet de pré-validation PERL (non testé)

1
thismat