123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- import {URL} from 'url'
- import {IGitCommandManager} from './git-command-manager'
- import * as core from '@actions/core'
- import * as github from '@actions/github'
- export const tagsRefSpec = '+refs/tags/*:refs/tags/*'
- export interface ICheckoutInfo {
- ref: string
- startPoint: string
- }
- export async function getCheckoutInfo(
- git: IGitCommandManager,
- ref: string,
- commit: string
- ): Promise<ICheckoutInfo> {
- if (!git) {
- throw new Error('Arg git cannot be empty')
- }
- if (!ref && !commit) {
- throw new Error('Args ref and commit cannot both be empty')
- }
- const result = ({} as unknown) as ICheckoutInfo
- const upperRef = (ref || '').toUpperCase()
- // SHA only
- if (!ref) {
- result.ref = commit
- }
- // refs/heads/
- else if (upperRef.startsWith('REFS/HEADS/')) {
- const branch = ref.substring('refs/heads/'.length)
- result.ref = branch
- result.startPoint = `refs/remotes/origin/${branch}`
- }
- // refs/pull/
- else if (upperRef.startsWith('REFS/PULL/')) {
- const branch = ref.substring('refs/pull/'.length)
- result.ref = `refs/remotes/pull/${branch}`
- }
- // refs/tags/
- else if (upperRef.startsWith('REFS/')) {
- result.ref = ref
- }
- // Unqualified ref, check for a matching branch or tag
- else {
- if (await git.branchExists(true, `origin/${ref}`)) {
- result.ref = ref
- result.startPoint = `refs/remotes/origin/${ref}`
- } else if (await git.tagExists(`${ref}`)) {
- result.ref = `refs/tags/${ref}`
- } else {
- throw new Error(
- `A branch or tag with the name '${ref}' could not be found`
- )
- }
- }
- return result
- }
- export function getRefSpecForAllHistory(ref: string, commit: string): string[] {
- const result = ['+refs/heads/*:refs/remotes/origin/*', tagsRefSpec]
- if (ref && ref.toUpperCase().startsWith('REFS/PULL/')) {
- const branch = ref.substring('refs/pull/'.length)
- result.push(`+${commit || ref}:refs/remotes/pull/${branch}`)
- }
- return result
- }
- export function getRefSpec(ref: string, commit: string): string[] {
- if (!ref && !commit) {
- throw new Error('Args ref and commit cannot both be empty')
- }
- const upperRef = (ref || '').toUpperCase()
- // SHA
- if (commit) {
- // refs/heads
- if (upperRef.startsWith('REFS/HEADS/')) {
- const branch = ref.substring('refs/heads/'.length)
- return [`+${commit}:refs/remotes/origin/${branch}`]
- }
- // refs/pull/
- else if (upperRef.startsWith('REFS/PULL/')) {
- const branch = ref.substring('refs/pull/'.length)
- return [`+${commit}:refs/remotes/pull/${branch}`]
- }
- // refs/tags/
- else if (upperRef.startsWith('REFS/TAGS/')) {
- return [`+${commit}:${ref}`]
- }
- // Otherwise no destination ref
- else {
- return [commit]
- }
- }
- // Unqualified ref, check for a matching branch or tag
- else if (!upperRef.startsWith('REFS/')) {
- return [
- `+refs/heads/${ref}*:refs/remotes/origin/${ref}*`,
- `+refs/tags/${ref}*:refs/tags/${ref}*`
- ]
- }
- // refs/heads/
- else if (upperRef.startsWith('REFS/HEADS/')) {
- const branch = ref.substring('refs/heads/'.length)
- return [`+${ref}:refs/remotes/origin/${branch}`]
- }
- // refs/pull/
- else if (upperRef.startsWith('REFS/PULL/')) {
- const branch = ref.substring('refs/pull/'.length)
- return [`+${ref}:refs/remotes/pull/${branch}`]
- }
- // refs/tags/
- else {
- return [`+${ref}:${ref}`]
- }
- }
- /**
- * Tests whether the initial fetch created the ref at the expected commit
- */
- export async function testRef(
- git: IGitCommandManager,
- ref: string,
- commit: string
- ): Promise<boolean> {
- if (!git) {
- throw new Error('Arg git cannot be empty')
- }
- if (!ref && !commit) {
- throw new Error('Args ref and commit cannot both be empty')
- }
- // No SHA? Nothing to test
- if (!commit) {
- return true
- }
- // SHA only?
- else if (!ref) {
- return await git.shaExists(commit)
- }
- const upperRef = ref.toUpperCase()
- // refs/heads/
- if (upperRef.startsWith('REFS/HEADS/')) {
- const branch = ref.substring('refs/heads/'.length)
- return (
- (await git.branchExists(true, `origin/${branch}`)) &&
- commit === (await git.revParse(`refs/remotes/origin/${branch}`))
- )
- }
- // refs/pull/
- else if (upperRef.startsWith('REFS/PULL/')) {
- // Assume matches because fetched using the commit
- return true
- }
- // refs/tags/
- else if (upperRef.startsWith('REFS/TAGS/')) {
- const tagName = ref.substring('refs/tags/'.length)
- return (
- (await git.tagExists(tagName)) && commit === (await git.revParse(ref))
- )
- }
- // Unexpected
- else {
- core.debug(`Unexpected ref format '${ref}' when testing ref info`)
- return true
- }
- }
- export async function checkCommitInfo(
- token: string,
- commitInfo: string,
- repositoryOwner: string,
- repositoryName: string,
- ref: string,
- commit: string
- ): Promise<void> {
- try {
- // GHES?
- if (isGhes()) {
- return
- }
- // Auth token?
- if (!token) {
- return
- }
- // Public PR synchronize, for workflow repo?
- if (
- fromPayload('repository.private') !== false ||
- github.context.eventName !== 'pull_request' ||
- fromPayload('action') !== 'synchronize' ||
- repositoryOwner !== github.context.repo.owner ||
- repositoryName !== github.context.repo.repo ||
- ref !== github.context.ref ||
- !ref.startsWith('refs/pull/') ||
- commit !== github.context.sha
- ) {
- return
- }
- // Head SHA
- const expectedHeadSha = fromPayload('after')
- if (!expectedHeadSha) {
- core.debug('Unable to determine head sha')
- return
- }
- // Base SHA
- const expectedBaseSha = fromPayload('pull_request.base.sha')
- if (!expectedBaseSha) {
- core.debug('Unable to determine base sha')
- return
- }
- // Expected message?
- const expectedMessage = `Merge ${expectedHeadSha} into ${expectedBaseSha}`
- if (commitInfo.indexOf(expectedMessage) >= 0) {
- return
- }
- // Extract details from message
- const match = commitInfo.match(/Merge ([0-9a-f]{40}) into ([0-9a-f]{40})/)
- if (!match) {
- core.debug('Unexpected message format')
- return
- }
- // Post telemetry
- const actualHeadSha = match[1]
- if (actualHeadSha !== expectedHeadSha) {
- core.debug(
- `Expected head sha ${expectedHeadSha}; actual head sha ${actualHeadSha}`
- )
- const octokit = new github.GitHub(token, {
- userAgent: `actions-checkout-tracepoint/1.0 (code=STALE_MERGE;owner=${repositoryOwner};repo=${repositoryName};pr=${fromPayload(
- 'number'
- )};run_id=${
- process.env['GITHUB_RUN_ID']
- };expected_head_sha=${expectedHeadSha};actual_head_sha=${actualHeadSha})`
- })
- await octokit.repos.get({owner: repositoryOwner, repo: repositoryName})
- }
- } catch (err) {
- core.debug(
- `Error when validating commit info: ${(err as any)?.stack ?? err}`
- )
- }
- }
- function fromPayload(path: string): any {
- return select(github.context.payload, path)
- }
- function select(obj: any, path: string): any {
- if (!obj) {
- return undefined
- }
- const i = path.indexOf('.')
- if (i < 0) {
- return obj[path]
- }
- const key = path.substr(0, i)
- return select(obj[key], path.substr(i + 1))
- }
- function isGhes(): boolean {
- const ghUrl = new URL(
- process.env['GITHUB_SERVER_URL'] || 'https://github.com'
- )
- return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'
- }
|