Dans un projet ouvert à des contributeurs externes, susceptibles de modifier des codes sources, il y a quelques éceuils à éviter pour ne pas que la base de code soit "cassé".
Je ne parle pas ici du
code reviewing mais de la prévention qu'il faut mettre en place pour protéger les sources de mauvaises manipulations. Les deux cas les plus fréquents sont:
- l'insertion de tabulations dans le code
- l'insertion par certains éditeurs de code sous windows, de carriage return avant les sauts de lignes
Ces deux problèmes peuvent tout simplement provoquer des erreurs, et parasiter le travail des développeurs.
Au lieu de lutter contre ces ajouts, de traquer les responsables ;), le plus simple est de mettre en place un contrôle automatique au niveau du dépôt de code centralisé.
Nettoyer automatiquement les sources entrants n'est pas une bonne solution, car les développeurs qui provoquent ces problèmes peuvent rester dans l'ignorance, et continuer à travailler sans corriger leur éditeur. Le mieux consiste à tout simplement bloquer les commits qui ne sont pas "valides".
Subversion propose un système de hook, qui permet de lancer un script Python à chaque commit, et de renvoyer un message au commiteur en cas d'erreur.
J'ai récupéré un script sur le web, que j'ai adapté. Le voici:
#!/usr/bin/python2.4
# -*- coding: UTF-8 -*-
# adapted from:
# http://blog.wordaligned.org/articles/2006/08/09/a-subversion-pre-commit-hook
# by Tarek Ziadé
from subprocess import Popen
from subprocess import PIPE
import re
import os
re_options = re.IGNORECASE | re.MULTILINE | re.DOTALL
class EOF(object):
def findall(self, content):
if content.endswith('\n'):
return []
return ['\n']
tab_catcher = re.compile(r'^\t', re_options)
windows_catcher = re.compile(r'\r\n$', re_options)
testers = (('found TAB', tab_catcher),
('found CR/LF', windows_catcher),
('no new line at the end', EOF()))
def command_output(cmd):
""" Capture a command's standard output."""
return Popen(cmd.split(), stdout=PIPE).communicate()[0]
def files_changed(look_cmd):
""" List the files added or updated by this transaction."""
def filename(line):
return line[4:]
def added_or_updated(line):
return line and line[0] in ("A", "U")
return [filename(line) for line in
command_output(look_cmd % "changed").split("\n")
if added_or_updated(line)]
def file_contents(filename, look_cmd):
"""Return a file's contents for this transaction"""
return command_output("%s %s" % (look_cmd % "cat", filename))
def test_expression(expr, filename, look_cmd):
"""test regexpr over file"""
return len(expr.findall(file_contents(filename, look_cmd))) > 0
def check_file(look_cmd):
"""checks Python files in this transaction"""
def is_python_file(fname):
return os.path.splitext(fname)[1] in ".py".split()
erroneous_files = []
for file in files_changed(look_cmd):
if not is_python_file(file):
continue
for error_type, tester in testers:
if test_expression(tester, file, look_cmd):
erroneous_files.append((error_type, file))
num_failures = len(erroneous_files)
if num_failures > 0:
sys.stderr.write("[ERROR] please check your files:\n")
for error_type, file in erroneous_files:
sys.stderr.write("[ERROR] %s in %s\n" % (error_type, file))
return num_failures
def main():
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-r", "--revision",
help="Test mode. TXN actually refers to a revision.",
action="store_true", default=False)
errors = 0
(opts, (repos, txn_or_rvn)) = parser.parse_args()
look_opt = ("--transaction", "--revision")[opts.revision]
look_cmd = "svnlook %s %s %s %s" % (
"%s", repos, look_opt, txn_or_rvn)
errors += check_file(look_cmd)
return errors
if __name__ == "__main__":
import sys
sys.exit(main())
J'ai ajouté aussi un contrôle de new line at end of file.
Ce script est à appeler dans le script
pre-commit hook, (référez-vous à la documentation de SVN ou à
ce très bon tutoriel, très complet)
L'appel resssemble à:
/chemin/vers/script/svn_check_source.py "$REPOS" "$TXN" || exit 1
Il est ensuite possible d'étendre ce principe en mettant en place des contrôles de qualité de code, avec des outils comme
pychecker par exemple, mais de manière non-bloquante: un mail peut par exemple être envoyé lorsque la qualité d'un source est en dessous d'un certain seuil.
*maj* Merci à David (Biologeek) pour un correctif sur le script (il manquait une parenthèse)