43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek#!/usr/bin/env python
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# A git pre-push hook that declines commits that don't contain a Reviewed-By:
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# tag. The tag must be present on the beginning of the line. To activate, copy
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# to $GIT_DIR/hooks/pre-push and make sure the executable flag is on.
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# The commit message should also be based on .git-commit-template, although
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# that is just best practice and not enforced
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekimport sys
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekimport re
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekimport subprocess
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekdef get_all_commits(ref_from, ref_to):
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek args = ['git', 'rev-list', '{:s}..{:s}'.format(ref_from, ref_to)]
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek out, err = p.communicate()
83ac838339d15192642e07710156a7c537f18248Lukas Slebodnik return [commit.strip() for commit in out.decode('UTF-8').split('\n') if commit != '']
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekdef commit_message(commit_hash):
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek args = ['git', 'cat-file', 'commit', commit_hash]
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek out, err = p.communicate()
83ac838339d15192642e07710156a7c537f18248Lukas Slebodnik return out.decode('UTF-8')
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekdef commit_has_rb(commit):
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek msg = commit_message(commit)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek for l in msg.split('\n'):
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek has_rb = re.search('^Reviewed-by:', l)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek if has_rb:
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek return True
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek return False
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekdef report_commit(commit_hash):
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek print("Commit {:s} does not have Reviewed-By!".format(commit_hash))
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek print("Full message:\n======")
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek print("{:s}".format(commit_message(commit_hash)))
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek print("======")
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# man 5 githooks says:
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# Information about what is to be pushed is provided on the hook's
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# standard input with lines of the form:
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekdef check_push(hook_input):
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek ref_to = hook_input.split()[1][:6]
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek ref_from = hook_input.split()[3][:6]
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek commit_list = get_all_commits(ref_from, ref_to)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek no_rb_list = []
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek for commit in commit_list:
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek if not commit_has_rb(commit):
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek no_rb_list.append(commit)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek return no_rb_list
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek# Don't warn when pushing to personal repositories, only origin
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekremote = sys.argv[1]
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekif remote != 'origin':
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek sys.exit(0)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozekfor hook_input in sys.stdin.readlines():
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek no_rb_list = check_push(hook_input)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek if len(no_rb_list) > 0:
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek for offender in no_rb_list:
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek report_commit(offender)
43147a9abaa1254235b853e643514cf1c7b150d7Jakub Hrozek sys.exit(1)