@@ -7,6 +7,7 @@ import del from "del";
77import * as yaml from "js-yaml" ;
88
99import * as actionsUtil from "./actions-util" ;
10+ import { getApiClient } from "./api-client" ;
1011import { setupCppAutobuild } from "./autobuild" ;
1112import {
1213 CODEQL_VERSION_ANALYSIS_SUMMARY_V2 ,
@@ -17,7 +18,6 @@ import * as configUtils from "./config-utils";
1718import { addDiagnostic , makeDiagnostic } from "./diagnostics" ;
1819import { EnvVar } from "./environment" ;
1920import { FeatureEnablement , Feature } from "./feature-flags" ;
20- import * as gitUtils from "./git-utils" ;
2121import { isScannedLanguage , Language } from "./languages" ;
2222import { Logger , withGroupAsync } from "./logging" ;
2323import { DatabaseCreationTimings , EventReport } from "./status-report" ;
@@ -240,7 +240,8 @@ async function finalizeDatabaseCreation(
240240 * Set up the diff-informed analysis feature.
241241 *
242242 * @param baseRef The base branch name, used for calculating the diff range.
243- * @param headRef The head branch name, used for calculating the diff range.
243+ * @param headLabel The label that uniquely identifies the head branch across
244+ * repositories, used for calculating the diff range.
244245 * @param codeql
245246 * @param logger
246247 * @param features
@@ -249,7 +250,7 @@ async function finalizeDatabaseCreation(
249250 */
250251export async function setupDiffInformedQueryRun (
251252 baseRef : string ,
252- headRef : string ,
253+ headLabel : string ,
253254 codeql : CodeQL ,
254255 logger : Logger ,
255256 features : FeatureEnablement ,
@@ -262,7 +263,7 @@ export async function setupDiffInformedQueryRun(
262263 async ( ) => {
263264 const diffRanges = await getPullRequestEditedDiffRanges (
264265 baseRef ,
265- headRef ,
266+ headLabel ,
266267 logger ,
267268 ) ;
268269 return writeDiffRangeDataExtensionPack ( logger , diffRanges ) ;
@@ -280,7 +281,8 @@ interface DiffThunkRange {
280281 * Return the file line ranges that were added or modified in the pull request.
281282 *
282283 * @param baseRef The base branch name, used for calculating the diff range.
283- * @param headRef The head branch name, used for calculating the diff range.
284+ * @param headLabel The label that uniquely identifies the head branch across
285+ * repositories, used for calculating the diff range.
284286 * @param logger
285287 * @returns An array of tuples, where each tuple contains the absolute path of a
286288 * file, the start line and the end line (both 1-based and inclusive) of an
@@ -289,107 +291,59 @@ interface DiffThunkRange {
289291 */
290292async function getPullRequestEditedDiffRanges (
291293 baseRef : string ,
292- headRef : string ,
294+ headLabel : string ,
293295 logger : Logger ,
294296) : Promise < DiffThunkRange [ ] | undefined > {
295- const checkoutPath = actionsUtil . getOptionalInput ( "checkout_path" ) ;
296- if ( checkoutPath === undefined ) {
297- return undefined ;
298- }
299-
300- // To compute the merge bases between the base branch and the PR topic branch,
301- // we need to fetch the commit graph from the branch heads to those merge
302- // babes. The following 6-step procedure does so while limiting the amount of
303- // history fetched.
304-
305- // Step 1: Deepen from the PR merge commit to the base branch head and the PR
306- // topic branch head, so that the PR merge commit is no longer considered a
307- // grafted commit.
308- await gitUtils . deepenGitHistory ( ) ;
309- // Step 2: Fetch the base branch shallow history. This step ensures that the
310- // base branch name is present in the local repository. Normally the base
311- // branch name would be added by Step 4. However, if the base branch head is
312- // an ancestor of the PR topic branch head, Step 4 would fail without doing
313- // anything, so we need to fetch the base branch explicitly.
314- await gitUtils . gitFetch ( baseRef , [ "--depth=1" ] ) ;
315- // Step 3: Fetch the PR topic branch history, stopping when we reach commits
316- // that are reachable from the base branch head.
317- await gitUtils . gitFetch ( headRef , [ `--shallow-exclude=${ baseRef } ` ] ) ;
318- // Step 4: Fetch the base branch history, stopping when we reach commits that
319- // are reachable from the PR topic branch head.
320- await gitUtils . gitFetch ( baseRef , [ `--shallow-exclude=${ headRef } ` ] ) ;
321- // Step 5: Repack the history to remove the shallow grafts that were added by
322- // the previous fetches. This step works around a bug that causes subsequent
323- // deepening fetches to fail with "fatal: error in object: unshallow <SHA>".
324- // See https://stackoverflow.com/q/63878612
325- await gitUtils . gitRepack ( [ "-d" ] ) ;
326- // Step 6: Deepen the history so that we have the merge bases between the base
327- // branch and the PR topic branch.
328- await gitUtils . deepenGitHistory ( ) ;
329-
330- // To compute the exact same diff as GitHub would compute for the PR, we need
331- // to use the same merge base as GitHub. That is easy to do if there is only
332- // one merge base, which is by far the most common case. If there are multiple
333- // merge bases, we stop without producing a diff range.
334- const mergeBases = await gitUtils . getAllGitMergeBases ( [ baseRef , headRef ] ) ;
335- logger . info ( `Merge bases: ${ mergeBases . join ( ", " ) } ` ) ;
336- if ( mergeBases . length !== 1 ) {
337- logger . info (
338- "Cannot compute diff range because baseRef and headRef " +
339- `have ${ mergeBases . length } merge bases (instead of exactly 1).` ,
340- ) ;
341- return undefined ;
342- }
343-
344- const diffHunkHeaders = await gitUtils . getGitDiffHunkHeaders (
345- mergeBases [ 0 ] ,
346- headRef ,
347- ) ;
348- if ( diffHunkHeaders === undefined ) {
349- return undefined ;
350- }
297+ await getFileDiffsWithBasehead ( baseRef , headLabel , logger ) ;
298+ return undefined ;
299+ }
351300
352- const results = new Array < DiffThunkRange > ( ) ;
353-
354- let changedFile = "" ;
355- for ( const line of diffHunkHeaders ) {
356- if ( line . startsWith ( "+++ " ) ) {
357- const filePath = gitUtils . decodeGitFilePath ( line . substring ( 4 ) ) ;
358- if ( filePath . startsWith ( "b/" ) ) {
359- // The file was edited: track all hunks in the file
360- changedFile = filePath . substring ( 2 ) ;
361- } else if ( filePath === "/dev/null" ) {
362- // The file was deleted: skip all hunks in the file
363- changedFile = "" ;
364- } else {
365- logger . warning ( `Failed to parse diff hunk header line: ${ line } ` ) ;
366- return undefined ;
367- }
368- continue ;
369- }
370- if ( line . startsWith ( "@@ " ) ) {
371- if ( changedFile === "" ) continue ;
301+ /**
302+ * This interface is an abbreviated version of the file diff object returned by
303+ * the GitHub API.
304+ */
305+ interface FileDiff {
306+ filename : string ;
307+ changes : number ;
308+ patch ?: string | undefined ;
309+ }
372310
373- const match = line . match ( / ^ @ @ - \d + (?: , \d + ) ? \+ ( \d + ) (?: , ( \d + ) ) ? @ @ / ) ;
374- if ( match === null ) {
375- logger . warning ( `Failed to parse diff hunk header line: ${ line } ` ) ;
376- return undefined ;
377- }
378- const startLine = parseInt ( match [ 1 ] , 10 ) ;
379- const numLines = parseInt ( match [ 2 ] , 10 ) ;
380- if ( numLines === 0 ) {
381- // The hunk was a deletion: skip it
382- continue ;
383- }
384- const endLine = startLine + ( numLines || 1 ) - 1 ;
385- results . push ( {
386- path : path . join ( checkoutPath , changedFile ) ,
387- startLine,
388- endLine,
389- } ) ;
311+ async function getFileDiffsWithBasehead (
312+ baseRef : string ,
313+ headLabel : string ,
314+ logger : Logger ,
315+ ) : Promise < FileDiff [ ] | undefined > {
316+ const ownerRepo = util . getRequiredEnvParam ( "GITHUB_REPOSITORY" ) . split ( "/" ) ;
317+ const owner = ownerRepo [ 0 ] ;
318+ const repo = ownerRepo [ 1 ] ;
319+ const basehead = `${ baseRef } ...${ headLabel } ` ;
320+ try {
321+ const response = await getApiClient ( ) . rest . repos . compareCommitsWithBasehead (
322+ {
323+ owner,
324+ repo,
325+ basehead,
326+ per_page : 1 ,
327+ } ,
328+ ) ;
329+ logger . debug (
330+ `Response from compareCommitsWithBasehead(${ basehead } ):` +
331+ `\n${ JSON . stringify ( response , null , 2 ) } ` ,
332+ ) ;
333+ return response . data . files ;
334+ } catch ( error : any ) {
335+ if ( error . status ) {
336+ logger . warning ( `Error retrieving diff ${ basehead } : ${ error . message } ` ) ;
337+ logger . debug (
338+ `Error running compareCommitsWithBasehead(${ basehead } ):` +
339+ `\nRequest: ${ JSON . stringify ( error . request , null , 2 ) } ` +
340+ `\nError Response: ${ JSON . stringify ( error . response , null , 2 ) } ` ,
341+ ) ;
342+ return undefined ;
343+ } else {
344+ throw error ;
390345 }
391346 }
392- return results ;
393347}
394348
395349/**
0 commit comments