Read-only mirror — the public repositories of the Forgejo instance, refreshed every 10 minutes. The live instance (fg.dieschoenewolke.de) is reachable only over the VPN; clone here with https://git.dieschoenewolke.de/git/<owner>/<repo>.git.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/git-command-manager.ts8
-rw-r--r--src/git-source-provider.ts28
-rw-r--r--src/ref-helper.ts48
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