1
0
mirror of https://github.com/actions/checkout.git synced 2026-07-05 18:53:46 +08:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Vìncent Le Goff
6517390cd2 Merge e7667abffb into 8e8c483db8 2025-12-03 16:43:32 +01:00
Vincent Le Goff
e7667abffb feat(inputs): add option to disable the post action 2025-07-28 15:27:08 +02:00
11 changed files with 110 additions and 365 deletions

View File

@@ -87,17 +87,6 @@ jobs:
- name: Verify fetch filter
run: __test__/verify-fetch-filter.sh
# Fetch tags
- name: Checkout with fetch-tags
uses: ./
with:
ref: test-data/v2/basic
path: fetch-tags-test
fetch-tags: true
- name: Verify fetch-tags
shell: bash
run: __test__/verify-fetch-tags.sh
# Sparse checkout
- name: Sparse checkout
uses: ./

View File

@@ -108,7 +108,7 @@ describe('Test fetchDepth and fetchTags options', () => {
jest.restoreAllMocks()
})
it('should call execGit with the correct arguments when fetchDepth is 0', async () => {
it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
@@ -122,7 +122,45 @@ describe('Test fetchDepth and fetchTags options', () => {
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 0
fetchDepth: 0,
fetchTags: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is false', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 0,
fetchTags: false
}
await git.fetch(refSpec, options)
@@ -145,45 +183,7 @@ describe('Test fetchDepth and fetchTags options', () => {
)
})
it('should call execGit with the correct arguments when fetchDepth is 0 and refSpec includes tags', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
const options = {
filter: 'filterValue',
fetchDepth: 0
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2',
'+refs/tags/*:refs/tags/*'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 1', async () => {
it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is false', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
@@ -197,7 +197,8 @@ describe('Test fetchDepth and fetchTags options', () => {
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 1
fetchDepth: 1,
fetchTags: false
}
await git.fetch(refSpec, options)
@@ -221,7 +222,7 @@ describe('Test fetchDepth and fetchTags options', () => {
)
})
it('should call execGit with the correct arguments when fetchDepth is 1 and refSpec includes tags', async () => {
it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
@@ -232,10 +233,11 @@ describe('Test fetchDepth and fetchTags options', () => {
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 1
fetchDepth: 1,
fetchTags: true
}
await git.fetch(refSpec, options)
@@ -246,15 +248,13 @@ describe('Test fetchDepth and fetchTags options', () => {
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'--depth=1',
'origin',
'refspec1',
'refspec2',
'+refs/tags/*:refs/tags/*'
'refspec2'
],
expect.any(Object)
)
@@ -338,7 +338,7 @@ describe('Test fetchDepth and fetchTags options', () => {
)
})
it('should call execGit with the correct arguments when showProgress is true and refSpec includes tags', async () => {
it('should call execGit with the correct arguments when fetchTags is true and showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
@@ -349,9 +349,10 @@ describe('Test fetchDepth and fetchTags options', () => {
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchTags: true,
showProgress: true
}
@@ -363,134 +364,15 @@ describe('Test fetchDepth and fetchTags options', () => {
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--progress',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2',
'+refs/tags/*:refs/tags/*'
'refspec2'
],
expect.any(Object)
)
})
})
describe('git user-agent with orchestration ID', () => {
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
})
afterEach(() => {
jest.restoreAllMocks()
// Clean up environment variable to prevent test pollution
delete process.env['ACTIONS_ORCHESTRATION_ID']
})
it('should include orchestration ID in user-agent when ACTIONS_ORCHESTRATION_ID is set', async () => {
const orchId = 'test-orch-id-12345'
process.env['ACTIONS_ORCHESTRATION_ID'] = orchId
let capturedEnv: any = null
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
}
// Capture env on any command
capturedEnv = options.env
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
// Call a git command to trigger env capture after user-agent is set
await git.init()
// Verify the user agent includes the orchestration ID
expect(git).toBeDefined()
expect(capturedEnv).toBeDefined()
expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
`git/2.18 (github-actions-checkout) actions_orchestration_id/${orchId}`
)
})
it('should sanitize invalid characters in orchestration ID', async () => {
const orchId = 'test (with) special/chars'
process.env['ACTIONS_ORCHESTRATION_ID'] = orchId
let capturedEnv: any = null
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
}
// Capture env on any command
capturedEnv = options.env
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
// Call a git command to trigger env capture after user-agent is set
await git.init()
// Verify the user agent has sanitized orchestration ID (spaces, parentheses, slash replaced)
expect(git).toBeDefined()
expect(capturedEnv).toBeDefined()
expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
'git/2.18 (github-actions-checkout) actions_orchestration_id/test__with__special_chars'
)
})
it('should not modify user-agent when ACTIONS_ORCHESTRATION_ID is not set', async () => {
delete process.env['ACTIONS_ORCHESTRATION_ID']
let capturedEnv: any = null
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
}
// Capture env on any command
capturedEnv = options.env
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
// Call a git command to trigger env capture after user-agent is set
await git.init()
// Verify the user agent does NOT contain orchestration ID
expect(git).toBeDefined()
expect(capturedEnv).toBeDefined()
expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
'git/2.18 (github-actions-checkout)'
)
})
})

