Add mergeSource GraphQL field to merge requests

What does this MR do and why?

Adds an experimental mergeSource field to MergeRequest in GraphQL. When a merge request is marked as merged because another merge request or a pushed commit included its changes, the field returns that merge request or the commit SHA. It is null for merge requests merged in GitLab.

This is the first of three MRs for #512551. The audit events and the merge request widget changes come next; the widget consumes this field in a later release.

Details
  • MergeRequest#merge_source returns merge_source_event for merged merge requests. That has_one selects the latest merged ResourceStateEvent with a source_merge_request_id or source_commit, in SQL.
  • MergeRequestMergeSource exposes mergeRequest (typed MergeRequest, so the type-level read_merge_request authorization returns null when the viewer cannot read it) and commitSha.
  • The lookahead preload merge_source: [{ merge_source_event: :source_merge_request }] keeps merge request lists free of N+1 queries. The request spec's N+1 check fails without it.
  • spec/requests/api/graphql/merge_request/merge_source_spec.rb covers granular tokens that do and don't authorize the parent merge request.
  • resource_state_event.rb is untouched, so this does not conflict with !260499, which fixes the race that sometimes records a commit source for merge requests merged through another merge request.
  • Verified: spec/models/merge_request_spec.rb and spec/requests/api/graphql/project/merge_requests_spec.rb (2332 examples, 0 failures), the new request spec and GraphQL type specs (38 examples, 0 failures), rubocop clean. GraphQL docs and introspection schema regenerated.
Database

New query from the merge_source_event association on resource_state_events, using index_resource_state_events_on_merge_request_id. Plans from Database Lab (gitlab-production-main) with real IDs: most merged MRs have no merge source row, so these use MRs merged through another MR.

Single merge request, !237820 (merged) (merge_request_id = 488817646):

SELECT "resource_state_events".* FROM "resource_state_events"
WHERE "resource_state_events"."merge_request_id" = 488817646
  AND "resource_state_events"."state" = 3
  AND (source_merge_request_id IS NOT NULL OR source_commit IS NOT NULL)
ORDER BY "resource_state_events"."id" DESC LIMIT 1
Limit  (actual time=14.151..14.152 rows=1 loops=1)
  Buffers: shared hit=6 read=5
  ->  Sort  (actual time=14.150..14.151 rows=1 loops=1)
        Sort Key: resource_state_events.id DESC
        Sort Method: quicksort  Memory: 25kB
        ->  Index Scan using index_resource_state_events_on_merge_request_id on public.resource_state_events  (actual time=14.132..14.134 rows=1 loops=1)
              Index Cond: (resource_state_events.merge_request_id = 488817646)
              Filter: (((source_merge_request_id IS NOT NULL) OR (source_commit IS NOT NULL)) AND (state = 3))
              Buffers: shared hit=3 read=5
Time: 15.366 ms (planning 1.168 ms, execution 14.198 ms, I/O read 14.077 ms)

Preload for a list: the 20 most recently merged MRs in gitlab-org/gitlab plus two MRs merged through another MR (!237820 (merged) and !203656 (merged)):

SELECT "resource_state_events".* FROM "resource_state_events"
WHERE "resource_state_events"."state" = 3
  AND (source_merge_request_id IS NOT NULL OR source_commit IS NOT NULL)
  AND "resource_state_events"."merge_request_id" IN (488817646, 412241647, 544090251, 548612180, 548532943, 547972333, 548130006, 547925392, 547765758, 546973204, 547282522, 526297059, 548070317, 547865112, 547864381, 547281800, 548486542, 548042329, 548427255, 548442425, 545159393, 540076582)
ORDER BY "resource_state_events"."id" DESC
Sort  (actual time=48.764..48.765 rows=2 loops=1)
  Sort Key: resource_state_events.id DESC
  Sort Method: quicksort  Memory: 25kB
  Buffers: shared hit=42 read=35
  ->  Index Scan using index_resource_state_events_on_merge_request_id on public.resource_state_events  (actual time=15.255..48.748 rows=2 loops=1)
        Index Cond: (resource_state_events.merge_request_id = ANY ('{488817646,412241647,...}'::bigint[]))
        Filter: (((source_merge_request_id IS NOT NULL) OR (source_commit IS NOT NULL)) AND (state = 3))
        Buffers: shared hit=39 read=35
Time: 49.933 ms (planning 1.121 ms, execution 48.812 ms, I/O read 48.586 ms)

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.

Edited by Kai Armstrong

Merge request reports

Loading
Loading