banniere.png

Protéger une base de code Python avec Subversion

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)

Vous aimez ce post ? partagez-le :


Trackback URL | Categories: coding, misc 0 comments - add

Tarek Ziadé. Copyright 2006. Tous droits réservés. Licence contenu site
BuzTrucs
Add to Technorati Favorites