Last active
August 29, 2015 14:07
-
-
Save dopuskh3/bcf437f0f0c1293c9b2d to your computer and use it in GitHub Desktop.
git commit validator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from nltk.corpus import verbnet | |
import sys | |
class CommitException(Exception): | |
def __init__(self, msg, line, start, end, fatal=False): | |
Exception.__init__(self,msg) | |
self._line = line | |
self._start = start | |
self._end = end | |
self._fatal = fatal | |
def formatError(self): | |
return "%d:%d:%s" % (self._line, self._start, str(self)) | |
def isFatal(self): | |
return self._fatal | |
class NoDescriptionException(CommitException): | |
pass | |
class InvalidTitleException(CommitException): | |
pass | |
class TrailingSpaceException(CommitException): | |
pass | |
class LineTooLongException(CommitException): | |
pass | |
class InvalidMessageException(CommitException): | |
pass | |
class ContextInfo(object): | |
def __init__(self, line): | |
self.line = line | |
class Validator(object): | |
def __init__(self, content, contextInfo=None): | |
self._content = content | |
self._context = contextInfo | |
def context(self, lineNumber): | |
if not self._context: | |
return lineNumber | |
return lineNumber + self._context.line | |
def validate(self, *args, **kwargs): | |
try: | |
self.run(*args, **kwargs) | |
return True | |
except CommitException, e: | |
print >>sys.stderr, e.formatError() | |
if e.isFatal(): | |
raise e | |
return False | |
class TrailingSpaceValidator(Validator): | |
def run(self): | |
for line, l in enumerate(self._content): | |
if l.rstrip(' ') != l: | |
raise TrailingSpaceException("Trailing space: [%s]" % l, | |
self.context(line+1), | |
len(l.rstrip(' '))+1, | |
len(l)+1) | |
class LineLengthValidator(Validator): | |
def __init__(self, content, max_len, context=None): | |
Validator.__init__(self, content, context) | |
self._max_len = max_len | |
def run(self): | |
for line, l in enumerate(self._content): | |
if len(l) > self._max_len: | |
raise LineTooLongException("Line [%s] is too long (max=%d, length=%d)" % (l, self._max_len, len(l)), | |
self.context(line+1), | |
self._max_len, | |
len(l)) | |
class TitleValidator(Validator): | |
def run(self): | |
TrailingSpaceValidator(self._content, self._context).validate() | |
LineLengthValidator(self._content, 50, self._context).validate() | |
firstWord = self._content[0].split(' ')[0] | |
if not firstWord[0].isupper(): | |
raise InvalidTitleException("First title character must be a capital letter: [%s]" % self._content[0], | |
self.context(1), | |
1, 2) | |
vclass = verbnet.classids(firstWord.lower()) | |
if not vclass: | |
raise InvalidTitleException("Title must begin with an infinitive verb [%s]" % self._content[0], | |
self.context(1), | |
1, len(firstWord)) | |
class EmptyLinesValidator(Validator): | |
def run(self): | |
if self._content and self._content[0]: | |
if not self._content[0].strip(' ').strip('\t'): | |
raise CommitException("Too many empty lines", self.context(1), 1, 2) | |
class DescriptionValidator(Validator): | |
def run(self): | |
EmptyLinesValidator(self._content, self._context).validate() | |
TrailingSpaceValidator(self._content, self._context).validate() | |
LineLengthValidator(self._content, 72, self._context).validate() | |
class CommitValidator(Validator): | |
def __init__(self, msg): | |
self._msg = msg | |
def run(self): | |
lines = self._msg.splitlines() | |
if len(lines) < 3: | |
raise NoDescriptionException("Commit message does not contain a description", 0, 1, | |
len(lines[0]), fatal=True) | |
if lines[1]: | |
raise InvalidMessageException("Second commit line must be empty", 1, 1, 2, fatal=True) | |
title, description = [lines[0]], lines[2:] | |
self._validateTitle(title) | |
self._validateDescription(description) | |
def _validateTitle(self, title): | |
TitleValidator(title).validate() | |
def _validateDescription(self, description): | |
DescriptionValidator(description, ContextInfo(2)).validate() | |
def validateFromFile(filename): | |
with open(filename, 'r') as fd: | |
msg = fd.read() | |
CommitValidator(msg).validate() | |
if __name__ == "__main__": | |
validateFromFile(sys.argv[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment