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.ts41
-rw-r--r--src/git-directory-helper.ts28
-rw-r--r--src/git-source-provider.ts23
-rw-r--r--src/ref-helper.ts66
4 files changed, 137 insertions, 21 deletions
diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts
index 495075a..9d2d45f 100644
--- a/src/git-command-manager.ts
+++ b/src/git-command-manager.ts
@@ -3,6 +3,7 @@ import * as exec from '@actions/exec'
import * as fshelper from './fs-helper'
import * as io from '@actions/io'
import * as path from 'path'
+import * as refHelper from './ref-helper'
import * as regexpHelper from './regexp-helper'
import * as retryHelper from './retry-helper'
import {GitVersion} from './git-version'
@@ -23,7 +24,7 @@ export interface IGitCommandManager {
globalConfig?: boolean
): Promise<void>
configExists(configKey: string, globalConfig?: boolean): Promise<boolean>
- fetch(fetchDepth: number, refSpec: string[]): Promise<void>
+ fetch(refSpec: string[], fetchDepth?: number): Promise<void>
getWorkingDirectory(): string
init(): Promise<void>
isDetached(): Promise<boolean>
@@ -32,7 +33,9 @@ export interface IGitCommandManager {
log1(): Promise<string>
remoteAdd(remoteName: string, remoteUrl: string): Promise<void>
removeEnvironmentVariable(name: string): void
+ revParse(ref: string): Promise<string>
setEnvironmentVariable(name: string, value: string): void
+ shaExists(sha: string): Promise<boolean>
submoduleForeach(command: string, recursive: boolean): Promise<string>
submoduleSync(recursive: boolean): Promise<void>
submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void>
@@ -164,17 +167,14 @@ class GitCommandManager {
return output.exitCode === 0
}
- async fetch(fetchDepth: number, refSpec: string[]): Promise<void> {
- const args = [
- '-c',
- 'protocol.version=2',
- 'fetch',
- '--no-tags',
- '--prune',
- '--progress',
- '--no-recurse-submodules'
- ]
- if (fetchDepth > 0) {
+ async fetch(refSpec: string[], fetchDepth?: number): Promise<void> {
+ const args = ['-c', 'protocol.version=2', 'fetch']
+ if (!refSpec.some(x => x === refHelper.tagsRefSpec)) {
+ args.push('--no-tags')
+ }
+
+ args.push('--prune', '--progress', '--no-recurse-submodules')
+ if (fetchDepth && fetchDepth > 0) {
args.push(`--depth=${fetchDepth}`)
} else if (
fshelper.fileExistsSync(
@@ -238,10 +238,27 @@ class GitCommandManager {
delete this.gitEnv[name]
}
+ /**
+ * Resolves a ref to a SHA. For a branch or lightweight tag, the commit SHA is returned.
+ * For an annotated tag, the tag SHA is returned.
+ * @param {string} ref For example: 'refs/heads/master' or '/refs/tags/v1'
+ * @returns {Promise<string>}
+ */
+ async revParse(ref: string): Promise<string> {
+ const output = await this.execGit(['rev-parse', ref])
+ return output.stdout.trim()
+ }
+
setEnvironmentVariable(name: string, value: string): void {
this.gitEnv[name] = value
}
+ async shaExists(sha: string): Promise<boolean> {
+ const args = ['rev-parse', '--verify', '--quiet', `${sha}^{object}`]
+ const output = await this.execGit(args, true)
+ return output.exitCode === 0
+ }
+
async submoduleForeach(command: string, recursive: boolean): Promise<string> {
const args = ['submodule', 'foreach']
if (recursive) {
diff --git a/src/git-directory-helper.ts b/src/git-directory-helper.ts
index 3866866..e792190 100644
--- a/src/git-directory-helper.ts
+++ b/src/git-directory-helper.ts
@@ -5,13 +5,13 @@ import * as fsHelper from './fs-helper'
import * as io from '@actions/io'
import * as path from 'path'
import {IGitCommandManager} from './git-command-manager'
-import {IGitSourceSettings} from './git-source-settings'
export async function prepareExistingDirectory(
git: IGitCommandManager | undefined,
repositoryPath: string,
repositoryUrl: string,
- clean: boolean
+ clean: boolean,
+ ref: string
): Promise<void> {
assert.ok(repositoryPath, 'Expected repositoryPath to be defined')
assert.ok(repositoryUrl, 'Expected repositoryUrl to be defined')
@@ -56,10 +56,26 @@ export async function prepareExistingDirectory(
await git.branchDelete(false, branch)
}
- // Remove all refs/remotes/origin/* to avoid conflicts
- branches = await git.branchList(true)
- for (const branch of branches) {
- await git.branchDelete(true, branch)
+ // Remove any conflicting refs/remotes/origin/*
+ // Example 1: Consider ref is refs/heads/foo and previously fetched refs/remotes/origin/foo/bar
+ // Example 2: Consider ref is refs/heads/foo/bar and previously fetched refs/remotes/origin/foo
+ if (ref) {
+ ref = ref.startsWith('refs/') ? ref : `refs/heads/${ref}`
+ if (ref.startsWith('refs/heads/')) {
+ const upperName1 = ref.toUpperCase().substr('REFS/HEADS/'.length)
+ const upperName1Slash = `${upperName1}/`
+ branches = await git.branchList(true)
+ for (const branch of branches) {
+ const upperName2 = branch.substr('origin/'.length).toUpperCase()
+ const upperName2Slash = `${upperName2}/`
+ if (
+ upperName1.startsWith(upperName2Slash) ||
+ upperName2.startsWith(upperName1Slash)
+ ) {
+ await git.branchDelete(true, branch)
+ }
+ }
+ }
}
core.endGroup()
diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts
index bde53bb..89c16b5 100644
--- a/src/git-source-provider.ts
+++ b/src/git-source-provider.ts
@@ -42,7 +42,8 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
git,
settings.repositoryPath,
repositoryUrl,
- settings.clean
+ settings.clean,
+ settings.ref
)
}
@@ -109,8 +110,24 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Fetch
core.startGroup('Fetching the repository')
- const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
- await git.fetch(settings.fetchDepth, refSpec)
+ if (settings.fetchDepth <= 0) {
+ // Fetch all branches and tags
+ let refSpec = refHelper.getRefSpecForAllHistory(
+ settings.ref,
+ settings.commit
+ )
+ await git.fetch(refSpec)
+
+ // When all history is fetched, the ref we're interested in may have moved to a different
+ // commit (push or force push). If so, fetch again with a targeted refspec.
+ if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
+ refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
+ await git.fetch(refSpec)
+ }
+ } else {
+ const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
+ await git.fetch(refSpec, settings.fetchDepth)
+ }
core.endGroup()
// Checkout info
diff --git a/src/ref-helper.ts b/src/ref-helper.ts
index f600179..381fa60 100644
--- a/src/ref-helper.ts
+++ b/src/ref-helper.ts
@@ -3,6 +3,8 @@ import {IGitCommandManager} from './git-command-manager'
import * as core from '@actions/core'
import * as github from '@actions/github'
+export const tagsRefSpec = '+refs/tags/*:refs/tags/*'
+
export interface ICheckoutInfo {
ref: string
startPoint: string
@@ -60,6 +62,16 @@ export async function getCheckoutInfo(
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')
@@ -111,6 +123,60 @@ export function getRefSpec(ref: string, commit: string): string[] {
}
}
+/**
+ * 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,