-
Notifications
You must be signed in to change notification settings - Fork 0
/
pre-receive
181 lines (152 loc) · 5.71 KB
/
pre-receive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python2.6
# Version 1.0
import sys
"""
Git Push Hook to use with JIRA v4.3.2
"""
__author__ = 'Praful Mathur'
__version__ = '0.1.0'
__license__ = 'BSD'
# GLOBALS
USERNAME = '' # Your JIRA username
PASSWORD = '' # Your JIRA password
git_help = "git rebase -i HEAD~" # changes in push_hook, appends # of commits
first_err = True # changes in git_status_out, used as flag
def git_status_out(msg, err=True):
"""
Git status out - Output to correct std stream, based on err flag
String Boolean? ->
SIDE EFFECTS: Outputs the given msg to (STDOUT | STDERR)
"""
global first_err
prepend = ">>>> "
output_to = sys.stderr
if err and first_err:
first_err = False
output_to.write(prepend + "-----------------------------------\n")
output_to.write(prepend + "ERROR: NOTHING IS PUSHED TO REMOTE!\n")
output_to.write(prepend + "-----------------------------------\n")
output_to.write(prepend + msg + "\n")
def ticket_status(ticket_number, request_lib):
"""
Ticket status - Check JIRA that ticket is 'In Progress`
Uses JIRA API v4.3.2:
http://confluence.atlassian.com/display/JIRA043/JIRA+Documentation
String Function -> Boolean
"""
import json
(get_method, ConnectionError) = request_lib
base_url = "https://{jira_url}"
api_suffix = "/rest/api/latest/issue/"
url = base_url + api_suffix + ticket_number
prepend_help = "Commit message fail? Try"
# set headers for JIRA request
headers = {'content-type': 'application/json'}
try:
req = get_method(url, auth=(USERNAME,PASSWORD), headers=headers)
except ConnectionError:
git_status_out("Connection to JIRA timed-out, check Portal")
return False
# Check ticket existence
if req.status_code == 200:
response = json.loads(req.content)
# ensure valid response from JIRA
if response.has_key('fields'):
ticket_state = response['fields']['status']['value']['name']
if ticket_state == "In Progress":
git_status_out("Committing to " + response['self'], err=False)
return True
else:
action = "set to 'In Progress'"
if ticket_state not in ['Open', 'Reopened']:
action = 're-open and ' + action
git_status_out("Ticket is '%s' please %s -- %s/browse/%s"\
%(ticket_state, action, base_url, ticket_number))
git_status_out("%s `%s`" %(prepend_help, git_help))
else:
git_status_out("Malformed JIRA response, please try again.")
# Auth Error
elif req.status_code == 401:
git_status_out("Login to JIRA failed, please contact systems immediately!")
# Ticket Not Found
elif req.status_code == 404:
git_status_out("Ticket does not exist")
git_status_out("%s `%s`" %(prepend_help, git_help))
# Unknown Error
else:
git_status_out("Unknown error, please contact systems immediately!")
return False
def ticket_exception(commit_msg):
"""
Ticket exception - Commits that can break the JIRA ticket rule
String -> Boolean
"""
# add exceptional commit actions (case-sensitive) to whitelist
whitelist = set(['Revert'])
action_word = commit_msg.split(' ')[0]
if action_word in whitelist:
git_status_out("Ticket is an exception to the JIRA rule, action: %s"
%(action_word), err=False)
return True
return False
def extract_ticket_number(commit_msg):
"""
Extract ticket number - Ticket is in JIRA format and prepends commit message
String -> (String | None)
"""
import re
ticket_regex = re.compile("^\w+-\d+")
ticket_match = ticket_regex.match(commit_msg)
git_status_out("Looking at commit message: '%s'" %(commit_msg), err=False)
if ticket_match:
ticket_number = ticket_match.group()
msg = "Found ticket number: %s" %(ticket_number)
git_status_out(msg, err=False)
return ticket_number
else:
git_status_out("No ticket number found at beginning of commit message.")
git_status_out("Example: 'PM-125 - Created a great human being'.")
git_status_out("`%s` and reword/squash commit message."
%(git_help))
return None
def check_commit(commit_msg, request_lib):
"""
Check commit - Check given commit_msg has an 'In Progress' ticket
String Function -> Boolean
"""
if ticket_exception(commit_msg):
return True
ticket_number = extract_ticket_number(commit_msg)
if ticket_number:
status = ticket_status(ticket_number, request_lib)
if(status): return True
return False
def push_hook(push_msg):
"""
Push hook - Check commit messages from pushed refs for ticket numbers
-> Boolean
"""
import subprocess, shlex
from requests import get, ConnectionError
(base_hash, commit_hash, ref) = push_msg.split(' ')
blackhole = open('/dev/null', 'w')
# excludes merge commits
git_cmd = "git log --no-merges --pretty=oneline %s..%s" %(base_hash, commit_hash)
git_exec = subprocess.Popen(shlex.split(git_cmd), stdout=subprocess.PIPE, stderr=blackhole)
git_log = git_exec.stdout.read()
commits = git_log.strip().split('\n')
global git_help
git_help += str(len(commits))
for c in commits:
spc_idx = c.find(' ')
commit_msg = c[spc_idx:].strip()
if not check_commit(commit_msg, (get, ConnectionError)):
return False
return True
if __name__ == '__main__':
""" SIDE EFFECTS: Return shell exit code (0 - success | 1 - failure) """
push_msg = sys.stdin.read()
if push_hook(push_msg):
sys.exit(0)
else:
sys.exit(1)