@@ -23,6 +23,12 @@ import { ImageSourceProvider } from "../workspace/image-source-provider";
2323
2424const  MAX_HISTORY_DEPTH  =  100 ; 
2525
26+ enum  Match  { 
27+     None  =  0 , 
28+     Loose  =  1 , 
29+     Exact  =  2 , 
30+ } 
31+ 
2632@injectable ( ) 
2733export  class  IncrementalWorkspaceService  { 
2834    @inject ( Config )  protected  readonly  config : Config ; 
@@ -84,88 +90,86 @@ export class IncrementalWorkspaceService {
8490        // Note: This query returns only not-garbage-collected prebuilds in order to reduce cardinality 
8591        // (e.g., at the time of writing, the Gitpod repository has 16K+ prebuilds, but only ~300 not-garbage-collected) 
8692        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-             } ) ; 
10093        const  imageSource  =  await  imageSourcePromise ; 
101-         for  ( const  recentPrebuild  of  sortedRecentPrebuilds )  { 
102-             if  ( 
103-                 this . isGoodBaseforIncrementalBuild ( 
94+ 
95+         // traverse prebuilds by commit history instead of their creationTime, so that we don't match prebuilds created for older revisions but triggered later 
96+         for  ( const  commit  of  history . commitHistory )  { 
97+             const  prebuildsForCommit  =  recentPrebuilds . filter ( ( {  prebuild } )  =>  prebuild . commit  ===  commit ) ; 
98+             for  ( const  entry  of  prebuildsForCommit )  { 
99+                 const  {  prebuild,  workspace }  =  entry ; 
100+                 const  match  =  this . isMatchForIncrementalBuild ( 
104101                    history , 
105102                    config , 
106103                    imageSource , 
107-                     recentPrebuild . prebuild , 
108-                     recentPrebuild . workspace , 
104+                     prebuild , 
105+                     workspace , 
109106                    includeUnfinishedPrebuilds , 
110-                 ) 
111-             )  { 
112-                 return  recentPrebuild . prebuild ; 
107+                 ) ; 
108+                 if  ( match  >  Match . None )  { 
109+                     console . log ( "Found base for incremental build" ,  { 
110+                         prebuild, 
111+                         workspace, 
112+                         exactMatch : match  ===  Match . Exact , 
113+                     } ) ; 
114+                     return  prebuild ; 
115+                 } 
113116            } 
114117        } 
115118
116119        return  undefined ; 
117120    } 
118121
119-     private  isGoodBaseforIncrementalBuild ( 
122+     private  isMatchForIncrementalBuild ( 
120123        history : WithCommitHistory , 
121124        config : WorkspaceConfig , 
122125        imageSource : WorkspaceImageSource , 
123126        candidatePrebuild : PrebuiltWorkspace , 
124127        candidateWorkspace : Workspace , 
125128        includeUnfinishedPrebuilds ?: boolean , 
126-     ) : boolean  { 
127-         if  ( ! history . commitHistory  ||  history . commitHistory . length  ===  0 )  { 
128-             return  false ; 
129+     ) : Match  { 
130+         // make typescript happy, we know that history.commitHistory is defined 
131+         if  ( ! history . commitHistory )  { 
132+             return  Match . None ; 
129133        } 
130134        if  ( ! CommitContext . is ( candidateWorkspace . context ) )  { 
131-             return  false ; 
135+             return  Match . None ; 
132136        } 
133137
134138        const  acceptableStates : PrebuiltWorkspaceState [ ]  =  [ "available" ] ; 
135139        if  ( includeUnfinishedPrebuilds )  { 
136140            acceptableStates . push ( "building" ) ; 
137141            acceptableStates . push ( "queued" ) ; 
138142        } 
139- 
140143        if  ( ! acceptableStates . includes ( candidatePrebuild . state ) )  { 
141-             return  false ; 
144+             return  Match . None ; 
142145        } 
143146
144-         // we are only considering full prebuilds 
145-         if  ( ! ! candidateWorkspace . basedOnPrebuildId )  { 
146-             return  false ; 
147+         // we are only considering full prebuilds (we are not building on top of incremental prebuilds)  
148+         if  ( candidateWorkspace . basedOnPrebuildId )  { 
149+             return  Match . None ; 
147150        } 
148151
152+         // check if the amount of additional repositories matches the candidate 
149153        if  ( 
150154            candidateWorkspace . context . additionalRepositoryCheckoutInfo ?. length  !== 
151155            history . additionalRepositoryCommitHistories ?. length 
152156        )  { 
153-             // different number of repos 
154-             return  false ; 
157+             return  Match . None ; 
155158        } 
156159
157160        const  candidateCtx  =  candidateWorkspace . context ; 
161+ 
162+         // check for overlapping commit history 
158163        if  ( ! history . commitHistory . some ( ( sha )  =>  sha  ===  candidateCtx . revision ) )  { 
159-             return  false ; 
164+             return  Match . None ; 
160165        } 
161- 
162-         // check the commits are included in the commit history 
163-         for  ( const  subRepo  of  candidateWorkspace . context . additionalRepositoryCheckoutInfo  ||  [ ] )  { 
164-             const  matchIngRepo  =  history . additionalRepositoryCommitHistories ?. find ( 
166+         // check for overlapping git history for each additional repo 
167+         for  ( const  subRepo  of  candidateWorkspace . context . additionalRepositoryCheckoutInfo  ??  [ ] )  { 
168+             const  matchingRepo  =  history . additionalRepositoryCommitHistories ?. find ( 
165169                ( repo )  =>  repo . cloneUrl  ===  subRepo . repository . cloneUrl , 
166170            ) ; 
167-             if  ( ! matchIngRepo  ||  ! matchIngRepo . commitHistory . some ( ( sha )  =>  sha  ===  subRepo . revision ) )  { 
168-                 return  false ; 
171+             if  ( ! matchingRepo  ||  ! matchingRepo . commitHistory . some ( ( sha )  =>  sha  ===  subRepo . revision ) )  { 
172+                 return  Match . None ; 
169173            } 
170174        } 
171175
@@ -175,29 +179,41 @@ export class IncrementalWorkspaceService {
175179                imageSource, 
176180                parentImageSource : candidateWorkspace . imageSource , 
177181            } ) ; 
178-             return  false ; 
182+             return  Match . None ; 
179183        } 
180184
181185        // ensure the tasks haven't changed 
182-         const  filterPrebuildTasks  =  ( tasks : TaskConfig [ ]  =  [ ] )  => 
183-             tasks 
184-                 . map ( ( task )  => 
185-                     Object . keys ( task ) 
186-                         . filter ( ( key )  =>  [ "before" ,  "init" ,  "prebuild" ] . includes ( key ) ) 
187-                         // @ts -ignore 
188-                         . reduce ( ( obj ,  key )  =>  ( {  ...obj ,  [ key ] : task [ key ]  } ) ,  { } ) , 
189-                 ) 
190-                 . filter ( ( task )  =>  Object . keys ( task ) . length  >  0 ) ; 
191-         const  prebuildTasks  =  filterPrebuildTasks ( config . tasks ) ; 
192-         const  parentPrebuildTasks  =  filterPrebuildTasks ( candidateWorkspace . config . tasks ) ; 
186+         const  prebuildTasks  =  this . filterPrebuildTasks ( config . tasks ) ; 
187+         const  parentPrebuildTasks  =  this . filterPrebuildTasks ( candidateWorkspace . config . tasks ) ; 
193188        if  ( JSON . stringify ( prebuildTasks )  !==  JSON . stringify ( parentPrebuildTasks ) )  { 
194189            log . debug ( `Skipping parent prebuild: Outdated prebuild tasks` ,  { 
195190                prebuildTasks, 
196191                parentPrebuildTasks, 
197192            } ) ; 
198-             return  false ; 
193+             return  Match . None ; 
194+         } 
195+ 
196+         if  ( candidatePrebuild . commit  ===  history . commitHistory [ 0 ] )  { 
197+             return  Match . Exact ; 
199198        } 
200199
201-         return  true ; 
200+         return  Match . Loose ; 
201+     } 
202+ 
203+     /** 
204+      * Given an array of tasks returns only the those which are to run during prebuilds, additionally stripping everything besides the prebuild-related configuration from them 
205+      */ 
206+     private  filterPrebuildTasks ( tasks : TaskConfig [ ]  =  [ ] ) : Record < string ,  string > [ ]  { 
207+         return  tasks 
208+             . map ( ( task )  =>  { 
209+                 const  filteredTask : Record < string ,  any >  =  { } ; 
210+                 for  ( const  key  of  Object . keys ( task ) )  { 
211+                     if  ( [ "before" ,  "init" ,  "prebuild" ] . includes ( key ) )  { 
212+                         filteredTask [ key ]  =  task [ key  as  keyof  TaskConfig ] ; 
213+                     } 
214+                 } 
215+                 return  filteredTask ; 
216+             } ) 
217+             . filter ( ( task )  =>  Object . keys ( task ) . length  >  0 ) ; 
202218    } 
203219} 
0 commit comments