44 * See License.AGPL.txt in the project root for license information.
55 */
66
7- import { TypeORM } from "@gitpod/gitpod-db/lib" ;
7+ import { TypeORM , WorkspaceDB } from "@gitpod/gitpod-db/lib" ;
88import { resetDB } from "@gitpod/gitpod-db/lib/test/reset-db" ;
99import {
1010 CommitContext ,
@@ -21,6 +21,7 @@ import {
2121 CommitInfo ,
2222 Repository ,
2323 RepositoryInfo ,
24+ WorkspaceConfig ,
2425} from "@gitpod/gitpod-protocol" ;
2526import * as chai from "chai" ;
2627import { Container } from "inversify" ;
@@ -64,23 +65,36 @@ const gitpodEmptyContext = {
6465} ;
6566
6667// MockRepositoryProvider is a class implementing the RepositoryProvider interface, which allows to pass in commitHistory and commitInfo as needed
68+
69+ type NamedBranch = Omit < Branch , "commit" > ;
70+ function toBranch ( b : { branch : NamedBranch ; commits : CommitInfo [ ] } ) : Branch {
71+ return {
72+ ...b . branch ,
73+ commit : {
74+ sha : head ( b . commits ) . sha ,
75+ author : head ( b . commits ) . author ,
76+ commitMessage : head ( b . commits ) . commitMessage ,
77+ } ,
78+ } ;
79+ }
6780class MockRepositoryProvider implements RepositoryProvider {
68- branches : Map < string , { branch : Branch ; commits : CommitInfo [ ] } > = new Map ( ) ;
81+ branches : Map < string , { branch : NamedBranch ; commits : CommitInfo [ ] } > = new Map ( ) ;
6982
7083 addBranch ( branch : Omit < Branch , "commit" > , commits : CommitInfo [ ] ) {
7184 this . branches . set ( branch . name , {
72- branch : {
73- ...branch ,
74- commit : {
75- sha : head ( commits ) . sha ,
76- author : head ( commits ) . author ,
77- commitMessage : head ( commits ) . commitMessage ,
78- } ,
79- } ,
85+ branch,
8086 commits,
8187 } ) ;
8288 }
8389
90+ pushCommit ( branch : string , commit : CommitInfo ) {
91+ const b = this . branches . get ( branch ) ;
92+ if ( ! b ) {
93+ throw new Error ( "branch not found" ) ;
94+ }
95+ b . commits . push ( commit ) ;
96+ }
97+
8498 async hasReadAccess ( user : any , owner : string , repo : string ) : Promise < boolean > {
8599 return true ;
86100 }
@@ -89,7 +103,7 @@ class MockRepositoryProvider implements RepositoryProvider {
89103 if ( ! branch ) {
90104 throw new Error ( "branch not found" ) ;
91105 }
92- return branch . branch ;
106+ return toBranch ( branch ) ;
93107 }
94108 async getRepo ( user : User , owner : string , repo : string ) : Promise < Repository > {
95109 return {
@@ -110,7 +124,7 @@ class MockRepositoryProvider implements RepositoryProvider {
110124 for ( const [ i , c ] of b . commits . entries ( ) ) {
111125 if ( c . sha === ref ) {
112126 // this commit, and everything before it
113- return b . commits . slice ( 0 , i + 1 ) . map ( ( c ) => c . sha ) ;
127+ return b . commits . slice ( 0 , i ) . map ( ( c ) => c . sha ) ;
114128 }
115129 }
116130 }
@@ -121,7 +135,7 @@ class MockRepositoryProvider implements RepositoryProvider {
121135 }
122136
123137 async getBranches ( user : User , owner : string , repo : string ) : Promise < Branch [ ] > {
124- return Array . from ( this . branches . values ( ) ) . map ( ( b ) => b . branch ) ;
138+ return Array . from ( this . branches . values ( ) ) . map ( ( b ) => toBranch ( b ) ) ;
125139 }
126140
127141 async getUserRepos ( user : User ) : Promise < RepositoryInfo [ ] > {
@@ -173,6 +187,17 @@ describe("ContextService", async () => {
173187 } ,
174188 } ;
175189 } ,
190+ defaultConfig : async ( organizationId ?: string ) : Promise < WorkspaceConfig > => {
191+ return {
192+ ports : [ ] ,
193+ tasks : [ ] ,
194+ image : "gitpod/workspace-base" ,
195+ ideCredentials : "some-credentials" ,
196+ } ;
197+ } ,
198+ getDefaultImage : async ( organizationId ?: string ) : Promise < string > => {
199+ return "gitpod/workspace-base" ;
200+ } ,
176201 } as any as ConfigProvider ) ;
177202
178203 const bindContextParser = ( ) => {
@@ -222,16 +247,42 @@ describe("ContextService", async () => {
222247 return c ( ) ;
223248 }
224249
225- const mainBranch = await mockRepositoryProvider . getBranch ( user , "gitpod-io" , "empty" , "main" ) ;
226- const r : CommitContext = {
227- title : mainBranch . commit . commitMessage ,
228- ref : mainBranch . name ,
229- refType : "branch" ,
230- revision : mainBranch . commit . sha ,
231- repository : await mockRepositoryProvider . getRepo ( user , "gitpod-io" , "empty" ) ,
232- normalizedContextURL : mainBranch . htmlUrl ,
233- } ;
234- return r ;
250+ async function createCommitContextForBranch ( branchName : string ) : Promise < CommitContext > {
251+ const branch = await mockRepositoryProvider . getBranch ( user , "gitpod-io" , "empty" , branchName ) ;
252+ const r : CommitContext = {
253+ title : branch . commit . commitMessage ,
254+ ref : branch . name ,
255+ refType : "branch" ,
256+ revision : branch . commit . sha ,
257+ repository : await mockRepositoryProvider . getRepo ( user , "gitpod-io" , "empty" ) ,
258+ normalizedContextURL : branch . htmlUrl ,
259+ } ;
260+ return r ;
261+ }
262+
263+ const branches = await mockRepositoryProvider . getBranches ( user , "gitpod-io" , "empty" ) ;
264+ for ( const b of branches ) {
265+ if ( b . htmlUrl === url ) {
266+ return createCommitContextForBranch ( b . name ) ;
267+ }
268+ }
269+ for ( const [ _ , b ] of mockRepositoryProvider . branches ) {
270+ for ( const commit of b . commits ) {
271+ const commitContextUrl = `https://github.com/gitpod-io/empty/commit/${ commit . sha } ` ;
272+ if ( commitContextUrl === url ) {
273+ const r : CommitContext = {
274+ title : commit . commitMessage ,
275+ ref : commit . sha ,
276+ refType : "revision" ,
277+ revision : commit . sha ,
278+ repository : await mockRepositoryProvider . getRepo ( user , "gitpod-io" , "empty" ) ,
279+ normalizedContextURL : commitContextUrl ,
280+ } ;
281+ return r ;
282+ }
283+ }
284+ }
285+ return createCommitContextForBranch ( gitpodEmptyContext . repository . defaultBranch ) ;
235286 } ,
236287 } as any as ContextParser ) ;
237288 } ;
@@ -381,6 +432,115 @@ describe("ContextService", async () => {
381432 expect ( PrebuiltWorkspaceContext . is ( ctx . context ) ) . to . equal ( true ) ;
382433 } ) ;
383434
435+ it ( "should ignore unfinished prebuild" , async ( ) => {
436+ // prepare test scenario: two prebuilds
437+ const revision1 = "000000" ;
438+ mockRepositoryProvider . addBranch (
439+ { name : "branch-with-history" , htmlUrl : "https://github.com/gitpod-io/empty/tree/branch-with-history" } ,
440+ [
441+ {
442+ sha : revision1 ,
443+ author : "some-dude" ,
444+ commitMessage : `commit ${ revision1 } ` ,
445+ } ,
446+ ] ,
447+ ) ;
448+
449+ // start two prebuilds: await 1st, fake 2nd to be building
450+ const prebuildManager = container . get ( PrebuildManager ) ;
451+ const workspaceDb : WorkspaceDB = container . get ( WorkspaceDB ) ;
452+ const prebuild1Result = await prebuildManager . triggerPrebuild ( { } , owner , project . id , "branch-with-history" ) ;
453+ const prebuild1 = await workspaceDb . findPrebuildByID ( prebuild1Result . prebuildId ) ;
454+ await workspaceDb . storePrebuiltWorkspace ( {
455+ ...prebuild1 ! ,
456+ state : "available" ,
457+ } ) ;
458+ const wsAndI = await workspaceDb . findWorkspaceAndInstance ( prebuild1 ! . buildWorkspaceId ) ;
459+ await workspaceDb . updateInstancePartial ( wsAndI ! . instanceId , { status : { phase : "stopped" } } ) ;
460+
461+ mockRepositoryProvider . pushCommit ( "branch-with-history" , {
462+ sha : "111111" ,
463+ author : "some-dude" ,
464+ commitMessage : "commit 111111" ,
465+ } ) ;
466+ const prebuild2Result = await prebuildManager . triggerPrebuild ( { } , owner , project . id , "branch-with-history" ) ;
467+ // fake prebuild2 to not be done, yet
468+ const prebuild2 = await workspaceDb . findPrebuildByID ( prebuild2Result . prebuildId ) ;
469+ await workspaceDb . storePrebuiltWorkspace ( {
470+ ...prebuild2 ! ,
471+ state : "building" ,
472+ } ) ;
473+
474+ // request a context for the branch (effectively 2nd commit)
475+ const svc = container . get ( ContextService ) ;
476+ const ctx = await svc . parseContext ( owner , `https://github.com/gitpod-io/empty/tree/branch-with-history` , {
477+ projectId : project . id ,
478+ organizationId : org . id ,
479+ forceDefaultConfig : false ,
480+ } ) ;
481+ expect ( ctx . project ?. id ) . to . equal ( project . id ) ;
482+ expect ( PrebuiltWorkspaceContext . is ( ctx . context ) ) . to . equal ( true ) ;
483+ expect ( ( ctx . context as any as PrebuiltWorkspaceContext ) . prebuiltWorkspace . id ) . to . equal (
484+ prebuild1Result . prebuildId ,
485+ ) ;
486+ expect ( ( ctx . context as any as PrebuiltWorkspaceContext ) . prebuiltWorkspace . commit ) . to . equal ( revision1 ) ;
487+ } ) ;
488+
489+ it ( "should prefer perfect-match prebuild" , async ( ) => {
490+ // prepare test scenario: two prebuilds
491+ const revision1 = "000000" ;
492+ mockRepositoryProvider . addBranch (
493+ { name : "branch-with-history" , htmlUrl : "https://github.com/gitpod-io/empty/tree/branch-with-history" } ,
494+ [
495+ {
496+ sha : revision1 ,
497+ author : "some-dude" ,
498+ commitMessage : `commit ${ revision1 } ` ,
499+ } ,
500+ ] ,
501+ ) ;
502+
503+ // trigger and "await" prebuilds for both commits.
504+ const prebuildManager = container . get ( PrebuildManager ) ;
505+ const workspaceDb : WorkspaceDB = container . get ( WorkspaceDB ) ;
506+ const prebuild1Result = await prebuildManager . triggerPrebuild ( { } , owner , project . id , "branch-with-history" ) ;
507+ const prebuild1 = await workspaceDb . findPrebuildByID ( prebuild1Result . prebuildId ) ;
508+ await workspaceDb . storePrebuiltWorkspace ( {
509+ ...prebuild1 ! ,
510+ state : "available" ,
511+ } ) ;
512+ const wsAndI1 = await workspaceDb . findWorkspaceAndInstance ( prebuild1 ! . buildWorkspaceId ) ;
513+ await workspaceDb . updateInstancePartial ( wsAndI1 ! . instanceId , { status : { phase : "stopped" } } ) ;
514+
515+ mockRepositoryProvider . pushCommit ( "branch-with-history" , {
516+ sha : "111111" ,
517+ author : "some-dude" ,
518+ commitMessage : "commit 111111" ,
519+ } ) ;
520+ const prebuild2Result = await prebuildManager . triggerPrebuild ( { } , owner , project . id , "branch-with-history" ) ;
521+ const prebuild2 = await workspaceDb . findPrebuildByID ( prebuild2Result . prebuildId ) ;
522+ await workspaceDb . storePrebuiltWorkspace ( {
523+ ...prebuild2 ! ,
524+ state : "available" ,
525+ } ) ;
526+ const wsAndI2 = await workspaceDb . findWorkspaceAndInstance ( prebuild2 ! . buildWorkspaceId ) ;
527+ await workspaceDb . updateInstancePartial ( wsAndI2 ! . instanceId , { status : { phase : "stopped" } } ) ;
528+
529+ // request context for the _first_ commit
530+ const svc = container . get ( ContextService ) ;
531+ const ctx = await svc . parseContext ( owner , `https://github.com/gitpod-io/empty/commit/000000` , {
532+ projectId : project . id ,
533+ organizationId : org . id ,
534+ forceDefaultConfig : false ,
535+ } ) ;
536+ expect ( ctx . project ?. id ) . to . equal ( project . id ) ;
537+ expect ( PrebuiltWorkspaceContext . is ( ctx . context ) ) . to . equal ( true ) ;
538+ expect ( ( ctx . context as any as PrebuiltWorkspaceContext ) . prebuiltWorkspace . id ) . to . equal (
539+ prebuild1Result . prebuildId ,
540+ ) ;
541+ expect ( ( ctx . context as any as PrebuiltWorkspaceContext ) . prebuiltWorkspace . commit ) . to . equal ( revision1 ) ;
542+ } ) ;
543+
384544 it ( "should parse snapshot context" , async ( ) => {
385545 const svc = container . get ( ContextService ) ;
386546 const ctx = await svc . parseContext ( owner , `snapshot/${ snapshot . id } ` , {
0 commit comments