Skip to content

Commit bc8ec6a

Browse files
committed
More error handling and comment on MR with possible reviewers
1 parent e7d1f15 commit bc8ec6a

File tree

4 files changed

+61
-15
lines changed

4 files changed

+61
-15
lines changed

marge/approvals.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import yaml
21
import logging as log
3-
import shlex
42
import fnmatch
3+
import shlex
54

65
from . import gitlab
76

@@ -27,14 +26,24 @@ def refetch_info(self):
2726
def get_approvers_ce(self):
2827
"""get approvers status using thumbs on merge request
2928
"""
29+
owner_globs = self.get_codeowners_ce()
30+
if len(owner_globs) == 0:
31+
log.info("No CODEOWNERS file in master, continuing without approvers flow")
32+
return dict(self._info, approvals_left=0, approved_by=[], codeowners=[])
33+
34+
codeowners = self.determine_responsible_owners(owner_globs, self.get_changes_ce())
35+
36+
if len(codeowners) == 0:
37+
log.info("No matched code owners, continuing without approvers flow")
38+
return dict(self._info, approvals_left=0, approved_by=[], codeowners=[])
3039

31-
codeowners = self.determine_responsible_owners(self.get_codeowners_ce(), self.get_changes_ce())
3240
awards = self.get_awards_ce()
3341

3442
up_votes = [e for e in awards if e['name'] == 'thumbsup' and e['user']['username'] in codeowners]
3543
approver_count = len(codeowners)
3644
approvals_left = max(approver_count - len(up_votes), 0)
37-
return dict(self._info, approvals_left=approvals_left, approved_by=up_votes)
45+
46+
return dict(self._info, approvals_left=approvals_left, approved_by=up_votes, codeowners=codeowners)
3847

3948
def determine_responsible_owners(self, owners_glob, changes):
4049
owners = set([])
@@ -44,6 +53,7 @@ def determine_responsible_owners(self, owners_glob, changes):
4453
owners.update(owners_glob['*'])
4554

4655
if 'changes' not in changes:
56+
log.info("No changes in merge request!?")
4757
return owners
4858

4959
for change in changes['changes']:
@@ -71,9 +81,9 @@ def get_codeowners_ce(self):
7181
if config_file is None:
7282
return owner_globs
7383

74-
for line in config_file.splitlines():
84+
for line in config_file['content'].splitlines():
7585
if line != "" and not line.startswith(' ') and not line.startswith('#'):
76-
elements = shlex.split(line) # line.split()
86+
elements = shlex.split(line)
7787
glob = elements.pop(0)
7888
owner_globs.setdefault(glob, set([]))
7989

@@ -107,18 +117,29 @@ def approver_ids(self):
107117
"""Return the uids of the approvers."""
108118
return [who['user']['id'] for who in self.info['approved_by']]
109119

120+
@property
121+
def codeowners(self):
122+
"""Only used for gitlab CE"""
123+
if 'approvers' in self.info:
124+
return self.info['codeowners']
125+
126+
return []
127+
110128
def reapprove(self):
111129
"""Impersonates the approvers and re-approves the merge_request as them.
112130
113131
The idea is that we want to get the approvers, push the rebased branch
114132
(which may invalidate approvals, depending on GitLab settings) and then
115133
restore the approval status.
116134
"""
117-
if self._api.version().release >= (9, 2, 2):
135+
gitlab_version = self._api.version()
136+
137+
if gitlab_version.release >= (9, 2, 2):
118138
approve_url = '/projects/{0.project_id}/merge_requests/{0.iid}/approve'.format(self)
119139
else:
120140
# GitLab botched the v4 api before 9.2.3
121141
approve_url = '/projects/{0.project_id}/merge_requests/{0.id}/approve'.format(self)
122142

123-
for uid in self.approver_ids:
124-
self._api.call(POST(approve_url), sudo=uid)
143+
if gitlab_version.is_ee:
144+
for uid in self.approver_ids:
145+
self._api.call(POST(approve_url), sudo=uid)

marge/merge_request.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,19 @@ def fetch_approvals(self):
188188
info = {'id': self.id, 'iid': self.iid, 'project_id': self.project_id}
189189
approvals = Approvals(self.api, info)
190190
approvals.refetch_info()
191+
192+
if not self._api.version().is_ee and approvals.approvals_left > 0 and len(approvals.codeowners) > 0:
193+
reviewer_string = ''
194+
if len(approvals.codeowners) == 1:
195+
reviewer_string = '@' + approvals.codeowners[0]
196+
else:
197+
reviewer_ats = ["@" + reviewer for reviewer in approvals.codeowners]
198+
reviewer_string = '{} or {}'.format(', '.join(reviewer_ats[:-1]), reviewer_ats[-1])
199+
200+
self.comment(
201+
"I can't merge without approval. Please ask {0} to review".format(reviewer_string)
202+
)
203+
191204
return approvals
192205

193206
def fetch_commits(self):

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
from distutils.core import setup
3-
VERSION = open(os.path.join(os.path.dirname(__file__), 'version')).read().strip()
3+
VERSION = open(os.path.join(os.path.dirname(__file__), 'version')).read().strip()
44
setup(
55
name='marge',
66
version=VERSION,

tests/test_approvals.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
# testing this here is more convenient
1010
from marge.job import CannotMerge, _get_reviewer_names_and_emails
1111

12-
CODEOWNERS = """
12+
CODEOWNERS = {
13+
"content": """
1314
# This is an example code owners file, lines starting with a `#` will
1415
# be ignored.
1516
@@ -18,8 +19,11 @@
1819
1920
unmatched/* @test5
2021
"""
22+
}
2123

22-
CODEOWNERS_FULL = """
24+
# pylint: disable=anomalous-backslash-in-string
25+
CODEOWNERS_FULL = {
26+
"content": """
2327
# This is an example code owners file, lines starting with a `#` will
2428
# be ignored.
2529
@@ -68,7 +72,8 @@
6872
6973
# If the path contains spaces, these need to be escaped like this:
7074
path\ with\ spaces/ @space-owner
71-
"""
75+
""" # noqa: W605
76+
}
7277

7378
AWARDS = [
7479
{
@@ -214,7 +219,12 @@ def test_fetch_from_merge_request_ce_compat(self):
214219

215220
api.call.assert_not_called()
216221
assert approvals.info == {
217-
'id': 74, 'iid': 6, 'project_id': 1234, 'approvals_left': 2, 'approved_by': [AWARDS[1]],
222+
'id': 74,
223+
'iid': 6,
224+
'project_id': 1234,
225+
'approvals_left': 2,
226+
'approved_by': [AWARDS[1]],
227+
'codeowners': {'default-codeowner', 'ebert', 'test-user1'},
218228
}
219229

220230
def test_properties(self):
@@ -290,7 +300,9 @@ def test_approvals_ce_get_codeowners_wildcard(self):
290300

291301
approvals = Approvals(api, {'id': 74, 'iid': 6, 'project_id': 1234})
292302

293-
assert approvals.get_codeowners_ce() == {'*': set(['default-codeowner', 'test-user1', 'ebert']), 'unmatched/*': {'test5'}}
303+
assert approvals.get_codeowners_ce() == {
304+
'*': set(['default-codeowner', 'test-user1', 'ebert']), 'unmatched/*': {'test5'}
305+
}
294306

295307
@patch('marge.approvals.Approvals.get_awards_ce', Mock(return_value=AWARDS))
296308
@patch('marge.approvals.Approvals.get_changes_ce', Mock(return_value=CHANGES))

0 commit comments

Comments
 (0)