|
#!/usr/bin/env python2 |
|
# Automatically add a summary of changed modules by using git subsum. |
|
# |
|
# To enable this hook, copy it to $repo/.git/hooks/prepare-commit-msg. |
|
|
|
import re |
|
import sys |
|
from subprocess import check_output, CalledProcessError, STDOUT |
|
|
|
|
|
def die(fmt, *args): |
|
sys.stderr.write((sys.argv[0] + ': ' + fmt + '\n') % args) |
|
sys.exit(1) |
|
|
|
|
|
def exit(msg, subsum, fname, fmt, *args): |
|
errmsg = (fmt + '\n') % args |
|
sys.stderr.write(sys.argv[0] + ': ' + errmsg) |
|
new_msg = '# Warning (git subsum):\n# \t' + errmsg |
|
if subsum is not None: |
|
new_msg = ("#\n# The new summary is:\n" + |
|
'\n'.join(('# ' + l for l in subsum.splitlines()))) |
|
new_msg += msg |
|
try: |
|
file(fname, 'w').write(new_msg) |
|
except Exception as e: |
|
die("Error writing file %s (%s)", fname, e) |
|
sys.exit(0) |
|
|
|
|
|
# Command line arguments |
|
fname = sys.argv[1] |
|
op = sys.argv[2] if len(sys.argv) > 2 else None |
|
ref = (' %s^' % sys.argv[3]) if len(sys.argv) > 3 else "" |
|
|
|
# Read the contents of the file with the message |
|
try: |
|
msg = file(sys.argv[1]).read() |
|
except Exception as e: |
|
die("Error reading file %s (%s)", fname, e) |
|
|
|
# Don't do anything if the command is not available |
|
try: |
|
check_output('git -c help.autocorrect=0 subsum -h'.split(), |
|
stderr=STDOUT) |
|
except CalledProcessError as e: |
|
exit(msg, None, fname, "Error running `git subsum`: %s", e) |
|
|
|
# Create a temporary file with the contents of the summary |
|
# (and remove it at program exit) |
|
try: |
|
subsum = check_output(('git subsum -c' + ref).split()) |
|
except CalledProcessError as e: |
|
exit(msg, fname, "Error invoking `git subsum%s` (%s)", ref, e) |
|
|
|
# See githooks(5) man for details on what each value means |
|
# No pre-existing message |
|
if not op: |
|
msg = '\n\n%s%s' % (subsum, msg) |
|
# Pre-existing message |
|
elif op in ('commit', 'message', 'merge'): |
|
# We assume the message is divided in 3 sections: |
|
# |
|
# 1. The user message |
|
# 2. A git subsum summary (optional) |
|
# 3. Comments added by git as a help |
|
# |
|
# We always start copying all the user message as is. When we detect |
|
# the summary started (via a regex), we start commenting out the old |
|
# summary. When the comments added by git are detected (a line starting |
|
# with '#'), then we go back again to copy all the lines as they are. |
|
# Just after we detect the end of the user message (being because the |
|
# summary or the comments started), we print the new subsum summary. |
|
# |
|
# So the resulting new message is: |
|
# |
|
# 1. The user message |
|
# 2. The new git subsum summary (if any) |
|
# 3. The old git subsum summary commented out (if any) |
|
# 4. The comments added by git as a help |
|
|
|
IN_MESSAGE = 1 |
|
IN_SUMMARY = 2 |
|
IN_COMMENT = 3 |
|
state = IN_MESSAGE |
|
|
|
# Regex to detect the beginning of the old summary information |
|
sum_re = re.compile(r'^\* [^ ]+ .*\([0-9a-f]{7}\)\.\.\.' |
|
r'.*\([0-9a-f]{7}\) \([0-9]+ commits\)$') |
|
|
|
new_msg = '' |
|
for line in msg.splitlines(): |
|
if state == IN_MESSAGE: |
|
if sum_re.match(line): |
|
state = IN_SUMMARY |
|
new_msg += subsum |
|
# only put comments for interactive message |
|
if op != 'message': |
|
new_msg += '# Old submodules ' + \ |
|
'summary:\n#\n# ' + line + '\n' |
|
continue |
|
if line.startswith('#'): |
|
state = IN_COMMENT |
|
new_msg += '\n' + subsum + line + '\n' |
|
continue |
|
new_msg += line + '\n' |
|
continue |
|
if state == IN_SUMMARY: |
|
if line.startswith('#'): |
|
status = IN_COMMENT |
|
new_msg += line + '\n' |
|
continue |
|
# only put comments for interactive message |
|
if op != 'message': |
|
new_msg += '# ' + line + '\n' |
|
continue |
|
if state == IN_COMMENT: |
|
new_msg += line + '\n' |
|
continue |
|
msg = new_msg |
|
# Squashing is ignored for now, as we could have multiple git subsum generated |
|
# messages, the simple message parsing used above is not good enough and |
|
# everything will break. But we let the user know via a comment in the commit |
|
# message. |
|
elif op in ('squash',): |
|
exit(msg, subsum, fname, "Squashing not supported yet, the summary " |
|
"wasn't updated") |
|
# We don't mess with people configuring templates at all. |
|
elif op in ('template',): |
|
sys.exit(0) |
|
# Any unknown operation is only reported to the user |
|
else: |
|
exit(msg, subsum, fname, "Unkown argument '%s' (cmd line: %s)", op, |
|
' '.join(["'%s'" % a for a in sys.argv])) |
|
|
|
try: |
|
file(fname, 'w').write(msg) |
|
except Exception as e: |
|
die("Error writing file %s (%s)", fname, e) |