Public/拉取-GitHub镜像.ps1

<#
.SYNOPSIS
    通过多个 GitHub 镜像站点自动尝试 git fetch,按历史成功率智能排序。
.DESCRIPTION
    自动检测当前仓库的 origin 远程和当前分支,通过多个镜像代理站点依次尝试 git fetch,
    并根据历史成功/失败记录智能排序,优先使用成功率最高的镜像。拉取成功后自动执行快进合并(--ff-only)。
    若快进合并失败(如本地与远程历史分叉),会交互式询问是否强行用远程版本覆盖本地分支。
.PARAMETER 仓库目录
    本地仓库目录,默认为当前工作目录。
.PARAMETER 镜像站前缀
    镜像站地址前缀列表,默认内置多个国内常用镜像站,并保留各网络环境下都可能可用的新老镜像。
.PARAMETER 远程名
    Git 远程名称,默认为 "origin"。
.PARAMETER 记录文件路径
    镜像统计记录文件的保存路径,默认为 "$env:TEMP\镜像拉取记录.json"。
.EXAMPLE
    拉取-GitHub镜像
    从 origin 拉取当前分支,使用默认镜像站列表,快进合并。
.EXAMPLE
    拉取-GitHub镜像 "D:\Repo\my-project"
    对指定仓库目录执行加速拉取。
.EXAMPLE
    拉取-GitHub镜像 -镜像站前缀 "https://gh-proxy.cn/", "https://gh-proxy.org/"
    仅使用指定的两个镜像站。
.EXAMPLE
    拉取-GitHub镜像 -远程名 "upstream"
    从 upstream 远程拉取当前分支。
#>

function 拉取-GitHub镜像 {
    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [string]$仓库目录 = ".",

        [Parameter(Position = 1)]
        [string[]]$镜像站前缀 = $script:镜像站前缀,

        [Parameter()]
        [string]$远程名 = "origin",

        [Parameter()]
        [string]$记录文件路径 = $script:默认记录路径
    )

    if (-not (Test-Path -LiteralPath $仓库目录 -PathType Container)) {
        throw "仓库目录不存在:$仓库目录"
    }

    Push-Location -LiteralPath $仓库目录
    try {
        $分支 = 读取-Git文本 @("branch", "--show-current")
        if ([string]::IsNullOrWhiteSpace($分支)) {
            throw "当前处于分离 HEAD 状态,无法执行加速拉取。"
        }

        $源仓库地址 = 读取-Git文本 @("remote", "get-url", $远程名)
        $HTTPS仓库地址 = 转换为-HTTPS仓库地址 $源仓库地址

        $镜像记录 = 读取-镜像记录 $记录文件路径

        # 统计按镜像站(前缀)记录,与具体仓库无关,可跨仓库共享历史经验;
        # 候选条目按各站历史成功率排序
        $候选镜像条目 = 排序-镜像候选 $镜像记录 $镜像站前缀 $HTTPS仓库地址

        $成功镜像地址 = ""
        foreach ($镜像条目 in $候选镜像条目) {
            $镜像地址 = $镜像条目.地址
            $显示评分 = [math]::Round($镜像条目.评分 * 100, 1)
            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")

        if (-not $快进成功) {
            $覆盖确认 = $PSCmdlet.ShouldContinue(
                "快进合并失败(本地与远程历史分叉或不相关)。强行覆盖将丢弃本地提交及未提交的修改,并用远程版本完全替换本地分支,是否继续?",
                "覆盖本地分支")
            if ($覆盖确认) {
                Write-Host "正在用远程版本覆盖本地分支..."
                执行-Git命令 @("reset", "--hard", "FETCH_HEAD")
            }
            else {
                throw "已取消覆盖,本地分支未发生变化。"
            }
        }

        $最新提交 = 读取-Git文本 @("log", "-1", "--pretty=format:%h %s")
        Write-Host "完成。当前最新提交:$最新提交"
    }
    finally {
        Pop-Location
    }
}