mirror of
https://github.com/odoo/runbot.git
synced 2025-03-27 13:25:47 +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)
|
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/)
|
# handle co-authored commits (https://help.github.com/articles/creating-a-commit-with-multiple-authors/)
|
||||||
m = self._parse_commit_message(message)
|
m = self._parse_commit_message(message)
|
||||||
pattern = r'( |{repository})#{pr.number}\b'.format(
|
pattern = r'( |{repository})#{pr.number}\b'.format(
|
||||||
@ -1084,12 +1084,15 @@ class PullRequests(models.Model):
|
|||||||
if not re.search(pattern, m.body):
|
if not re.search(pattern, m.body):
|
||||||
m.body += '\n\ncloses {pr.display_name}'.format(pr=self)
|
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:
|
if self.reviewed_by:
|
||||||
m.headers.add('signed-off-by', self.reviewed_by.formatted_email)
|
m.headers.add('signed-off-by', self.reviewed_by.formatted_email)
|
||||||
|
|
||||||
return str(m)
|
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]
|
# nb: pr_commits is oldest to newest so pr.head is pr_commits[-1]
|
||||||
_, prdict = gh.pr(self.number)
|
_, prdict = gh.pr(self.number)
|
||||||
commits = prdict['commits']
|
commits = prdict['commits']
|
||||||
@ -1110,25 +1113,26 @@ class PullRequests(models.Model):
|
|||||||
|
|
||||||
# NOTE: lost merge v merge/copy distinction (head being
|
# NOTE: lost merge v merge/copy distinction (head being
|
||||||
# a merge commit reused instead of being re-merged)
|
# 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
|
# updates head commit with PR number (if necessary) then rebases
|
||||||
# on top of target
|
# 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
|
commits[-1]['commit']['message'] = msg
|
||||||
head, mapping = gh.rebase(self.number, target, commits=commits)
|
head, mapping = gh.rebase(self.number, target, commits=commits)
|
||||||
self.commits_map = json.dumps({**mapping, '': head})
|
self.commits_map = json.dumps({**mapping, '': head})
|
||||||
return head
|
return head
|
||||||
|
|
||||||
def _stage_rebase_merge(self, gh, target, commits):
|
def _stage_rebase_merge(self, gh, target, commits, related_prs=()):
|
||||||
msg = self._build_merge_message(self.message)
|
msg = self._build_merge_message(self.message, related_prs=related_prs)
|
||||||
h, mapping = gh.rebase(self.number, target, reset=True, commits=commits)
|
h, mapping = gh.rebase(self.number, target, reset=True, commits=commits)
|
||||||
merge_head = gh.merge(h, target, msg)['sha']
|
merge_head = gh.merge(h, target, msg)['sha']
|
||||||
self.commits_map = json.dumps({**mapping, '': merge_head})
|
self.commits_map = json.dumps({**mapping, '': merge_head})
|
||||||
return 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
|
pr_head = commits[-1] # oldest to newest
|
||||||
base_commit = None
|
base_commit = None
|
||||||
head_parents = {p['sha'] for p in pr_head['parents']}
|
head_parents = {p['sha'] for p in pr_head['parents']}
|
||||||
@ -1148,7 +1152,7 @@ class PullRequests(models.Model):
|
|||||||
original_head = gh.head(target)
|
original_head = gh.head(target)
|
||||||
merge_tree = gh.merge(pr_head['sha'], target, 'temp merge')['tree']['sha']
|
merge_tree = gh.merge(pr_head['sha'], target, 'temp merge')['tree']['sha']
|
||||||
new_parents = [original_head] + list(head_parents - {base_commit})
|
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={
|
copy = gh('post', 'git/commits', json={
|
||||||
'message': msg,
|
'message': msg,
|
||||||
'tree': merge_tree,
|
'tree': merge_tree,
|
||||||
@ -1735,7 +1739,7 @@ class Batch(models.Model):
|
|||||||
target = 'tmp.{}'.format(pr.target.name)
|
target = 'tmp.{}'.format(pr.target.name)
|
||||||
original_head = gh.head(target)
|
original_head = gh.head(target)
|
||||||
try:
|
try:
|
||||||
method, new_heads[pr] = pr._stage(gh, target)
|
method, new_heads[pr] = pr._stage(gh, target, related_prs=(prs - pr))
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"Staged pr %s:%s to %s by %s: %s -> %s",
|
"Staged pr %s:%s to %s by %s: %s -> %s",
|
||||||
pr.repository.name, pr.number,
|
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, \
|
assert pr_a.staging_id == pr_b.staging_id, \
|
||||||
"branch-matched PRs should be part of the same staging"
|
"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):
|
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
|
""" 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
|
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')
|
for c in repo_a.log('heads/staging.master')
|
||||||
] == [
|
] == [
|
||||||
re_matches('^force rebuild'),
|
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'
|
'initial'
|
||||||
], "dummy commit + squash-merged PR commit + root commit"
|
], "dummy commit + squash-merged PR commit + root commit"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user