mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[IMP] runbot_merge: add related PRs to top comment
Discussing #238 with @odony, the main concern was the difficulty of understanding if things merged in one repo were related to things merged in an other repo: currently, knowing this requires going to the merged PR, getting its label, and checking the PRs with the same HEAD in the other repository to see if there's a correlation (e.g. PRs merged around the same time). The current structure of the mergebot makes it reasonably easy to add the other PRs of the batch in the pseudo-headers, such that we get links to all "related" PRs in the head commit (and links back from the commits which is probably less useful but...) Fixes #238
This commit is contained in:
parent
1b5a05e40c
commit
629e00a117
@ -1074,7 +1074,7 @@ class PullRequests(models.Model):
|
||||
"""
|
||||
return Message.from_message(message)
|
||||
|
||||
def _build_merge_message(self, message):
|
||||
def _build_merge_message(self, message, related_prs=()):
|
||||
# handle co-authored commits (https://help.github.com/articles/creating-a-commit-with-multiple-authors/)
|
||||
m = self._parse_commit_message(message)
|
||||
pattern = r'( |{repository})#{pr.number}\b'.format(
|
||||
@ -1084,12 +1084,15 @@ class PullRequests(models.Model):
|
||||
if not re.search(pattern, m.body):
|
||||
m.body += '\n\ncloses {pr.display_name}'.format(pr=self)
|
||||
|
||||
for r in related_prs:
|
||||
m.headers.add('Related', r.display_name)
|
||||
|
||||
if self.reviewed_by:
|
||||
m.headers.add('signed-off-by', self.reviewed_by.formatted_email)
|
||||
|
||||
return str(m)
|
||||
|
||||
def _stage(self, gh, target):
|
||||
def _stage(self, gh, target, related_prs=()):
|
||||
# nb: pr_commits is oldest to newest so pr.head is pr_commits[-1]
|
||||
_, prdict = gh.pr(self.number)
|
||||
commits = prdict['commits']
|
||||
@ -1110,25 +1113,26 @@ class PullRequests(models.Model):
|
||||
|
||||
# NOTE: lost merge v merge/copy distinction (head being
|
||||
# a merge commit reused instead of being re-merged)
|
||||
return method, getattr(self, '_stage_' + method.replace('-', '_'))(gh, target, pr_commits)
|
||||
return method, getattr(self, '_stage_' + method.replace('-', '_'))(
|
||||
gh, target, pr_commits, related_prs=related_prs)
|
||||
|
||||
def _stage_rebase_ff(self, gh, target, commits):
|
||||
def _stage_rebase_ff(self, gh, target, commits, related_prs=()):
|
||||
# updates head commit with PR number (if necessary) then rebases
|
||||
# on top of target
|
||||
msg = self._build_merge_message(commits[-1]['commit']['message'])
|
||||
msg = self._build_merge_message(commits[-1]['commit']['message'], related_prs=related_prs)
|
||||
commits[-1]['commit']['message'] = msg
|
||||
head, mapping = gh.rebase(self.number, target, commits=commits)
|
||||
self.commits_map = json.dumps({**mapping, '': head})
|
||||
return head
|
||||
|
||||
def _stage_rebase_merge(self, gh, target, commits):
|
||||
msg = self._build_merge_message(self.message)
|
||||
def _stage_rebase_merge(self, gh, target, commits, related_prs=()):
|
||||
msg = self._build_merge_message(self.message, related_prs=related_prs)
|
||||
h, mapping = gh.rebase(self.number, target, reset=True, commits=commits)
|
||||
merge_head = gh.merge(h, target, msg)['sha']
|
||||
self.commits_map = json.dumps({**mapping, '': merge_head})
|
||||
return merge_head
|
||||
|
||||
def _stage_merge(self, gh, target, commits):
|
||||
def _stage_merge(self, gh, target, commits, related_prs=()):
|
||||
pr_head = commits[-1] # oldest to newest
|
||||
base_commit = None
|
||||
head_parents = {p['sha'] for p in pr_head['parents']}
|
||||
@ -1148,7 +1152,7 @@ class PullRequests(models.Model):
|
||||
original_head = gh.head(target)
|
||||
merge_tree = gh.merge(pr_head['sha'], target, 'temp merge')['tree']['sha']
|
||||
new_parents = [original_head] + list(head_parents - {base_commit})
|
||||
msg = self._build_merge_message(pr_head['commit']['message'])
|
||||
msg = self._build_merge_message(pr_head['commit']['message'], related_prs=related_prs)
|
||||
copy = gh('post', 'git/commits', json={
|
||||
'message': msg,
|
||||
'tree': merge_tree,
|
||||
@ -1735,7 +1739,7 @@ class Batch(models.Model):
|
||||
target = 'tmp.{}'.format(pr.target.name)
|
||||
original_head = gh.head(target)
|
||||
try:
|
||||
method, new_heads[pr] = pr._stage(gh, target)
|
||||
method, new_heads[pr] = pr._stage(gh, target, related_prs=(prs - pr))
|
||||
_logger.info(
|
||||
"Staged pr %s:%s to %s by %s: %s -> %s",
|
||||
pr.repository.name, pr.number,
|
||||
|
@ -126,6 +126,17 @@ def test_stage_match(env, project, repo_a, repo_b, config):
|
||||
assert pr_a.staging_id == pr_b.staging_id, \
|
||||
"branch-matched PRs should be part of the same staging"
|
||||
|
||||
for repo in [repo_a, repo_b]:
|
||||
with repo:
|
||||
repo.post_status('staging.master', 'success', 'legal/cla')
|
||||
repo.post_status('staging.master', 'success', 'ci/runbot')
|
||||
env.run_crons()
|
||||
assert pr_a.state == 'merged'
|
||||
assert pr_b.state == 'merged'
|
||||
|
||||
assert 'Related: {}#{}'.format(repo_b.name, pr_b.number) in repo_a.commit('master').message
|
||||
assert 'Related: {}#{}'.format(repo_a.name, pr_a.number) in repo_b.commit('master').message
|
||||
|
||||
def test_unmatch_patch(env, project, repo_a, repo_b, config):
|
||||
""" When editing files via the UI for a project you don't have write
|
||||
access to, a branch called patch-XXX is automatically created in your
|
||||
@ -263,7 +274,12 @@ def test_merge_fail(env, project, repo_a, repo_b, users, config):
|
||||
for c in repo_a.log('heads/staging.master')
|
||||
] == [
|
||||
re_matches('^force rebuild'),
|
||||
'commit_do-b-thing_00\n\ncloses %s#2\n\nSigned-off-by: %s' % (repo_a.name, reviewer),
|
||||
"""commit_do-b-thing_00
|
||||
|
||||
closes %s#%d
|
||||
|
||||
Related: %s#%d
|
||||
Signed-off-by: %s""" % (repo_a.name, pr2a.number, repo_b.name, pr2b.number, reviewer),
|
||||
'initial'
|
||||
], "dummy commit + squash-merged PR commit + root commit"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user