mirror of
https://github.com/actions/checkout.git
synced 2026-02-23 22:22:05 +00:00
Re: https://github.com/actions/checkout/issues/1186 @dscho discovered that the checkout action could stall for a considerable amount of time on Windows runners waiting for PowerShell invocations made from 'windows-release' npm package to complete. Then I studied the dependency chain to figure out where 'windows-release' was imported: '@actions/checkout'@main <- '@actions/github'@2.2.0 <- '@octokit/endpoint'@6.0.1 <- '@octokit/graphql'@4.3.1 <- '@octokit/request'@5.4.2 <- '@octokit/rest'@16.43.1 <- 'universal-user-agent'@4.0.1 <- 'os-name'@3.1.0 <- 'windows-release'@3.1.0 'universal-user-agent' package dropped its dependency on 'os-name' in https://github.com/gr2m/universal-user-agent/releases/tag/v6.0.0 . '@actions/github' v3 removed dependency on '@octokit/rest'@16.43.1 and allows users to move away from the old 'universal-user-agent' v4. (https://github.com/actions/toolkit/pull/453) This pull request attempts to update the version of '@actions/github' used in the checkout action to avoid importing 'windows-release'. Based on testing in my own repositories, I can see an improvement in reduced wait time between entering the checkout action and git actually starts to do useful work.
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
import * as assert from 'assert'
|
|
import * as core from '@actions/core'
|
|
import * as fs from 'fs'
|
|
import * as github from '@actions/github'
|
|
import * as io from '@actions/io'
|
|
import * as path from 'path'
|
|
import * as retryHelper from './retry-helper'
|
|
import * as toolCache from '@actions/tool-cache'
|
|
import {default as uuid} from 'uuid/v4'
|
|
|
|
const IS_WINDOWS = process.platform === 'win32'
|
|
|
|
export async function downloadRepository(
|
|
authToken: string,
|
|
owner: string,
|
|
repo: string,
|
|
ref: string,
|
|
commit: string,
|
|
repositoryPath: string,
|
|
baseUrl?: string
|
|
): Promise<void> {
|
|
// Determine the default branch
|
|
if (!ref && !commit) {
|
|
core.info('Determining the default branch')
|
|
ref = await getDefaultBranch(authToken, owner, repo, baseUrl)
|
|
}
|
|
|
|
// Download the archive
|
|
let archiveData = await retryHelper.execute(async () => {
|
|
core.info('Downloading the archive')
|
|
return await downloadArchive(authToken, owner, repo, ref, commit, baseUrl)
|
|
})
|
|
|
|
// Write archive to disk
|
|
core.info('Writing archive to disk')
|
|
const uniqueId = uuid()
|
|
const archivePath = path.join(repositoryPath, `${uniqueId}.tar.gz`)
|
|
await fs.promises.writeFile(archivePath, archiveData)
|
|
archiveData = Buffer.from('') // Free memory
|
|
|
|
// Extract archive
|
|
core.info('Extracting the archive')
|
|
const extractPath = path.join(repositoryPath, uniqueId)
|
|
await io.mkdirP(extractPath)
|
|
if (IS_WINDOWS) {
|
|
await toolCache.extractZip(archivePath, extractPath)
|
|
} else {
|
|
await toolCache.extractTar(archivePath, extractPath)
|
|
}
|
|
await io.rmRF(archivePath)
|
|
|
|
// Determine the path of the repository content. The archive contains
|
|
// a top-level folder and the repository content is inside.
|
|
const archiveFileNames = await fs.promises.readdir(extractPath)
|
|
assert.ok(
|
|
archiveFileNames.length == 1,
|
|
'Expected exactly one directory inside archive'
|
|
)
|
|
const archiveVersion = archiveFileNames[0] // The top-level folder name includes the short SHA
|
|
core.info(`Resolved version ${archiveVersion}`)
|
|
const tempRepositoryPath = path.join(extractPath, archiveVersion)
|
|
|
|
// Move the files
|
|
for (const fileName of await fs.promises.readdir(tempRepositoryPath)) {
|
|
const sourcePath = path.join(tempRepositoryPath, fileName)
|
|
const targetPath = path.join(repositoryPath, fileName)
|
|
if (IS_WINDOWS) {
|
|
await io.cp(sourcePath, targetPath, {recursive: true}) // Copy on Windows (Windows Defender may have a lock)
|
|
} else {
|
|
await io.mv(sourcePath, targetPath)
|
|
}
|
|
}
|
|
await io.rmRF(extractPath)
|
|
}
|
|
|
|
/**
|
|
* Looks up the default branch name
|
|
*/
|
|
export async function getDefaultBranch(
|
|
authToken: string,
|
|
owner: string,
|
|
repo: string,
|
|
baseUrl?: string
|
|
): Promise<string> {
|
|
return await retryHelper.execute(async () => {
|
|
core.info('Retrieving the default branch name')
|
|
const octokit = github.getOctokit(authToken, {baseUrl: baseUrl})
|
|
let result: string
|
|
try {
|
|
// Get the default branch from the repo info
|
|
const response = await octokit.rest.repos.get({owner, repo})
|
|
result = response.data.default_branch
|
|
assert.ok(result, 'default_branch cannot be empty')
|
|
} catch (err) {
|
|
// Handle .wiki repo
|
|
if (
|
|
(err as any)?.status === 404 &&
|
|
repo.toUpperCase().endsWith('.WIKI')
|
|
) {
|
|
result = 'master'
|
|
}
|
|
// Otherwise error
|
|
else {
|
|
throw err
|
|
}
|
|
}
|
|
|
|
// Print the default branch
|
|
core.info(`Default branch '${result}'`)
|
|
|
|
// Prefix with 'refs/heads'
|
|
if (!result.startsWith('refs/')) {
|
|
result = `refs/heads/${result}`
|
|
}
|
|
|
|
return result
|
|
})
|
|
}
|
|
|
|
async function downloadArchive(
|
|
authToken: string,
|
|
owner: string,
|
|
repo: string,
|
|
ref: string,
|
|
commit: string,
|
|
baseUrl?: string
|
|
): Promise<Buffer> {
|
|
const octokit = github.getOctokit(authToken, {baseUrl: baseUrl})
|
|
const download = IS_WINDOWS
|
|
? octokit.rest.repos.downloadZipballArchive
|
|
: octokit.rest.repos.downloadTarballArchive
|
|
const response = await download({
|
|
owner: owner,
|
|
repo: repo,
|
|
ref: commit || ref
|
|
})
|
|
if (response.status != 200) {
|
|
throw new Error(
|
|
`Unexpected response from GitHub API. Status: ${response.status}, Data: ${response.data}`
|
|
)
|
|
}
|
|
|
|
return Buffer.from(response.data as ArrayBuffer) // response.data is ArrayBuffer
|
|
}
|