view check_whitespace.py @ 11:ff07accd0692

Don't use print; complain when used as program instead of python hook.
author Sjoerd Mullender <sjoerd@acm.org>
date Fri, 20 Mar 2015 15:26:10 +0100 (2015-03-20)
parents bd1ca3d502d6
children 915cf73f6afa
line wrap: on
line source
#!/usr/bin/env python
#
# Mercurial hook to check for inappropriate whitespace.  Currently it
# only checks Python files.
#
# This script is based on the code in the Mercurial book
# <http://hgbook.red-bean.com/read/handling-repository-events-with-hooks.html#id403945>

'''hook to refuse commits that introduce some bad inputs.
Inputs that are refused are conflict markers (rows of < or >
characters), and tabs and trailing white space in Python sources.

Usage: in your ~/.hgrc file add:
[hooks]
pretxncommit.whitespace = python:/path/to/check_whitespace.py:hook
pretxnchangegroup.whitespace = python:/path/to/check_whitespace.py:hook
'''

import re
import os

binary_suffixes = (
    '.bam',
    '.bmp',
    '.bz2',
    '.chm',
    '.cpl',
    '.dia',
    '.dll',
    '.gz',
    '.ico',
    '.pdf',
    '.png',
    '.rtf',
    '.zip',
)

def trailing_whitespace(difflines):
    linenum = 0
    header = False
    filename = ''
    fnre = re.compile(r'(?:---|\+\+\+) (?P<filename>[^\t\r\n]+)')
    lnre = re.compile(r'@@ -\d+,\d+ \+(?P<lineno>\d+),')
    wsre = re.compile(r'\+.*[ \t]$')
    tbre = re.compile(r'\+.*\t')
    resre = re.compile(r'\+(<<<<<<<|>>>>>>>)')
    adding = False
    islink = False              # symlinks can have no newline at end

    for chunk in difflines:
        for line in chunk.split('\n'):
            if header:
                if line.startswith('new file mode'):
                    islink = '120000' in line
                # remember the name of the file that this diff affects
                m = fnre.match(line)
                if m is not None and m.group('filename') != '/dev/null':
                    filename = m.group('filename').split('/', 1)[-1]
                if line.startswith('+++ '):
                    header = False
                continue
            if line.startswith('diff '):
                header = True
                adding = False
                islink = False
                continue
            # hunk header - save the line number
            m = lnre.match(line)
            if m is not None:
                linenum = int(m.group('lineno'))
                continue
            if header or not filename:
                continue
            if line[:1] == '+':
                adding = True
            elif line[:1] in (' ', '-'):
                adding = False
            elif adding \
                 and not islink \
                 and line.startswith(r'\ No newline at end of file') \
                 and not filename.endswith('vertoo.data') \
                 and not os.path.splitext(filename)[1] in binary_suffixes:
                adding = False
                yield filename, linenum, 'no newline at end of file'
            # hunk body - check for an added line with bad whitespace
            if filename[-3:] == '.py' or filename[-5:] == '.py.in':
                m = tbre.match(line)
                if m is not None:
                    yield filename, linenum, 'TABs'
            # trailing whitespace, for now only in Python source
            m = wsre.match(line)
            if m is not None:
                if filename[-3:] == '.py' or filename[-5:] == '.py.in':
                    yield filename, linenum, 'trailing whitespace'
            # conflict markers
            m = resre.match(line)
            if m is not None:
                yield filename, linenum, 'conflict marker'
            if line and line[0] in ' +':
                linenum += 1

def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
    import os, sys, subprocess
    if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
        ui.write('Hook should be pretxncommit/pretxnchangegroup not "%s".' % hooktype)
        return 1
    added = False
    branches = {}
    for rev in xrange(repo[node], len(repo)):
        branch = repo[rev].branch()
        if not branches.has_key(branch):
            # first time we see this branch, remember parents to diff against
            branches[branch] = repo[rev].parents()
        desc = 0
        for d in repo[rev].descendants():
            break
        else:
            # no descendants for this revision, check diff with saved parents
            for p in branches[branch]:
                for filename, linenum, msg in trailing_whitespace(repo[rev].diff(p)):
                    ui.write('%s, line %d: %s added\n' % (filename, linenum, msg))
                    added = True
    if added:
        # save the commit message so we don't need to retype it
        if source != 'serve':
            cmtsv = os.path.join('.hg','commit.save')
            subprocess.call(['hg', 'tip', '--template', '{desc}'],
                            stdout=open(cmtsv, 'w'))
            ui.write('commit message saved to %s\n' % cmtsv)
        return 1

if __name__ == '__main__':
    import sys
    sys.stderr.write('call this hook using the Python interface.\n'
                     'In your .hgrc file you shoud have:\n'
                     'pretxncommit.whitespace = python:<path-to-check_whitespace-directory>/check_whitespace.py:hook\n')

    sys.exit(1)