Eagerly load DiffCollection in preload_gitaly_data

Summary

preload_gitaly_data creates a DiffCollection wrapping a lazy gRPC stream (DiffStitcher) but does not consume it. The actual Gitaly CommitDiff streaming RPC data transfer happens when DiffCollection#each is first called inside save_diffsreal_sizepopulate!, which runs inside the after_create :save_git_content callback — meaning it executes inside the implicit save! database transaction.

For large MRs with 1000+ changed files, this holds the DB transaction open for seconds while streaming diff data from Gitaly.

What this MR does

Adds eager_load_diff_collection which force-populates the DiffCollection (consuming the gRPC stream) during preload_gitaly_data, before save! is called. This moves the Gitaly network I/O outside the database transaction.

Guarded by the eager_load_diff_collection feature flag.

Proof

Instrumented testing confirms:

@populated after preload gRPC stream consumed inside txn
Without FF nil (lazy) true
With FF true (eager) nil (already done)

Both produce identical results (same files, commits, state).

Call chain affected

MergeRequestMergeabilityCheckWorker
  → MergeabilityCheckService#execute
    → ReloadMergeHeadDiffService#recreate_merge_head_diff
      → merge_head_diff.preload_gitaly_data  ← fix is here
      → merge_head_diff.save!  ← transaction opens here

Feature Flag

eager_load_diff_collection — scoped to project, default disabled.

Merge request reports

Loading