mirror of
				https://github.com/actions/checkout.git
				synced 2025-11-04 07:48:09 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			284 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {IGitCommandManager} from './git-command-manager'
 | 
						|
import * as core from '@actions/core'
 | 
						|
import * as github from '@actions/github'
 | 
						|
import {getServerApiUrl, isGhes} from './url-helper'
 | 
						|
 | 
						|
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,
 | 
						|
  baseUrl?: string
 | 
						|
): Promise<void> {
 | 
						|
  try {
 | 
						|
    // GHES?
 | 
						|
    if (isGhes(baseUrl)) {
 | 
						|
      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 = github.getOctokit(token, {
 | 
						|
        baseUrl: getServerApiUrl(baseUrl),
 | 
						|
        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.rest.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))
 | 
						|
}
 |