使用PowerShell开发脚本程序进行批量SVN提交

使用PowerShell开发脚本程序进行批量SVN提交

随着软件开发的不断进步,版本控制系统如Subversion (SVN) 成为了团队协作和代码管理的重要工具。当需要一次性提交大量文件时,手动操作效率低下且容易出错。为此,可以使用PowerShell编写一个脚本来简化这一过程,并通过PS2EXE将脚本转换为.exe文件,以方便非技术用户执行。升级到PowerShell 7解决了输出中文字符时的乱码问题。

主脚本

主程序逻辑包括询问用户SVN工作目录、验证目录有效性、扫描目录并调用批量提交函数。在结束时,确保返回到原始位置:


# 在脚本开头添加
$scriptPath = $PSScriptRoot
if ([string]::IsNullOrEmpty($scriptPath)) {
    $scriptPath = Split-Path -Parent ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)
}

# 修改默认SVN路径逻辑
$configPath = Join-Path $scriptPath "config.json"
$defaultSvnPath = "E:\safeware\Apache-Subversion-1.14.2\bin\svn.exe"

# 尝试从配置文件读取SVN路径
if (Test-Path $configPath) {
    try {
        $config = Get-Content $configPath -Raw | ConvertFrom-Json
        $defaultSvnPath = $config.svnPath
    } catch {
        Write-Host "读取配置文件失败,使用默认SVN路径" -ForegroundColor Yellow
    }
}

do {
    Write-Host "请输入SVN可执行文件路径 (直接回车使用默认路径: $defaultSvnPath):"
    $inputPath = Read-Host
    
    if ([string]::IsNullOrWhiteSpace($inputPath)) {
        $svnExecutable = $defaultSvnPath
    } else {
        $svnExecutable = $inputPath
    }

    if (-not (Test-Path $svnExecutable)) {
        Write-Host "SVN可执行文件路径无效或不存在,请重新输入。" -ForegroundColor Red
        continue
    }
    
    # 测试SVN命令是否可用
    try {
        $svnVersion = & $svnExecutable --version 2>&1
        if ($LASTEXITCODE -eq 0) {
            Write-Host "已成功找到SVN程序。" -ForegroundColor Green
            break
        } else {
            Write-Host "SVN程序测试失败,请检查路径是否正确。" -ForegroundColor Red
        }
    } catch {
        Write-Host "无法执行SVN程序,请检查路径是否正确。" -ForegroundColor Red
    }
} while ($true)

# 在脚本开头添加
Add-Type -AssemblyName System.Web

# 函数:执行SVN命令并处理错误
function Invoke-SvnCommand {
    param (
        [string[]]$Arguments,
        [switch]$IgnoreError
    )
    try {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $svnExecutable
        $pinfo.Arguments = $Arguments -join ' '
        $pinfo.RedirectStandardOutput = $true
        $pinfo.RedirectStandardError = $true
        $pinfo.UseShellExecute = $false
        $pinfo.StandardOutputEncoding = [System.Text.Encoding]::Default
        $pinfo.StandardErrorEncoding = [System.Text.Encoding]::Default
        $pinfo.CreateNoWindow = $true
        
        Write-Host "执行命令: svn $($pinfo.Arguments)" -ForegroundColor Gray
        
        $process = New-Object System.Diagnostics.Process
        $process.StartInfo = $pinfo
        $process.Start() | Out-Null
        $output = $process.StandardOutput.ReadToEnd()
        $errorOutput = $process.StandardError.ReadToEnd()
        $process.WaitForExit()
        
        if ($process.ExitCode -ne 0 -and -not $IgnoreError) {
            Write-Host "SVN命令执行失败: $errorOutput" -ForegroundColor Red
            return $false
        }
        if (-not [string]::IsNullOrEmpty($output)) {
            Write-Host $output
        }
        return $true
    }
    catch {
        Write-Host "执行SVN命令时出错: $_" -ForegroundColor Red
        return $false
    }
}

