Форум сайта python.su
Какой клиент используете? Может дело в нем?Клиент - svn-1.6.3 (Subversion Win32 binaries for Apache 2.2.x)
Давайте свой репозиторий, попробую у себя воспроизвести. Только из-за ваших виндовых штучек типа этого: ‘src\\’, ‘checkstyle\\checkstyle_errors.xml’ и подобных мне нужно будет править код, чтобы оно хоть как-то заработало. Так что если можно, то сделайте это платформо-независымым, плз.
Да, я уже писал об этом, но вы видимо пропустили:
svn://localhost/ - не лучший метод для работы локально, по-моему. Для этого есть file://
Офлайн
soberВ общем мне нужен только репозиторий и описание того, как выйти на ошибку. Вместо вашего чекера я запущу что-нибудь свое или вообще ничего, посколько виндовой машины у меня в распоряжении нет.
Скрипт, исправлю, но репозиторий, к сожалению могу использовать только локальный, если Вас это не остановит, готов предоставить архив со всем необходимым, включая датасет для тестирования. :)
Отредактировано (Авг. 6, 2009 10:25:07)
Офлайн
Проблема в том, что клиент молча отваливается при попытке записать в sys.stderr длинный лог. Скрипт исправил:
"""
A subversion pre-commit hook that validates the commited files for
checkstyle violations.
"""
__authors__ = [
# alphabetical order by last name, please
'sober',
]
import logging
import os, sys
import pysvn
import re
import shutil
import subprocess as subp
from xml.dom import minidom
# directory in which this script is located
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
SVN_CLIENT = pysvn.Client()
# setup LOGGER
LOGGER = logging.getLogger(__name__)
FORMATTER = logging.Formatter('%(asctime)s - %(filename)s - %(levelname)s - %(message)s')
FILE_HANDLER = logging.FileHandler(os.path.join(CURRENT_DIR, 'pre-commit.log'), mode='w')
FILE_HANDLER.setFormatter(FORMATTER)
LOGGER.addHandler(FILE_HANDLER)
LOGGER.addHandler(logging.StreamHandler())
def prepare_dir(filename):
""" Creates directory tree if not exists """
dir_path = os.path.dirname(filename)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
def clean_dir(path):
""" Removes all content of the specified directory """
if os.path.exists(path):
shutil.rmtree(path, True)
os.makedirs(path)
else:
raise OSError('The system cannot find the file specified: \'%s\'' % path)
def get_expanded_path(path, root=None):
"""Returns expanded, local, native filesystem working copy path.
Args:
path: path to expand and convert to local filesystem directory separators
root: if present, prepended to path first
"""
path = path.replace('/', os.sep)
if root:
path = os.path.join(root.replace('/', os.sep), path)
return path
def run_checkstyle(path):
""" Executes checkstyle validation as the separate process
which executes the Ant checkstyle task
Args:
path - path that should be checked
"""
cmd = 'ant -Dcheck.dir=%s' % path
proc = subp.Popen(cmd, shell = True, stdout = subp.PIPE, stderr = subp.STDOUT, cwd=CURRENT_DIR)
proc.communicate()
def export_repos_files(changes, path):
""" Exports committed files from transaction to the specified path
Args:
changes - list of changed files
path - root destination of the files being exported
"""
export_list = {}
for entry in changes:
src = ''.join(['svn://localhost/', entry])
dest = get_expanded_path(entry, root=path)
export_list[src] = dest
for src in export_list:
dest = export_list[src]
prepare_dir(dest)
SVN_CLIENT.export(src, dest)
def get_checkstyle_report(path):
""" Returns checkstyle report as a dictionary which contains amount
of checkstyle violations for every file
Args:
path - path to the checkstyle report
Returns:
dictionary which contains amount of checkstyle violations for every file
"""
report = {}
xmldoc = minidom.parse(path)
file_tags = xmldoc.getElementsByTagName('file')
for tag in file_tags:
key = tag.getAttribute('name')
value = len(tag.getElementsByTagName('error'))
if key in report:
report[key] += value
else:
report[key] = value
return report
def main (repos, txn):
""" Hook entry point """
# magic keyword which allows to skip checkstyle
checkstyle_pattern = '.*skipcheckstyle.*'
# directory which should contain files to be checked
check_dir = os.path.join(CURRENT_DIR, 'src')
# path to the checkstyle report
errors_report_path = os.path.join(CURRENT_DIR, 'checkstyle/checkstyle_errors.xml')
# maximum capacity of errors in report
max_error_cnt = 25
trans = pysvn.Transaction(repos, txn)
# checking the value of 'skip.checkstyle' option (set in the commit log)
log_msg = trans.revpropget('svn:log')
if re.search(checkstyle_pattern, log_msg):
LOGGER.info('skipping checkstyle')
sys.exit(0)
changes = trans.changed()
all_changes = []
modified = []
for path in changes:
if path.endswith('.java'):
status = changes.get(path)[0]
if status is not 'D':
all_changes.append(path)
if status is 'R':
modified.append(path)
if all_changes:
# preparing check dir
if not os.path.exists(check_dir):
os.makedirs(check_dir)
else:
clean_dir(check_dir)
# recieving contents of the files
for path in all_changes:
file_path = get_expanded_path(path,root=check_dir)
prepare_dir(file_path)
f = file(file_path, 'w')
f.write(trans.cat(path))
f.close()
#LOGGER.info('Executing checkstyle over committed files ...')
run_checkstyle(check_dir)
cur_report = get_checkstyle_report(errors_report_path)
clean_dir(check_dir)
export_repos_files(modified, check_dir)
#LOGGER.info('Executing checkstyle over repository versions of commited files ...')
run_checkstyle(check_dir)
prev_report = get_checkstyle_report(errors_report_path)
errors = []
cur_errors = 0
msg_pattern = 'The checkstyle violations amount in file \'%s\' has increased from %s to %s'
for path in cur_report:
if len(errors) < max_error_cnt:
cur_errors = cur_report[path]
if path not in prev_report and cur_errors:
errors.append((msg_pattern % (path[len(check_dir):], '0', cur_errors)))
elif prev_report[path] < cur_errors:
errors.append((msg_pattern % (path[len(check_dir):], prev_report[path], cur_errors)))
else:
pass
if errors:
LOGGER.error('\nCheckstyle violations amount in committed files has exceeded:\n')
for e in errors:
LOGGER.error(e)
return 1
if __name__ == '__main__':
if len(sys.argv) < 3:
sys.stdout.write("\nUsage: %s REPOS TXN\n" % (sys.argv[0]))
else:
sys.exit(main(sys.argv[1], sys.argv[2]))
Отредактировано (Авг. 6, 2009 18:08:16)
Офлайн
soberВот мой тестовый скриптик:
Проблема в том, что клиент молча отваливается при попытке записать в sys.stderr длинный лог.
#!/usr/bin/env python
import sys
import pysvn
def hook(repo, trn):
trans = pysvn.Transaction(repo, trn)
for path, props in trans.changed().iteritems():
if props[1] == pysvn.node_kind.file:
sys.stderr.write('content of %s: %s\n' % (path, trans.cat(path)))
sys.stderr.write('very long string' * 1000000)
return 1
if __name__ == '__main__':
sys.exit(hook(*sys.argv[1:]))
soberЕстественно, асинхронно. Вот вы асинхронно и проверяйте, зачем вам синхронно. В вашем скрипте используется жесткое имя каталога для проверки, из-за этого могут быть проблемы. Используйте временный каталог, либо просто добавьте номер транзакции - получите уникальное имя. Также проверьте, чтобы логи и другие файлы, которые вы используете были уникальными, вот и будет вам асинхронность.
Самым интересным и неожиданным оказалось то, что скрипт работает некорректно в случае с несколькими одновременными коммитами. Оказалось, что хук скрипты выполняются асинхронно, таким образом, результаты проверок получаются неверными. Кто-нибудь знает, есть ли возможность как-то контролировать порядок выполнения хуков?
Офлайн
soberВот, что говорит pylint про ваш скрипт, если вам лень запустить :):
Скрипт исправил
Офлайн
EdУ меня под linux тоже этой проблемы не было, не знаю с чем это связано …
Прекрасно вывел мне 16000186 байт и ничего не сказал. Нужно больше?
EdТочно! Как я сам не додумался…
Используйте временный каталог, либо просто добавьте номер транзакции - получите уникальное имя. Также проверьте, чтобы логи и другие файлы, которые вы используете были уникальными, вот и будет вам асинхронность.
"""
A subversion pre-commit hook that validates the commited files for
checkstyle violations.
"""
__authors__ = [
# alphabetical order by last name, please
'sober',
]
import logging
import os, sys
import pysvn
import re
import shutil
import subprocess as subp
from xml.dom import minidom
# directory in which this script is located
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
SVN_CLIENT = pysvn.Client()
# setup LOGGER
LOGGER = logging.getLogger(__name__)
FORMATTER = logging.Formatter('%(asctime)s - %(filename)s - %(levelname)s - %(message)s')
LOGGER.addHandler(logging.StreamHandler())
def prepare_dir(filename):
""" Creates directory tree if not exists """
dir_path = os.path.dirname(filename)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
def clean_dir(path):
""" Removes all content of the specified directory """
if os.path.exists(path):
shutil.rmtree(path, True)
os.makedirs(path)
else:
raise OSError('The system cannot find the file specified: \'%s\'' % path)
def get_expanded_path(path, root=None):
"""Returns expanded, local, native filesystem working copy path.
Args:
path: path to expand and convert to local filesystem directory separators
root: if present, prepended to path first
"""
path = path.replace('/', os.sep)
if root:
path = os.path.join(root.replace('/', os.sep), path)
return path
def run_checkstyle(path, log_path):
""" Executes checkstyle validation as the separate process
which executes the Ant checkstyle task
Args:
path - path that should be checked
"""
cmd = 'ant -Dcheck.dir=%s -Dlog.path=%s' % (path, log_path)
proc = subp.Popen(cmd, shell = True, stdout = subp.PIPE, \
stderr = subp.STDOUT, cwd=CURRENT_DIR)
proc.communicate()
def export_repos_files(changes, path):
""" Exports committed files from transaction to the specified path
Args:
changes - list of changed files
path - root destination of the files being exported
"""
export_list = {}
for entry in changes:
src = ''.join(['svn://localhost/', entry])
dest = get_expanded_path(entry, root=path)
export_list[src] = dest
for src in export_list:
dest = export_list[src]
prepare_dir(dest)
SVN_CLIENT.export(src, dest)
def get_checkstyle_report(path):
""" Returns checkstyle report as a dictionary which contains amount
of checkstyle violations for every file
Args:
path - path to the checkstyle report
Returns:
dictionary which contains amount of checkstyle violations for every file
"""
report = {}
xmldoc = minidom.parse(path)
file_tags = xmldoc.getElementsByTagName('file')
for tag in file_tags:
key = tag.getAttribute('name')
value = len(tag.getElementsByTagName('error'))
if key in report:
report[key] += value
else:
report[key] = value
return report
def main (repos, txn):
""" Hook entry point """
log_dir = os.path.join(CURRENT_DIR, 'log')
if not os.path.exists(log_dir):
os.mkdir(os.path.join(CURRENT_DIR, 'log'))
# adding file handler to logger
log_path = os.path.join(log_dir, '%s_pre_commit.log' % txn)
file_handler = logging.FileHandler(log_path, mode='w')
file_handler.setFormatter(FORMATTER)
LOGGER.addHandler(file_handler)
# magic keyword which allows to skip checkstyle
checkstyle_pattern = '.*skipcheckstyle.*'
# directory which should contain files to be checked
check_dir = os.path.join(CURRENT_DIR, 'src', txn)
# path to the checkstyle report
errors_report_path = os.path.join(log_dir, \
'%s_checkstyle_errors.xml' % txn)
# maximum capacity of errors in report
max_error_cnt = 25
trans = pysvn.Transaction(repos, txn)
# checking the value of 'skip.checkstyle' option (set in the commit log)
log_msg = trans.revpropget('svn:log')
if re.search(checkstyle_pattern, log_msg):
LOGGER.info('skipping checkstyle')
sys.exit(0)
changes = trans.changed()
all_changes = []
modified = []
for path in changes:
if path.endswith('.java'):
status = changes.get(path)[0]
if status is not 'D':
all_changes.append(path)
if status is 'R':
modified.append(path)
if all_changes:
# preparing check dir
if not os.path.exists(check_dir):
os.makedirs(check_dir)
else:
clean_dir(check_dir)
# recieving contents of the files
for path in all_changes:
file_path = get_expanded_path(path, root=check_dir)
prepare_dir(file_path)
fobj = file(file_path, 'w')
fobj.write(trans.cat(path))
fobj.close()
#LOGGER.info('Executing checkstyle over committed files ...')
run_checkstyle(check_dir, errors_report_path)
cur_report = get_checkstyle_report(errors_report_path)
clean_dir(check_dir)
export_repos_files(modified, check_dir)
#LOGGER.info('Executing checkstyle over repository versions of commited files ...')
run_checkstyle(check_dir, errors_report_path)
prev_report = get_checkstyle_report(errors_report_path)
errors = []
cur_errors = 0
msg_pattern = 'The checkstyle violations amount in file \'%s\' has increased from %s to %s'
for path in cur_report:
if len(errors) < max_error_cnt:
cur_errors = cur_report[path]
if path not in prev_report and cur_errors:
errors.append((msg_pattern % (path[len(check_dir):], \
'0', cur_errors)))
elif prev_report[path] < cur_errors:
errors.append((msg_pattern % (path[len(check_dir):], \
prev_report[path], cur_errors)))
else:
pass
os.unlink(errors_report_path)
shutil.rmtree(check_dir, True)
if errors:
LOGGER.error('\nAmount of checkstyle violations has been increased:\n')
for err in errors:
LOGGER.error(err)
return 1
if __name__ == '__main__':
if len(sys.argv) < 3:
sys.stdout.write("\nUsage: %s REPOS TXN\n" % (sys.argv[0]))
else:
sys.exit(main(sys.argv[1], sys.argv[2]))
Отредактировано (Авг. 7, 2009 15:34:38)
Офлайн
Eсли кому-то понадобится этот хук, вот build.xml, который используется для запуска чекстайла:
<?xml version="1.0" encoding="windows-1251"?>
<project name="checkstyle-checks" default="checkstyle" basedir=".">
<property name="checkstyle.home" location="checkstyle"/>
<property name="checkstyle.ruleset" value="${checkstyle.home}/checkstyle.xml"/>
<property name="checkstyle.errors" value="${checkstyle.home}/checkstyle_errors.xml"/>
<property name="checkstyle.properties" value="${checkstyle.home}/checkstyle_properties.xml"/>
<property name="check.dir" value="src"/>
<property name="log.path" value="${basedir}/checkstyle/checkstyle_errors.xml"/>
<path id="checkstyle.jars">
<fileset dir="${checkstyle.home}" includes="**/*.jar"/>
</path>
<taskdef resource="checkstyletask.properties" classpathref="checkstyle.jars"/>
<target name="checkstyle" unless="skip.checkstyle">
<checkstyle config="${checkstyle.ruleset}" failonviolation="false" maxWarnings="2147483647" maxErrors="0" failureproperty="checkstyle.failed">
<classpath>
<path refid="checkstyle.jars"/>
</classpath>
<fileset dir="${check.dir}" includes="**/*.java" excludes="**/org/apache/**/*.java">
<exclude name="**/**/antlr/*.java"/>
</fileset>
<formatter type="xml" toFile="${log.path}"/>
</checkstyle>
<condition property="checkstyle.passed">
<not>
<isset property="checkstyle.failed"/>
</not>
</condition>
</target>
</project>
Отредактировано (Авг. 7, 2009 17:49:20)
Офлайн
Ну, теперь все гораздо лучше выглядит, учитывая ваш небольшой опыт работы с Питоном.
Вот вам бонус за упорство. Я убрал ненужные глобальные переменные, подправил по мелочи докстринги. Pylint оценил это в 9.47. Я бы еще и отформатировал все это по ширине 80 и разгрузил main, уж очень он большой. Но это уже дело хозяйское, как говорится.
--- pc_hook2.py.orig 2009-08-09 15:55:42.000000000 +0300
+++ pc_hook2.py 2009-08-09 16:13:51.000000000 +0300
@@ -16,33 +16,25 @@
import subprocess as subp
from xml.dom import minidom
-# directory in which this script is located
-CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
-SVN_CLIENT = pysvn.Client()
-
-# setup LOGGER
-LOGGER = logging.getLogger(__name__)
-FORMATTER = logging.Formatter('%(asctime)s - %(filename)s - %(levelname)s - %(message)s')
-
-
-LOGGER.addHandler(logging.StreamHandler())
+LOG_FORMAT = '%(asctime)s - %(filename)s - %(levelname)s - %(message)s'
def prepare_dir(filename):
- """ Creates directory tree if not exists """
+ """Create directory tree if doesn't exist."""
dir_path = os.path.dirname(filename)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
def clean_dir(path):
- """ Removes all content of the specified directory """
+ """Remove all content of the specified directory."""
if os.path.exists(path):
shutil.rmtree(path, True)
os.makedirs(path)
else:
- raise OSError('The system cannot find the file specified: \'%s\'' % path)
+ raise OSError(\
+ 'The system cannot find the file specified: \'%s\'' % path)
def get_expanded_path(path, root=None):
- """Returns expanded, local, native filesystem working copy path.
+ """Return expanded, local, native filesystem working copy path.
Args:
path: path to expand and convert to local filesystem directory separators
@@ -54,23 +46,23 @@
return path
-def run_checkstyle(path, log_path):
- """ Executes checkstyle validation as the separate process
- which executes the Ant checkstyle task
+def run_checkstyle(path, log_path, current_dir):
+ """Execute checkstyle validation as a separate process
+ which executes the Ant checkstyle task
- Args:
- path - path that should be checked
+ Args:
+ path - path that should be checked
"""
cmd = 'ant -Dcheck.dir=%s -Dlog.path=%s' % (path, log_path)
proc = subp.Popen(cmd, shell = True, stdout = subp.PIPE, \
- stderr = subp.STDOUT, cwd=CURRENT_DIR)
+ stderr = subp.STDOUT, cwd=current_dir)
proc.communicate()
-def export_repos_files(changes, path):
- """ Exports committed files from transaction to the specified path
- Args:
- changes - list of changed files
- path - root destination of the files being exported
+def export_repos_files(changes, path, svn_client):
+ """Export committed files from transaction to the specified path
+ Args:
+ changes - list of changed files
+ path - root destination of the files being exported
"""
export_list = {}
for entry in changes:
@@ -78,14 +70,13 @@
dest = get_expanded_path(entry, root=path)
export_list[src] = dest
- for src in export_list:
- dest = export_list[src]
+ for src, dest in export_list.iteritems():
prepare_dir(dest)
- SVN_CLIENT.export(src, dest)
+ svn_client.export(src, dest)
def get_checkstyle_report(path):
- """ Returns checkstyle report as a dictionary which contains amount
- of checkstyle violations for every file
+ """Return checkstyle report as a dictionary which contains amount
+ of checkstyle violations for every file
Args:
path - path to the checkstyle report
Returns:
@@ -105,22 +96,28 @@
return report
def main (repos, txn):
- """ Hook entry point """
+ """Hook entry point."""
- log_dir = os.path.join(CURRENT_DIR, 'log')
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ log_dir = os.path.join(current_dir, 'log')
if not os.path.exists(log_dir):
- os.mkdir(os.path.join(CURRENT_DIR, 'log'))
+ os.mkdir(os.path.join(current_dir, 'log'))
+
+ # setup logger
+ logger = logging.getLogger(__name__)
+ logger.addHandler(logging.StreamHandler())
# adding file handler to logger
log_path = os.path.join(log_dir, '%s_pre_commit.log' % txn)
file_handler = logging.FileHandler(log_path, mode='w')
- file_handler.setFormatter(FORMATTER)
- LOGGER.addHandler(file_handler)
+ file_handler.setFormatter(logging.Formatter(LOG_FORMAT))
+ logger.addHandler(file_handler)
# magic keyword which allows to skip checkstyle
checkstyle_pattern = '.*skipcheckstyle.*'
# directory which should contain files to be checked
- check_dir = os.path.join(CURRENT_DIR, 'src', txn)
+ check_dir = os.path.join(current_dir, 'src', txn)
# path to the checkstyle report
errors_report_path = os.path.join(log_dir, \
'%s_checkstyle_errors.xml' % txn)
@@ -132,7 +129,7 @@
# checking the value of 'skip.checkstyle' option (set in the commit log)
log_msg = trans.revpropget('svn:log')
if re.search(checkstyle_pattern, log_msg):
- LOGGER.info('skipping checkstyle')
+ logger.info('skipping checkstyle')
sys.exit(0)
changes = trans.changed()
@@ -163,15 +160,15 @@
fobj.write(trans.cat(path))
fobj.close()
- #LOGGER.info('Executing checkstyle over committed files ...')
- run_checkstyle(check_dir, errors_report_path)
+ #logger.info('Executing checkstyle over committed files ...')
+ run_checkstyle(check_dir, errors_report_path, current_dir)
cur_report = get_checkstyle_report(errors_report_path)
clean_dir(check_dir)
- export_repos_files(modified, check_dir)
- #LOGGER.info('Executing checkstyle over repository versions of commited files ...')
- run_checkstyle(check_dir, errors_report_path)
+ export_repos_files(modified, check_dir, pysvn.Client())
+ #logger.info('Executing checkstyle over repository versions of commited files ...')
+ run_checkstyle(check_dir, errors_report_path, current_dir)
prev_report = get_checkstyle_report(errors_report_path)
errors = []
@@ -195,9 +192,9 @@
shutil.rmtree(check_dir, True)
if errors:
- LOGGER.error('\nAmount of checkstyle violations has been increased:\n')
+ logger.error('\nAmount of checkstyle violations has been increased:\n')
for err in errors:
- LOGGER.error(err)
+ logger.error(err)
return 1
Отредактировано (Авг. 9, 2009 16:21:07)
Офлайн
Большое спасибо за патч. Применил :)
Нашел в скрипте странный баг:
for path in all_changes:
file_path = get_expanded_path(path, root=check_dir)
prepare_dir(file_path)
fobj = file(file_path, 'w')
fobj.write(trans.cat(path))
fobj.close()
Офлайн
soberпопробуй вместо флага ‘w’ поставить ‘wb’
fobj = file(file_path, ‘w’)
Офлайн