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

How to set up and validate locally

  1. In a project with merge trains enabled, add a .gitlab-ci.yml with 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.yml

    and a child.yml:

    test:
      script: echo test
      rules:
        - when: always
  2. Add a merge request to the merge train and let it merge. The train ref is deleted when the car leaves the train.

  3. Run the child job in the merge train pipeline. It fails with Reference not found.

  4. Enable the flag:

    Feature.enable(:skip_reference_existence_check_for_child_pipelines, Project.find_by_full_path('<path>'))
  5. Retry the child job. 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.

🤖 Generated with Claude Code

Edited by Hordur Freyr Yngvason

Merge request reports

Loading
Loading