# 函数:检查目录是否在SVN工作副本中
function Test-SvnWorkingCopy {
    param (
        [string]$Path
    )
    
    $currentPath = $Path
    while ($currentPath) {
        try {
            $pinfo = New-Object System.Diagnostics.ProcessStartInfo
            $pinfo.FileName = $svnExecutable
            $pinfo.Arguments = "info `"$currentPath`""
            $pinfo.RedirectStandardOutput = $true
            $pinfo.RedirectStandardError = $true
            $pinfo.UseShellExecute = $false
            $pinfo.StandardOutputEncoding = [System.Text.Encoding]::Default
            $pinfo.StandardErrorEncoding = [System.Text.Encoding]::Default
            $pinfo.CreateNoWindow = $true
            
            $process = New-Object System.Diagnostics.Process
            $process.StartInfo = $pinfo
            $process.Start() | Out-Null
            $output = $process.StandardOutput.ReadToEnd()
            $errorOutput = $process.StandardError.ReadToEnd()
            $process.WaitForExit()
            
            if ($process.ExitCode -eq 0) {
                # 找到了SVN工作副本的根目录
                return $true
            }
        }
        catch {
            Write-Host "检查SVN工作副本时出错: $_" -ForegroundColor Red
        }
        
        # 向上查找父目录
        $currentPath = Split-Path -Parent $currentPath
    }
    return $false
}

# 函数:检查路径是否在SVN版本控制中
function Test-SvnPath {
    param (
        [string]$Path
    )
    
    try {
        # 使用 Start-Process 来执行 SVN 命令
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $svnExecutable
        
        # 直接使用路径,不进行编码转换
        $pinfo.Arguments = "status --non-recursive `"$Path`""
        $pinfo.RedirectStandardOutput = $true
        $pinfo.RedirectStandardError = $true
        $pinfo.UseShellExecute = $false
        # 设置为默认的系统编码(通常是GBK)
        $pinfo.StandardOutputEncoding = [System.Text.Encoding]::Default
        $pinfo.StandardErrorEncoding = [System.Text.Encoding]::Default
        $pinfo.CreateNoWindow = $true
        
        Write-Host "准备执行命令: $($pinfo.FileName) $($pinfo.Arguments)" -ForegroundColor Gray
        
        $process = New-Object System.Diagnostics.Process
        $process.StartInfo = $pinfo
        $process.Start() | Out-Null
        
        # 同时读取标准输出和错误输出
        $output = $process.StandardOutput.ReadToEnd()
        $errorOutput = $process.StandardError.ReadToEnd()
        $process.WaitForExit()
        
        Write-Host "命令输出:"
        if ([string]::IsNullOrEmpty($output)) {
            Write-Host "  标准输出为空"
        } else {
            Write-Host $output
        }
        
        if (-not [string]::IsNullOrEmpty($errorOutput)) {
            Write-Host "错误输出:" -ForegroundColor Yellow
            Write-Host $errorOutput -ForegroundColor Yellow
        }
        
        Write-Host "退出代码: $($process.ExitCode)"
        Write-Host "检查路径: $Path"
        Write-Host "--------------------------------"
        
        if ($process.ExitCode -eq 0) {
            if ([string]::IsNullOrEmpty($output)) {
                return $true
            }
            if ($output -match '^\?') {
                return $false
            }
            return $true
        }
        return $false
    }
    catch {
        Write-Host "检查路径状态时出错: $_" -ForegroundColor Red
        Write-Host "错误详情: $($_.Exception.Message)" -ForegroundColor Red
        Write-Host "堆栈跟踪: $($_.ScriptStackTrace)" -ForegroundColor Red
        return $false
    }
}

# 函数:批量提交文件
function Submit-BatchFiles {
    param (
        [string[]]$Files,
        [int]$BatchNumber,
        [int]$ProcessedFiles,
        [int]$TotalFiles
    )
    
    if ($Files.Count -eq 0) { return }
    
    Write-Host "[批次 $BatchNumber] [总进度: $ProcessedFiles/$TotalFiles] 正在提交 $($Files.Count) 个文件..."
    Write-Host "提交的文件列表:"
    $Files | ForEach-Object { Write-Host "  $_" }
    
    $commitMessage = "Batch commit #$BatchNumber ($ProcessedFiles/$TotalFiles)"
    
    try {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $svnExecutable
        
        # 获取文件的绝对路径并构建命令参数
        $absolutePaths = $Files | ForEach-Object { 
            # 从当前工作目录获取绝对路径
            $absolutePath = Join-Path (Get-Location).Path $_
            # 规范化路径(处理 .. 和 . 等)
            $absolutePath = [System.IO.Path]::GetFullPath($absolutePath)
            "`"$absolutePath`""
        }
        $fileArgs = [string]::Join(' ', $absolutePaths)
        $pinfo.Arguments = "commit $fileArgs -m `"$commitMessage`""
        
        $pinfo.RedirectStandardOutput = $true
        $pinfo.RedirectStandardError = $true
        $pinfo.UseShellExecute = $false
        $pinfo.StandardOutputEncoding = [System.Text.Encoding]::Default
        $pinfo.StandardErrorEncoding = [System.Text.Encoding]::Default
        $pinfo.CreateNoWindow = $true
        
        Write-Host "执行命令: svn $($pinfo.Arguments)" -ForegroundColor Gray
        
        $process = New-Object System.Diagnostics.Process
        $process.StartInfo = $pinfo
        $process.Start() | Out-Null
        $output = $process.StandardOutput.ReadToEnd()
        $errorOutput = $process.StandardError.ReadToEnd()
        $process.WaitForExit()
        
        Write-Host "命令输出:"
        if ([string]::IsNullOrEmpty($output)) {
            Write-Host "  标准输出为空"
        } else {
            Write-Host $output
        }
        
        if (-not [string]::IsNullOrEmpty($errorOutput)) {
            Write-Host "错误输出:" -ForegroundColor Yellow
            Write-Host $errorOutput -ForegroundColor Yellow
        }
        
        Write-Host "退出代码: $($process.ExitCode)"
        Write-Host "--------------------------------"
        
        if ($process.ExitCode -ne 0) {
            Write-Host "SVN提交失败: $errorOutput" -ForegroundColor Red
            return $false
        }
        return $true
    }
    catch {
        Write-Host "执行SVN提交时出错: $_" -ForegroundColor Red
        Write-Host "错误详情: $($_.Exception.Message)" -ForegroundColor Red
        Write-Host "堆栈跟踪: $($_.ScriptStackTrace)" -ForegroundColor Red
        return $false
    }
}

# 主程序开始
try {
    # 询问用户SVN工作目录
    do {
        $svnDir = Read-Host "请输入SVN工作目录路径"
        if (-not (Test-Path $svnDir)) {
            Write-Host "提供的SVN工作目录路径无效或不存在,请检查后重新输入。" -ForegroundColor Red
            continue
        }
        
        # 检查是否在SVN工作副本中
        if (-not (Test-SvnWorkingCopy $svnDir)) {
            Write-Host "提供的目录不在任何SVN工作副本中,请检查后重新输入。" -ForegroundColor Red
            continue
        }

        break  # 如果验证通过,跳出循环
    } while ($true)

    # 切换到SVN工作目录
    Push-Location $svnDir

    # 获取所有文件夹和文件
    Write-Host "`n正在扫描工作目录..."
    Write-Host "正在获取所有文件夹..."
    # 修改:按照目录深度排序文件夹
    $allDirs = Get-ChildItem -Path $svnDir -Directory -Recurse | 
        Sort-Object @{Expression={($_.FullName -split '\\').Count}; Ascending=$true}
    Write-Host "找到 $($allDirs.Count) 个文件夹"
    
    Write-Host "正在获取所有文件..."
    $allFiles = Get-ChildItem -Path $svnDir -File -Recurse
    Write-Host "找到 $($allFiles.Count) 个文件"

    # 第一阶段:处理所有文件夹
    Write-Host "`n第一阶段:处理所有文件夹..."
    $unversionedDirs = @()
    foreach ($dir in $allDirs) {
        Write-Host "检查文件夹: $($dir.FullName)" -ForegroundColor Gray
        if (-not (Test-SvnPath $dir.FullName)) {
            Write-Host "  未版本控制: $($dir.FullName)" -ForegroundColor Yellow
            $unversionedDirs += $dir.FullName
        }
    }

    # 提交所有未版本控制的文件夹
    if ($unversionedDirs.Count -gt 0) {
        Write-Host "`n正在添加未版本控制的文件夹..."
        foreach ($dir in $unversionedDirs) {
            Write-Host "正在添加文件夹: $dir"
            if (Invoke-SvnCommand @("add", "--depth=empty", "--non-recursive", "`"$dir`"") -IgnoreError) {
                # 立即提交每个文件夹
                $commitMessage = "Add directory: $([System.IO.Path]::GetFileName($dir))"
                # 修改:路径和消息都使用双引号
                $dirPath = "`"$dir`""
                $quotedMessage = "`"$commitMessage`""
                Invoke-SvnCommand @("commit", $dirPath, "-m", $quotedMessage)
            }
        }
    }

    # 第二阶段:处理所有文件
    Write-Host "`n第二阶段:处理所有文件..."
    $unversionedFiles = @()
    foreach ($file in $allFiles) {
        Write-Host "检查文件: $($file.FullName)" -ForegroundColor Gray
        if (-not (Test-SvnPath $file.FullName)) {
            Write-Host "  未版本控制: $($file.FullName)" -ForegroundColor Yellow
            $unversionedFiles += $file.FullName
        }
    }

    # 添加所有未版本控制的文件
    if ($unversionedFiles.Count -gt 0) {
        Write-Host "`n正在添加未版本控制的文件..."
        foreach ($file in $unversionedFiles) {
            Write-Host "正在添加文件: $file"
            Invoke-SvnCommand @("add", $file) -IgnoreError
        }
    }

    # 获取所有需要提交的文件并批量提交
    Write-Host "`n第三阶段:批量提交文件更改..."
    $filesToCommit = @()
    Write-Host "正在获取修改状态..."
    $svnStatus = & $svnExecutable status
    foreach ($line in $svnStatus) {
        if ($line -match '^[AM]\s+(.+)$') {
            $filesToCommit += $matches[1]
            Write-Host "  待提交: $($matches[1])"
        }
    }

    Write-Host "`n共有 $($filesToCommit.Count) 个文件需要提交"

    # 批量提交文件
    $batchSize = 50
    $batchNumber = 1
    $processedFiles = 0

    for ($i = 0; $i -lt $filesToCommit.Count; $i += $batchSize) {
        $batch = $filesToCommit[$i..([Math]::Min($i + $batchSize - 1, $filesToCommit.Count - 1))]
        $processedFiles += $batch.Count
        Submit-BatchFiles -Files $batch -BatchNumber $batchNumber -ProcessedFiles $processedFiles -TotalFiles $filesToCommit.Count
        $batchNumber++
    }

    Write-Host "`n所有操作完成。共处理 $processedFiles 个文件。" -ForegroundColor Green
}
catch {
    Write-Host "发生错误: $_" -ForegroundColor Red
    Write-Host $_.ScriptStackTrace
}
finally {
    # 恢复原始目录
    Pop-Location
    Read-Host "按回车键退出"
}

将PS脚本转换为EXE

为了让脚本更易于分发给其他同事或客户,选择了PS2EXE模块。安装方法如下:

Install-Module -Name PS2EXE

之后,使用ps2exe cmdlet将.ps1脚本转换为独立的.exe文件:

  • 构建脚本
# 设置版本和路径
$version = "1.0.0"
# 使用绝对路径
$scriptPath = $PSScriptRoot
$outputPath = Join-Path $scriptPath "dist"
$iconPath = Join-Path $scriptPath "icon.ico"

# 创建输出目录
New-Item -ItemType Directory -Force -Path $outputPath

# 转换为exe
$exeName = Join-Path $outputPath "SVN批量提交工具.exe"
Invoke-ps2exe `
    -InputFile (Join-Path $scriptPath "batchSvnCommit.ps1") `
    -OutputFile $exeName `
    -NoConsole:$false `
    -RequireAdmin:$false `
    -Version $version `
    -Title "SVN批量提交工具" `
    -Company "Your Company" `
    -Product "SVN Tools" `
    -Copyright "Copyright © 2024" `
    -Description "SVN文件批量提交工具" `
    -IconFile $iconPath

  • 直接构建
ps2exe .\build.ps1

生成的可执行文件可以在没有安装PowerShell的环境中运行,提升了使用的便利性。

解决乱码问题

升级到PowerShell 7解决了输出中文字符时的乱码问题。PowerShell 7基于.NET Core构建,优化了对UTF-8编码的支持,确保无论是输出还是输入中文字符,都能正确显示。

posted @   松哥_ai_自动化  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示