[IMP] runbot_merge: make uniquifier commit optional

Prepares the possibility of either more direct communication with the
CI platform(s) or just assuming CI has gotten reliable enough and
colleagues intelligent enough that this is not an issue anymore
because they've stopped pushing empty branches (which we know is not
the case).

Fixes #806
This commit is contained in:
Xavier Morel 2023-10-06 15:19:36 +02:00
parent a15086a8a9
commit 2cd3fb8999
4 changed files with 34 additions and 19 deletions

View File

@ -51,6 +51,15 @@ class Project(models.Model):
freeze_id = fields.Many2one('runbot_merge.project.freeze', compute='_compute_freeze')
freeze_reminder = fields.Text()
uniquifier = fields.Boolean(
default=True,
help="Whether to add a uniquifier commit on repositories without PRs"
" during staging. The lack of uniquifier can lead to CI conflicts"
" as github works off of commits, so it's possible for an"
" unrelated build to trigger a failure if somebody is a dummy and"
" includes repos they have no commit for."
)
@api.depends('github_token')
def _compute_identity(self):
s = requests.Session()

View File

@ -90,23 +90,11 @@ def try_staging(branch: Branch) -> Optional[Stagings]:
heads = []
commits = []
for repo, it in staging_state.items():
if it.head != original_heads[repo]:
# if we staged something for that repo, just create a record for
# that commit, or flag existing one as to-recheck in case there are
# already statuses we want to propagate to the staging or something
env.cr.execute(
"INSERT INTO runbot_merge_commit (sha, to_check, statuses) "
"VALUES (%s, true, '{}') "
"ON CONFLICT (sha) DO UPDATE SET to_check=true "
"RETURNING id",
[it.head]
)
[commit] = [head] = env.cr.fetchone()
else:
# if we didn't stage anything for that repo, create a dummy commit
# (with a uniquifier to ensure we don't hit a previous version of
# the same) to ensure the staging head is new and we're building
# everything
if it.head == original_heads[repo] and branch.project_id.uniquifier:
# if we didn't stage anything for that repo and uniquification is
# enabled, create a dummy commit with a uniquifier to ensure we
# don't hit a previous version of the same to ensure the staging
# head is new and we're building everything
project = branch.project_id
uniquifier = base64.b64encode(os.urandom(12)).decode('ascii')
dummy_head = it.repo.with_config(check=True).commit_tree(
@ -135,6 +123,18 @@ For-Commit-Id: {it.head}
)
([commit], [head]) = env.cr.fetchall()
it.head = dummy_head
else:
# otherwise just create a record for that commit, or flag existing
# one as to-recheck in case there are already statuses we want to
# propagate to the staging or something
env.cr.execute(
"INSERT INTO runbot_merge_commit (sha, to_check, statuses) "
"VALUES (%s, true, '{}') "
"ON CONFLICT (sha) DO UPDATE SET to_check=true "
"RETURNING id",
[it.head]
)
[commit] = [head] = env.cr.fetchone()
heads.append(fields.Command.create({
'repository_id': repo.id,

View File

@ -87,9 +87,11 @@ def make_branch(repo, name, message, tree, protect=True):
repo.protect(name)
return c
def test_stage_one(env, project, repo_a, repo_b, config):
@pytest.mark.parametrize('uniquifier', [False, True])
def test_stage_one(env, project, repo_a, repo_b, config, uniquifier):
""" First PR is non-matched from A => should not select PR from B
"""
project.uniquifier = uniquifier
project.batch_limit = 1
with repo_a:
@ -112,7 +114,10 @@ def test_stage_one(env, project, repo_a, repo_b, config):
assert pra_id.state == 'ready'
assert pra_id.staging_id
assert repo_a.commit('staging.master').message.startswith('commit_A_00')
assert repo_b.commit('staging.master').message.startswith('force rebuild')
if uniquifier:
assert repo_b.commit('staging.master').message.startswith('force rebuild')
else:
assert repo_b.commit('staging.master').message == 'initial'
prb_id = to_pr(env, pr_b)
assert prb_id.state == 'ready'

View File

@ -32,6 +32,7 @@
<field name="secret"/>
</group>
<group>
<field name="uniquifier"/>
<field name="ci_timeout"/>
<field name="batch_limit"/>
</group>