Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BERTE-561 skip the merge through the queue when not needed #158

Merged
merged 14 commits into from
Jan 29, 2024
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Change Log
All notable changes to this project will be documented in this file.

## [3.11.0] - 2024-01-26
# Added
- Support of merging a PR and skip the queue when it is not needed.

## [3.10.0] - 2023-11-14
# Added
- Support of tags created with `v` prefix.
Expand Down
1 change: 1 addition & 0 deletions bert_e/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class Meta:
quiet = fields.Bool(required=False, load_default=False)
disable_queues = fields.Bool(required=False, load_default=False)
use_queues = fields.Bool(required=False, load_default=True)
skip_queue_when_possible = fields.Bool(required=False, load_default=False)
cmd_line_options = fields.List(fields.Str(), load_default=[])

client_id = fields.Str(required=False, load_default='')
Expand Down
34 changes: 34 additions & 0 deletions bert_e/tests/test_bert_e.py
Original file line number Diff line number Diff line change
Expand Up @@ -7690,6 +7690,40 @@ def test_job_rebuild_queues_without_hotfix(self):
self.assertFalse(self.gitrepo.remote_branch_exists(branch),
"branch %s shouldn't exist" % branch)

def test_no_need_queuing(self):
"""Expect Bert-E to skip the queue when there is no need to queue."""

# Two PRs created at the same time
# At the moment they were created they are both up to date with the
# destination branch
self.init_berte(options=self.bypass_all, skip_queue_when_possible=True)
first_pr = self.create_pr('feature/TEST-1', 'development/4.3')
second_pr = self.create_pr('feature/TEST-2', 'development/4.3')
# The first PR is ready to merge, and is expected to merge directly
# without going through the queue.
self.process_pr_job(first_pr, 'SuccessMessage')
# When the second PR is merged we expect it to go through the queue
# as it is no longer up to date with the destination branch.
self.process_pr_job(second_pr, 'Queued')
# At this point the repository should now contain queue branches.
# We force the merge to get everything setup according for the next
# scenario.
self.process_job(ForceMergeQueuesJob(bert_e=self.berte), 'Merged')
# We expect the PR to be merged so there should be nothing left to do.
self.process_pr_job(second_pr, 'NothingToDo')
# We get the local repo setup for a third PR that should be up to
# date with the latest changes.
self.gitrepo.cmd('git checkout development/4.3')
self.gitrepo.cmd('git branch --set-upstream-to=origin/development/4.3')
self.gitrepo.cmd('git pull')
third_pr = self.create_pr('feature/TEST-3', 'development/4.3')
fourth_pr = self.create_pr('feature/TEST-4', 'development/4.3')
# Just like the first PR, we expect this one to be merged directly.
self.process_pr_job(third_pr, 'SuccessMessage')
# Now we want to know if when the queue is a bit late is Bert-E
# capable of reeastablishing the Queue in order, and queue PR number 4.
self.process_pr_job(fourth_pr, 'Queued')

def test_bypass_prefixes(self):
self.init_berte()
pr = self.create_pr('documentation/stuff', 'development/4.3')
Expand Down
18 changes: 14 additions & 4 deletions bert_e/workflow/gitwaterflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,14 @@ def _handle_pull_request(job: PullRequestJob):
# check for conflicts), and all builds were green, and we reached
# this point without an error, then all conditions are met to enter
# the queue.
if job.settings.use_queue:
# validate current state of queues
queues = queueing.build_queue_collection(job) if job.settings.use_queue \
else None

# If we need to go through the queue, we need to check if we can
# merge the integration branches right away, or if we need to add
# the pull request to the queue.
if queueing.is_needed(job, wbranches, queues):
try:
queues = queueing.build_queue_collection(job)
queues.validate()
except messages.IncoherentQueues as err:
raise messages.QueueOutOfOrder(
Expand All @@ -224,8 +228,14 @@ def _handle_pull_request(job: PullRequestJob):
issue=job.git.src_branch.jira_issue_key,
author=job.pull_request.author_display_name,
active_options=job.active_options)

else:
# If we don't need to go through the queue, we can merge the
# integration branches right away.
# But if the bot is configured with the 'use_queue' option, we
# still need to delete the queue to ensure that we don't raise
# IncohorentQueues in the next runs.
tcarmet marked this conversation as resolved.
Show resolved Hide resolved
if queues is not None:
queues.delete()
merge_integration_branches(job, wbranches)
job.bert_e.add_merged_pr(job.pull_request.id)
job.git.cascade.validate()
Expand Down
12 changes: 12 additions & 0 deletions bert_e/workflow/gitwaterflow/branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,18 @@ def has_version_queued_prs(self, version):
return (self._queues.get(version, {}).get(QueueIntegrationBranch)
is not None)

def delete(self):
"""Delete the queues entirely."""

for branch in self._queues.values():
queue: QueueBranch = branch[QueueBranch]
queue.dst_branch.checkout()
queue.remove(do_push=True)
queue_integration: QueueIntegrationBranch | None = branch.get(
QueueIntegrationBranch)
if queue_integration:
queue_integration.remove(do_push=True)


class BranchCascade(object):
def __init__(self):
Expand Down
46 changes: 41 additions & 5 deletions bert_e/workflow/gitwaterflow/queueing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@

from bert_e import exceptions
from bert_e.job import handler as job_handler
from bert_e.job import QueuesJob
from bert_e.job import QueuesJob, PullRequestJob
from bert_e.lib import git

from ..git_utils import clone_git_repo, consecutive_merge, robust_merge, push
from ..pr_utils import send_comment
from .branches import (BranchCascade, DevelopmentBranch, IntegrationBranch,
QueueBranch, QueueIntegrationBranch,
branch_factory, build_queue_collection)
from .branches import (BranchCascade, DevelopmentBranch, GWFBranch,
IntegrationBranch, QueueBranch, QueueCollection,
QueueIntegrationBranch, branch_factory,
build_queue_collection)
from .integration import get_integration_branches

from typing import List
LOG = logging.getLogger(__name__)


Expand Down Expand Up @@ -207,3 +208,38 @@ def close_queued_pull_request(job, pr_id, cascade):
except git.RemoveFailedException:
# not critical
pass


def is_needed(
job: PullRequestJob,
wbranches: List[GWFBranch],
queues: QueueCollection | None):
"""Determine if queuing is required to merge the given PR.

Queuing a pull request should only be done if:
- The PR or the integration branches are not up to date
with the destination branch.
- Other PRs are already in the queue.

Returns:
- True if the PR should be queued.
- False otherwise.
"""

if queues is None or job.settings.use_queue is False:
return False

if (job.settings.skip_queue_when_possible is False or
already_in_queue(job, wbranches) or
len(queues.queued_prs) > 0):
return True

if not job.git.src_branch.includes_commit(
job.git.dst_branch.get_latest_commit()):
return True
# Check if the wbranches all contain the commits in the dst branches
for branch, dst_branch in zip(wbranches, job.git.cascade.dst_branches):
if not branch.includes_commit(dst_branch.get_latest_commit()):
return True

return False
Loading