View File

@@ -152,22 +152,7 @@ describe('ref-helper tests', () => {
it('getRefSpec sha + refs/tags/', async () => {
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', commit)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe(`+refs/tags/my-tag:refs/tags/my-tag`)
})
it('getRefSpec sha + refs/tags/ with fetchTags', async () => {
// When fetchTags is true, only include tags wildcard (specific tag is redundant)
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', commit, true)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
})
it('getRefSpec sha + refs/heads/ with fetchTags', async () => {
// When fetchTags is true, include both the branch refspec and tags wildcard
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', commit, true)
expect(refSpec.length).toBe(2)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
expect(refSpec[1]).toBe(`+${commit}:refs/remotes/origin/my/branch`)
expect(refSpec[0]).toBe(`+${commit}:refs/tags/my-tag`)
})
it('getRefSpec sha only', async () => {
@@ -183,14 +168,6 @@ describe('ref-helper tests', () => {
expect(refSpec[1]).toBe('+refs/tags/my-ref*:refs/tags/my-ref*')
})
it('getRefSpec unqualified ref only with fetchTags', async () => {
// When fetchTags is true, skip specific tag pattern since wildcard covers all
const refSpec = refHelper.getRefSpec('my-ref', '', true)
expect(refSpec.length).toBe(2)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
expect(refSpec[1]).toBe('+refs/heads/my-ref*:refs/remotes/origin/my-ref*')
})
it('getRefSpec refs/heads/ only', async () => {
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '')
expect(refSpec.length).toBe(1)
@@ -210,21 +187,4 @@ describe('ref-helper tests', () => {
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe('+refs/tags/my-tag:refs/tags/my-tag')
})
it('getRefSpec refs/tags/ only with fetchTags', async () => {
// When fetchTags is true, only include tags wildcard (specific tag is redundant)
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', '', true)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
})
it('getRefSpec refs/heads/ only with fetchTags', async () => {
// When fetchTags is true, include both the branch refspec and tags wildcard
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '', true)
expect(refSpec.length).toBe(2)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
expect(refSpec[1]).toBe(
'+refs/heads/my/branch:refs/remotes/origin/my/branch'
)
})
})

View File

@@ -1,9 +0,0 @@
#!/bin/sh
# Verify tags were fetched
TAG_COUNT=$(git -C ./fetch-tags-test tag | wc -l)
if [ "$TAG_COUNT" -eq 0 ]; then
echo "Expected tags to be fetched, but found none"
exit 1
fi
echo "Found $TAG_COUNT tags"

View File

@@ -98,6 +98,10 @@ inputs:
github-server-url:
description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com
required: false
skip-cleanup:
description: Skips the cleanup phase on post action hook
required: false
default: false
outputs:
ref:
description: 'The branch, tag or SHA that was checked out'

78
dist/index.js vendored
View File

@@ -653,6 +653,7 @@ const fs = __importStar(__nccwpck_require__(7147));
const fshelper = __importStar(__nccwpck_require__(7219));
const io = __importStar(__nccwpck_require__(7436));
const path = __importStar(__nccwpck_require__(1017));
const refHelper = __importStar(__nccwpck_require__(8601));
const regexpHelper = __importStar(__nccwpck_require__(3120));
const retryHelper = __importStar(__nccwpck_require__(2155));
const git_version_1 = __nccwpck_require__(3142);
@@ -830,9 +831,9 @@ class GitCommandManager {
fetch(refSpec, options) {
return __awaiter(this, void 0, void 0, function* () {
const args = ['-c', 'protocol.version=2', 'fetch'];
// Always use --no-tags for explicit control over tag fetching
// Tags are fetched explicitly via refspec when needed
args.push('--no-tags');
if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
args.push('--no-tags');
}
args.push('--prune', '--no-recurse-submodules');
if (options.showProgress) {
args.push('--progress');
@@ -1205,17 +1206,7 @@ class GitCommandManager {
}
}
// Set the user agent
let gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`;
// Append orchestration ID if set
const orchId = process.env['ACTIONS_ORCHESTRATION_ID'];
if (orchId) {
// Sanitize the orchestration ID to ensure it contains only valid characters
// Valid characters: 0-9, a-z, _, -, .
const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_');
if (sanitizedId) {
gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`;
}
}
const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`;
core.debug(`Set git useragent to: ${gitHttpUserAgent}`);
this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent;
});
@@ -1538,26 +1529,13 @@ function getSource(settings) {
if (!(yield refHelper.testRef(git, settings.ref, settings.commit))) {
refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
yield git.fetch(refSpec, fetchOptions);
// Verify the ref now matches. For branches, the targeted fetch above brings
// in the specific commit. For tags (fetched by ref), this will fail if
// the tag was moved after the workflow was triggered.
if (!(yield refHelper.testRef(git, settings.ref, settings.commit))) {
throw new Error(`The ref '${settings.ref}' does not point to the expected commit '${settings.commit}'. ` +
`The ref may have been updated after the workflow was triggered.`);
}
}
}
else {
fetchOptions.fetchDepth = settings.fetchDepth;
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit, settings.fetchTags);
fetchOptions.fetchTags = settings.fetchTags;
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
yield git.fetch(refSpec, fetchOptions);
// For tags, verify the ref still points to the expected commit.
// Tags are fetched by ref (not commit), so if a tag was moved after the
// workflow was triggered, we would silently check out the wrong commit.
if (!(yield refHelper.testRef(git, settings.ref, settings.commit))) {
throw new Error(`The ref '${settings.ref}' does not point to the expected commit '${settings.commit}'. ` +
`The ref may have been updated after the workflow was triggered.`);
}
}
core.endGroup();
// Checkout info
@@ -2296,67 +2274,53 @@ function getRefSpecForAllHistory(ref, commit) {
}
return result;
}
function getRefSpec(ref, commit, fetchTags) {
function getRefSpec(ref, commit) {
if (!ref && !commit) {
throw new Error('Args ref and commit cannot both be empty');
}
const upperRef = (ref || '').toUpperCase();
const result = [];
// When fetchTags is true, always include the tags refspec
if (fetchTags) {
result.push(exports.tagsRefSpec);
}
// SHA
if (commit) {
// refs/heads
if (upperRef.startsWith('REFS/HEADS/')) {
const branch = ref.substring('refs/heads/'.length);
result.push(`+${commit}:refs/remotes/origin/${branch}`);
return [`+${commit}:refs/remotes/origin/${branch}`];
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length);
result.push(`+${commit}:refs/remotes/pull/${branch}`);
return [`+${commit}:refs/remotes/pull/${branch}`];
}
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
if (!fetchTags) {
result.push(`+${ref}:${ref}`);
}
return [`+${commit}:${ref}`];
}
// Otherwise no destination ref
else {
result.push(commit);
return [commit];
}
}
// Unqualified ref, check for a matching branch or tag
else if (!upperRef.startsWith('REFS/')) {
result.push(`+refs/heads/${ref}*:refs/remotes/origin/${ref}*`);
if (!fetchTags) {
result.push(`+refs/tags/${ref}*:refs/tags/${ref}*`);
}
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);
result.push(`+${ref}:refs/remotes/origin/${branch}`);
return [`+${ref}:refs/remotes/origin/${branch}`];
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length);
result.push(`+${ref}:refs/remotes/pull/${branch}`);
return [`+${ref}:refs/remotes/pull/${branch}`];
}
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
if (!fetchTags) {
result.push(`+${ref}:${ref}`);
}
}
// Other refs
else {
result.push(`+${ref}:${ref}`);
return [`+${ref}:${ref}`];
}
return result;
}
/**
* Tests whether the initial fetch created the ref at the expected commit
@@ -2392,9 +2356,7 @@ function testRef(git, ref, commit) {
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
const tagName = ref.substring('refs/tags/'.length);
// Use ^{commit} to dereference annotated tags to their underlying commit
return ((yield git.tagExists(tagName)) &&
commit === (yield git.revParse(`${ref}^{commit}`)));
return ((yield git.tagExists(tagName)) && commit === (yield git.revParse(ref)));
}
// Unexpected
else {

View File

@@ -37,6 +37,7 @@ export interface IGitCommandManager {
options: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
}
): Promise<void>
@@ -279,13 +280,14 @@ class GitCommandManager {
options: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
}
): Promise<void> {
const args = ['-c', 'protocol.version=2', 'fetch']
// Always use --no-tags for explicit control over tag fetching
// Tags are fetched explicitly via refspec when needed
args.push('--no-tags')
if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
args.push('--no-tags')
}
args.push('--prune', '--no-recurse-submodules')
if (options.showProgress) {
@@ -728,19 +730,7 @@ class GitCommandManager {
}
}
// Set the user agent
let gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`
// Append orchestration ID if set
const orchId = process.env['ACTIONS_ORCHESTRATION_ID']
if (orchId) {
// Sanitize the orchestration ID to ensure it contains only valid characters
// Valid characters: 0-9, a-z, _, -, .
const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_')
if (sanitizedId) {
gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`
}
}
const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`
core.debug(`Set git useragent to: ${gitHttpUserAgent}`)
this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent
}

View File

@@ -159,6 +159,7 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
const fetchOptions: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
} = {}
@@ -181,35 +182,12 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec, fetchOptions)
// Verify the ref now matches. For branches, the targeted fetch above brings
// in the specific commit. For tags (fetched by ref), this will fail if
// the tag was moved after the workflow was triggered.
if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
throw new Error(
`The ref '${settings.ref}' does not point to the expected commit '${settings.commit}'. ` +
`The ref may have been updated after the workflow was triggered.`
)
}
}
} else {
fetchOptions.fetchDepth = settings.fetchDepth
const refSpec = refHelper.getRefSpec(
settings.ref,
settings.commit,
settings.fetchTags
)
fetchOptions.fetchTags = settings.fetchTags
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec, fetchOptions)
// For tags, verify the ref still points to the expected commit.
// Tags are fetched by ref (not commit), so if a tag was moved after the
// workflow was triggered, we would silently check out the wrong commit.
if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
throw new Error(
`The ref '${settings.ref}' does not point to the expected commit '${settings.commit}'. ` +
`The ref may have been updated after the workflow was triggered.`
)
}
}
core.endGroup()

