GitHub加速.psm1
|
# GitHub加速 - 通过多个 GitHub 镜像站点自动尝试 git fetch # 按历史成功率智能排序,实现国内无障碍拉取 GitHub 仓库。 Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" # ============================================================ # 内部辅助函数 # ============================================================ function 执行-Git命令 { param( [Parameter(Mandatory = $true)] [string[]]$参数 ) & git @参数 if ($LASTEXITCODE -ne 0) { throw "git $($参数 -join ' ') 执行失败。" } } function 尝试-Git命令 { param( [Parameter(Mandatory = $true)] [string[]]$参数 ) & git @参数 return $LASTEXITCODE -eq 0 } function 读取-Git文本 { param( [Parameter(Mandatory = $true)] [string[]]$参数 ) $输出 = & git @参数 if ($LASTEXITCODE -ne 0) { throw "git $($参数 -join ' ') 执行失败。" } return ($输出 -join "`n").Trim() } function 新建-镜像记录 { return [pscustomobject]@{ 版本 = 1 最后更新 = "" 镜像 = [pscustomobject]@{} } } function 确保-对象属性 { param( [Parameter(Mandatory = $true)] [pscustomobject]$对象, [Parameter(Mandatory = $true)] [string]$属性名, [Parameter(Mandatory = $true)] [AllowNull()] $默认值 ) if ($null -eq $对象.PSObject.Properties[$属性名]) { $对象 | Add-Member -NotePropertyName $属性名 -NotePropertyValue $默认值 } } function 确保-记录结构 { param( [Parameter(Mandatory = $true)] [pscustomobject]$记录 ) 确保-对象属性 $记录 "版本" 1 确保-对象属性 $记录 "最后更新" "" 确保-对象属性 $记录 "镜像" ([pscustomobject]@{}) if ($null -eq $记录.镜像) { $记录.镜像 = [pscustomobject]@{} } return $记录 } function 确保-镜像统计结构 { param( [Parameter(Mandatory = $true)] [pscustomobject]$统计 ) 确保-对象属性 $统计 "成功次数" 0 确保-对象属性 $统计 "失败次数" 0 确保-对象属性 $统计 "最近结果" "" 确保-对象属性 $统计 "最近耗时毫秒" $null 确保-对象属性 $统计 "最后尝试时间" "" 确保-对象属性 $统计 "最后成功时间" "" 确保-对象属性 $统计 "最后失败时间" "" return $统计 } function 读取-镜像记录 { param( [Parameter(Mandatory = $true)] [string]$路径 ) if (-not (Test-Path -LiteralPath $路径)) { return 新建-镜像记录 } $内容 = Get-Content -Raw -LiteralPath $路径 if ([string]::IsNullOrWhiteSpace($内容)) { return 新建-镜像记录 } $记录 = $内容 | ConvertFrom-Json return 确保-记录结构 $记录 } function 保存-镜像记录 { param( [Parameter(Mandatory = $true)] [pscustomobject]$记录, [Parameter(Mandatory = $true)] [string]$路径 ) $目录 = Split-Path -Parent $路径 if (-not [string]::IsNullOrWhiteSpace($目录) -and -not (Test-Path -LiteralPath $目录)) { New-Item -ItemType Directory -Path $目录 | Out-Null } $记录.最后更新 = (Get-Date).ToString("o") $记录 | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $路径 -Encoding UTF8 } function 读取-镜像统计 { param( [Parameter(Mandatory = $true)] [pscustomobject]$记录, [Parameter(Mandatory = $true)] [string]$镜像地址 ) $镜像属性 = $记录.镜像.PSObject.Properties[$镜像地址] if ($null -eq $镜像属性) { $统计 = [pscustomobject]@{} $记录.镜像 | Add-Member -NotePropertyName $镜像地址 -NotePropertyValue $统计 return 确保-镜像统计结构 $统计 } return 确保-镜像统计结构 $镜像属性.Value } function 计算-镜像评分 { param( [Parameter(Mandatory = $true)] [pscustomobject]$统计 ) $成功次数 = [int]$统计.成功次数 $失败次数 = [int]$统计.失败次数 $总次数 = $成功次数 + $失败次数 if ($总次数 -gt 0) { $成功率 = [double]$成功次数 / [double]$总次数 } else { $成功率 = 0.5 } $最近结果修正 = 0 if ($统计.最近结果 -eq "成功") { $最近结果修正 = 10 } elseif ($统计.最近结果 -eq "失败") { $最近结果修正 = -10 } return ($成功率 * 100) + ($成功次数 * 2) - ($失败次数 * 2) + $最近结果修正 } function 记录-镜像尝试 { param( [Parameter(Mandatory = $true)] [pscustomobject]$记录, [Parameter(Mandatory = $true)] [string]$镜像地址, [Parameter(Mandatory = $true)] [bool]$成功, [Parameter(Mandatory = $true)] [int]$耗时毫秒 ) $统计 = 读取-镜像统计 $记录 $镜像地址 $当前时间 = (Get-Date).ToString("o") $统计.最后尝试时间 = $当前时间 $统计.最近耗时毫秒 = $耗时毫秒 if ($成功) { $统计.成功次数 = [int]$统计.成功次数 + 1 $统计.最近结果 = "成功" $统计.最后成功时间 = $当前时间 } else { $统计.失败次数 = [int]$统计.失败次数 + 1 $统计.最近结果 = "失败" $统计.最后失败时间 = $当前时间 } } function 转换为-HTTPS仓库地址 { param( [Parameter(Mandatory = $true)] [string]$仓库地址 ) if ($仓库地址 -match "^git@github\.com:(.+)$") { return "https://github.com/$($Matches[1])" } if ($仓库地址 -match "^ssh://git@github\.com/(.+)$") { return "https://github.com/$($Matches[1])" } return $仓库地址 } function 合成-镜像仓库地址 { param( [Parameter(Mandatory = $true)] [string]$镜像站, [Parameter(Mandatory = $true)] [string]$仓库地址 ) return "$($镜像站.TrimEnd('/'))/$仓库地址" } # ============================================================ # 公开函数 # ============================================================ <# .SYNOPSIS 通过多个 GitHub 镜像站点自动尝试 git fetch,按历史成功率智能排序。 .DESCRIPTION 自动检测当前仓库的 origin 远程和当前分支,通过多个镜像代理站点依次尝试 git fetch, 并根据历史成功/失败记录智能排序,优先使用成功率最高的镜像。拉取成功后自动执行快进合并(--ff-only)。 .PARAMETER 镜像站前缀 镜像站地址前缀列表,默认内置 5 个国内常用镜像站。 .PARAMETER 远程名 Git 远程名称,默认为 "origin"。 .PARAMETER 记录文件路径 镜像统计记录文件的保存路径,默认为 "$env:TEMP\镜像拉取记录.json"。 .EXAMPLE 拉取-GitHub镜像 从 origin 拉取当前分支,使用默认镜像站列表,快进合并。 .EXAMPLE 拉取-GitHub镜像 -镜像站前缀 "https://gh.llkk.cc/", "https://gh-proxy.com/" 仅使用指定的两个镜像站。 .EXAMPLE 拉取-GitHub镜像 -远程名 "upstream" 从 upstream 远程拉取当前分支。 #> function 拉取-GitHub镜像 { [CmdletBinding()] param( [Parameter(Position = 0)] [string[]]$镜像站前缀 = @( "https://gh.llkk.cc/", "https://gh-proxy.com/", "https://ghfast.top/", "https://gh-proxy.net/", "https://hub.gitmirror.com/" ), [Parameter()] [string]$远程名 = "origin", [Parameter()] [string]$记录文件路径 = (Join-Path $env:TEMP "镜像拉取记录.json") ) $分支 = 读取-Git文本 @("branch", "--show-current") if ([string]::IsNullOrWhiteSpace($分支)) { throw "当前处于分离 HEAD 状态,无法执行加速拉取。" } $源仓库地址 = 读取-Git文本 @("remote", "get-url", $远程名) $HTTPS仓库地址 = 转换为-HTTPS仓库地址 $源仓库地址 $候选镜像地址 = @() foreach ($镜像站 in $镜像站前缀) { if (-not [string]::IsNullOrWhiteSpace($镜像站)) { $候选镜像地址 += 合成-镜像仓库地址 $镜像站 $HTTPS仓库地址 } } $候选镜像地址 = @($候选镜像地址 | Select-Object -Unique) if ($候选镜像地址.Count -eq 0) { throw "没有可用的镜像站地址,请检查 -镜像站前缀 参数。" } $镜像记录 = 读取-镜像记录 $记录文件路径 $候选镜像条目 = @() $原始序号 = 0 foreach ($镜像地址 in $候选镜像地址) { $统计 = 读取-镜像统计 $镜像记录 $镜像地址 $评分 = 计算-镜像评分 $统计 $候选镜像条目 += [pscustomobject]@{ 地址 = $镜像地址 评分 = $评分 排序值 = ($评分 * 1000000) - $原始序号 } $原始序号 += 1 } $候选镜像条目 = @($候选镜像条目 | Sort-Object -Property 排序值 -Descending) $成功镜像地址 = "" foreach ($镜像条目 in $候选镜像条目) { $镜像地址 = $镜像条目.地址 $显示评分 = [math]::Round($镜像条目.评分, 2) Write-Host "尝试从镜像拉取(评分 $显示评分):$镜像地址" $开始时间 = Get-Date $拉取成功 = 尝试-Git命令 @("fetch", $镜像地址, $分支) $耗时毫秒 = [int]((Get-Date) - $开始时间).TotalMilliseconds 记录-镜像尝试 $镜像记录 $镜像地址 $拉取成功 $耗时毫秒 保存-镜像记录 $镜像记录 $记录文件路径 if ($拉取成功) { $成功镜像地址 = $镜像地址 break } Write-Host "该镜像不可用,继续尝试下一个。" } if ([string]::IsNullOrWhiteSpace($成功镜像地址)) { throw "所有镜像站都未能获取 $分支。" } Write-Host "将当前分支快进到镜像中的 $分支..." 执行-Git命令 @("merge", "--ff-only", "FETCH_HEAD") $最新提交 = 读取-Git文本 @("log", "-1", "--pretty=format:%h %s") Write-Host "完成。当前最新提交:$最新提交" } <# .SYNOPSIS 通过镜像站点克隆 GitHub 仓库,仅获取默认分支的最新提交(浅克隆)。 .DESCRIPTION 接受仓库远程地址和本地路径作为参数,使用与拉取相同的动态镜像排序策略, 依次尝试克隆,成功后记录镜像统计。仅克隆默认分支(--single-branch)且 只获取最新一次提交(--depth 1),适合快速获取大型仓库的最新代码。 .PARAMETER 仓库地址 GitHub 仓库的 HTTPS 或 SSH 地址,如 https://github.com/用户/仓库.git 或 git@github.com:用户/仓库.git。 .PARAMETER 本地路径 克隆到的本地目标路径。若已存在则报错退出。 .PARAMETER 镜像站前缀 镜像站地址前缀列表,默认内置 5 个国内常用镜像站。 .PARAMETER 记录文件路径 镜像统计记录文件的保存路径,默认为 "$env:TEMP\镜像拉取记录.json"。 .EXAMPLE 克隆-GitHub镜像 -仓库地址 "https://github.com/PowerShell/PowerShell.git" -本地路径 "D:\PowerShell" 通过镜像站克隆 PowerShell 仓库到本地 D:\PowerShell。 .EXAMPLE 克隆-GitHub镜像 "https://github.com/torvalds/linux.git" "D:\linux" -镜像站前缀 "https://gh.llkk.cc/" 仅使用指定镜像站克隆 Linux 内核仓库。 #> function 克隆-GitHub镜像 { [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [string]$仓库地址, [Parameter(Mandatory = $true, Position = 1)] [string]$本地路径, [Parameter(Position = 2)] [string[]]$镜像站前缀 = @( "https://gh.llkk.cc/", "https://gh-proxy.com/", "https://ghfast.top/", "https://gh-proxy.net/", "https://hub.gitmirror.com/" ), [Parameter()] [string]$记录文件路径 = (Join-Path $env:TEMP "镜像拉取记录.json") ) if (Test-Path -LiteralPath $本地路径) { throw "目标路径已存在:$本地路径" } $HTTPS仓库地址 = 转换为-HTTPS仓库地址 $仓库地址 $候选镜像地址 = @() foreach ($镜像站 in $镜像站前缀) { if (-not [string]::IsNullOrWhiteSpace($镜像站)) { $候选镜像地址 += 合成-镜像仓库地址 $镜像站 $HTTPS仓库地址 } } $候选镜像地址 = @($候选镜像地址 | Select-Object -Unique) if ($候选镜像地址.Count -eq 0) { throw "没有可用的镜像站地址,请检查 -镜像站前缀 参数。" } $镜像记录 = 读取-镜像记录 $记录文件路径 $候选镜像条目 = @() $原始序号 = 0 foreach ($镜像地址 in $候选镜像地址) { $统计 = 读取-镜像统计 $镜像记录 $镜像地址 $评分 = 计算-镜像评分 $统计 $候选镜像条目 += [pscustomobject]@{ 地址 = $镜像地址 评分 = $评分 排序值 = ($评分 * 1000000) - $原始序号 } $原始序号 += 1 } $候选镜像条目 = @($候选镜像条目 | Sort-Object -Property 排序值 -Descending) $成功镜像地址 = "" foreach ($镜像条目 in $候选镜像条目) { $镜像地址 = $镜像条目.地址 $显示评分 = [math]::Round($镜像条目.评分, 2) Write-Host "尝试从镜像克隆(评分 $显示评分):$镜像地址" $开始时间 = Get-Date $克隆成功 = 尝试-Git命令 @("clone", "--depth", "1", "--single-branch", $镜像地址, $本地路径) $耗时毫秒 = [int]((Get-Date) - $开始时间).TotalMilliseconds 记录-镜像尝试 $镜像记录 $镜像地址 $克隆成功 $耗时毫秒 保存-镜像记录 $镜像记录 $记录文件路径 if ($克隆成功) { $成功镜像地址 = $镜像地址 break } Write-Host "该镜像不可用,继续尝试下一个。" # 克隆失败会残留空目录,清理之 if (Test-Path -LiteralPath $本地路径) { Remove-Item -LiteralPath $本地路径 -Recurse -Force -ErrorAction SilentlyContinue } } if ([string]::IsNullOrWhiteSpace($成功镜像地址)) { throw "所有镜像站都未能克隆仓库:$仓库地址" } Push-Location $本地路径 try { $最新提交 = 读取-Git文本 @("log", "-1", "--pretty=format:%h %s") Write-Host "克隆完成。当前最新提交:$最新提交" } finally { Pop-Location } } <# .SYNOPSIS 查看所有镜像站的历史成功率统计。 .DESCRIPTION 读取本地镜像拉取记录,按评分高低列出所有镜像站的成功次数、失败次数、成功率和最后尝试时间。 .PARAMETER 记录文件路径 镜像统计记录文件的保存路径,默认为 "$env:TEMP\镜像拉取记录.json"。 .EXAMPLE 查看-镜像统计 查看默认记录文件中的镜像统计。 .EXAMPLE 查看-镜像统计 -记录文件路径 "D:\MyData\镜像记录.json" 查看指定记录文件中的镜像统计。 #> function 查看-镜像统计 { [CmdletBinding()] param( [Parameter()] [string]$记录文件路径 = (Join-Path $env:TEMP "镜像拉取记录.json") ) $镜像记录 = 读取-镜像记录 $记录文件路径 if ($镜像记录.镜像.PSObject.Properties.Count -eq 0) { Write-Host "暂无镜像使用记录。" return } $统计列表 = @() foreach ($属性 in $镜像记录.镜像.PSObject.Properties) { $统计 = 确保-镜像统计结构 $属性.Value $评分 = 计算-镜像评分 $统计 $总次数 = [int]$统计.成功次数 + [int]$统计.失败次数 if ($总次数 -gt 0) { $成功率 = [math]::Round(([double]$统计.成功次数 / [double]$总次数) * 100, 1) } else { $成功率 = $null } $统计列表 += [pscustomobject]@{ 镜像地址 = $属性.Name 成功次数 = [int]$统计.成功次数 失败次数 = [int]$统计.失败次数 成功率百分比 = $成功率 评分 = [math]::Round($评分, 2) 最近结果 = $统计.最近结果 最近耗时毫秒 = $统计.最近耗时毫秒 最后尝试 = $统计.最后尝试时间 } } $统计列表 | Sort-Object -Property 评分 -Descending | Format-Table -AutoSize } <# .SYNOPSIS 重置所有镜像站的历史统计记录。 .DESCRIPTION 删除本地镜像拉取记录文件,下次运行时将从零开始统计各镜像站的成功率。 .PARAMETER 记录文件路径 镜像统计记录文件的保存路径,默认为 "$env:TEMP\镜像拉取记录.json"。 .EXAMPLE 重置-镜像统计 重置默认记录文件。 #> function 重置-镜像统计 { [CmdletBinding()] param( [Parameter()] [string]$记录文件路径 = (Join-Path $env:TEMP "镜像拉取记录.json") ) if (Test-Path -LiteralPath $记录文件路径) { Remove-Item -LiteralPath $记录文件路径 -Force Write-Host "已删除镜像统计记录:$记录文件路径" } else { Write-Host "镜像统计记录文件不存在,无需重置。" } } # ============================================================ # 模块初始化:加载时输出提示 # ============================================================ Write-Verbose "GitHub加速 模块已加载。使用 '拉取-GitHub镜像' 开始拉取。" -Verbose:$false |