1#!/usr/bin/env python3 2 3# Copyright (C) 2020 Agilent Technologies, Inc. 4# Author: Chris Laplante <chris.laplante@agilent.com> 5 6# This sendemail-validate hook injects 'From: ' header lines into outgoing 7# emails sent via 'git send-email', to ensure that accurate commit authorship 8# information is present. It was created because some email servers 9# (notably Microsoft Exchange / Office 360) seem to butcher outgoing patches, 10# resulting in incorrect authorship. 11 12# Current limitations: 13# 1. Assumes one per patch per email 14# 2. Minimal error checking 15# 16# Installation: 17# 1. Copy to .git/hooks/sendemail-validate 18# 2. chmod +x .git/hooks/sendemail-validate 19 20 21import enum 22import re 23import subprocess 24import sys 25 26 27class Subject(enum.IntEnum): 28 NOT_SEEN = 0 29 CONSUMING = 1 30 SEEN = 2 31 32 33def make_from_line(): 34 cmd = ["git", "var", "GIT_COMMITTER_IDENT"] 35 proc = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, universal_newlines=True) 36 regex = re.compile(r"^(.*>).*$") 37 match = regex.match(proc.stdout) 38 assert match is not None 39 return "From: {0}".format(match.group(1)) 40 41 42def main(): 43 email = sys.argv[1] 44 45 with open(email, "r") as f: 46 email_lines = f.read().split("\n") 47 48 subject_seen = Subject.NOT_SEEN 49 first_body_line = None 50 for i, line in enumerate(email_lines): 51 if (subject_seen == Subject.NOT_SEEN) and line.startswith("Subject: "): 52 subject_seen = Subject.CONSUMING 53 continue 54 if subject_seen == Subject.CONSUMING: 55 if not line.strip(): 56 subject_seen = Subject.SEEN 57 continue 58 if subject_seen == Subject.SEEN: 59 first_body_line = i 60 break 61 62 assert subject_seen == Subject.SEEN 63 assert first_body_line is not None 64 65 from_line = make_from_line() 66 # Only add FROM line if it is not already there 67 if email_lines[first_body_line] != from_line: 68 email_lines.insert(first_body_line, from_line) 69 email_lines.insert(first_body_line + 1, "") 70 with open(email, "w") as f: 71 f.write("\n".join(email_lines)) 72 73 return 0 74 75 76if __name__ == "__main__": 77 sys.exit(main()) 78 79