Instantly share code, notes, and snippets.
Last active
January 4, 2018 18:24
-
Save keyan/a340e336029d51baba4ca3c24b6a516c to your computer and use it in GitHub Desktop.
A script you can run in your repo to scan for TODO messages and email the authors asking them to fix it! Can be easily adapted for other search strings.
This file contains 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/python | |
""" | |
This script searches the git repo looking for TODO messages, determines the | |
author of the line, then emails that author asking them to repair the issue. | |
It is intended to be run at a regular interval on a CI server or cron-job | |
server. By changing the `SEARCH_STRING` the script can be easily adapted to | |
trigger on any undesired code. | |
Usage: | |
./email_todo_authors | |
""" | |
import email.message | |
import smtplib | |
import subprocess | |
SENDER_EMAIL = "email@gmail.com" | |
SENDER_PASSWORD = "password" | |
SEARCH_STRING = "TODO" | |
SEARCH_PATH = "*" | |
REPO_ADDR = "https://github.com/<org>/<repo>" | |
MESSAGE = "Hey there! Please take a look at your TODO and make any neccessary changes to cleanup your code. Thanks!" | |
def find_offending_lines(): | |
""" | |
Grep for the `SEARCH_STRING` in the repo. | |
Returns a list of raw strings returned from grep. | |
""" | |
command = "git grep -n '{}' {}".format(SEARCH_STRING, SEARCH_PATH) | |
res = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) | |
return [line for line in res.stdout] | |
def build_email_line_details(lines): | |
""" | |
Processes a list of raw search results from grep. | |
Returns a list of lists, where each element contains details regarding a | |
single offense. In the form: | |
[email_address, filename, line_num, commit_sha] | |
""" | |
output = [] | |
for line in lines: | |
line_details = line.split(':') | |
filename = line_details[0] | |
line_num = line_details[1] | |
commit_sha, email = get_author_email_and_commit(line_num, filename) | |
if email: | |
output.append([email, filename, line_num, commit_sha]) | |
return output | |
def get_author_email_and_commit(line_num, filename): | |
""" | |
Given the filename and line number of an offense, return author/commit info. | |
""" | |
command = "git blame -L {},{} {} -e".format(line_num, line_num, filename) | |
res = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) | |
try: | |
detail_str = res.stdout.next() | |
except StopIteration: | |
print "Could not find blame details for filename: {}, line: {}".format(filename, line_num) | |
return None | |
return detail_str.split(' ')[0], detail_str[detail_str.find('<') + 1:detail_str.find('>')] | |
def start_smtp_connection(): | |
""" | |
Initialize the SMTP server connection. | |
""" | |
try: | |
server = smtplib.SMTP("smtp.gmail.com", 587) | |
server.ehlo() | |
server.starttls() | |
server.ehlo() | |
server.login(SENDER_EMAIL, SENDER_PASSWORD) | |
return server | |
except Exception as e: | |
print "Was not able to connect to email server" | |
raise e | |
def build_email(email_address, filename, line_num, commit_sha): | |
mail = email.message.Message() | |
mail["Subject"] = "ATTENTION NEEDED: TODO message in API" | |
mail["From"] = SENDER_EMAIL | |
mail.add_header("Content-Type", "text/html") | |
mail["To"] = email_address | |
msg_body = ( | |
"<html>" + | |
"{}<br><br>".format(MESSAGE) + | |
"Offending line: {}/blob/master/{}#L{}<br>".format(REPO_ADDR, filename, line_num) + | |
"Commit link: {}/commit/{}<br>".format(REPO_ADDR, commit_sha) + | |
"</html>" | |
) | |
mail.set_payload(msg_body) | |
return mail | |
def send_emails(email_line_details): | |
""" | |
Given a list of offense details, email each author. | |
Note that there is no attempt to merge offenses by the same author at this | |
time. Exceptions during SMTP server connection will end the attempt, | |
however, individual exceptions during email processing will be ignored and | |
the email will be skipped. | |
""" | |
server = start_smtp_connection() | |
for line_detail in email_line_details: | |
email_address, filename, line_num, commit_sha = line_detail | |
mail = build_email(email_address, filename, line_num, commit_sha) | |
try: | |
server.sendmail(mail["From"], mail["To"], mail.as_string()) | |
print "Successfully emailed: {}".format(email_address) | |
except Exception as e: | |
print "Failed to email {}".format(email_address) | |
print "Caught exception, {}".format(e) | |
print "Closing email server connection" | |
server.quit() | |
print "Done sending emails" | |
if __name__ == "__main__": | |
lines = find_offending_lines() | |
if not lines: | |
exit("No TODO messages found") | |
email_line_details = build_email_line_details(lines) | |
if not email_line_details: | |
exit("Could not determine author details for all: {} lines".format(len(lines))) | |
send_emails(email_line_details) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment