From 3dda8bf9d2c04635fe3b6ec8bedb665c867ebb37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Sun, 27 Sep 2026 00:48:35 +0000 Subject: [PATCH 1/2] fix(provider-review): apply the collected commit title and message to GitLab merges --- apps/web/src/lib/provider-review/gitlab-write.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/web/src/lib/provider-review/gitlab-write.ts b/apps/web/src/lib/provider-review/gitlab-write.ts index ebfd21fcdc..a178a070b4 100644 --- a/apps/web/src/lib/provider-review/gitlab-write.ts +++ b/apps/web/src/lib/provider-review/gitlab-write.ts @@ -423,7 +423,11 @@ function requireHeadShaFence(mr: GitLabMergeRequestDetail, expectedHeadSha: stri /** * Merge the MR. The caller's `expectedHeadSha` is re-verified against a fresh * fetch and passed to GitLab as `sha`, so the merge can only land the exact - * revision the reviewer saw. + * revision the reviewer saw. GitLab's merge endpoint accepts only a whole + * commit message (`squash_commit_message` when `squash` is true, otherwise + * `merge_commit_message`) and has no separate title parameter, so the sheet's + * `commitTitle` becomes the message's leading line and `commitMessage` follows + * as the body. */ export async function mergePullRequest( target: GitLabMrTarget & { @@ -450,6 +454,9 @@ export async function mergePullRequest( if (mr.state === 'closed' || mr.state === 'locked') { throw new GitLabReviewError('bad_request', 'The merge request is closed.'); } + const fullCommitMessage = [target.commitTitle, target.commitMessage] + .filter((part): part is string => Boolean(part)) + .join('\n\n'); await requestGitLabJson(access, `${mrPath(access, target.mrIid)}/merge`, { method: 'PUT', body: { @@ -458,8 +465,11 @@ export async function mergePullRequest( ...(target.shouldRemoveSourceBranch !== undefined ? { should_remove_source_branch: target.shouldRemoveSourceBranch } : {}), - ...(target.commitTitle ? { merge_commit_title: target.commitTitle } : {}), - ...(target.commitMessage ? { merge_commit_message: target.commitMessage } : {}), + ...(fullCommitMessage + ? target.squash + ? { squash_commit_message: fullCommitMessage } + : { merge_commit_message: fullCommitMessage } + : {}), }, }); return { done: true, replayed: false }; From 9b967a83ba646103e32d558d719bd4cdd2707fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Sun, 27 Sep 2026 01:12:46 +0000 Subject: [PATCH 2/2] test(provider-review): pin GitLab merge commit-message body per squash mode --- .../lib/provider-review/gitlab-write.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/apps/web/src/lib/provider-review/gitlab-write.test.ts b/apps/web/src/lib/provider-review/gitlab-write.test.ts index 0cc1f1157f..f41b739ab3 100644 --- a/apps/web/src/lib/provider-review/gitlab-write.test.ts +++ b/apps/web/src/lib/provider-review/gitlab-write.test.ts @@ -646,6 +646,42 @@ describe('mergePullRequest', () => { }); }); + it('squashes the collected title and body into squash_commit_message', async () => { + const result = await mergePullRequest({ + ...TARGET, + expectedHeadSha: 'sha-head', + squash: true, + commitTitle: 'Add nested deploy script', + commitMessage: 'Explain the deploy script.', + }); + + expect(result).toEqual({ done: true, replayed: false }); + const { init } = lastRequest(); + expect(JSON.parse(String(init.body))).toEqual({ + sha: 'sha-head', + squash: true, + squash_commit_message: 'Add nested deploy script\n\nExplain the deploy script.', + }); + }); + + it('uses the collected title and body as merge_commit_message when not squashing', async () => { + const result = await mergePullRequest({ + ...TARGET, + expectedHeadSha: 'sha-head', + squash: false, + commitTitle: 'Add nested deploy script', + commitMessage: 'Explain the deploy script.', + }); + + expect(result).toEqual({ done: true, replayed: false }); + const { init } = lastRequest(); + expect(JSON.parse(String(init.body))).toEqual({ + sha: 'sha-head', + squash: false, + merge_commit_message: 'Add nested deploy script\n\nExplain the deploy script.', + }); + }); + it('refuses a stale revision with the exact reason and never merges', async () => { mockFetchGitLabMergeRequest.mockResolvedValue(openMrFixture('sha-moved'));