Harden CreateRefService against rebase collapse

What does this MR do and why?

Hardens MergeRequests::CreateRefService against rebase collapse — the root cause of the data loss in the parent work item — and closes the same gap for merge trains, which build their ref through the same service and were not covered by the fast_forward! guard added in !239586 (merged).

When a fast-forward source is rebased onto the target tip but carries no unique commits (e.g. it was branched off another MR whose changes are now in the target), the generated ref collapses back to the target tip. Recording that as a merge writes no commit yet still deletes the source branch — silently losing the MR's work.

Changes

  • CreateRefServicereject_rebase_collapse! rejects a generated ref that equals the captured target tip (first_parent_sha) or is blank. Any legitimate merge (ff, squash, or merge commit) produces a SHA beyond the tip, so the guard only fires on a genuine no-op collapse.
  • MergeStrategies::FromSourceBranch — propagates the CreateRefService error as a StrategyError instead of falling into the nil src_sha path.
  • MergeTrains::CreateRefService — returns the error before persisting empty train_ref merge params.

Feature flag

Gated behind a new flag, verify_create_ref_advancement (gitlab_com_derisk, default off). All three changes — the reject_rebase_collapse! guard and both error-propagation sites — are behind the flag, so with it disabled this MR is a no-op and behaviour is byte-for-byte identical to today.

This is a separate flag from verify_ff_merge_advancement, which guards the fast_forward! layer added in !239586 (merged). They defend the same data-loss class but at different layers (CreateRefService is the only protection for the merge-train path), and a dedicated flag lets this guard roll out and roll back independently.

Rollout issue: #603343

Testing

For each of the three sites, specs cover both the flag-on path (error raised / propagated) and the flag-off path (legacy behaviour preserved): the collapse guard in CreateRefService (error vs legacy no-op success), error propagation in FromSourceBranch (StrategyError vs legacy fast-forward), and the merge-train short-circuit in MergeTrains::CreateRefService (early return vs legacy empty train_ref persistence). All passing; RuboCop clean.

Follow-up scope

This is item 1 of the follow-up. Items 2 and 3 (mandatory target_sha: arg; pre-merge short-circuit) are explicitly gated on first cleaning up the feature flag and are out of scope here.

References

Edited by Marc Shaw

Merge request reports

Loading