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