diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/git-command-manager.ts | 8 | ||||
| -rw-r--r-- | src/git-source-provider.ts | 28 | ||||
| -rw-r--r-- | src/ref-helper.ts | 48 |
3 files changed, 63 insertions, 21 deletions
diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index eba285a..f5ba40e 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -37,7 +37,6 @@ export interface IGitCommandManager { options: { filter?: string fetchDepth?: number - fetchTags?: boolean showProgress?: boolean } ): Promise<void> @@ -280,14 +279,13 @@ class GitCommandManager { options: { filter?: string fetchDepth?: number - fetchTags?: boolean showProgress?: boolean } ): Promise<void> { const args = ['-c', 'protocol.version=2', 'fetch'] - if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) { - args.push('--no-tags') - } + // Always use --no-tags for explicit control over tag fetching + // Tags are fetched explicitly via refspec when needed + args.push('--no-tags') args.push('--prune', '--no-recurse-submodules') if (options.showProgress) { diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts index 2d35138..ec87178 100644 --- a/src/git-source-provider.ts +++ b/src/git-source-provider.ts @@ -159,7 +159,6 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> { const fetchOptions: { filter?: string fetchDepth?: number - fetchTags?: boolean showProgress?: boolean } = {} @@ -182,12 +181,35 @@ 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 - fetchOptions.fetchTags = settings.fetchTags - const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) + const refSpec = refHelper.getRefSpec( + settings.ref, + settings.commit, + settings.fetchTags + ) 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() diff --git a/src/ref-helper.ts b/src/ref-helper.ts index 58f9290..5130f53 100644 --- a/src/ref-helper.ts +++ b/src/ref-helper.ts @@ -76,55 +76,75 @@ export function getRefSpecForAllHistory(ref: string, commit: string): string[] { return result } -export function getRefSpec(ref: string, commit: string): string[] { +export function getRefSpec( + ref: string, + commit: string, + fetchTags?: boolean +): 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) - return [`+${commit}:refs/remotes/origin/${branch}`] + result.push(`+${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}`] + result.push(`+${commit}:refs/remotes/pull/${branch}`) } // refs/tags/ else if (upperRef.startsWith('REFS/TAGS/')) { - return [`+${commit}:${ref}`] + if (!fetchTags) { + result.push(`+${ref}:${ref}`) + } } // Otherwise no destination ref else { - return [commit] + result.push(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}*` - ] + result.push(`+refs/heads/${ref}*:refs/remotes/origin/${ref}*`) + if (!fetchTags) { + result.push(`+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}`] + result.push(`+${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}`] + result.push(`+${ref}:refs/remotes/pull/${branch}`) } // refs/tags/ + else if (upperRef.startsWith('REFS/TAGS/')) { + if (!fetchTags) { + result.push(`+${ref}:${ref}`) + } + } + // Other refs else { - return [`+${ref}:${ref}`] + result.push(`+${ref}:${ref}`) } + + return result } /** @@ -170,8 +190,10 @@ 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)) + (await git.tagExists(tagName)) && + commit === (await git.revParse(`${ref}^{commit}`)) ) } // Unexpected |
