Skip to content

Commit 657cabd

Browse files
committed
fix: fetch previous release commit for proper commit range in set-commits --auto
Without previousCommit, Sentry has no baseline and reports 0 commits. Fetch the previous release's last commit via the /previous-with-commits/ endpoint (same as the reference sentry-cli) and pass it as previousCommit in the refs payload. Gracefully degrades if no previous release exists.
1 parent 714f742 commit 657cabd

File tree

2 files changed

+73
-8
lines changed

2 files changed

+73
-8
lines changed

src/lib/api/releases.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,41 @@ export async function createReleaseDeploy(
271271
return data as unknown as DeployResponse;
272272
}
273273

274+
/**
275+
* Get the last commit SHA from the previous release that has commits.
276+
*
277+
* Uses the undocumented `/previous-with-commits/` endpoint (same as the
278+
* reference sentry-cli) to determine the commit baseline for range-based
279+
* commit association. Without this, Sentry can't compute which commits
280+
* are new in the current release and reports 0 commits.
281+
*
282+
* @param orgSlug - Organization slug
283+
* @param version - Current release version
284+
* @returns Previous release's last commit SHA, or undefined if no previous release
285+
*/
286+
async function getPreviousReleaseCommit(
287+
orgSlug: string,
288+
version: string
289+
): Promise<string | undefined> {
290+
try {
291+
const regionUrl = await resolveOrgRegion(orgSlug);
292+
const encodedVersion = encodeURIComponent(version);
293+
const { data } = await apiRequestToRegion<{
294+
lastCommit?: { id: string } | null;
295+
}>(
296+
regionUrl,
297+
`organizations/${orgSlug}/releases/${encodedVersion}/previous-with-commits/`,
298+
{ method: "GET" }
299+
);
300+
return data?.lastCommit?.id;
301+
} catch {
302+
// Not critical — if we can't get the previous commit, we still send
303+
// refs without previousCommit. Sentry will try to determine the range
304+
// from its own data (may result in 0 commits for first releases).
305+
return;
306+
}
307+
}
308+
274309
/**
275310
* Set commits on a release using auto-discovery mode.
276311
*
@@ -323,9 +358,16 @@ export async function setCommitsAuto(
323358
);
324359
if (match) {
325360
const headCommit = getHeadCommit(cwd);
326-
return setCommitsWithRefs(orgSlug, version, [
327-
{ repository: match.name, commit: headCommit },
328-
]);
361+
const previousCommit = await getPreviousReleaseCommit(orgSlug, version);
362+
const ref: {
363+
repository: string;
364+
commit: string;
365+
previousCommit?: string;
366+
} = { repository: match.name, commit: headCommit };
367+
if (previousCommit) {
368+
ref.previousCommit = previousCommit;
369+
}
370+
return setCommitsWithRefs(orgSlug, version, [ref]);
329371
}
330372

331373
if (!result.nextCursor) {

test/isolated/set-commits-auto.test.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@ afterEach(() => {
7777
});
7878

7979
describe("setCommitsAuto", () => {
80-
test("lists repos, discovers HEAD, and sends refs to the API", async () => {
80+
test("lists repos, discovers HEAD, fetches previous commit, and sends refs", async () => {
8181
const withCommits = { ...SAMPLE_RELEASE, commitCount: 5 };
8282
const requests: { method: string; url: string }[] = [];
8383

8484
globalThis.fetch = mockFetch(async (input, init) => {
8585
const req = new Request(input!, init);
8686
requests.push({ method: req.method, url: req.url });
8787

88-
// First request: list org repositories (SDK uses /repos/ endpoint)
88+
// List org repositories (SDK uses /repos/ endpoint)
8989
if (req.url.includes("/repos/")) {
9090
expect(req.method).toBe("GET");
9191
return new Response(JSON.stringify([SAMPLE_REPO]), {
@@ -94,16 +94,32 @@ describe("setCommitsAuto", () => {
9494
});
9595
}
9696

97-
// Second request: PUT refs on the release
97+
// Previous release commit lookup
98+
if (req.url.includes("/previous-with-commits/")) {
99+
expect(req.method).toBe("GET");
100+
return new Response(
101+
JSON.stringify({
102+
lastCommit: { id: "prev000000000000000000000000000000000000" },
103+
}),
104+
{ status: 200, headers: { "Content-Type": "application/json" } }
105+
);
106+
}
107+
108+
// PUT refs on the release
98109
expect(req.method).toBe("PUT");
99110
expect(req.url).toContain("/releases/1.0.0/");
100111
const body = (await req.json()) as {
101-
refs: Array<{ repository: string; commit: string }>;
112+
refs: Array<{
113+
repository: string;
114+
commit: string;
115+
previousCommit?: string;
116+
}>;
102117
};
103118
expect(body.refs).toEqual([
104119
{
105120
repository: "getsentry/cli",
106121
commit: "abc123def456789012345678901234567890abcd",
122+
previousCommit: "prev000000000000000000000000000000000000",
107123
},
108124
]);
109125
return new Response(JSON.stringify(withCommits), {
@@ -115,7 +131,6 @@ describe("setCommitsAuto", () => {
115131
const release = await setCommitsAuto("test-org", "1.0.0", "/tmp");
116132

117133
expect(release.commitCount).toBe(5);
118-
expect(requests).toHaveLength(2);
119134
});
120135

121136
test("throws ApiError when org has no repositories", async () => {
@@ -174,6 +189,14 @@ describe("setCommitsAuto", () => {
174189
});
175190
}
176191

192+
// Previous release commit lookup (no previous release)
193+
if (req.url.includes("/previous-with-commits/")) {
194+
return new Response(JSON.stringify({}), {
195+
status: 200,
196+
headers: { "Content-Type": "application/json" },
197+
});
198+
}
199+
177200
// PUT refs on the release
178201
return new Response(JSON.stringify(withCommits), {
179202
status: 200,

0 commit comments

Comments
 (0)