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
diff options
context:
space:
mode:
authoreric sciple <ericsciple@users.noreply.github.com>2020-05-27 09:54:28 -0400
committerGitHub <noreply@github.com>2020-05-27 09:54:28 -0400
commit1af3b46e902378dcbdf104a32ddae1644c64c366 (patch)
treeeb40b22d94abbd6860bb2ec1eac5b1ca62ea542a
parent31d9f35c90667154e76ccca096898caab3e5bba7 (diff)
Fetch all history for all tags and branches when fetch-depth=0 (#258)
-rw-r--r--README.md37
-rw-r--r--__test__/git-auth-helper.test.ts2
-rw-r--r--__test__/git-directory-helper.test.ts90
-rw-r--r--adrs/0153-checkout-v2.md2
-rw-r--r--dist/index.js135
-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
9 files changed, 338 insertions, 86 deletions
diff --git a/README.md b/README.md
index 745ee8a..9104f8b 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it.
-Only a single commit is fetched by default, for the ref/SHA that triggered the workflow. Set `fetch-depth` to fetch more history. Refer [here](https://help.github.com/en/articles/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events.
+Only a single commit is fetched by default, for the ref/SHA that triggered the workflow. Set `fetch-depth: 0` to fetch all history for all branches and tags. Refer [here](https://help.github.com/en/articles/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events.
The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out.
@@ -110,6 +110,7 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
# Scenarios
+- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches)
- [Checkout a different branch](#Checkout-a-different-branch)
- [Checkout HEAD^](#Checkout-HEAD)
- [Checkout multiple repos (side by side)](#Checkout-multiple-repos-side-by-side)
@@ -117,9 +118,14 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
- [Checkout multiple repos (private)](#Checkout-multiple-repos-private)
- [Checkout pull request HEAD commit instead of merge commit](#Checkout-pull-request-HEAD-commit-instead-of-merge-commit)
- [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event)
-- [Fetch all tags](#Fetch-all-tags)
-- [Fetch all branches](#Fetch-all-branches)
-- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches)
+
+## Fetch all history for all tags and branches
+
+```yaml
+- uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+```
## Checkout a different branch
@@ -207,29 +213,6 @@ jobs:
- uses: actions/checkout@v2
```
-## Fetch all tags
-
-```yaml
-- uses: actions/checkout@v2
-- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
-```
-
-## Fetch all branches
-
-```yaml
-- uses: actions/checkout@v2
-- run: |
- git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
-```
-
-## Fetch all history for all tags and branches
-
-```yaml
-- uses: actions/checkout@v2
-- run: |
- git fetch --prune --unshallow --tags
-```
-
# License
The scripts and documentation in this project are released under the [MIT License](LICENSE)
diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts
index 1d5c3d5..92a462a 100644
--- a/__test__/git-auth-helper.test.ts
+++ b/__test__/git-auth-helper.test.ts
@@ -722,9 +722,11 @@ async function setup(testName: string): Promise<void> {
log1: jest.fn(),
remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
+ revParse: jest.fn(),
setEnvironmentVariable: jest.fn((name: string, value: string) => {
git.env[name] = value
}),
+ shaExists: jest.fn(),
submoduleForeach: jest.fn(async () => {
return ''
}),
diff --git a/__test__/git-directory-helper.test.ts b/__test__/git-directory-helper.test.ts
index c39a2a5..7283102 100644
--- a/__test__/git-directory-helper.test.ts
+++ b/__test__/git-directory-helper.test.ts
@@ -9,6 +9,7 @@ const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper')
let repositoryPath: string
let repositoryUrl: string
let clean: boolean
+let ref: string
let git: IGitCommandManager
describe('git-directory-helper tests', () => {
@@ -41,7 +42,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -63,7 +65,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -88,7 +91,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -109,7 +113,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -137,7 +142,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -163,7 +169,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
differentRepositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -187,7 +194,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -212,7 +220,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -236,7 +245,8 @@ describe('git-directory-helper tests', () => {
undefined,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -260,7 +270,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -290,7 +301,8 @@ describe('git-directory-helper tests', () => {
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
@@ -305,29 +317,66 @@ describe('git-directory-helper tests', () => {
expect(git.tryReset).not.toHaveBeenCalled()
})
- const removesRemoteBranches = 'removes local branches'
- it(removesRemoteBranches, async () => {
+ const removesAncestorRemoteBranch = 'removes ancestor remote branch'
+ it(removesAncestorRemoteBranch, async () => {
// Arrange
- await setup(removesRemoteBranches)
+ await setup(removesAncestorRemoteBranch)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
mockBranchList.mockImplementation(async (remote: boolean) => {
- return remote ? ['remote-branch-1', 'remote-branch-2'] : []
+ return remote ? ['origin/remote-branch-1', 'origin/remote-branch-2'] : []
})
+ ref = 'remote-branch-1/conflict'
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
- clean
+ clean,
+ ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
- expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-1')
- expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-2')
+ expect(git.branchDelete).toHaveBeenCalledTimes(1)
+ expect(git.branchDelete).toHaveBeenCalledWith(
+ true,
+ 'origin/remote-branch-1'
+ )
+ })
+
+ const removesDescendantRemoteBranches = 'removes descendant remote branch'
+ it(removesDescendantRemoteBranches, async () => {
+ // Arrange
+ await setup(removesDescendantRemoteBranches)
+ await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
+ const mockBranchList = git.branchList as jest.Mock<any, any>
+ mockBranchList.mockImplementation(async (remote: boolean) => {
+ return remote
+ ? ['origin/remote-branch-1/conflict', 'origin/remote-branch-2']
+ : []
+ })
+ ref = 'remote-branch-1'
+
+ // Act
+ await gitDirectoryHelper.prepareExistingDirectory(
+ git,
+ repositoryPath,
+ repositoryUrl,
+ clean,
+ ref
+ )
+
+ // Assert
+ const files = await fs.promises.readdir(repositoryPath)
+ expect(files.sort()).toEqual(['.git', 'my-file'])
+ expect(git.branchDelete).toHaveBeenCalledTimes(1)
+ expect(git.branchDelete).toHaveBeenCalledWith(
+ true,
+ 'origin/remote-branch-1/conflict'
+ )
})
})
@@ -344,6 +393,9 @@ async function setup(testName: string): Promise<void> {
// Clean
clean = true
+ // Ref
+ ref = ''
+
// Git command manager
git = {
branchDelete: jest.fn(),
@@ -364,7 +416,9 @@ async function setup(testName: string): Promise<void> {
log1: jest.fn(),
remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn(),
+ revParse: jest.fn(),
setEnvironmentVariable: jest.fn(),
+ shaExists: jest.fn(),
submoduleForeach: jest.fn(),
submoduleSync: jest.fn(),
submoduleUpdate: jest.fn(),
diff --git a/adrs/0153-checkout-v2.md b/adrs/0153-checkout-v2.md
index b9536a5..f174b1a 100644
--- a/adrs/0153-checkout-v2.md
+++ b/adrs/0153-checkout-v2.md
@@ -70,7 +70,7 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching'
default: true
fetch-depth:
- description: 'Number of commits to fetch. 0 indicates all history.'
+ description: 'Number of commits to fetch. 0 indicates all history for all tags and branches.'
default: 1
lfs:
description: 'Whether to download Git-LFS files'
diff --git a/dist/index.js b/dist/index.js
index 86601d1..0c78d25 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -3383,6 +3383,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
const url_1 = __webpack_require__(835);
const core = __importStar(__webpack_require__(470));
const github = __importStar(__webpack_require__(469));
+exports.tagsRefSpec = '+refs/tags/*:refs/tags/*';
function getCheckoutInfo(git, ref, commit) {
return __awaiter(this, void 0, void 0, function* () {
if (!git) {
@@ -3429,6 +3430,15 @@ function getCheckoutInfo(git, ref, commit) {
});
}
exports.getCheckoutInfo = getCheckoutInfo;
+function getRefSpecForAllHistory(ref, commit) {
+ const result = ['+refs/heads/*:refs/remotes/origin/*', exports.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;
+}
+exports.getRefSpecForAllHistory = getRefSpecForAllHistory;
function getRefSpec(ref, commit) {
if (!ref && !commit) {
throw new Error('Args ref and commit cannot both be empty');
@@ -3478,6 +3488,50 @@ function getRefSpec(ref, commit) {
}
}
exports.getRefSpec = getRefSpec;
+/**
+ * Tests whether the initial fetch created the ref at the expected commit
+ */
+function testRef(git, ref, commit) {
+ return __awaiter(this, void 0, void 0, function* () {
+ 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 yield git.shaExists(commit);
+ }
+ const upperRef = ref.toUpperCase();
+ // refs/heads/
+ if (upperRef.startsWith('REFS/HEADS/')) {
+ const branch = ref.substring('refs/heads/'.length);
+ return ((yield git.branchExists(true, `origin/${branch}`)) &&
+ commit === (yield 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 ((yield git.tagExists(tagName)) && commit === (yield git.revParse(ref)));
+ }
+ // Unexpected
+ else {
+ core.debug(`Unexpected ref format '${ref}' when testing ref info`);
+ return true;
+ }
+ });
+}
+exports.testRef = testRef;
function checkCommitInfo(token, commitInfo, repositoryOwner, repositoryName, ref, commit) {
return __awaiter(this, void 0, void 0, function* () {
try {
@@ -5634,6 +5688,7 @@ const exec = __importStar(__webpack_require__(986));
const fshelper = __importStar(__webpack_require__(618));
const io = __importStar(__webpack_require__(1));
const path = __importStar(__webpack_require__(622));
+const refHelper = __importStar(__webpack_require__(227));
const regexpHelper = __importStar(__webpack_require__(528));
const retryHelper = __importStar(__webpack_require__(587));
const git_version_1 = __webpack_require__(559);
@@ -5749,18 +5804,14 @@ class GitCommandManager {
return output.exitCode === 0;
});
}
- fetch(fetchDepth, refSpec) {
+ fetch(refSpec, fetchDepth) {
return __awaiter(this, void 0, void 0, function* () {
- const args = [
- '-c',
- 'protocol.version=2',
- 'fetch',
- '--no-tags',
- '--prune',
- '--progress',
- '--no-recurse-submodules'
- ];
- if (fetchDepth > 0) {
+ 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(path.join(this.workingDirectory, '.git', 'shallow'))) {
@@ -5819,9 +5870,28 @@ class GitCommandManager {
removeEnvironmentVariable(name) {
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>}
+ */
+ revParse(ref) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const output = yield this.execGit(['rev-parse', ref]);
+ return output.stdout.trim();
+ });
+ }
setEnvironmentVariable(name, value) {
this.gitEnv[name] = value;
}
+ shaExists(sha) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const args = ['rev-parse', '--verify', '--quiet', `${sha}^{object}`];
+ const output = yield this.execGit(args, true);
+ return output.exitCode === 0;
+ });
+ }
submoduleForeach(command, recursive) {
return __awaiter(this, void 0, void 0, function* () {
const args = ['submodule', 'foreach'];
@@ -6060,7 +6130,7 @@ function getSource(settings) {
core.endGroup();
// Prepare existing directory, otherwise recreate
if (isExisting) {
- yield gitDirectoryHelper.prepareExistingDirectory(git, settings.repositoryPath, repositoryUrl, settings.clean);
+ yield gitDirectoryHelper.prepareExistingDirectory(git, settings.repositoryPath, repositoryUrl, settings.clean, settings.ref);
}
if (!git) {
// Downloading using REST API
@@ -6102,8 +6172,21 @@ function getSource(settings) {
}
// Fetch
core.startGroup('Fetching the repository');
- const refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
- yield git.fetch(settings.fetchDepth, refSpec);
+ if (settings.fetchDepth <= 0) {
+ // Fetch all branches and tags
+ let refSpec = refHelper.getRefSpecForAllHistory(settings.ref, settings.commit);
+ yield 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 (!(yield refHelper.testRef(git, settings.ref, settings.commit))) {
+ refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
+ yield git.fetch(refSpec);
+ }
+ }
+ else {
+ const refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
+ yield git.fetch(refSpec, settings.fetchDepth);
+ }
core.endGroup();
// Checkout info
core.startGroup('Determining the checkout info');
@@ -7454,7 +7537,7 @@ const fs = __importStar(__webpack_require__(747));
const fsHelper = __importStar(__webpack_require__(618));
const io = __importStar(__webpack_require__(1));
const path = __importStar(__webpack_require__(622));
-function prepareExistingDirectory(git, repositoryPath, repositoryUrl, clean) {
+function prepareExistingDirectory(git, repositoryPath, repositoryUrl, clean, ref) {
return __awaiter(this, void 0, void 0, function* () {
assert.ok(repositoryPath, 'Expected repositoryPath to be defined');
assert.ok(repositoryUrl, 'Expected repositoryUrl to be defined');
@@ -7494,10 +7577,24 @@ function prepareExistingDirectory(git, repositoryPath, repositoryUrl, clean) {
for (const branch of branches) {
yield git.branchDelete(false, branch);
}
- // Remove all refs/remotes/origin/* to avoid conflicts
- branches = yield git.branchList(true);
- for (const branch of branches) {
- yield 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 = yield 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)) {
+ yield git.branchDelete(true, branch);
+ }
+ }
+ }
}
core.endGroup();
// Clean
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,