authorgravatar for snorteman@gmail.comScott Norteman <snorteman@gmail.com> 2026-10-12 19:20:50-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-12 18:20:50-05:00
loga3c4e9e9360d65225fb726ebfe460427a6874741
tree68f9b5d7e719b3d42715faa5155a4abe5bcee3dd
parent511d20c6f20c2ed6ff787aba068d7d673e2949b9
signature Signed by PGP key B5690EEEBB952194

fix(gitlab): merge request details report their head commit (#18608)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

2 files changed, 20 insertions(+), 0 deletions(-)

packages/source-control-gitlab/src/server/gitLabMergeRequestJson.test.ts+14
......@@ -181,6 +181,20 @@ describe("decodeMergeRequestDetailJson", () => {
181181 expect(detail.changedFiles).toBe(0);
182182 });
183183
184 it("reads the head commit, and says nothing where GitLab omitted it", () => {
185 const headSha = (entry: Record<string, unknown>) =>
186 expectSuccess(decodeMergeRequestDetailJson(detailJson(entry))).headSha;
187 const sha = "afce4da4d5fe7d2fd0647b5b864526ea0a2827cb";
188
189 expect(headSha({ sha })).toBe(sha);
190 // A new merge request can report its head before GitLab has computed `diff_refs`.
191 expect(headSha({ sha, diff_refs: null })).toBe(sha);
192 expect(headSha({ sha, merge_commit_sha: "0".repeat(40) })).toBe(sha);
193 expect(headSha({})).toBeUndefined();
194 expect(headSha({ sha: null })).toBeUndefined();
195 expect(headSha({ sha: " " })).toBeUndefined();
196 });
197
184198 it("reads either auto-merge field, and says nothing where GitLab named neither", () => {
185199 const armed = (entry: Record<string, unknown>) =>
186200 expectSuccess(decodeMergeRequestDetailJson(detailJson(entry))).autoMergeEnabled;
packages/source-control-gitlab/src/server/gitLabMergeRequestJson.ts+6
......@@ -51,6 +51,8 @@ const RawMergeRequestSchema = Schema.Struct({
5151 author: Schema.optional(Schema.NullOr(RawUserSchema)),
5252 source_branch: Schema.String,
5353 target_branch: Schema.String,
54 /** The source branch's head commit. */
55 sha: Schema.optional(Schema.NullOr(Schema.String)),
5456 state: Schema.optional(Schema.NullOr(Schema.String)),
5557 draft: Schema.optional(Schema.Boolean),
5658 work_in_progress: Schema.optional(Schema.Boolean),
......@@ -240,6 +242,8 @@ export interface GitLabMergeRequestDetail extends GitLabMergeRequestListItem {
240242 * it: an install too old to answer must not be read as saying the branch is current.
241243 */
242244 readonly divergedCommits?: number;
245 /** Absent where GitLab did not report the head commit. */
246 readonly headSha?: string;
243247}
244248
245249function trimmed(value: string | null | undefined): string | null {
......@@ -365,6 +369,7 @@ function toListItem(
365369
366370function toDetail(raw: Schema.Schema.Type<typeof RawMergeRequestSchema>): GitLabMergeRequestDetail {
367371 const listItem = toListItem(raw);
372 const headSha = trimmed(raw.sha);
368373 const autoMerge =
369374 raw.merge_when_pipeline_succeeds == null && raw.auto_merge_enabled == null
370375 ? undefined
......@@ -390,6 +395,7 @@ function toDetail(raw: Schema.Schema.Type<typeof RawMergeRequestSchema>): GitLab
390395 ? { autoMergeMethod: "squash" as const }
391396 : {}),
392397 ...(raw.diverged_commits_count == null ? {} : { divergedCommits: raw.diverged_commits_count }),
398 ...(headSha === null ? {} : { headSha }),
393399 };
394400}
395401