View File

@@ -118,4 +118,9 @@ export interface IGitSourceSettings {
* User override on the GitHub Server/Host URL that hosts the repository to be cloned
*/
githubServerUrl: string | undefined
/**
* Disable the post action cleanup phase
*/
skipCleanup: boolean
}

View File

@@ -30,6 +30,12 @@ async function run(): Promise<void> {
}
async function cleanup(): Promise<void> {
const sourceSettings = await inputHelper.getInputs()
if (sourceSettings.skipCleanup) {
return
}
try {
await gitSourceProvider.cleanup(stateHelper.RepositoryPath)
} catch (error) {

View File

@@ -76,75 +76,55 @@ export function getRefSpecForAllHistory(ref: string, commit: string): string[] {
return result
}
export function getRefSpec(
ref: string,
commit: string,
fetchTags?: boolean
): string[] {
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()
const result: string[] = []
// When fetchTags is true, always include the tags refspec
if (fetchTags) {
result.push(tagsRefSpec)
}
// SHA
if (commit) {
// refs/heads
if (upperRef.startsWith('REFS/HEADS/')) {
const branch = ref.substring('refs/heads/'.length)
result.push(`+${commit}:refs/remotes/origin/${branch}`)
return [`+${commit}:refs/remotes/origin/${branch}`]
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length)
result.push(`+${commit}:refs/remotes/pull/${branch}`)
return [`+${commit}:refs/remotes/pull/${branch}`]
}
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
if (!fetchTags) {
result.push(`+${ref}:${ref}`)
}
return [`+${commit}:${ref}`]
}
// Otherwise no destination ref
else {
result.push(commit)
return [commit]
}
}
// Unqualified ref, check for a matching branch or tag
else if (!upperRef.startsWith('REFS/')) {
result.push(`+refs/heads/${ref}*:refs/remotes/origin/${ref}*`)
if (!fetchTags) {
result.push(`+refs/tags/${ref}*:refs/tags/${ref}*`)
}
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)
result.push(`+${ref}:refs/remotes/origin/${branch}`)
return [`+${ref}:refs/remotes/origin/${branch}`]
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length)
result.push(`+${ref}:refs/remotes/pull/${branch}`)
return [`+${ref}:refs/remotes/pull/${branch}`]
}
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
if (!fetchTags) {
result.push(`+${ref}:${ref}`)
}
}
// Other refs
else {
result.push(`+${ref}:${ref}`)
return [`+${ref}:${ref}`]
}
return result
}
/**
@@ -190,10 +170,8 @@ export async function testRef(
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
const tagName = ref.substring('refs/tags/'.length)
// Use ^{commit} to dereference annotated tags to their underlying commit
return (
(await git.tagExists(tagName)) &&
commit === (await git.revParse(`${ref}^{commit}`))
(await git.tagExists(tagName)) && commit === (await git.revParse(ref))
)
}
// Unexpected