Skip to content

Commit baedbc1

Browse files
committed
Merge remote-tracking branch 'origin/master' into test-commands
Fix merge conflict in homu/main.py
2 parents 2f9b34c + 4bea22d commit baedbc1

File tree

5 files changed

+120
-37
lines changed

5 files changed

+120
-37
lines changed

cfg.sample.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ name = "Travis CI - Branch"
158158
#
159159
# String name of the Checks run.
160160
#name = ""
161+
#
162+
# String name of the Checks run used for try runs.
163+
# If the field is omitted the same name as the auto build will be used.
164+
#try_name = ""
161165

162166
# Use buildbot for running tests
163167
#[repo.NAME.buildbot]

homu/comments.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,59 @@ def jsonify(self):
1818
return json.dumps(out, separators=(',', ':'))
1919

2020

21+
class Approved(Comment):
22+
def __init__(self, bot=None, **args):
23+
# Because homu needs to leave a comment for itself to kick off a build,
24+
# we need to know the correct botname to use. However, we don't want to
25+
# save that botname in our state JSON. So we need a custom constructor
26+
# to grab the botname and delegate the rest of the keyword args to the
27+
# Comment constructor.
28+
super().__init__(**args)
29+
self.bot = bot
30+
31+
params = ["sha", "approver"]
32+
33+
def render(self):
34+
# The comment here is required because Homu wants a full, unambiguous,
35+
# pinned commit hash to kick off the build, and this note-to-self is
36+
# how it gets it. This is to safeguard against situations where Homu
37+
# reloads and another commit has been pushed since the approval.
38+
message = ":pushpin: Commit {sha} has been " + \
39+
"approved by `{approver}`\n\n" + \
40+
"<!-- @{bot} r={approver} {sha} -->"
41+
return message.format(
42+
sha=self.sha,
43+
approver=self.approver,
44+
bot=self.bot
45+
)
46+
47+
48+
class ApprovalIgnoredWip(Comment):
49+
def __init__(self, wip_keyword=None, **args):
50+
# We want to use the wip keyword in the message, but not in the json
51+
# blob.
52+
super().__init__(**args)
53+
self.wip_keyword = wip_keyword
54+
55+
params = ["sha"]
56+
57+
def render(self):
58+
message = ':clipboard:' + \
59+
' Looks like this PR is still in progress,' + \
60+
' ignoring approval.\n\n' + \
61+
'Hint: Remove **{wip_keyword}** from this PR\'s title when' + \
62+
' it is ready for review.'
63+
return message.format(wip_keyword=self.wip_keyword)
64+
65+
66+
class Delegated(Comment):
67+
params = ["delegator", "delegate"]
68+
69+
def render(self):
70+
message = ':v: @{} can now approve this pull request'
71+
return message.format(self.delegate)
72+
73+
2174
class BuildStarted(Comment):
2275
params = ["head_sha", "merge_sha"]
2376

