среда, 30 мая 2012 г.

Хуки на SVN: pre-commit хук на VBS

Хук - это набор команд на определенное действие с репозиторием SVN.

Хуки срабатывают на разные события Subversion, вот некоторые из них:
  • start-commit — запускается до начала транзакции, может быть использован для проверки прав.
  • pre-commit — запускается в конце транзакции, но до commit, часто используется для валидации данных, например для проверки не пустых лог-собщений.
  • post-commit — запускается после транзакции, может быть использовано для отправки e-mail или для резервирования хранилища.
  • pre-revprop-change — запускается до изменений в ревизии, могут быть использованы для проверки доступа.
  • post-revprop-change — запускается после изменений в ревизии, могут быть использованы для отправки e-mail или для резервирования изменений.
  • Есть еще «post-lock», «post-unlock», «pre-lock» и «pre-unlock», как видно из названий он срабатывают при блокировке.

Самый интересный из всех - это конечно "pre-commit". Именно в этот момент мы имеем весь текст транзакции, можем ее обработать или даже изменить.
Хук должен находится на сервере svn в папке "hooks", файл прекоммита называется "pre-commit.bat". Т.е. предполагается, что хук будет на батниках, к которым у меня хроническая нелюбовь. Так что мой батник будет вызывать vbs, а вся логика будет в вбсине.

Часть 1: батник, вызывающий VBS:
SETLOCAL
SET PATH=C:\Windows;C:\Windows\system32;C:\Program Files\VisualSVN Server\bin;
cscript.exe //NoLogo D:\Repositories\TradingSystem\hooks\pre-commit.wsf %1 %2
IF %ERRORLEVEL% EQU 1 GOTO fail
IF %ERRORLEVEL% EQU 2 GOTO fail2

:success
EXIT 0

:fail
echo Введите описание к commit! 1>&2
EXIT 1

:fail2
echo Обнаружено не закрытое подключение к базе! 1>&2
EXIT 1
Где "C:\Program Files\VisualSVN Server\bin" - путь до исполняемых файлов сервера SVN
"D:\Repositories\TradingSystem\hooks\pre-commit.wsf" - путь до запускаемого скрипта VBS
Т.е. роль нашего bat файла, вызвать VBS с параметрами и получить код возврата.

Сам VBS под катом: В скриптах мы ограничены списком объектов. В блоке "reference" мы должны указать список объектов, которые мы будем использовать.
VBS принимает от BAT 2 параметра:
  • strRepoPath - путь до репозитория
  • strRevision - идентификатор, добавляемой ревизии
Наш VBS будет проверять наличие комментария к ревизии, и еще несколько уже по самому тексту комита.
<package>
<job id="hPreCommit">
<reference object="WScript.Shell"/>
<reference object="Scripting.Dictionary"/>
<reference object="CDO.Message"/>
<reference object="Scripting.FileSystemObject"/>
<script language="VBScript">
Option Explicit
'путь до файла svnlook
Public Const SVNLOOK_PATH = "C:\Program Files\VisualSVN Server\bin\svnlook.exe"

Private retVal
Private strRepoPath
Private strRevision
Private colArgs
Public wshShell

Set wshShell = WScript.CreateObject("WScript.Shell")
Set colArgs = WScript.Arguments
strRepoPath = colArgs(0)
strRevision = colArgs(1)

Dim command

'1. получаем лог комита, если он пустой, то ругаемся
command = "svnlook log " & strRepoPath & " -t " & strRevision
Dim res:res = runCMD(command)

'1.1. если есть ключевое слово, то пропускаем
if InStr(res, "!skip") > 0 Then
  WScript.Quit(0)
end if

Dim l:l = Len(res)

if l < 3 THEN
  WScript.Quit(1)
end if

'1.5 если добавление, то пропускаем
command = "svnlook changed " & strRepoPath & " -t " & strRevision
res = runCMD(command)
if InStr(res, "A ") > 0 Then
  WScript.Quit(0)
end if


'Ищем ключевые слова в добавляемом тексте 2. connected = true
command = "svnlook diff " & strRepoPath & " -t " & strRevision & " --no-diff-deleted --no-diff-added -x -w -x -u -x --ignore-eol-style"
res = runCMD(command)

if InStr(res, "Connected = True") > 0 AND InStr(res, "-  Connected = True") = 0 AND InStr(res, "-    Connected = True") = 0 Then
  WScript.Quit(2)
end if

'передаем результат в BAT
WScript.Quit(0)

'пишем в лог ошибку провеки
Function SendMail(subj, text, mailto)
  'smtp сервер компании
  'Dim smtp:smtp = "rassrv.domain.central"
  'Dim myMail:Set myMail = CreateObject("CDO.Message")
  'myMail.Subject = subj
  'myMail.From = "svn@maxi-net.ru"
  'myMail.To = mailto & "@central.local"
  'myMail.TextBody = text
  'myMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
  'myMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = smtp
  'myMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
  'myMail.Configuration.Fields.Update
  'myMail.Send

  Dim fso:Set fso = CreateObject("Scripting.FileSystemObject")
  Dim cerr:Set cerr = fso.OpenTextFile(strRepoPath & "\hooks\error.log", 8, 1)
  cerr.WriteLine(Now() & ": " & text)
  cerr.Close

  SendMail = true
End Function

'функция запускает cmd команду
'на команду дается 45 секунд, если не успеваем, то выходим
Function runCMD(strRunCmd)
    Dim strOut
    Dim objExec

    Set objExec = wshShell.Exec(strRunCmd)
    Dim i

    Do While objExec.Status = 0
        WScript.Sleep 100
        i = i + 1
        'ограничение в 45 секунд на комит
        IF i = 450 THEN
          runCMD = Empty
          SendMail "Commit Err", strRunCmd, "skan"
          WScript.Quit(4)
          exit function
        END IF
    Loop

    strOut = objExec.StdOut.ReadAll()
    Set objExec = Nothing
    strOut = Trim(strOut)
    If (Len(strOut) = 0) Then
        runCMD = Empty
    Else
        runCMD = strOut
    End If
End Function

</script>
</job>
</package>

Вот и все. Есть только один косяк - хук на содержание "svnlook diff" довольно часто зависает, эту проблему я пока не решил, буду рад, если кто подскажет.