From 2cd3fb8999bdc349cf2d5b1f0786d34196588b1e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 6 Oct 2023 15:19:36 +0200 Subject: [PATCH] [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 --- runbot_merge/models/project.py | 9 ++++++ runbot_merge/models/stagings_create.py | 34 ++++++++++----------- runbot_merge/tests/test_multirepo.py | 9 ++++-- runbot_merge/views/runbot_merge_project.xml | 1 + 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/runbot_merge/models/project.py b/runbot_merge/models/project.py index e828d29b..57fc1a33 100644 --- a/runbot_merge/models/project.py +++ b/runbot_merge/models/project.py @@ -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() diff --git a/runbot_merge/models/stagings_create.py b/runbot_merge/models/stagings_create.py index 42b1aad5..7e083e0e 100644 --- a/runbot_merge/models/stagings_create.py +++ b/runbot_merge/models/stagings_create.py @@ -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, diff --git a/runbot_merge/tests/test_multirepo.py b/runbot_merge/tests/test_multirepo.py index 3ba105ca..dbae165c 100644 --- a/runbot_merge/tests/test_multirepo.py +++ b/runbot_merge/tests/test_multirepo.py @@ -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' diff --git a/runbot_merge/views/runbot_merge_project.xml b/runbot_merge/views/runbot_merge_project.xml index 4795b502..5aa49d47 100644 --- a/runbot_merge/views/runbot_merge_project.xml +++ b/runbot_merge/views/runbot_merge_project.xml @@ -32,6 +32,7 @@ +