Skip to content

Resolve "Missing MR links on rebased merge train commits (FF merge)"

What does this MR do and why?

Right now, when we have merge requests that ran on a merge train and were merged via semi linear or fast forward merge, the commit shas get rewritten, when the merge request gets rebased. Therefore, we can no longer say from the repository commit view, which merged commit was originally which commit from a merge request.

To address this issue, we have decided to create a new table that maps the "new" commit hash (as in 'wandered into master, got a new sha, because it was rebased') of an "old" commit from a merge request with its merge request and the project that it comes from.

References

Screenshots or screen recordings

Before: image

After: image

How to set up and validate locally

  1. Check out this branch, let all the migrations run.
  2. Restart gdk
  3. Setup a project with semi linear or fast forward merge request strategy and merge trains. You can use bundle exec rake gitlab:seed:merge_trains:project to create a project with some merge requests.
  4. Set up a .gitlab-ci.yml like this for the newly created repository:
    job1:
      script:
        - echo "This job runs in merge request pipelines"
        - sleep 30
      rules:
        - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
  1. Merge one of the newly merge requests (be sure that it says 'add to merge train')
  2. After the merge request that you merged is merged, click on the "Commits" menu on the left side of your Gitlab installation and look for the newly created commits
  3. Click e.g. on commit #2 (closed) of mr #1 (closed) and see, if a merge request is linked to that. If so, then everything works :slight_smile::thumbsup:

MR acceptance checklist

Query-Plan:

  1. bulk_insert:
 INSERT INTO "generated_ref_commits" ("commit_sha","merge_request_id","created_at","project_id","updated_at") VALUES ('\xffe687697427a82afaeaa2308f056a162a3a4c0f', 204, '2025-06-27 10:47:03.260217', 26, CURRENT_TIMESTAMP), ('\x5054416054dfd1c694c954cf0028e278fc8bad4c', 204, '2025-06-27 10:47:03.260227', 26, CURRENT_TIMESTAMP), ('\xb929d19dac87514e3e8fae4a09cab7fb68d59503', 204, '2025-06-27 10:47:03.260229', 26, CURRENT_TIMESTAMP), ('\xab34122d06a64d7c6c4dad0de548a8c08f43169e', 204, '2025-06-27 10:47:03.260231', 26, CURRENT_TIMESTAMP), ('\x1491ee8d12675874d40347eaacca245d2ec30eb7', 205, '2025-06-27 10:47:18.123022', 26, CURRENT_TIMESTAMP), ('\x1dad6383cdfd71a3d5d0e7e9fa7ade96e58c0311', 205, '2025-06-27 10:47:18.123031', 26, CURRENT_TIMESTAMP), ('\xe1ba2a699b1cb04a5cbd00c253f9fbf2ce4e0d7e', 205, '2025-06-27 10:47:18.123032', 26, CURRENT_TIMESTAMP), ('\xaaa2472eae64c061ea6034c3e50e29bf8bb6cff0', 205, '2025-06-27 10:47:18.123034', 26, CURRENT_TIMESTAMP), ('\x3f937010b85c47631a99ba9077a0b49bf217ea2f', 206, '2025-06-28 08:52:47.529902', 26, CURRENT_TIMESTAMP), ('\x6352c6104e24c0c5ae47863e01397f97d234f138', 206, '2025-06-28 08:52:47.529912', 26, CURRENT_TIMESTAMP), ('\xdc4051245287df14803e631c1592d29ef9d4142c', 206, '2025-06-28 08:52:47.529913', 26, CURRENT_TIMESTAMP), ('\xb4df9032c200cd7f3967cf3b3cc08de4c58c2ea4', 206, '2025-06-28 08:52:47.529915', 26, CURRENT_TIMESTAMP), ('\xca3fae3b7e774fb4ae455595a82737b3e8d4a350', 207, '2025-06-28 08:53:21.652842', 26, CURRENT_TIMESTAMP), ('\xbf4df654956f3665d31f10251e80e9ceb8b1b894', 207, '2025-06-28 08:53:21.652852', 26, CURRENT_TIMESTAMP), ('\xa089875d183003b7de80fe64e673ed187b4fceba', 207, '2025-06-28 08:53:21.652854', 26, CURRENT_TIMESTAMP), ('\xbd5d5bdf8bd6cae7af63ed084f688d1f25960345', 207, '2025-06-28 08:53:21.652856', 26, CURRENT_TIMESTAMP), ('\x08793073ccf56c924ee49fdf91f69e1174deb3d4', 209, '2025-06-30 09:55:19.515194', 26, CURRENT_TIMESTAMP), ('\x801fc7154c4738f7e5b4e806732530fca89dda17', 209, '2025-06-30 09:55:19.515203', 26, CURRENT_TIMESTAMP), ('\xde021a65c1d88dc96403dd09263dc30e980cc345', 209, '2025-06-30 09:55:19.515205', 26, CURRENT_TIMESTAMP), ('\x70fb586edd8848d4df8ac8761b4572fe48652872', 209, '2025-06-30 09:55:19.515207', 26, CURRENT_TIMESTAMP), ('\xa010980be8c9fec67c1e05429536fed2f48ffdcf', 211, '2025-07-01 15:50:37.941457', 26, CURRENT_TIMESTAMP), ('\xee5dcee70d5a56789e780857f76111f18ecd0a67', 211, '2025-07-01 15:50:37.941479', 26, CURRENT_TIMESTAMP), ('\x0ea236959ee338b1ed04997fe6186e749f34b0c7', 211, '2025-07-01 15:50:37.941481', 26, CURRENT_TIMESTAMP), ('\xc54b18cfa6e067ef867b38eb17eb99cd16ee88fc', 211, '2025-07-01 15:50:37.941483', 26, CURRENT_TIMESTAMP) ON CONFLICT ("commit_sha") DO UPDATE SET updated_at=(CASE WHEN ("generated_ref_commits"."merge_request_id" IS NOT DISTINCT FROM excluded."merge_request_id" AND "generated_ref_commits"."created_at" IS NOT DISTINCT FROM excluded."created_at" AND "generated_ref_commits"."project_id" IS NOT DISTINCT FROM excluded."project_id") THEN "generated_ref_commits".updated_at ELSE CURRENT_TIMESTAMP END),"merge_request_id"=excluded."merge_request_id","created_at"=excluded."created_at","project_id"=excluded."project_id" RETURNING "id"

                                                          QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
 Insert on generated_ref_commits  (cost=0.00..0.36 rows=24 width=80) (actual time=0.753..0.913 rows=24 loops=1)
   Conflict Resolution: UPDATE
   Conflict Arbiter Indexes: index_grmrc_on_commit_sha
   Tuples Inserted: 0
   Conflicting Tuples: 24
   ->  Values Scan on "*VALUES*"  (cost=0.00..0.36 rows=24 width=80) (actual time=0.208..0.247 rows=24 loops=1)
 Planning Time: 0.132 ms
 Execution Time: 0.965 ms
