datatracker/hooks/pre-commit
Henrik Levkowetz 7366ca6efd Various commit hooks in use currently.
- Legacy-Id: 10557
2015-12-09 21:12:36 +00:00

147 lines
4.6 KiB
Python
Executable file

#!/usr/bin/env python
"""
An SVN pre-commit hook which requires that commits either are marked as
whitespace cleanup commits, and contain no non-whitespace changes, or
leave whitespace alone on lines without code changes.
"""
import os
import sys
import difflib
import debug
from pysvn import Client, Transaction
prog = os.path.basename(sys.argv[0])
def die(msg):
sys.stderr.write("\n%s: Error: %s\n" % (prog, msg))
sys.exit(1)
if len(sys.argv) <= 1:
die("Expected arguments: REPOSITORY TRANSACTION, found none")
if len(sys.argv) <= 2:
die( "Expected arguments: REPOSITORY TRANSACTION, found only '%s'" % sys.argv[1])
repo = sys.argv[1]
txname = sys.argv[2]
tx = Transaction(repo, txname)
client = Client()
is_whitespace_cleanup = "whitespace cleanup" in tx.revpropget("svn:log").lower()
def normalize(s):
return s.rstrip().expandtabs()
def normalize_sequence(seq):
o = []
for l in seq:
o.append(normalize(l))
return o
def count(gen):
return sum(1 for _ in gen)
# Function with side effects. Acts on global varaibles
def inc_ab(flag):
global a, b
if flag == ' ':
a += 1; b += 1
elif flag == '-':
a += 1
elif flag == '+':
b += 1
elif flag == '?':
pass
else:
raise ValueError("Unexpected ndiff mark: '%s' in: %s" % (flag, plain_diff[i]))
def get_chunks(unidiff):
if not unidiff:
return []
chunks = []
chunk = []
intro = unidiff[0:2]
for line in unidiff[2:]:
if line.startswith("@@"):
if chunk:
chunks.append(chunk)
chunk = [line]
else:
chunk.append(line)
chunks.append(chunk)
return intro, chunks
changes = tx.changed()
issues = {}
context = 3
for path in changes:
action, kind, mod, propmod = changes[path]
# Don't try to diff added or deleted files, on ly changed text files
if not (mod and action == "R"):
continue
# Don't try do diff binary files
mimetype = tx.propget("svn:mime-type", path)
if mimetype and not mimetype.startswith("text/"):
continue
new = tx.cat(path).splitlines()
old = client.cat("file://"+os.path.join(repo,path)).splitlines()
plain_diff = list(difflib.unified_diff(old, new, "%s (repository)"%path, "%s (commit)"%path, lineterm="" ))
old = normalize_sequence(old)
new = normalize_sequence(new)
white_diff = list(difflib.unified_diff(old, new, "%s (repository)"%path, "%s (commit)"%path, lineterm=""))
plain_count = len(plain_diff)
white_count = len(white_diff)
# for i in range(len(white_diff)):
# sys.stderr.write("%-80s | %-80s\n" % (normalize(plain_diff[i][:80]), normalize(white_diff[i][:80])))
if white_count != plain_count and not is_whitespace_cleanup:
intro, plain_chunks = get_chunks(plain_diff)
intro, white_chunks = get_chunks(white_diff)
for chunk in white_chunks:
for i in range(len(plain_chunks)):
if chunk == plain_chunks[i]:
del plain_chunks[i]
issue = intro
for chunk in plain_chunks:
issue += chunk
if len(plain_chunks) > 1:
are = "are"; s = "s"; an = ""
else:
are = "is"; s = ""; an = "an "
issues[path] = issue
if white_count != 0 and is_whitespace_cleanup:
intro, white_chunks = get_chunks(white_diff)
if len(white_chunks) > 1:
are = "are"; s = "s"; an = ""
else:
are = "is"; s = ""; an = "an "
issues[path] = white_diff
if issues:
if is_whitespace_cleanup:
die("It looks as if there are non-whitespace changes in\n"
"this commit, but it was marked as a whitespace cleanup commit.\n\n"
"Here %s the diff chunk%s with unexpected change%s:\n\n%s\n\n"
"Declining the commit due to a mix of code and spaces-only changes. Please\n"
"avoid mixing whitespace-only changes with code changes. See details above." %
(are, s, s, '\n\n'.join([ '\n'.join(issues[path]) for path in issues ]))
)
else:
die("It looks as if there are spaces-only changes in this\n"
"commit, but it was not marked as a whitespace cleanup commit.\n\n"
"Here %s the diff chunk%s with unexpected change%s:\n\n%s\n\n"
"Declining the commit due to a mix of code and spaces-only changes. Please\n"
"avoid mixing whitespace-only changes with code changes. See details above." %
(are, s, s, '\n\n'.join([ '\n'.join(issues[path]) for path in issues ]))
)
sys.exit(0)