Prevent reopening merge request when a branch is missing
What does this MR do and why?
Reopening a closed merge request whose source branch was deleted (and whose merge request
head ref has since expired) recreated the diff from a nil source branch SHA. That produced
an empty diff which replaced the stored one and became the latest version, silently hiding the
commits and changes that were still visible while the merge request was closed.
Reopening is now rejected when the source or target branch no longer exists. Branch
existence is validated as part of the reopen itself, via a flag-gated model validation
(MergeRequest#validate_branch_existence_on_reopen, enabled while
MergeRequests::ReopenService sets the reopening flag):
- The reopen state transition's own
savefails, so the merge request stays closed and no empty diff is created. - The error is durable — it is re-added on every
valid?/save, so it survives the latersaveinIssuableBaseService#update(a combined reopen + attribute change now fails atomically instead of silently dropping the error and persisting the other change) and the REST API's revalidation. - It surfaces everywhere: the REST API returns
422, the GraphQLmergeRequestUpdatemutation returns the error, the web controller renders it, and the MR widget shows it in a toast ("Cannot reopen this merge request because the source or target branch no longer exists."). - The EE reopen side effects (approval deletion, policy resync, event publishing) only run when the merge request is actually reopened.
- A deleted fork (nil
source_project) is handled too — it is rejected with the error rather than reopening.
The behaviour is gated behind the default-off prevent_reopen_merge_request_without_branch
feature flag, so there is no change until it is enabled.
References
- Resolves #605052
Screenshots or screen recordings
Not applicable — no visual UI changes (the widget reuses its existing failure toast).
How to set up and validate locally
-
Enable the feature flag in the Rails console:
Feature.enable(:prevent_reopen_merge_request_without_branch) -
Create a merge request with commits, then close it.
-
Delete the source branch of the closed merge request.
-
Attempt to reopen it:
- In the UI, click Reopen in the merge request widget — it stays closed and a toast shows "Cannot reopen this merge request because the source or target branch no longer exists."
- Via REST (
PUT /projects/278964/merge_requests/:iidwithstate_event=reopen) — returns422with that message. - Via the GraphQL
mergeRequestUpdatemutation withstate: OPEN— returns the same error.
-
Confirm the merge request stays closed and its original diff/commits remain visible (no empty diff created). Disable the flag and confirm reopening behaves as before.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.