(8 rows)
  1. Select merge request by project id and sha:
SELECT "merge_requests"."id", "merge_requests"."target_branch", "merge_requests"."source_branch", "merge_requests"."source_project_id", "merge_requests"."author_id", "merge_requests"."assignee_id", "merge_requests"."title", "merge_requests"."created_at", "merge_requests"."updated_at", "merge_requests"."milestone_id", "merge_requests"."merge_status", "merge_requests"."target_project_id", "merge_requests"."iid", "merge_requests"."description", "merge_requests"."updated_by_id", "merge_requests"."merge_error", "merge_requests"."merge_params", "merge_requests"."merge_when_pipeline_succeeds", "merge_requests"."merge_user_id", "merge_requests"."merge_commit_sha", "merge_requests"."approvals_before_merge", "merge_requests"."rebase_commit_sha", "merge_requests"."in_progress_merge_commit_sha", "merge_requests"."lock_version", "merge_requests"."title_html", "merge_requests"."description_html", "merge_requests"."time_estimate", "merge_requests"."squash", "merge_requests"."cached_markdown_version", "merge_requests"."last_edited_at", "merge_requests"."last_edited_by_id", "merge_requests"."merge_jid", "merge_requests"."discussion_locked", "merge_requests"."latest_merge_request_diff_id", "merge_requests"."allow_maintainer_to_push", "merge_requests"."state_id", "merge_requests"."rebase_jid", "merge_requests"."squash_commit_sha", "merge_requests"."sprint_id", "merge_requests"."merge_ref_sha", "merge_requests"."draft", "merge_requests"."prepared_at", "merge_requests"."merged_commit_sha", "merge_requests"."override_requested_changes", "merge_requests"."head_pipeline_id", "merge_requests"."imported_from", "merge_requests"."retargeted" FROM "merge_requests" INNER JOIN generated_ref_commits ON generated_ref_commits.merge_request_id = merge_requests.id WHERE "merge_requests"."target_project_id" = 26 AND "generated_ref_commits"."commit_sha" = '\xc54b18cfa6e067ef867b38eb17eb99cd16ee88fc'

Hint: psql uses seq scan, because my table has not many entries locally. When we force it to use index scan (SET enable_seqscan = OFF;), it will correctly use index scan.

Seq scan:

                                                                 QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.14..3.51 rows=1 width=918) (actual time=0.077..0.078 rows=1 loops=1)
   ->  Seq Scan on generated_ref_commits  (cost=0.00..1.30 rows=1 width=8) (actual time=0.037..0.037 rows=1 loops=1)
         Filter: (commit_sha = '\xc54b18cfa6e067ef867b38eb17eb99cd16ee88fc'::bytea)
         Rows Removed by Filter: 23
   ->  Index Scan using merge_requests_pkey on merge_requests  (cost=0.14..2.17 rows=1 width=918) (actual time=0.034..0.034 rows=1 loops=1)
         Index Cond: (id = generated_ref_commits.merge_request_id)
         Filter: (target_project_id = 26)
 Planning Time: 1.016 ms
 Execution Time: 0.166 ms
(9 rows)

Index scan:

                                                                             QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.28..4.37 rows=1 width=918) (actual time=0.029..0.030 rows=1 loops=1)
   ->  Nested Loop  (cost=0.28..4.37 rows=1 width=918) (actual time=0.029..0.029 rows=1 loops=1)
         ->  Index Scan using index_generated_ref_mr_commits_on_project_id_and_commit_sha on generated_ref_commits  (cost=0.14..2.16 rows=1 width=8) (actual time=0.024..0.024 rows=1 loops=1)
               Index Cond: ((project_id = 26) AND (commit_sha = '\x9d05150a29a4f55a45308d10d6455101254b2c03'::bytea))
         ->  Index Scan using merge_requests_pkey on merge_requests  (cost=0.14..2.17 rows=1 width=918) (actual time=0.003..0.003 rows=1 loops=1)
               Index Cond: (id = generated_ref_commits.merge_request_id)
               Filter: (target_project_id = 26)
 Planning Time: 0.236 ms
 Execution Time: 0.049 ms

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #436943 (closed)

Edited by Daniel Prause

Merge request reports

Loading