Skip to content

Commit c74c426

Browse files
committed
Prefer the syncing version's own release as scan base
1 parent 44aca7d commit c74c426

4 files changed

Lines changed: 70 additions & 8 deletions

File tree

src/base-sha.test.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ function commit(cwd: string, file: string, content: string, message: string): st
1818
return runGit("rev-parse HEAD", cwd);
1919
}
2020

21-
function release(name: string, commitSha: string | undefined, daysAgoCreated: number): Release {
21+
function release(name: string, commitSha: string | undefined, daysAgoCreated: number, version?: string): Release {
2222
return {
2323
id: `id-${name}`,
2424
name,
25+
version,
2526
commitSha,
2627
createdAt: new Date(Date.now() - daysAgoCreated * 24 * 60 * 60 * 1000).toISOString(),
2728
};
@@ -61,11 +62,11 @@ function buildRepo() {
6162

6263
// Back to main
6364
runGit("checkout -q main", cwd);
64-
commit(cwd, "f", "2", "m2");
65+
const m2 = commit(cwd, "f", "2", "m2");
6566
const mainPrev = commit(cwd, "f", "3", "m3 (1.71.0 release)");
6667
const mainHead = commit(cwd, "f", "4", "m4 (1.72.0 HEAD)");
6768

68-
return { cwd, hotfixSha, hotfixHead, mainPrev, mainHead };
69+
return { cwd, m1, m2, hotfixSha, hotfixHead, mainPrev, mainHead };
6970
}
7071

7172
describe("findBaseSha", () => {
@@ -127,6 +128,40 @@ describe("findBaseSha", () => {
127128
it("scenario F — empty list (first-ever sync): returns fallback", () => {
128129
expect(findBaseSha([], repo.mainHead, deps)).toEqual({ kind: "fallback" });
129130
});
131+
132+
it("scenario G — zombie ordering without a version match: picks the stale reachable candidate", () => {
133+
// Server-side recency ordering is the guard against stale started releases
134+
// appearing first; without a version match, the existing walk is preserved.
135+
const candidates = [
136+
release("stale started release", repo.m1, 21),
137+
release("newer completed release", repo.mainPrev, 1),
138+
];
139+
expect(findBaseSha(candidates, repo.mainHead, deps)).toEqual({ kind: "found", sha: repo.m1 });
140+
});
141+
142+
it("scenario H — reachable version match: takes priority over an earlier reachable candidate", () => {
143+
const candidates = [
144+
release("stale started release", repo.m1, 21),
145+
release("newer completed release", repo.mainPrev, 1),
146+
release("matching release", repo.m2, 2, "1.72.0"),
147+
];
148+
expect(findBaseSha(candidates, repo.mainHead, deps, "1.72.0")).toEqual({
149+
kind: "found",
150+
sha: repo.m2,
151+
});
152+
});
153+
154+
it("scenario I — non-ancestor version match: falls back to the normal walk", () => {
155+
const candidates = [
156+
release("stale started release", repo.m1, 21),
157+
release("unreachable matching release", repo.hotfixSha, 2, "1.72.0"),
158+
release("newer completed release", repo.mainPrev, 1),
159+
];
160+
expect(findBaseSha(candidates, repo.mainHead, deps, "1.72.0")).toEqual({
161+
kind: "found",
162+
sha: repo.m1,
163+
});
164+
});
130165
});
131166

132167
/**

src/base-sha.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,27 @@ export type FindBaseShaDeps = {
1212
* release candidates (most-relevant first). Returns the first candidate whose
1313
* `commitSha` is reachable from `headSha` — the API can't disambiguate
1414
* concurrent release trains via SQL alone, so we use git as ground truth.
15+
* When a syncing version is supplied, a reachable candidate with that version
16+
* takes priority over the list order.
1517
*/
16-
export function findBaseSha(candidates: Release[], headSha: string, deps: FindBaseShaDeps): BaseShaResult {
18+
export function findBaseSha(
19+
candidates: Release[],
20+
headSha: string,
21+
deps: FindBaseShaDeps,
22+
syncingVersion?: string,
23+
): BaseShaResult {
24+
if (syncingVersion !== undefined) {
25+
for (const candidate of candidates) {
26+
if (candidate.version !== syncingVersion || !candidate.commitSha) {
27+
continue;
28+
}
29+
if (deps.verifyAncestorReachable(candidate.commitSha, headSha)) {
30+
verbose(`Using base SHA from release "${candidate.name}" (${candidate.commitSha.slice(0, 7)})`);
31+
return { kind: "found", sha: candidate.commitSha };
32+
}
33+
}
34+
}
35+
1736
for (const candidate of candidates) {
1837
const sha = candidate.commitSha;
1938
if (!sha) {

src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ async function syncCommand(): Promise<{
292292
}
293293

294294
const recentReleases = await getRecentReleases();
295-
const scanBase = getScanBase(recentReleases, currentCommit.commit);
295+
const scanBase = getScanBase(recentReleases, currentCommit.commit, releaseVersion);
296296
let latestSha = scanBase.sha;
297297
let inspectingOnlyCurrentCommit = false;
298298

@@ -542,6 +542,7 @@ async function getRecentReleases(): Promise<Release[]> {
542542
recentReleasesByAccessKey(limit: $limit) {
543543
id
544544
name
545+
version
545546
createdAt
546547
commitSha
547548
}
@@ -553,7 +554,7 @@ async function getRecentReleases(): Promise<Release[]> {
553554
return response.data.recentReleasesByAccessKey;
554555
}
555556

556-
function getScanBase(candidates: Release[], currentSha: string): ScanBase {
557+
function getScanBase(candidates: Release[], currentSha: string, syncingVersion?: string): ScanBase {
557558
if (baseRef) {
558559
let resolvedSha: string;
559560
try {
@@ -566,7 +567,13 @@ function getScanBase(candidates: Release[], currentSha: string): ScanBase {
566567
return { kind: "base-ref", sha: resolvedSha, ref: baseRef };
567568
}
568569

569-
const scanBase = selectAutomaticScanBase(candidates, currentSha, { verifyAncestorReachable });
570+
const scanBase = selectAutomaticScanBase(
571+
candidates,
572+
currentSha,
573+
{ verifyAncestorReachable },
574+
undefined,
575+
syncingVersion,
576+
);
570577
if (scanBase.kind !== "first-sync") {
571578
return scanBase;
572579
}

src/scan-base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ export function selectAutomaticScanBase(
4646
currentSha: string,
4747
deps: FindBaseShaDeps,
4848
cwd: string = process.cwd(),
49+
syncingVersion?: string,
4950
): ScanBase {
50-
const result = findBaseSha(candidates, currentSha, deps);
51+
const result = findBaseSha(candidates, currentSha, deps, syncingVersion);
5152
if (result.kind === "found") {
5253
return { kind: "release", sha: result.sha };
5354
}

0 commit comments

Comments
 (0)