Skip to content

Commit

Permalink
BERTE-561 skip the merge through the queue when not needed (#158)
Browse files Browse the repository at this point in the history
  • Loading branch information
tcarmet authored Jan 29, 2024
1 parent b20cd32 commit 653081a
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 9 deletions.
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
2 changes: 2 additions & 0 deletions bert_e/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ 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_not_needed = 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
35 changes: 35 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,41 @@ 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_not_needed=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
# IncoherentQueues in the next runs.
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_not_needed 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

0 comments on commit 653081a

Please sign in to comment.