@@ -103,32 +103,153 @@ function isConfig(config: any): config is Config {
103103 commit_sha : event . after
104104 } ) ;
105105
106- // Calculate which file paths have changed in this push
107- const { stdout : filesChangedRaw } = await execFile ( 'git' , [ 'diff' , '--name-only' , `${ event . before } ..${ event . after } ` ] ) ;
108- const queriesChanged = new Set ( filesChangedRaw . split ( '\n' )
109- . map ( s => s . trim ( ) )
110- . filter ( s => s . endsWith ( '.ql' ) ) ) ;
111- console . log ( `${ pluralize ( queriesChanged . size , 'query' ) } updated in this push` ) ;
112- comment += `${ pluralize ( queriesChanged . size , 'query' ) } changed `
113- comment += `[between \`${ event . before . substr ( 0 , 7 ) } \` and \`${ event . after . substr ( 0 , 7 ) } \`]`
114- comment += `(${ event . repository . html_url } /compare/${ event . before } ...${ event . after } ) after push to \`${ event . ref } \`` ;
115- if ( queriesChanged . size > 0 ) {
116- comment += ':\n' ;
117- for ( const query of queriesChanged ) {
118- console . log ( `- ${ query } ` ) ;
119- const exists = await access ( query , fs . constants . R_OK ) . then ( ( ) => true , ( ) => false ) ;
120- comment += `* \`${ query } \`${ exists ? '' : ' *(deleted)*' } \n` ;
106+ /**
107+ * File paths changed by the user (if we're not just running all queries)
108+ *
109+ * This is used to reduce the number of queries we need to run to only those
110+ * that currently interest the user.
111+ */
112+ const queriesChanged = new Set < string > ( ) ;
113+ let unableToGetChangedQueries = false ;
114+
115+ if ( RUN_ALL ) {
116+
117+ /*
118+ * There are a few different ways in which we may determine which queries
119+ * are currently interesting to the user, in decreasing usefulness:
120+ *
121+ * 1. If the user just pushed to a branch that currently has an open pull
122+ * request, the interesting queries are those that are changed in the pull
123+ * request (and not just those changed in the most recent push).
124+ * 2. If there's no active pull request, then what's probably most
125+ * interesting are the queries that have changed in the last push (i.e.
126+ * between the previous head and the new head)
127+ * 3. If that's not possible (e.g. the push could have created the branch for
128+ * the first time, and so there is no "previous ref"), then comparing this
129+ * branch to the default branch of a repo will probably give the most
130+ * accurate results.
131+ * 4. Finally, if all else fails (e.g. the push was the initial push to the
132+ * default branch of the repo), then we should just run every query we
133+ * recognize (as if RUN_ALL was true). We do this by setting
134+ * unableToGetChangedQueries to true.
135+ */
136+
137+ /**
138+ * The output from a successful call to `git diff --name-only`
139+ */
140+ let diff : {
141+ baseSha : string ;
142+ filesChangedRaw : string ;
143+ } | null = null ;
144+
145+ // Try (1) - find any PR associated with the branch of this push
146+
147+ // Get branch name
148+ // This is expected to fail if e.g. the push was to a tag not a branch
149+ const branch = / ^ r e f s \/ h e a d s \/ ( .* ) $ / . exec ( event . ref ) ?. [ 1 ] ;
150+
151+ if ( branch ) {
152+ try {
153+ const pulls = await api . pulls . list ( {
154+ owner : event . repository . owner . login ,
155+ repo : event . repository . name ,
156+ head : `${ event . repository . owner . login } :${ branch } `
157+ } ) ;
158+ if ( pulls && pulls . data . length > 0 ) {
159+ // Just use first PR
160+ const pr = pulls . data [ 0 ] ;
161+ const baseBranch = pr . base . ref ;
162+ // Ensure we have the commits from that ref
163+ await execFile ( 'git' , [ 'fetch' , 'origin' , baseBranch ] ) ;
164+ diff = {
165+ baseSha : baseBranch ,
166+ filesChangedRaw : ( await execFile (
167+ 'git' , [ 'diff' , '--name-only' , `origin/${ baseBranch } ..${ event . after } ` ]
168+ ) ) . stdout
169+ }
170+ } else {
171+ console . log ( 'No pull requests associated with the current push' ) ;
172+ }
173+ } catch ( err ) {
174+ console . warn ( err ) ;
175+ console . log ( `Failed to use PRs to calculate changed files branch ${ branch } .` ) ;
176+ }
177+ } else {
178+ console . log (
179+ 'Push was not for a branch, calculating changed files differently'
180+ ) ;
181+ }
182+
183+ // Try (2) - see what files have changed in the last push
184+
185+ if ( ! diff ) {
186+ try {
187+ const result = await execFile (
188+ 'git' , [ 'diff' , '--name-only' , `${ event . before } ..${ event . after } ` ]
189+ ) ;
190+ if ( result )
191+ diff = {
192+ baseSha : event . before ,
193+ filesChangedRaw : result . stdout
194+ } ;
195+ } catch ( err ) {
196+ console . warn ( err ) ;
197+ console . log ( 'Failed to get diff for push' ) ;
198+ }
199+ }
200+
201+ // Try (3) - see how the current HEAD differs from the default branch
202+
203+ if ( ! diff ) {
204+ try {
205+ const defaultBranchSha = await ( await execFile (
206+ 'git' , [ 'rev-parse' , 'refs/remotes/origin/HEAD' ]
207+ ) ) . stdout . trim ( ) ;
208+ const result = await execFile (
209+ 'git' , [ 'diff' , '--name-only' , `${ defaultBranchSha } ..${ event . after } ` ]
210+ ) ;
211+ if ( result )
212+ diff = {
213+ baseSha : defaultBranchSha ,
214+ filesChangedRaw : result . stdout
215+ }
216+ } catch ( err ) {
217+ console . warn ( err ) ;
218+ console . log ( 'Failed to diff against default branch' ) ;
219+ }
220+ }
221+
222+ if ( ! diff ) {
223+ unableToGetChangedQueries = true ;
224+ } else {
225+ // We have successfully obtained the diff for this push
226+ diff . filesChangedRaw . split ( '\n' )
227+ . map ( s => s . trim ( ) )
228+ . filter ( s => s . endsWith ( '.ql' ) )
229+ . forEach ( s => queriesChanged . add ( s ) ) ;
230+ console . log ( `${ pluralize ( queriesChanged . size , 'query' ) } updated in this push` ) ;
231+ comment += `${ pluralize ( queriesChanged . size , 'query' ) } changed `
232+ comment += `[between \`${ diff . baseSha . substr ( 0 , 7 ) } \` and \`${ event . after . substr ( 0 , 7 ) } \`]`
233+ comment += `(${ event . repository . html_url } /compare/${ diff . baseSha } ...${ event . after } ) after push to \`${ event . ref } \`` ;
234+ if ( queriesChanged . size > 0 ) {
235+ comment += ':\n' ;
236+ for ( const query of queriesChanged ) {
237+ console . log ( `- ${ query } ` ) ;
238+ const exists = await access ( query , fs . constants . R_OK ) . then ( ( ) => true , ( ) => false ) ;
239+ comment += `* \`${ query } \`${ exists ? '' : ' *(deleted)*' } \n` ;
240+ }
241+ } else {
242+ comment += '\n' ;
243+ }
121244 }
122- } else {
123- comment += '\n' ;
124245 }
125246
126247 // Work out which queries to run, based on config and changed & existing queries
127248 const queriesToRun : string [ ] = [ ] ;
128249 for ( const query of Object . keys ( config . expectedResults ) ) {
129250 const exists = await access ( query , fs . constants . R_OK ) . then ( ( ) => true , ( ) => false ) ;
130251 // Run the query if either it's changed, or runAll is true
131- if ( exists && ( RUN_ALL || queriesChanged . has ( query ) ) ) {
252+ if ( exists && ( RUN_ALL || unableToGetChangedQueries || queriesChanged . has ( query ) ) ) {
132253 queriesToRun . push ( query ) ;
133254 }
134255 }
0 commit comments