diff --git a/src/git.ts b/src/git.ts index c1376546..c0d3714b 100644 --- a/src/git.ts +++ b/src/git.ts @@ -3,6 +3,8 @@ import {ChildProcess, spawn} from 'child_process' import {Octokit} from '@octokit/rest' import {delimiter} from 'path' import * as fs from 'fs' +import https from 'https' +import os from 'os' // If present, do prefer the build agent's copy of Git const externalsGitDir = `${process.env.AGENT_HOMEDIRECTORY}/externals/git` @@ -109,54 +111,36 @@ async function updateHEAD( }) } -export async function getViaGit( - flavor: string, - architecture: string, - githubToken?: string -): Promise<{ +type GetViaGitResult = { artifactName: string id: string download: ( outputDirectory: string, verbose?: number | boolean ) => Promise -}> { +} + +export async function getViaGit( + flavor: string, + architecture: string, + githubToken?: string +): Promise { const owner = 'git-for-windows' const {repo, artifactName} = getArtifactMetadata(flavor, architecture) const octokit = githubToken ? new Octokit({auth: githubToken}) : new Octokit() - let head_sha: string + if (flavor === 'minimal') { - const info = await octokit.actions.listWorkflowRuns({ - owner, - repo, - workflow_id: 938271, - status: 'success', - branch: 'main', - event: 'push', - per_page: 1 - }) - head_sha = info.data.workflow_runs[0].head_sha - /* - * There was a GCC upgrade to v14.1 that broke the build with `DEVELOPER=1`, - * and `ci-artifacts` was not updated to test-build with `DEVELOPER=1` (this - * was fixed in https://github.com/git-for-windows/git-sdk-64/pull/83). - * - * Work around that by forcing the incorrectly-passing revision back to the - * last one before that GCC upgrade. - */ - if (head_sha === '5f6ba092f690c0bbf84c7201be97db59cdaeb891') { - head_sha = 'e37e3f44c1934f0f263dabbf4ed50a3cfb6eaf71' - } - } else { - const info = await octokit.repos.getBranch({ - owner, - repo, - branch: 'main' - }) - head_sha = info.data.commit.sha + return getMinimalFlavor(owner, repo, artifactName, octokit, githubToken) } + + const info = await octokit.repos.getBranch({ + owner, + repo, + branch: 'main' + }) + const head_sha = info.data.commit.sha const id = `${artifactName}-${head_sha}${head_sha === 'e37e3f44c1934f0f263dabbf4ed50a3cfb6eaf71' ? '-2' : ''}` core.info(`Got commit ${head_sha} for ${repo}`) @@ -239,3 +223,111 @@ export async function getViaGit( } } } + +async function getMinimalFlavor( + owner: string, + repo: string, + artifactName: string, + octokit: Octokit, + githubToken?: string +): Promise { + const ciArtifactsResponse = await octokit.repos.getReleaseByTag({ + owner, + repo, + tag: 'ci-artifacts' + }) + + if (ciArtifactsResponse.status !== 200) { + throw new Error( + `Failed to get ci-artifacts release from the ${owner}/${repo} repo: ${ciArtifactsResponse.status}` + ) + } + + const tarGzArtifact = ciArtifactsResponse.data.assets.find(asset => + asset.name.endsWith('.tar.gz') + ) + + if (!tarGzArtifact) { + throw new Error( + `Failed to find a tar.gz artifact in the ci-artifacts release of the ${owner}/${repo} repo` + ) + } + + return { + artifactName, + id: `ci-artifacts-${tarGzArtifact.updated_at}`, + download: async ( + outputDirectory: string, + verbose: number | boolean = false + ): Promise => { + const tmpFile = `${os.tmpdir()}/${tarGzArtifact.name}` + await downloadFile( + tarGzArtifact.browser_download_url, + { + headers: { + ...(githubToken ? {Authorization: `Bearer ${githubToken}`} : {}), + Accept: 'application/octet-stream' + } + }, + tmpFile + ) + const traceArg = verbose ? ['-v'] : [] + const child = spawn('C:\\Windows\\system32\\tar.exe', [ + '-xzf', + ...traceArg, + tmpFile, + '-C', + outputDirectory + ]) + return new Promise((resolve, reject) => { + child.on('close', code => { + core.endGroup() + if (code === 0) { + fs.rm(tmpFile, () => resolve()) + } else { + reject(new Error(`tar -xzf process exited with code ${code}`)) + } + }) + }) + } + } +} + +async function downloadFile( + url: string, + options: https.RequestOptions, + destination: string +): Promise { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(destination) + https + .get(url, options, async response => { + if (response.statusCode === 301 || response.statusCode === 302) { + if (!response.headers.location) { + throw new Error( + `Got a 301 or 302 response code, but no location header was set. Cannot continue.` + ) + } + return await downloadFile( + response.headers.location, + options, + destination + ) + } + if (response.statusCode === 200) { + response.pipe(file) + file.on('finish', () => { + file.close(() => resolve()) + }) + } else { + file.close() + fs.unlink(destination, () => + reject(new Error(`Failed to download file: ${response.statusCode}`)) + ) + } + }) + .on('error', err => { + fs.unlink(destination, () => reject(err)) + }) + }) +}