Create child pipelines when the parent MR ref was deleted
What does this MR do and why?
Child pipelines on merge request refs, including merge train refs, can now be created after the parent pipeline's ref is deleted.
Like jobs, a child pipeline runs on its parent's ref and SHA, which were validated when the parent pipeline was created. Jobs don't check the ref again when they are played or retried, but child pipeline creation does. Merge train refs are deleted when a car leaves the train, so triggering or retrying a child pipeline after that fails with Reference not found.
With this change, Gitlab::Git::RefResolver takes a skip_existence_check: option. Chain::Command sets it only when it creates a child pipeline of a merge request pipeline: the ref is a merge request ref (refs/merge-requests/*) and the pipeline has a merge request. The parent's full ref path is then treated as resolved. The commit check still applies, and the parent's SHA is kept by its keep-around ref. Multi-project pipelines and pipelines created without a bridge are unchanged.
Why branch and tag refs keep the existence check
Ci::BuildRunnerPresenter#refspecs sends refs/heads/<ref> or refs/tags/<ref> alongside the persistent pipeline ref when git_depth is set (the default). Once that ref is deleted, the fetch fails with couldn't find remote ref, so a child pipeline on it would be created and then every job would fail. Failing at creation with Reference not found is clearer.
Merge request pipeline jobs, including those in child pipelines, fetch only the persistent pipeline ref (+<sha>:refs/pipelines/<id>), so they are not affected.
Child pipelines on deleted branches or tags that would have worked (git_depth: 0, or GIT_STRATEGY: none) are still rejected, as before.
Why not skip the Validate::Repository step instead?
Validate::Abilities uses merge_request_ref?/branch?/tag? to pick the permission check, and falls through to true when none match. If only the existence check were skipped, a child pipeline on a deleted ref would skip the permission check entirely. Resolving the ref from its full path keeps the merge request permission check in place. The new abilities spec covers this.
The change is behind the skip_reference_existence_check_for_child_pipelines feature flag (gitlab_com_derisk, default off).
References
- Issue: #428852
- Rollout issue: #569488
- Supersedes !204279 (closed), which was blocked on https://gitlab.com/gitlab-org/gitlab/-/work_items/512692 and https://gitlab.com/gitlab-org/gitlab/-/work_items/570059 (both resolved).
How to set up and validate locally
-
In a project with merge trains enabled, add a
.gitlab-ci.ymlwith a manual child pipeline trigger that runs in merge request pipelines:workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" build: script: echo build child: when: manual allow_failure: true trigger: include: child.ymland a
child.yml:test: script: echo test rules: - when: always -
Add a merge request to the merge train and let it merge. The train ref is deleted when the car leaves the train.
-
Run the
childjob in the merge train pipeline. It fails withReference not found. -
Enable the flag:
Feature.enable(:skip_reference_existence_check_for_child_pipelines, Project.find_by_full_path('<path>')) -
Retry the
childjob. The child pipeline is created on the parent's SHA.
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.