[FIX] runbot_merge: updating of commit date on rebase

On #509, the rebasing process was changed to forcefully update the
commit date of the commits, in order to force trigger builds.

However when squashing was re-enabled for #539 for some fool reason it
implemented its own bespoke rebasing (despite that not actually saving
any API call that I can see), meaning it did *not* update the commit
date. As such, an old commit being squashed would not get picked up by
the runbot, which is what happened to odoo/documentation#1226 (which
ultimately had to be hand-rebased after some confusion as to why it
did not work).

Update `_stage_squash` to go through `rebase` the normal way, also
update `rebase` to pop the commit date entirely instead of setting it
manually, and update the squashing test to check that the commit date
gets properly updated.

Fixes #579, closes #582
This commit is contained in:
Xavier Morel 2022-02-07 12:00:31 +01:00
parent 2285965e22
commit 2898c7edd4
3 changed files with 25 additions and 21 deletions

View File

@ -308,15 +308,14 @@ class GH(object):
prev = original_head prev = original_head
mapping = {} mapping = {}
for c in commits: for c in commits:
committer = c['commit']['committer']
committer.pop('date')
copy = self('post', 'git/commits', json={ copy = self('post', 'git/commits', json={
'message': c['commit']['message'], 'message': c['commit']['message'],
'tree': c['new_tree'], 'tree': c['new_tree'],
'parents': [prev], 'parents': [prev],
'author': c['commit']['author'], 'author': c['commit']['author'],
'committer': dict( 'committer': committer,
c['commit']['committer'],
date=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
),
}, check={409: MergeError}).json() }, check={409: MergeError}).json()
logger.debug('copied %s to %s (parent: %s)', c['sha'], copy['sha'], prev) logger.debug('copied %s to %s (parent: %s)', c['sha'], copy['sha'], prev)
prev = mapping[c['sha']] = copy['sha'] prev = mapping[c['sha']] = copy['sha']

View File

@ -1260,20 +1260,12 @@ class PullRequests(models.Model):
gh, target, pr_commits, related_prs=related_prs) gh, target, pr_commits, related_prs=related_prs)
def _stage_squash(self, gh, target, commits, related_prs=()): def _stage_squash(self, gh, target, commits, related_prs=()):
original_head = gh.head(target) assert len(commits) == 1, "can only squash a single commit"
msg = self._build_merge_message(self, related_prs=related_prs) msg = self._build_merge_message(self, related_prs=related_prs)
[commit] = commits commits[0]['commit']['message'] = str(msg)
merge_tree = gh.merge(commit['sha'], target, 'temp')['tree']['sha'] head, mapping = gh.rebase(self.number, target, commits=commits)
squashed = gh('post', 'git/commits', json={ self.commits_map = json.dumps({**mapping, '': head})
'message': str(msg), return head
'tree': merge_tree,
'author': commit['commit']['author'],
'committer': commit['commit']['committer'],
'parents': [original_head],
}).json()['sha']
gh.set_ref(target, squashed)
self.commits_map = json.dumps({commit['sha']: squashed, '': squashed})
return squashed
def _stage_rebase_ff(self, gh, target, commits, related_prs=()): 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

View File

@ -1,12 +1,12 @@
import datetime import datetime
import itertools import itertools
import json import json
import re
import textwrap import textwrap
import time
from unittest import mock from unittest import mock
import pytest import pytest
from lxml import html, etree from lxml import html
import odoo import odoo
from utils import _simple_init, seen, re_matches, get_partner, Commit, pr_page, to_pr, part_of from utils import _simple_init, seen, re_matches, get_partner, Commit, pr_page, to_pr, part_of
@ -1958,7 +1958,11 @@ Part-of: {pr_id.display_name}"""
with repo: with repo:
repo.make_commits(None, Commit('initial', tree={'a': '0'}), ref='heads/master') repo.make_commits(None, Commit('initial', tree={'a': '0'}), ref='heads/master')
repo.make_commits('master', Commit('sub', tree={'b': '0'}), ref='heads/other') repo.make_commits(
'master',
Commit('sub', tree={'b': '0'}, committer={'name': 'bob', 'email': 'builder@example.org', 'date': '1999-04-12T08:19:30Z'}),
ref='heads/other'
)
pr1 = repo.make_pr(title='first pr', target='master', head='other') pr1 = repo.make_pr(title='first pr', target='master', head='other')
repo.post_status('other', 'success', 'legal/cla') repo.post_status('other', 'success', 'legal/cla')
repo.post_status('other', 'success', 'ci/runbot') repo.post_status('other', 'success', 'ci/runbot')
@ -1988,12 +1992,21 @@ Part-of: {pr_id.display_name}"""
(users['reviewer'], 'hansen r+ squash'), (users['reviewer'], 'hansen r+ squash'),
(users['user'], 'Merge method set to squash') (users['user'], 'Merge method set to squash')
] ]
assert repo.commit('master').message == f"""first pr merged_head = repo.commit('master')
assert merged_head.message == f"""first pr
closes {pr1_id.display_name} closes {pr1_id.display_name}
Signed-off-by: {get_partner(env, users["reviewer"]).formatted_email}\ Signed-off-by: {get_partner(env, users["reviewer"]).formatted_email}\
""" """
assert merged_head.committer['name'] == 'bob'
assert merged_head.committer['email'] == 'builder@example.org'
commit_date = datetime.datetime.strptime(merged_head.committer['date'], '%Y-%m-%dT%H:%M:%SZ')
# using timestamp (and working in seconds) because `pytest.approx`
# silently fails on datetimes (#8395)
assert commit_date.timestamp() == pytest.approx(time.time(), abs=5*60), \
"the commit date of the merged commit should be about now, despite" \
" the source commit being >20 years old"
pr2_id = to_pr(env, pr2) pr2_id = to_pr(env, pr2)
assert pr2_id.state == 'ready' assert pr2_id.state == 'ready'