-
Notifications
You must be signed in to change notification settings - Fork 136
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
Optimistic batching for batch merging #252
base: master
Are you sure you want to change the base?
Changes from 1 commit
71f9754
4598b2c
fe20e15
07dfe3a
05f562e
f34d399
89f6bf0
4e8f99e
cd0e198
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,7 +112,6 @@ def accept_mr( | |
self, | ||
merge_request, | ||
expected_remote_target_branch_sha, | ||
source_repo_url=None, | ||
): | ||
log.info('Fusing MR !%s', merge_request.iid) | ||
approvals = merge_request.fetch_approvals() | ||
|
@@ -122,21 +121,6 @@ def accept_mr( | |
if new_target_sha != expected_remote_target_branch_sha: | ||
raise CannotBatch('Someone was naughty and by-passed marge') | ||
|
||
# FIXME: we should only add tested-by for the last MR in the batch | ||
_, _, actual_sha = self.update_from_target_branch_and_push( | ||
merge_request, | ||
source_repo_url=source_repo_url, | ||
) | ||
|
||
sha_now = Commit.last_on_branch( | ||
merge_request.source_project_id, merge_request.source_branch, self._api, | ||
).id | ||
# Make sure no-one managed to race and push to the branch in the | ||
# meantime, because we're about to impersonate the approvers, and | ||
# we don't want to approve unreviewed commits | ||
if sha_now != actual_sha: | ||
raise CannotMerge('Someone pushed to branch while we were trying to merge') | ||
|
||
# As we're not using the API to merge the MR, we don't strictly need to reapprove it. However, | ||
# it's a little weird to look at the merged MR to find it has no approvals, so let's do it anyway. | ||
self.maybe_reapprove(merge_request, approvals) | ||
|
@@ -198,6 +182,9 @@ def execute(self): | |
merge_request.source_branch, | ||
'%s/%s' % (merge_request_remote, merge_request.source_branch), | ||
) | ||
# Apply the trailers before running the batch MR | ||
self.add_trailers(merge_request) | ||
self.push_force_to_mr(merge_request, True, source_repo_url, skip_ci=True) | ||
|
||
# Update <source_branch> on latest <batch> branch so it contains previous MRs | ||
self.fuse( | ||
|
@@ -207,6 +194,9 @@ def execute(self): | |
local=True, | ||
) | ||
|
||
# Ensure that individual branch commit SHA matches matches that of its equivalent in batch MR | ||
self.push_force_to_mr(merge_request, True, source_repo_url, skip_ci=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we want to push here - the source branch for the MR will now contain commits of other MRs that have previously been added to the batch branch - we don't want to mess with the original MRs here, especially as the batch can fail. |
||
|
||
# Update <batch> branch with MR changes | ||
self._repo.fast_forward( | ||
BatchMergeJob.BATCH_BRANCH_NAME, | ||
|
@@ -225,36 +215,34 @@ def execute(self): | |
working_merge_requests.append(merge_request) | ||
if len(working_merge_requests) <= 1: | ||
raise CannotBatch('not enough ready merge requests') | ||
if self._project.only_allow_merge_if_pipeline_succeeds: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We seem to have lost this check. Whilst batching is a bit weird if you don't have pipelines enabled, we'd still want to support simply merging the batch branch in without waiting for any pipelines (which won't exist now) to finish. |
||
# This switches git to <batch> branch | ||
self.push_batch() | ||
batch_mr = self.create_batch_mr( | ||
target_branch=target_branch, | ||
) | ||
# This switches git to <batch> branch | ||
self.push_batch() | ||
batch_mr = self.create_batch_mr( | ||
target_branch=target_branch, | ||
) | ||
for merge_request in working_merge_requests: | ||
merge_request.comment('I will attempt to batch this MR (!{})...'.format(batch_mr.iid)) | ||
try: | ||
self.wait_for_ci_to_pass(batch_mr) | ||
except CannotMerge as err: | ||
for merge_request in working_merge_requests: | ||
merge_request.comment('I will attempt to batch this MR (!{})...'.format(batch_mr.iid)) | ||
try: | ||
self.wait_for_ci_to_pass(batch_mr) | ||
except CannotMerge as err: | ||
for merge_request in working_merge_requests: | ||
merge_request.comment( | ||
'Batch MR !{batch_mr_iid} failed: {error} I will retry later...'.format( | ||
batch_mr_iid=batch_mr.iid, | ||
error=err.reason, | ||
), | ||
) | ||
raise CannotBatch(err.reason) from err | ||
merge_request.comment( | ||
'Batch MR !{batch_mr_iid} failed: {error} I will retry later...'.format( | ||
batch_mr_iid=batch_mr.iid, | ||
error=err.reason, | ||
), | ||
) | ||
raise CannotBatch(err.reason) from err | ||
for merge_request in working_merge_requests: | ||
try: | ||
# FIXME: this should probably be part of the merge request | ||
_, source_repo_url, merge_request_remote = self.fetch_source_project(merge_request) | ||
self.ensure_mr_not_changed(merge_request) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why has this check been lost? |
||
self.ensure_mergeable_mr(merge_request) | ||
remote_target_branch_sha = self.accept_mr( | ||
merge_request, | ||
remote_target_branch_sha, | ||
source_repo_url=source_repo_url, | ||
) | ||
if merge_request == working_merge_requests[-1]: | ||
self.accept_mr( | ||
batch_mr, | ||
remote_target_branch_sha, | ||
) | ||
except CannotBatch as err: | ||
merge_request.comment( | ||
"I couldn't merge this branch: {error} I will retry later...".format( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -131,7 +131,7 @@ def remove_branch(self, branch, *, new_current_branch='master'): | |
def checkout_branch(self, branch, start_point=''): | ||
self.git('checkout', '-B', branch, start_point, '--') | ||
|
||
def push(self, branch, *, source_repo_url=None, force=False): | ||
def push(self, branch, *, source_repo_url=None, force=False, skip_ci=False): | ||
self.git('checkout', branch, '--') | ||
|
||
self.git('diff-index', '--quiet', 'HEAD') # check it is not dirty | ||
|
@@ -146,7 +146,14 @@ def push(self, branch, *, source_repo_url=None, force=False): | |
else: | ||
source = 'origin' | ||
force_flag = '--force' if force else '' | ||
self.git('push', force_flag, source, '%s:%s' % (branch, branch)) | ||
# The following needs to be split into 2 variables as any whitespace in the string | ||
# causes it to be quoted which makes the string ignored by git | ||
if skip_ci: | ||
skip_1 = '-o' | ||
skip_2 = 'ci.skip' | ||
else: | ||
skip_1, skip_2 = '', '' | ||
self.git('push', force_flag, skip_1, skip_2, source, '%s:%s' % (branch, branch)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can just have:
and then:
|
||
|
||
def get_commit_hash(self, rev='HEAD'): | ||
"""Return commit hash for `rev` (default "HEAD").""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nicer to specify keyword args for the boolean, as makes it easier to know what this is doing (i.e.
branch_was_modified=True
)