#!/usr/bin/python import sys from subprocess import Popen, PIPE from reindent import Reindenter import re import cpp_format svnlook = "/usr/bin/svnlook" def _check_do_not_commit(line, filename, num, errors): marker = 'DO NOT' + ' COMMIT' if line.find(marker) >= 0: errors.append('%s:%d: Line contains the string "%s"' \ % (filename, num+1, marker)); def get_modified_files(txn, repos): """Get a list of all files modified or added in this transaction""" modfiles = [] cmd = [svnlook, 'changed', '-t', txn, repos] for line in Popen(cmd, shell=False, stdout=PIPE).stdout: if len(line) > 4 and (line[0] == 'A' or line[0] == 'U'): modfiles.append(line[4:].rstrip('\r\n')) return modfiles def check_c_file(filename, txn, repos, errors): """Check each modified C file to make sure it adheres to the standards""" cmd = [svnlook, 'cat', '-t', txn, repos, filename] pipe = Popen(cmd, shell=False, stdout=PIPE).stdout srch = re.compile('\s+$') url = re.compile('https?://') blank = False for (num, line) in enumerate(pipe): line = line.rstrip('\r\n') # No way to split URLs, so let them exceed 80 characters: if len(line) > 80 and not url.search(line): errors.append('%s:%d: Line is longer than 80 characters.' \ % (filename, num+1)) if line.find('\t') >= 0: errors.append('%s:%d: Line contains tabs.' % (filename, num+1)) _check_do_not_commit(line, filename, num, errors) if srch.search(line): errors.append('%s:%d: Line has trailing whitespace' \ % (filename, num+1)) if not filename.endswith(".cpp") and line.startswith("#define ") \ and not line.startswith("#define IMP") \ and not line.startswith("#define EIGEN_YES_I_KNOW_SPARSE_" "MODULE_IS_NOT_STABLE_YET"): errors.append('%s:%d: Preprocessor symbols must start with IMP' \ % (filename, num+1)) blank = (len(line) == 0) if blank and num == 0: errors.append('File %s has leading blank line(s)' % filename) if blank: errors.append('File %s has trailing blank line(s)' % filename) def check_python_file(filename, txn, repos, errors): """Check each modified Python file to make sure it adheres to the standards""" cmd = [svnlook, 'cat', '-t', txn, repos, filename] temptest = re.compile('\s+def\s+temp_hide_test.*') test= re.compile('\s+def\s+(test_[abcdefghijklmnopqrstuvwxyz' '0123456789_]*)\(') tests = [] for (num, line) in enumerate(Popen(cmd, shell=False, stdout=PIPE).stdout): _check_do_not_commit(line, filename, num, errors) if temptest.match(line): errors.append('%s:%d: Test case has the temp_hide_ prefix' \ % (filename, num+1)) m = test.match(line) if m: g = m.group(1) if g in tests: errors.append('%s:%d: Test case has multiple tests with ' 'the same name %s' % (filename, num+1, g)) tests.append(g) p1 = Popen(cmd, shell=False, stdout=PIPE).stdout r = Reindenter(p1) try: if 'compat_python' not in filename and r.run(): errors.append('Python file ' + filename + ' has odd indentation; ' \ + 'please run through reindent.py first.') except Exception: print >> sys.stderr, "reindent.py FAILED on %s:" % filename raise def get_file(filename, txn, repos): cmd = [svnlook, 'cat', '-t', txn, repos, filename] pipe = Popen(cmd, shell=False, stdout=PIPE).stdout return (pipe, filename) def check_modified_file(filename, txn, repos, errors): """Check each modified file to make sure it adheres to the standards""" # skip code that isn't ours if filename.find("dependency") != -1: return if filename.endswith('.h') or filename.endswith('.cpp') \ or filename.endswith('.c'): check_c_file(filename, txn, repos, errors) if filename.endswith('.h'): cpp_format.check_header_file(get_file(filename, txn, repos), errors) elif filename.endswith('.cpp'): cpp_format.check_cpp_file(get_file(filename, txn, repos), errors) elif filename.endswith('.py') or filename.endswith('SConscript') \ or filename.endswith('SConstruct'): check_python_file(filename, txn, repos, errors) def is_symlink(filename, txn, repos, errors): """Return True if the given filename is a symlink. Also report an error if a symlink also has the executable bit set, since this can trigger Subversion bug #2344.""" cmd = [svnlook, 'proplist', '-v', '-t', txn, repos, filename] res = Popen(cmd, shell=False, stdout=PIPE).stdout.read() symlink = "svn:special" in res executable = "svn:executable" in res if symlink and executable: errors.append('You have set the svn:executable property on the ' \ + 'symlink ' + filename + '; please remove it, since ' \ + 'this can trigger SVN bug #2344.') return symlink def check_logmsg(txn, repos, errors): """Make sure a suitable log message was supplied""" marker = 'DO NOT' + ' COMMIT' cmd = [svnlook, 'log', '-t', txn, repos] pipe = Popen(cmd, shell=False, stdout=PIPE).stdout logmsg = pipe.readline().rstrip('\r\n') if len(logmsg) < 8: errors.append("Please provide a log message to describe " \ + "what you changed."); elif logmsg.find(marker) >= 0: errors.append('Log message contains the string "%s"' % marker) def main(repos, txn): errors = [] modfiles = get_modified_files(txn, repos) for filename in modfiles: # Don't check symlinks, since they are not stored as true symlinks in # the SVN transaction, so the files aren't valid C++, Python etc. if not is_symlink(filename, txn, repos, errors): check_modified_file(filename, txn, repos, errors) check_logmsg(txn, repos, errors) if len(errors) > 0: sys.stderr.write("Change rejected by pre-commit for the " \ + "following reasons:\n\n") sys.stderr.write("\n".join(errors)) sys.stderr.write("\n") sys.exit(1) if __name__ == '__main__': main(sys.argv[1], sys.argv[2])