homu/html/build_res.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ <h1>Homu build results - <a href="{{repo_url}}/pull/{{pull}}" target="_blank">{{
4747
<tr>
4848
<td class="hide">{{loop.index}}</td>
4949
<td>{{builder.name}}</td>
50-
<td class="{{builder.result}}"><a href="{{builder.url}}">{{builder.result}}</a></td>
50+
<td class="{{builder.result}}">
51+
{%- if builder.url -%}
52+
<a href="{{builder.url}}">{{builder.result}}</a>
53+
{%- else -%}
54+
{{ builder.result }}
55+
{%- endif -%}
56+
</td>
5157
</tr>
5258
{% endfor %}
5359
</tbody>

homu/main.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,13 @@ def record_retry_log(self, src, body):
408408
[self.repo_label, self.num, src, body],
409409
)
410410

411+
@property
412+
def author(self):
413+
"""
414+
Get the GitHub login name of the author of the pull request
415+
"""
416+
return self.get_issue().user.login
417+
411418

412419
def sha_cmp(short, full):
413420
return len(short) >= 4 and short == full[:len(short)]
@@ -489,13 +496,10 @@ def parse_commands(body, username, repo_label, repo_cfg, state, my_username,
489496
for wip_kw in ['WIP', 'TODO', '[WIP]', '[TODO]', '[DO NOT MERGE]']:
490497
if state.title.upper().startswith(wip_kw):
491498
if realtime:
492-
state.add_comment((
493-
':clipboard:'
494-
' Looks like this PR is still in progress,'
495-
' ignoring approval.\n\n'
496-
'Hint: Remove **{}** from this PR\'s title when'
497-
' it is ready for review.'
498-
).format(wip_kw))
499+
state.add_comment(comments.ApprovalIgnoredWip(
500+
sha=state.head_sha,
501+
wip_keyword=wip_kw,
502+
))
499503
is_wip = True
500504
break
501505
if is_wip:
@@ -557,14 +561,10 @@ def parse_commands(body, username, repo_label, repo_cfg, state, my_username,
557561
.format(msg, state.head_sha)
558562
)
559563
else:
560-
state.add_comment(
561-
':pushpin: Commit {} has been approved by `{}`\n\n<!-- @{} r={} {} -->' # noqa
562-
.format(
563-
state.head_sha,
564-
approver,
565-
my_username,
566-
approver,
567-
state.head_sha,
564+
state.add_comment(comments.Approved(
565+
sha=state.head_sha,
566+
approver=approver,
567+
bot=my_username,
568568
))
569569
treeclosed = state.blocked_by_closed_tree()
570570
if treeclosed:
@@ -575,9 +575,18 @@ def parse_commands(body, username, repo_label, repo_cfg, state, my_username,
575575
state.change_labels(LabelEvent.APPROVED)
576576

577577
elif command.action == 'unapprove':
578-
if not verify_auth(username, repo_label, repo_cfg, state,
579-
AuthState.REVIEWER, realtime, my_username):
580-
continue
578+
# Allow the author of a pull request to unapprove their own PR. The
579+
# author can already perform other actions that effectively
580+
# unapprove the PR (change the target branch, push more commits,
581+
# etc.) so allowing them to directly unapprove it is also allowed.
582+
583+
# Because verify_auth has side-effects (especially, it may leave a
584+
# comment on the pull request if the user is not authorized), we
585+
# need to do the author check BEFORE the verify_auth check.
586+
if state.author != username:
587+
if not verify_auth(username, repo_label, repo_cfg, state,
588+
AuthState.REVIEWER, realtime, my_username):
589+
continue
581590

582591
state.approved_by = ''
583592
state.save()
@@ -610,10 +619,10 @@ def parse_commands(body, username, repo_label, repo_cfg, state, my_username,
610619
state.save()
611620

612621
if realtime:
613-
state.add_comment(
614-
':v: @{} can now approve this pull request'
615-
.format(state.delegate)
616-
)
622+
state.add_comment(comments.Delegated(
623+
delegator=username,
624+
delegate=state.delegate
625+
))
617626

618627
elif command.action == 'undelegate':
619628
# TODO: why is this a TRY?
@@ -630,10 +639,10 @@ def parse_commands(body, username, repo_label, repo_cfg, state, my_username,
630639
state.save()
631640

632641
if realtime:
633-
state.add_comment(
634-
':v: @{} can now approve this pull request'
635-
.format(state.delegate)
636-
)
642+
state.add_comment(comments.Delegated(
643+
delegator=username,
644+
delegate=state.delegate
645+
))
637646

638647
elif command.action == 'retry' and realtime:
639648
if not _try_auth_verified():
@@ -662,6 +671,10 @@ def parse_commands(body, username, repo_label, repo_cfg, state, my_username,
662671

663672
state.save()
664673
if realtime and state.try_:
674+
# If we've tried before, the status will be 'success', and this
675+
# new try will not be picked up. Set the status back to ''
676+
# so the try will be run again.
677+
state.set_status('')
665678
# `try-` just resets the `try` bit and doesn't correspond to
666679
# any meaningful labeling events.
667680
state.change_labels(LabelEvent.TRY)
@@ -1224,7 +1237,11 @@ def start_build(state, repo_cfgs, buildbot_slots, logger, db, git_cfg):
12241237
if found_travis_context and len(builders) == 1:
12251238
can_try_travis_exemption = True
12261239
if 'checks' in repo_cfg:
1227-
builders += ['checks-' + key for key, value in repo_cfg['checks'].items() if 'name' in value] # noqa
1240+
builders += [
1241+
'checks-' + key
1242+
for key, value in repo_cfg['checks'].items()
1243+
if 'name' in value or (state.try_ and 'try_name' in value)
1244+
]
12281245
only_status_builders = False
12291246

12301247
if len(builders) == 0:

homu/server.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def result(repo_label, pull):
8282
states = [state for state in g.states[repo_label].values()
8383
if state.num == pull]
8484
if len(states) == 0:
85-
abort(204, 'No build results for pull request {}'.format(pull))
85+
abort(404, 'No build results for pull request {}'.format(pull))
8686

8787
state = states[0]
8888
builders = []
@@ -94,15 +94,15 @@ def result(repo_label, pull):
9494
if data['res'] is not None:
9595
result = "success" if data['res'] else "failed"
9696

97-
if not data['url']:
98-
# This happens to old try builds
99-
abort(204, 'No build results for pull request {}'.format(pull))
100-
101-
builders.append({
102-
'url': data['url'],
97+
builder_details = {
10398
'result': result,
10499
'name': builder,
105-
})
100+
}
101+
102+
if data['url']:
103+
builder_details['url'] = data['url']
104+
105+
builders.append(builder_details)
106106

107107
return g.tpls['build_res'].render(repo_label=repo_label, repo_url=repo_url,
108108
builders=builders, pull=pull)
@@ -604,7 +604,10 @@ def fail(err):
604604
checks_name = None
605605
if 'checks' in repo_cfg:
606606
for name, value in repo_cfg['checks'].items():
607-
if 'name' in value and value['name'] == current_run_name:
607+
if state.try_ and 'try_name' in value:
608+
if value['try_name'] == current_run_name:
609+
checks_name = name
610+
elif 'name' in value and value['name'] == current_run_name:
608611
checks_name = name
609612
if checks_name is None:
610613
return 'OK'

0 commit comments

Comments
 (0)