mirror of
https://github.com/actions/checkout.git
synced 2026-11-11 04:55:24 +08:00
Merge 44c2bc1922 into f548e57e54
This commit is contained in:
commit
2786465fb3
63
__test__/partial-clone.test.ts
Normal file
63
__test__/partial-clone.test.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import {execFileSync} from 'node:child_process'
|
||||||
|
import {mkdtemp, rm, writeFile} from 'node:fs/promises'
|
||||||
|
import {tmpdir} from 'node:os'
|
||||||
|
import {join} from 'node:path'
|
||||||
|
import {pathToFileURL} from 'node:url'
|
||||||
|
import {expect, it} from '@jest/globals'
|
||||||
|
import {createCommandManager} from '../src/git-command-manager.js'
|
||||||
|
|
||||||
|
function git(cwd: string, ...args: string[]): string {
|
||||||
|
return execFileSync('git', args, {cwd, encoding: 'utf8'}).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
it.each([false, true])(
|
||||||
|
'recovers full history after a filtered fetch (already unshallowed: %s)',
|
||||||
|
async alreadyUnshallowed => {
|
||||||
|
const directory = await mkdtemp(join(tmpdir(), 'checkout-partial-'))
|
||||||
|
try {
|
||||||
|
git(directory, 'init', '-q', '-b', 'main', 'remote')
|
||||||
|
git(directory, 'init', '-q', 'local')
|
||||||
|
const remote = join(directory, 'remote')
|
||||||
|
const local = join(directory, 'local')
|
||||||
|
git(remote, 'config', 'uploadpack.allowFilter', 'true')
|
||||||
|
git(remote, 'config', 'user.name', 'Checkout Test')
|
||||||
|
git(remote, 'config', 'user.email', 'checkout@example.invalid')
|
||||||
|
await writeFile(join(remote, 'historical.txt'), 'historical blob\n')
|
||||||
|
git(remote, 'add', 'historical.txt')
|
||||||
|
git(remote, 'commit', '-qm', 'Add historical blob')
|
||||||
|
git(remote, 'rm', '-q', 'historical.txt')
|
||||||
|
git(remote, 'commit', '-qm', 'Remove historical blob')
|
||||||
|
|
||||||
|
git(local, 'remote', 'add', 'origin', pathToFileURL(remote).href)
|
||||||
|
const refSpec = ['+refs/heads/main:refs/remotes/origin/main']
|
||||||
|
const manager = await createCommandManager(local, false, false)
|
||||||
|
await manager.fetch(refSpec, {filter: 'blob:none', fetchDepth: 1})
|
||||||
|
if (alreadyUnshallowed) {
|
||||||
|
await manager.fetch(refSpec, {filter: 'blob:none', fetchDepth: 0})
|
||||||
|
}
|
||||||
|
await writeFile(join(local, 'keep.txt'), 'worktree file\n')
|
||||||
|
|
||||||
|
await manager.fetch(refSpec, {fetchDepth: 0})
|
||||||
|
|
||||||
|
expect(git(local, 'rev-parse', '--is-shallow-repository')).toBe('false')
|
||||||
|
expect(
|
||||||
|
git(
|
||||||
|
local,
|
||||||
|
'rev-list',
|
||||||
|
'--objects',
|
||||||
|
'--all',
|
||||||
|
'--quiet',
|
||||||
|
'--missing=print'
|
||||||
|
)
|
||||||
|
).toBe('')
|
||||||
|
expect(
|
||||||
|
await manager.configExists('remote.origin.partialclonefilter')
|
||||||
|
).toBe(false)
|
||||||
|
expect(await manager.configExists('remote.origin.promisor')).toBe(false)
|
||||||
|
expect(git(local, 'status', '--short')).toBe('?? keep.txt')
|
||||||
|
} finally {
|
||||||
|
await rm(directory, {recursive: true, force: true})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
20000
|
||||||
|
)
|
||||||
49
dist/index.js
vendored
49
dist/index.js
vendored
|
|
@ -35597,6 +35597,8 @@ class GitVersion {
|
||||||
// sparse-checkout not [well-]supported before 2.28 (see https://github.com/actions/checkout/issues/1386)
|
// sparse-checkout not [well-]supported before 2.28 (see https://github.com/actions/checkout/issues/1386)
|
||||||
const MinimumGitVersion = new GitVersion('2.18');
|
const MinimumGitVersion = new GitVersion('2.18');
|
||||||
const MinimumGitSparseCheckoutVersion = new GitVersion('2.28');
|
const MinimumGitSparseCheckoutVersion = new GitVersion('2.28');
|
||||||
|
const MinimumGitRefetchVersion = new GitVersion('2.36');
|
||||||
|
const MinimumGitFetchWithoutWritingHeadVersion = new GitVersion('2.29');
|
||||||
async function createCommandManager(workingDirectory, lfs, doSparseCheckout) {
|
async function createCommandManager(workingDirectory, lfs, doSparseCheckout) {
|
||||||
return await GitCommandManager.createCommandManager(workingDirectory, lfs, doSparseCheckout);
|
return await GitCommandManager.createCommandManager(workingDirectory, lfs, doSparseCheckout);
|
||||||
}
|
}
|
||||||
|
|
@ -35749,9 +35751,19 @@ class GitCommandManager {
|
||||||
if (options.showProgress) {
|
if (options.showProgress) {
|
||||||
args.push('--progress');
|
args.push('--progress');
|
||||||
}
|
}
|
||||||
|
const inheritedFilter = !options.filter &&
|
||||||
|
(await this.configExists('remote.origin.partialclonefilter'));
|
||||||
|
const restoreFullHistory = inheritedFilter && !(options.fetchDepth && options.fetchDepth > 0);
|
||||||
if (options.filter) {
|
if (options.filter) {
|
||||||
args.push(`--filter=${options.filter}`);
|
args.push(`--filter=${options.filter}`);
|
||||||
}
|
}
|
||||||
|
else if (inheritedFilter) {
|
||||||
|
args.push('--no-filter');
|
||||||
|
if (restoreFullHistory &&
|
||||||
|
this.gitVersion.checkMinimum(MinimumGitRefetchVersion)) {
|
||||||
|
args.push('--refetch');
|
||||||
|
}
|
||||||
|
}
|
||||||
if (options.fetchDepth && options.fetchDepth > 0) {
|
if (options.fetchDepth && options.fetchDepth > 0) {
|
||||||
args.push(`--depth=${options.fetchDepth}`);
|
args.push(`--depth=${options.fetchDepth}`);
|
||||||
}
|
}
|
||||||
|
|
@ -35766,6 +35778,43 @@ class GitCommandManager {
|
||||||
await execute(async () => {
|
await execute(async () => {
|
||||||
await that.execGit(args);
|
await that.execGit(args);
|
||||||
});
|
});
|
||||||
|
if (restoreFullHistory) {
|
||||||
|
const canSkipFetchHead = this.gitVersion.checkMinimum(MinimumGitFetchWithoutWritingHeadVersion);
|
||||||
|
let fetchedMissingObjects = false;
|
||||||
|
let previousMissing = new Set();
|
||||||
|
while (true) {
|
||||||
|
const output = await this.execGit(['rev-list', '--objects', '--all', '--quiet', '--missing=print'], false, true);
|
||||||
|
const missing = [
|
||||||
|
...output.stdout.matchAll(/^\?([0-9a-f]{40}|[0-9a-f]{64})\r?$/gm)
|
||||||
|
].map(match => match[1]);
|
||||||
|
if (!missing.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (missing.some(oid => previousMissing.has(oid))) {
|
||||||
|
throw new Error('Unable to fetch all objects from the previous partial checkout.');
|
||||||
|
}
|
||||||
|
previousMissing = new Set(missing);
|
||||||
|
for (let i = 0; i < missing.length; i += 100) {
|
||||||
|
await this.execGit([
|
||||||
|
'-c',
|
||||||
|
'protocol.version=2',
|
||||||
|
'fetch',
|
||||||
|
'--no-tags',
|
||||||
|
'--no-recurse-submodules',
|
||||||
|
'--no-filter',
|
||||||
|
canSkipFetchHead ? '--no-write-fetch-head' : '--append',
|
||||||
|
'origin',
|
||||||
|
...missing.slice(i, i + 100)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
fetchedMissingObjects = true;
|
||||||
|
}
|
||||||
|
if (fetchedMissingObjects && !canSkipFetchHead) {
|
||||||
|
await this.execGit(args.filter(arg => arg !== '--unshallow'));
|
||||||
|
}
|
||||||
|
await this.tryConfigUnset('remote.origin.partialclonefilter');
|
||||||
|
await this.tryConfigUnset('remote.origin.promisor');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async getDefaultBranch(repositoryUrl) {
|
async getDefaultBranch(repositoryUrl) {
|
||||||
let output;
|
let output;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import {GitVersion} from './git-version.js'
|
||||||
// sparse-checkout not [well-]supported before 2.28 (see https://github.com/actions/checkout/issues/1386)
|
// sparse-checkout not [well-]supported before 2.28 (see https://github.com/actions/checkout/issues/1386)
|
||||||
export const MinimumGitVersion = new GitVersion('2.18')
|
export const MinimumGitVersion = new GitVersion('2.18')
|
||||||
export const MinimumGitSparseCheckoutVersion = new GitVersion('2.28')
|
export const MinimumGitSparseCheckoutVersion = new GitVersion('2.28')
|
||||||
|
const MinimumGitRefetchVersion = new GitVersion('2.36')
|
||||||
|
const MinimumGitFetchWithoutWritingHeadVersion = new GitVersion('2.29')
|
||||||
|
|
||||||
export interface IGitCommandManager {
|
export interface IGitCommandManager {
|
||||||
branchDelete(remote: boolean, branch: string): Promise<void>
|
branchDelete(remote: boolean, branch: string): Promise<void>
|
||||||
|
|
@ -292,8 +294,22 @@ class GitCommandManager {
|
||||||
args.push('--progress')
|
args.push('--progress')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inheritedFilter =
|
||||||
|
!options.filter &&
|
||||||
|
(await this.configExists('remote.origin.partialclonefilter'))
|
||||||
|
const restoreFullHistory =
|
||||||
|
inheritedFilter && !(options.fetchDepth && options.fetchDepth > 0)
|
||||||
|
|
||||||
if (options.filter) {
|
if (options.filter) {
|
||||||
args.push(`--filter=${options.filter}`)
|
args.push(`--filter=${options.filter}`)
|
||||||
|
} else if (inheritedFilter) {
|
||||||
|
args.push('--no-filter')
|
||||||
|
if (
|
||||||
|
restoreFullHistory &&
|
||||||
|
this.gitVersion.checkMinimum(MinimumGitRefetchVersion)
|
||||||
|
) {
|
||||||
|
args.push('--refetch')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.fetchDepth && options.fetchDepth > 0) {
|
if (options.fetchDepth && options.fetchDepth > 0) {
|
||||||
|
|
@ -315,6 +331,52 @@ class GitCommandManager {
|
||||||
await retryHelper.execute(async () => {
|
await retryHelper.execute(async () => {
|
||||||
await that.execGit(args)
|
await that.execGit(args)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (restoreFullHistory) {
|
||||||
|
const canSkipFetchHead = this.gitVersion.checkMinimum(
|
||||||
|
MinimumGitFetchWithoutWritingHeadVersion
|
||||||
|
)
|
||||||
|
let fetchedMissingObjects = false
|
||||||
|
let previousMissing = new Set<string>()
|
||||||
|
while (true) {
|
||||||
|
const output = await this.execGit(
|
||||||
|
['rev-list', '--objects', '--all', '--quiet', '--missing=print'],
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
const missing = [
|
||||||
|
...output.stdout.matchAll(/^\?([0-9a-f]{40}|[0-9a-f]{64})\r?$/gm)
|
||||||
|
].map(match => match[1])
|
||||||
|
if (!missing.length) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (missing.some(oid => previousMissing.has(oid))) {
|
||||||
|
throw new Error(
|
||||||
|
'Unable to fetch all objects from the previous partial checkout.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
previousMissing = new Set(missing)
|
||||||
|
for (let i = 0; i < missing.length; i += 100) {
|
||||||
|
await this.execGit([
|
||||||
|
'-c',
|
||||||
|
'protocol.version=2',
|
||||||
|
'fetch',
|
||||||
|
'--no-tags',
|
||||||
|
'--no-recurse-submodules',
|
||||||
|
'--no-filter',
|
||||||
|
canSkipFetchHead ? '--no-write-fetch-head' : '--append',
|
||||||
|
'origin',
|
||||||
|
...missing.slice(i, i + 100)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
fetchedMissingObjects = true
|
||||||
|
}
|
||||||
|
if (fetchedMissingObjects && !canSkipFetchHead) {
|
||||||
|
await this.execGit(args.filter(arg => arg !== '--unshallow'))
|
||||||
|
}
|
||||||
|
await this.tryConfigUnset('remote.origin.partialclonefilter')
|
||||||
|
await this.tryConfigUnset('remote.origin.promisor')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDefaultBranch(repositoryUrl: string): Promise<string> {
|
async getDefaultBranch(repositoryUrl: string): Promise<string> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user