使用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编码的支持,确保无论是输出还是输入中文字符,都能正确显示。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?