view check_whitespace.py @ 6:95e8189b5fb4

Check for newline at end of file.
author Sjoerd Mullender <sjoerd@acm.org>
date Fri, 31 Oct 2014 16:19:24 +0100 (2014-10-31)
parents 32c51a111990
children 2ac85cc1e11b
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

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

    for chunk in difflines:
        for line in chunk.split('\n'):
            if header:
                # 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
                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 line.startswith(r'\ No newline at end of file'):
                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, **kwargs):
    import os, sys, subprocess

    added = 0
    for filename, linenum, msg in trailing_whitespace(repo[node].diff()):
        print >> sys.stderr, ('%s, line %d: %s added' %
                              (filename, linenum, msg))
        added += 1
    if added:
        # save the commit message so we don't need to retype it
        cmtsv = os.path.join('.hg','commit.save')
        subprocess.call(['hg', 'tip', '--template', '{desc}'],
                        stdout=open(cmtsv, 'w'))
        print >> sys.stderr, 'commit message saved to %s' % cmtsv
        return True

if __name__ == '__main__':
    if hook(None, None, None, None):
        sys.exit(1)