diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/git-command-manager.ts | 41 | ||||
| -rw-r--r-- | src/git-directory-helper.ts | 28 | ||||
| -rw-r--r-- | src/git-source-provider.ts | 23 | ||||
| -rw-r--r-- | src/ref-helper.ts | 66 |
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, |
