Skip to content

Commit b261e8b

Browse files
committed
Fix prebuild lookup for prebuilds run in reverse-chronological order
1 parent 4a70961 commit b261e8b

File tree

5 files changed

+106
-8
lines changed

5 files changed

+106
-8
lines changed

components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class BitbucketServerRepositoryProvider implements RepositoryProvider {
190190
owner,
191191
repoKind,
192192
repositorySlug: repo,
193-
query: { shaOrRevision: ref, limit: 1000 },
193+
query: { shaOrRevision: ref, limit: 1000 }, // ft: why do we limit to 1000 and not maxDepth?
194194
});
195195

196196
const commits = commitsResult.values || [];

components/server/src/bitbucket/bitbucket-repository-provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export class BitbucketRepositoryProvider implements RepositoryProvider {
143143
): Promise<string[]> {
144144
const api = await this.apiFactory.create(user);
145145
// TODO(janx): To get more results than Bitbucket API's max pagelen (seems to be 100), pagination should be handled.
146-
// The additional property 'page' may be helfpul.
146+
// The additional property 'page' may be helpful.
147147
const result = await api.repositories.listCommitsAt({
148148
workspace: owner,
149149
repo_slug: repo,

components/server/src/github/github-repository-provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class GithubRepositoryProvider implements RepositoryProvider {
130130
}
131131

132132
// TODO(janx): To get more results than GitHub API's max page size (seems to be 100), pagination should be handled.
133-
// These additional history properties may be helfpul:
133+
// These additional history properties may be helpful:
134134
// totalCount,
135135
// pageInfo {
136136
// haxNextPage,

components/server/src/prebuilds/incremental-workspace-service.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export class IncrementalWorkspaceService {
4545
context.revision,
4646
maxDepth,
4747
);
48+
4849
history.commitHistory.unshift(context.revision);
4950
if (context.additionalRepositoryCheckoutInfo && context.additionalRepositoryCheckoutInfo.length > 0) {
5051
const histories = context.additionalRepositoryCheckoutInfo.map(async (info) => {
@@ -83,8 +84,21 @@ export class IncrementalWorkspaceService {
8384
// Note: This query returns only not-garbage-collected prebuilds in order to reduce cardinality
8485
// (e.g., at the time of writing, the Gitpod repository has 16K+ prebuilds, but only ~300 not-garbage-collected)
8586
const recentPrebuilds = await this.workspaceDB.findPrebuildsWithWorkspace(projectId);
87+
88+
const sortedRecentPrebuilds = recentPrebuilds
89+
.filter((prebuild) => {
90+
return history.commitHistory?.includes(prebuild.prebuild.commit);
91+
})
92+
.sort((a, b) => {
93+
// instead of the DB-returned creation time we use the commit history to sort the prebuilds
94+
// this way we can return the correct prebuild even if the prebuild was first created for a later commit and then another one for an earlier commit
95+
const aIdx = history.commitHistory?.indexOf(a.prebuild.commit) ?? -1;
96+
const bIdx = history.commitHistory?.indexOf(b.prebuild.commit) ?? -1;
97+
98+
return aIdx - bIdx;
99+
});
86100
const imageSource = await imageSourcePromise;
87-
for (const recentPrebuild of recentPrebuilds) {
101+
for (const recentPrebuild of sortedRecentPrebuilds) {
88102
if (
89103
this.isGoodBaseforIncrementalBuild(
90104
history,
@@ -98,6 +112,7 @@ export class IncrementalWorkspaceService {
98112
return recentPrebuild.prebuild;
99113
}
100114
}
115+
101116
return undefined;
102117
}
103118

@@ -156,10 +171,6 @@ export class IncrementalWorkspaceService {
156171

157172
// ensure the image source hasn't changed (skips older images)
158173
if (JSON.stringify(imageSource) !== JSON.stringify(candidateWorkspace.imageSource)) {
159-
log.debug(`Skipping parent prebuild: Outdated image`, {
160-
imageSource,
161-
parentImageSource: candidateWorkspace.imageSource,
162-
});
163174
return false;
164175
}
165176

components/server/src/workspace/context-service.spec.db.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class MockRepositoryProvider implements RepositoryProvider {
130130
}
131131
throw new Error(`ref ${ref} not found`);
132132
}
133+
133134
async getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise<CommitInfo | undefined> {
134135
return headu(this.branches.get(ref)?.commits);
135136
}
@@ -541,6 +542,92 @@ describe("ContextService", async () => {
541542
expect((ctx.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.commit).to.equal(revision1);
542543
});
543544

545+
it("should handle triggering prebuilds out of order with respect to commits", async () => {
546+
const commit1 = {
547+
sha: "69420",
548+
author: "some-dude",
549+
commitMessage: `commit 69420`,
550+
};
551+
const commit2 = {
552+
sha: "69421",
553+
author: "some-other-dude",
554+
commitMessage: "commit 69421",
555+
};
556+
const branchName = "branch-2";
557+
mockRepositoryProvider.addBranch(
558+
{ name: branchName, htmlUrl: `https://github.com/gitpod-io/empty/tree/${branchName}` },
559+
[commit1],
560+
);
561+
mockRepositoryProvider.pushCommit(branchName, commit2);
562+
563+
// request context for both commits separately
564+
const svc = container.get(ContextService);
565+
let ctx1 = await svc.parseContext(owner, `https://github.com/gitpod-io/empty/commit/${commit1.sha}`, {
566+
projectId: project.id,
567+
organizationId: org.id,
568+
forceDefaultConfig: false,
569+
});
570+
let ctx2 = await svc.parseContext(owner, `https://github.com/gitpod-io/empty/commit/${commit2.sha}`, {
571+
projectId: project.id,
572+
organizationId: org.id,
573+
forceDefaultConfig: false,
574+
});
575+
576+
// trigger and "await" prebuilds for both commits in push order
577+
const prebuildManager = container.get(PrebuildManager);
578+
const workspaceDb: WorkspaceDB = container.get(WorkspaceDB);
579+
const prebuildForCommit2 = await prebuildManager.startPrebuild(
580+
{},
581+
{ user: owner, project, commitInfo: commit2, context: ctx2.context as CommitContext },
582+
);
583+
const prebuild1 = await workspaceDb.findPrebuildByID(prebuildForCommit2.prebuildId);
584+
await workspaceDb.storePrebuiltWorkspace({
585+
...prebuild1!,
586+
state: "available",
587+
});
588+
const wsAndI1 = await workspaceDb.findWorkspaceAndInstance(prebuild1!.buildWorkspaceId);
589+
await workspaceDb.updateInstancePartial(wsAndI1!.instanceId, { status: { phase: "stopped" } });
590+
591+
const prebuildForCommit1 = await prebuildManager.startPrebuild(
592+
{},
593+
{ user: owner, project, commitInfo: commit1, context: ctx1.context as CommitContext, forcePrebuild: true },
594+
);
595+
const prebuild2 = await workspaceDb.findPrebuildByID(prebuildForCommit1.prebuildId);
596+
await workspaceDb.storePrebuiltWorkspace({
597+
...prebuild2!,
598+
state: "available",
599+
});
600+
const wsAndI2 = await workspaceDb.findWorkspaceAndInstance(prebuild2!.buildWorkspaceId);
601+
await workspaceDb.updateInstancePartial(wsAndI2!.instanceId, { status: { phase: "stopped" } });
602+
603+
// after everything has settled, request context for both commits again
604+
605+
ctx1 = await svc.parseContext(owner, `https://github.com/gitpod-io/empty/commit/${commit1.sha}`, {
606+
projectId: project.id,
607+
organizationId: org.id,
608+
forceDefaultConfig: false,
609+
});
610+
ctx2 = await svc.parseContext(owner, `https://github.com/gitpod-io/empty/commit/${commit2.sha}`, {
611+
projectId: project.id,
612+
organizationId: org.id,
613+
forceDefaultConfig: false,
614+
});
615+
616+
expect(ctx1.project?.id).to.equal(project.id);
617+
expect(PrebuiltWorkspaceContext.is(ctx1.context)).to.equal(true);
618+
expect((ctx1.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.id).to.equal(
619+
prebuildForCommit1.prebuildId,
620+
);
621+
expect((ctx1.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.commit).to.equal(commit1.sha);
622+
623+
expect(ctx2.project?.id).to.equal(project.id);
624+
expect(PrebuiltWorkspaceContext.is(ctx2.context)).to.equal(true);
625+
expect((ctx2.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.id).to.equal(
626+
prebuildForCommit2.prebuildId,
627+
);
628+
expect((ctx2.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.commit).to.equal(commit2.sha);
629+
});
630+
544631
it("should parse snapshot context", async () => {
545632
const svc = container.get(ContextService);
546633
const ctx = await svc.parseContext(owner, `snapshot/${snapshot.id}`, {

0 commit comments

Comments
 (0)