mirror of
				https://github.com/actions/checkout.git
				synced 2025-10-28 21:30:32 +00: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))
 | |
| }
 | 
