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)
|
||||
const MinimumGitVersion = new GitVersion('2.18');
|
||||
const MinimumGitSparseCheckoutVersion = new GitVersion('2.28');
|
||||
const MinimumGitRefetchVersion = new GitVersion('2.36');
|
||||
const MinimumGitFetchWithoutWritingHeadVersion = new GitVersion('2.29');
|
||||
async function createCommandManager(workingDirectory, lfs, doSparseCheckout) {
|
||||
return await GitCommandManager.createCommandManager(workingDirectory, lfs, doSparseCheckout);
|
||||
}
|
||||
|
|
@ -35749,9 +35751,19 @@ class GitCommandManager {
|
|||
if (options.showProgress) {
|
||||
args.push('--progress');
|
||||
}
|
||||
const inheritedFilter = !options.filter &&
|
||||
(await this.configExists('remote.origin.partialclonefilter'));
|
||||
const restoreFullHistory = inheritedFilter && !(options.fetchDepth && options.fetchDepth > 0);
|
||||
if (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) {
|
||||
args.push(`--depth=${options.fetchDepth}`);
|
||||
}
|
||||
|
|
@ -35766,6 +35778,43 @@ class GitCommandManager {
|
|||
await execute(async () => {
|
||||
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) {
|
||||
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)
|
||||
export const MinimumGitVersion = new GitVersion('2.18')
|
||||
export const MinimumGitSparseCheckoutVersion = new GitVersion('2.28')
|
||||
const MinimumGitRefetchVersion = new GitVersion('2.36')
|
||||
const MinimumGitFetchWithoutWritingHeadVersion = new GitVersion('2.29')
|
||||
|
||||
export interface IGitCommandManager {
|
||||
branchDelete(remote: boolean, branch: string): Promise<void>
|
||||
|
|
@ -292,8 +294,22 @@ class GitCommandManager {
|
|||
args.push('--progress')
|
||||
}
|
||||
|
||||
const inheritedFilter =
|
||||
!options.filter &&
|
||||
(await this.configExists('remote.origin.partialclonefilter'))
|
||||
const restoreFullHistory =
|
||||
inheritedFilter && !(options.fetchDepth && options.fetchDepth > 0)
|
||||
|
||||
if (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) {
|
||||
|
|
@ -315,6 +331,52 @@ class GitCommandManager {
|
|||
await retryHelper.execute(async () => {
|
||||
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> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user