| 
									
										
										
										
											2019-12-03 10:28:59 -05:00
										 |  |  | import {IGitCommandManager} from './git-command-manager' | 
					
						
							| 
									
										
										
										
											2020-05-21 11:09:16 -04:00
										 |  |  | import * as core from '@actions/core' | 
					
						
							|  |  |  | import * as github from '@actions/github' | 
					
						
							| 
									
										
										
										
											2022-09-26 17:34:52 +01:00
										 |  |  | import {isGhes} from './url-helper' | 
					
						
							| 
									
										
										
										
											2019-12-03 10:28:59 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-27 09:54:28 -04:00
										 |  |  | export const tagsRefSpec = '+refs/tags/*:refs/tags/*' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-03 10:28:59 -05:00
										 |  |  | 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-27 09:54:28 -04:00
										 |  |  | 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-03 10:28:59 -05:00
										 |  |  | 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}`] | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-05-21 11:09:16 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-27 09:54:28 -04:00
										 |  |  | /** | 
					
						
							|  |  |  |  * 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 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-21 11:09:16 -04:00
										 |  |  | export async function checkCommitInfo( | 
					
						
							|  |  |  |   token: string, | 
					
						
							|  |  |  |   commitInfo: string, | 
					
						
							|  |  |  |   repositoryOwner: string, | 
					
						
							|  |  |  |   repositoryName: string, | 
					
						
							|  |  |  |   ref: string, | 
					
						
							| 
									
										
										
										
											2022-09-26 17:34:52 +01:00
										 |  |  |   commit: string, | 
					
						
							|  |  |  |   baseUrl?: string | 
					
						
							| 
									
										
										
										
											2020-05-21 11:09:16 -04:00
										 |  |  | ): Promise<void> { | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     // GHES?
 | 
					
						
							| 
									
										
										
										
											2022-09-26 17:34:52 +01:00
										 |  |  |     if (isGhes(baseUrl)) { | 
					
						
							| 
									
										
										
										
											2020-05-21 11:09:16 -04:00
										 |  |  |       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}` | 
					
						
							|  |  |  |       ) | 
					
						
							| 
									
										
										
										
											2023-04-12 19:55:27 +08:00
										 |  |  |       const octokit = github.getOctokit(token, { | 
					
						
							| 
									
										
										
										
											2022-09-26 17:34:52 +01:00
										 |  |  |         baseUrl: baseUrl, | 
					
						
							| 
									
										
										
										
											2020-05-21 11:09:16 -04:00
										 |  |  |         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})`
 | 
					
						
							|  |  |  |       }) | 
					
						
							| 
									
										
										
										
											2023-04-12 19:55:27 +08:00
										 |  |  |       await octokit.rest.repos.get({ | 
					
						
							|  |  |  |         owner: repositoryOwner, | 
					
						
							|  |  |  |         repo: repositoryName | 
					
						
							|  |  |  |       }) | 
					
						
							| 
									
										
										
										
											2020-05-21 11:09:16 -04:00
										 |  |  |     } | 
					
						
							|  |  |  |   } catch (err) { | 
					
						
							| 
									
										
										
										
											2021-10-19 09:52:57 -05:00
										 |  |  |     core.debug( | 
					
						
							|  |  |  |       `Error when validating commit info: ${(err as any)?.stack ?? err}` | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2020-05-21 11:09:16 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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)) | 
					
						
							|  |  |  | } |