Allow enabling auto-merge before the merge request diff exists
glab mr create --auto-merge creates the MR and immediately asks the merge endpoint to set auto-merge. On 19.4.1 that request returns 422 "Branch cannot be merged", because the MR is still preparing and has no diff yet.
This MR lets the when-checks-pass auto-merge strategies skip the commits check while the MR is preparing. The request then returns 200 and auto-merge is set. The merge itself still waits for the full mergeability checks, so nothing can merge early.
The change is behind the auto_merge_skip_commits_check_while_preparing flag (gitlab_com_derisk, default off, actor: project). With the flag off, behavior is unchanged.
Detailed context for AI agents
Root cause chain
glab mr create --auto-mergesendsPOST /projects/:id/merge_requests, then right away sendsPUT /projects/:id/merge_requests/:iid/mergewithauto_merge: true.MergeRequests::CreateServicedefers creating the merge request diff toNewMergeRequestWorker(viaskip_ensure_merge_request_diff). Until Sidekiq runs that worker, the MR is inpreparingstate with no diff.- With no diff,
has_no_commits?is true, soMergeRequests::Mergeability::CheckCommitsStatusServicefails. That check was not skippable. - Because the check fails,
AutoMergeService#preferred_strategyreturns nil. - There is no pipeline yet, so
pipeline_allows_merge?inlib/api/merge_requests.rbis true. The endpoint falls through toexecute_immediate_merge!. mergeable?is false there, so the endpoint renders 422 "Branch cannot be merged".- The same PUT a moment later, after the worker has run, returns 200 and enables auto-merge.
Earlier versions returned 405 for a later window (diff present, conflict check still checking). That was fixed by !245582 (merged) (flag auto_merge_skip_conflict_check, since removed). This MR is the next check in the same chain.
The fix
MergeRequest#skipped_auto_merge_checksnow includesskip_commits_check.- It is true only when all of these hold: the strategy is
merge_when_checks_passoradd_to_merge_train_when_checks_pass, the MR ispreparing?, and the flag is enabled for the project. CheckCommitsStatusService#skip?now honoursparams[:skip_commits_check].- Flag on: the PUT returns 200 and sets auto-merge during the preparing window.
- Flag off: behavior is unchanged (422).
Why there is no new race
The skip only affects the availability check, meaning whether auto-merge may be enabled. It does not affect the merge.
AutoMerge::MergeWhenChecksPassService#processre-runsmergeable?(use_cache: false)with no skips before it merges.- The conflict check fails until the worker's mergeability check moves the MR to
can_be_merged. AfterCreateServiceregisters pipeline creation (create_pipeline_for) beforecheck_mergeability. So the CI check holds the merge until the pipeline is settled.- This is the same window !245582 (merged) already opened, only reached earlier.
- A throwaway probe spec ran
AutoMergeProcessWorkerin three windows: no diff; diff present but pipeline not yet registered; prepared with the pipeline still being created. The MR stayed open in all three.
Alternatives rejected
- Fix it in glab by polling until
detailed_merge_statusleavespreparingbefore the PUT. Rejected as the primary fix, because the API should accept a valid request. glab could still do this to support older instances.
Known side effects
- Auto-merge can now be enabled on a branch that turns out to have no commits. It stays enabled, with the commits check failing, until someone cancels it.
- Clients that pass
shastill get 409 in the no-diff window, becausediff_head_shais nil. glab does not passsha, so it is not affected.
Flag and rollout state
- Flag:
auto_merge_skip_commits_check_while_preparing, type gitlab_com_derisk, default off, actor: project. Definition file:config/feature_flags/gitlab_com_derisk/auto_merge_skip_commits_check_while_preparing.yml. - Rollout issue: not created yet. It will be linked here once it exists.
- Milestone: 19.5.
- No changelog entry, because the change is behind a default-off flag.
Tests
spec/models/merge_request_spec.rb:#skipped_auto_merge_checkswhile preparing, and with the flag off.ee/spec/models/merge_request_spec.rb: the exclusion list.spec/services/merge_requests/mergeability/check_commits_status_service_spec.rb:#skip?.spec/requests/api/merge_requests_spec.rb: new context "when the MR is still preparing and its diff has not been created yet". Flag on returns 200; flag off returns 422.- Result: 14 examples, 0 failures locally. Rubocop is clean. An end-to-end request spec (POST then immediate PUT with Sidekiq faked) returns 200 with the fix and 422 without it.
How to validate locally
- Use a project that has a repository, and pause Sidekiq (or plan to run the two requests back to back in a script).
- Run
POST /api/v4/projects/:id/merge_requeststo create an MR. - Immediately run
PUT /api/v4/projects/:id/merge_requests/:iid/mergewithauto_merge=true. - With the flag off, expect 422.
- Run
Feature.enable(:auto_merge_skip_commits_check_while_preparing, project)and repeat steps 2 and 3 with a new MR. Expect 200 andauto_merge_enabledtrue. - Alternatively, run
glab mr create --auto-mergeagainst the instance with the flag off and then on.
Out of scope
- glab prints "Failed to create merge request. Created recovery file" even though the MR was created. That is a separate glab bug.
- Making glab wait for
preparingto finish for older instances. - Handling the 409 for clients that pass
shain the no-diff window.
References
- Work item: cli#8140 (closed)
- Earlier fix for the 405 window: !245582 (merged)