思路

先用partial clone和git fetch来下载metadata,然后再用ls-tree得到文件树,最后一个文件一个文件地checkout

代码

checkout_partial.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

param (
[Parameter(Mandatory = $true)]
[string]$RepoUrl,

[int]$BatchSize = 1
)

# ---------- Encoding & git global-friendly settings ----------
try {
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = New-Object System.Text.UTF8Encoding $false
} catch {
Write-Host "Warning: Unable to set console encoding. Continuing..."
}

# Helpful git defaults for Unicode output
git config --global core.quotepath false | Out-Null
git config --global core.precomposeUnicode true | Out-Null
git config --global i18n.logOutputEncoding utf-8 | Out-Null

# ---------- Prepare repo dir ----------
$RepoDir = [System.IO.Path]::GetFileNameWithoutExtension($RepoUrl)
Write-Host "Preparing repository: $RepoDir"

if (Test-Path $RepoDir) {
if (Test-Path (Join-Path $RepoDir ".git")) {
Write-Host "Found existing git repo at '$RepoDir' — reusing it (no reclone)."
Set-Location $RepoDir
Write-Host "Fetching remote metadata..."
git -c core.quotepath=false -c i18n.logOutputEncoding=utf-8 fetch --all
if ($LASTEXITCODE -ne 0) {
Write-Host "Warning: git fetch --all returned non-zero exit code."
}
} else {
Write-Host "Directory '$RepoDir' exists but is not a git repo. Aborting to avoid overwrite."
exit 1
}
} else {
Write-Host "Cloning (partial) into '$RepoDir' ..."
git -c core.quotepath=false -c i18n.logOutputEncoding=utf-8 clone --filter=blob:none --no-checkout $RepoUrl $RepoDir
if ($LASTEXITCODE -ne 0) {
Write-Host "Clone failed. You can try a full clone (without --filter=blob:none) as fallback."
exit 1
}
Set-Location $RepoDir
Write-Host "Fetching metadata..."
git -c core.quotepath=false -c i18n.logOutputEncoding=utf-8 fetch --all
}

# ---------- Get file list from HEAD ----------
# Use -z to safely handle filenames including newlines and unicode
$FileListRaw = & git -c core.quotepath=false -c i18n.logOutputEncoding=utf-8 ls-tree -r --name-only -z HEAD 2>$null
if (-not $FileListRaw) {
Write-Host "No files found in HEAD (maybe repository empty or no HEAD). Exiting."
exit 0
}
$FileList = $FileListRaw -split "`0" | Where-Object { $_ -ne "" }

Write-Host "Total files to checkout: $($FileList.Count)"
Write-Host "Starting checkout in batches of $BatchSize ..."

# ---------- Checkout loop with skip logic ----------
for ($i = 0; $i -lt $FileList.Count; $i += $BatchSize) {
$Batch = $FileList[$i..([Math]::Min($i + $BatchSize - 1, $FileList.Count - 1))]
$start = $i + 1
$end = [Math]::Min($i + $BatchSize, $FileList.Count)
Write-Host "Batch $start - $end ..."

$BatchToCheckout = @()
foreach ($rel in $Batch) {
# Ensure we test the correct file path relative to repo root
$fullPath = Join-Path (Get-Location) $rel

if (-not (Test-Path $fullPath)) {
$BatchToCheckout += $rel
} else {
# If file exists but size == 0, re-checkout (could be placeholder)
try {
$length = (Get-Item $fullPath -ErrorAction Stop).Length
if ($length -eq 0) {
Write-Host " Re-checkout (empty file): $rel"
$BatchToCheckout += $rel
} else {
Write-Host " Skipping existing file: $rel"
}
} catch {
Write-Host " Skipping (unable to stat): $rel"
}
}
}

if ($BatchToCheckout.Count -eq 0) {
Write-Host " All files in this batch already present — skipping."
continue
}

# Try checkout; if fails, try fetching shallow blobs and retry once
& git -c core.quotepath=false -c i18n.logOutputEncoding=utf-8 checkout HEAD -- $BatchToCheckout
if ($LASTEXITCODE -ne 0) {
Write-Host " Checkout failed for some files in this batch. Attempting conservative fetch and retry..."
& git fetch --depth=1 origin
& git -c core.quotepath=false -c i18n.logOutputEncoding=utf-8 checkout HEAD -- $BatchToCheckout
if ($LASTEXITCODE -ne 0) {
Write-Host " Still failed on this batch. Possible causes:"
Write-Host " - missing blobs on server / partial-clone negotiation issue"
Write-Host " - repository uses Git LFS (you may see pointer files)"
Write-Host " These files will remain unchecked out; continuing to next batch."
} else {
Write-Host " Retry succeeded for this batch."
}
} else {
Write-Host " Checkout succeeded for this batch."
}
}


使用

1
2
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\checkout_partial.ps1 -RepoUrl "你的git url" -BatchSize 5