mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
Introduce exponential backoff, jitter and retry logic across IPC components to improve robustness and avoid tight retry loops; make disposal idempotent and add connection guards. Key changes: - LauncherCoordinatorIpcServer / LauncherIpcServer: add backoff constants, ComputeBackoff(), consecutive error tracking and delayed retries with jitter. - LanMountainDesktopIpcClient / LauncherIpcClient: add connect retry loops, timeouts, delayed retries, improved error logging, and use ArrayPool for buffered async writes; ensure proper cleanup on failures. - PublicIpcHostService: add disposed flag, guard OnPeerConnected and Dispose, and clear connected peers on dispose. - Add many auto-generated commit analysis docs under docs/auto_commit_md and new scripts for analyzing/generating commit docs. These changes aim to make IPC connection handling more resilient and resource-safe.
553 lines
16 KiB
PowerShell
553 lines
16 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Git Commit 深度分析工具
|
|
用于解析 Git 对象文件并生成详细的代码变更分析报告
|
|
#>
|
|
|
|
param(
|
|
[string]$RepoPath = "d:\github\LanMountainDesktop",
|
|
[string]$OutputDir = "docs\auto_commit_md"
|
|
)
|
|
|
|
# 添加压缩支持
|
|
Add-Type -AssemblyName System.IO.Compression
|
|
|
|
function Read-GitObject {
|
|
param([string]$RepoPath, [string]$ObjHash)
|
|
|
|
if ($ObjHash.Length -lt 4) { return $null }
|
|
|
|
$objDir = $ObjHash.Substring(0, 2)
|
|
$objFile = $ObjHash.Substring(2)
|
|
$objPath = Join-Path $RepoPath ".git\objects\$objDir\$objFile"
|
|
|
|
if (-not (Test-Path $objPath)) { return $null }
|
|
|
|
try {
|
|
$compressedData = [System.IO.File]::ReadAllBytes($objPath)
|
|
|
|
# 使用 .NET 解压缩
|
|
$ms = New-Object System.IO.MemoryStream(,$compressedData)
|
|
$deflate = New-Object System.IO.Compression.DeflateStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
|
|
$reader = New-Object System.IO.StreamReader($deflate)
|
|
$content = $reader.ReadToEnd()
|
|
$reader.Close()
|
|
$deflate.Close()
|
|
$ms.Close()
|
|
|
|
# 解析对象头
|
|
$nullIdx = $content.IndexOf("`0")
|
|
if ($nullIdx -eq -1) { return $null }
|
|
|
|
$header = $content.Substring(0, $nullIdx)
|
|
$body = $content.Substring($nullIdx + 1)
|
|
$objType = $header.Split(' ')[0]
|
|
|
|
return @{
|
|
Type = $objType
|
|
Content = $body
|
|
RawContent = [System.Text.Encoding]::UTF8.GetBytes($body)
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host "Error reading object ${ObjHash}: $_" -ForegroundColor Red
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Parse-Commit {
|
|
param([string]$RepoPath, [string]$CommitHash)
|
|
|
|
$obj = Read-GitObject -RepoPath $RepoPath -ObjHash $CommitHash
|
|
if (-not $obj -or $obj.Type -ne 'commit') { return $null }
|
|
|
|
$content = $obj.Content
|
|
$lines = $content -split "`n"
|
|
|
|
$parent = $null
|
|
$tree = $null
|
|
$author = $null
|
|
$email = $null
|
|
$timestamp = $null
|
|
$timezone = $null
|
|
$messageLines = @()
|
|
$inMessage = $false
|
|
|
|
foreach ($line in $lines) {
|
|
if ($inMessage) {
|
|
$messageLines += $line
|
|
}
|
|
elseif ($line -match '^tree (.+)') {
|
|
$tree = $matches[1].Trim()
|
|
}
|
|
elseif ($line -match '^parent (.+)') {
|
|
$parent = $matches[1].Trim()
|
|
}
|
|
elseif ($line -match '^author (.+) <(.+)> (\d+) ([+-]\d+)') {
|
|
$author = $matches[1]
|
|
$email = $matches[2]
|
|
$timestamp = [int]$matches[3]
|
|
$timezone = $matches[4]
|
|
}
|
|
elseif ($line -eq '') {
|
|
$inMessage = $true
|
|
}
|
|
}
|
|
|
|
$message = ($messageLines -join "`n").Trim()
|
|
|
|
return @{
|
|
Hash = $CommitHash
|
|
Parent = $parent
|
|
Tree = $tree
|
|
Author = $author
|
|
Email = $email
|
|
Timestamp = $timestamp
|
|
Timezone = $timezone
|
|
Message = $message
|
|
}
|
|
}
|
|
|
|
function Parse-Tree {
|
|
param([string]$RepoPath, [string]$TreeHash)
|
|
|
|
$obj = Read-GitObject -RepoPath $RepoPath -ObjHash $TreeHash
|
|
if (-not $obj -or $obj.Type -ne 'tree') { return @{} }
|
|
|
|
$entries = @{}
|
|
$content = $obj.RawContent
|
|
$idx = 0
|
|
|
|
while ($idx -lt $content.Length) {
|
|
# 查找空格
|
|
$spaceIdx = [Array]::IndexOf($content, [byte][char]' ', $idx)
|
|
if ($spaceIdx -eq -1) { break }
|
|
|
|
$mode = [System.Text.Encoding]::UTF8.GetString($content[$idx..($spaceIdx-1)])
|
|
|
|
# 查找 null
|
|
$nullIdx = [Array]::IndexOf($content, [byte]0, $spaceIdx)
|
|
if ($nullIdx -eq -1) { break }
|
|
|
|
$name = [System.Text.Encoding]::UTF8.GetString($content[($spaceIdx+1)..($nullIdx-1)])
|
|
|
|
# 读取 20 字节 SHA
|
|
$shaStart = $nullIdx + 1
|
|
$shaEnd = $shaStart + 20
|
|
if ($shaEnd -gt $content.Length) { break }
|
|
|
|
$shaBytes = $content[$shaStart..($shaEnd-1)]
|
|
$sha = [BitConverter]::ToString($shaBytes).Replace("-", "").ToLower()
|
|
|
|
$entries[$name] = $sha
|
|
$idx = $shaEnd
|
|
}
|
|
|
|
return $entries
|
|
}
|
|
|
|
function Get-CommitChanges {
|
|
param([string]$RepoPath, [string]$CommitHash)
|
|
|
|
$commit = Parse-Commit -RepoPath $RepoPath -CommitHash $CommitHash
|
|
if (-not $commit) { return @() }
|
|
|
|
$currentTree = Parse-Tree -RepoPath $RepoPath -TreeHash $commit.Tree
|
|
$parentTree = @{}
|
|
|
|
if ($commit.Parent) {
|
|
$parentCommit = Parse-Commit -RepoPath $RepoPath -CommitHash $commit.Parent
|
|
if ($parentCommit) {
|
|
$parentTree = Parse-Tree -RepoPath $RepoPath -TreeHash $parentCommit.Tree
|
|
}
|
|
}
|
|
|
|
$changes = @()
|
|
$stats = @{ Added = 0; Modified = 0; Deleted = 0 }
|
|
|
|
$allPaths = ($currentTree.Keys + $parentTree.Keys) | Select-Object -Unique
|
|
|
|
foreach ($path in $allPaths) {
|
|
if ($currentTree.ContainsKey($path) -and -not $parentTree.ContainsKey($path)) {
|
|
$changes += @{ Path = $path; Type = 'added' }
|
|
$stats.Added++
|
|
}
|
|
elseif (-not $currentTree.ContainsKey($path) -and $parentTree.ContainsKey($path)) {
|
|
$changes += @{ Path = $path; Type = 'deleted' }
|
|
$stats.Deleted++
|
|
}
|
|
elseif ($currentTree[$path] -ne $parentTree[$path]) {
|
|
$changes += @{ Path = $path; Type = 'modified' }
|
|
$stats.Modified++
|
|
}
|
|
}
|
|
|
|
return @{
|
|
Changes = $changes
|
|
Stats = $stats
|
|
Commit = $commit
|
|
}
|
|
}
|
|
|
|
function Assess-Importance {
|
|
param([string]$Message, [array]$Changes, [hashtable]$Stats)
|
|
|
|
$msgLower = $Message.ToLower()
|
|
|
|
$criticalKeywords = @('fix', 'bug', 'security', 'crash', 'memory leak', 'deadlock')
|
|
$featureKeywords = @('feat', 'feature', 'add', 'implement', 'new')
|
|
$refactorKeywords = @('refactor', 'restructure', 'cleanup', 'optimize')
|
|
|
|
foreach ($kw in $criticalKeywords) {
|
|
if ($msgLower -like "*$kw*") { return 'critical' }
|
|
}
|
|
|
|
foreach ($kw in $featureKeywords) {
|
|
if ($msgLower -like "*$kw*") { return 'feature' }
|
|
}
|
|
|
|
$totalChanges = $Stats.Added + $Stats.Modified + $Stats.Deleted
|
|
if ($totalChanges -gt 20) { return 'major' }
|
|
|
|
foreach ($kw in $refactorKeywords) {
|
|
if ($msgLower -like "*$kw*") { return 'refactor' }
|
|
}
|
|
|
|
return 'minor'
|
|
}
|
|
|
|
function Get-FileTypeDistribution {
|
|
param([array]$Changes)
|
|
|
|
$fileTypes = @{}
|
|
foreach ($change in $Changes) {
|
|
$ext = [System.IO.Path]::GetExtension($change.Path)
|
|
if ([string]::IsNullOrEmpty($ext)) { $ext = 'no_extension' }
|
|
if (-not $fileTypes.ContainsKey($ext)) { $fileTypes[$ext] = 0 }
|
|
$fileTypes[$ext]++
|
|
}
|
|
return $fileTypes
|
|
}
|
|
|
|
function Analyze-Impact {
|
|
param([array]$Changes, [string]$Message)
|
|
|
|
$impacts = @()
|
|
|
|
# 分析受影响的模块
|
|
$modules = @{}
|
|
foreach ($change in $Changes) {
|
|
$parts = $change.Path -split '/'
|
|
if ($parts.Length -gt 1) {
|
|
if (-not $modules.ContainsKey($parts[0])) { $modules[$parts[0]] = 0 }
|
|
$modules[$parts[0]]++
|
|
}
|
|
}
|
|
|
|
if ($modules.Count -gt 0) {
|
|
$moduleList = ($modules.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 5 | ForEach-Object { $_.Key }) -join ', '
|
|
$impacts += "受影响的模块: $moduleList"
|
|
}
|
|
|
|
# 分析文件类型
|
|
$fileTypes = Get-FileTypeDistribution -Changes $Changes
|
|
if ($fileTypes.ContainsKey('.cs')) {
|
|
$impacts += "涉及 $($fileTypes['.cs']) 个 C# 文件变更"
|
|
}
|
|
if ($fileTypes.ContainsKey('.axaml') -or $fileTypes.ContainsKey('.xaml')) {
|
|
$impacts += "涉及 UI/XAML 文件变更"
|
|
}
|
|
if ($fileTypes.ContainsKey('.md')) {
|
|
$impacts += "涉及文档更新"
|
|
}
|
|
|
|
# 根据提交消息分析
|
|
$msgLower = $Message.ToLower()
|
|
if ($msgLower -like '*fix*') {
|
|
$impacts += "这是一个修复性提交,可能解决现有问题"
|
|
}
|
|
if ($msgLower -like '*feat*' -or $msgLower -like '*feature*') {
|
|
$impacts += "这是一个功能新增提交,扩展了项目能力"
|
|
}
|
|
if ($msgLower -like '*refactor*') {
|
|
$impacts += "这是一个重构提交,改善了代码结构"
|
|
}
|
|
|
|
return $impacts
|
|
}
|
|
|
|
function Generate-ReviewPoints {
|
|
param([array]$Changes, [string]$Message)
|
|
|
|
$points = @()
|
|
|
|
# 检查关键文件
|
|
$criticalPatterns = @('Program.cs', 'App.axaml', 'MainWindow', 'Core', 'Service')
|
|
foreach ($change in $Changes) {
|
|
foreach ($pattern in $criticalPatterns) {
|
|
if ($change.Path -like "*$pattern*") {
|
|
$points += "关键文件变更: $($change.Path) - 需要特别关注"
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
# 检查提交消息质量
|
|
if ($Message.Length -lt 10) {
|
|
$points += "提交消息较短,建议提供更详细的变更说明"
|
|
}
|
|
|
|
if ($Message.ToLower() -like '*wip*' -or $Message.ToLower() -like '*todo*') {
|
|
$points += "提交包含 WIP/TODO 标记,确认是否已完成"
|
|
}
|
|
|
|
# 检查文件删除
|
|
$deleted = $Changes | Where-Object { $_.Type -eq 'deleted' }
|
|
if ($deleted.Count -gt 0) {
|
|
$points += "删除了 $($deleted.Count) 个文件,确认是否有其他代码依赖这些文件"
|
|
}
|
|
|
|
return $points
|
|
}
|
|
|
|
function Get-KeySnippets {
|
|
param([string]$RepoPath, [array]$Changes)
|
|
|
|
$snippets = @()
|
|
$count = 0
|
|
|
|
foreach ($change in $Changes | Select-Object -First 10) {
|
|
if ($change.Type -eq 'deleted') { continue }
|
|
|
|
$filePath = Join-Path $RepoPath $change.Path
|
|
if (Test-Path $filePath -PathType Leaf) {
|
|
try {
|
|
$content = Get-Content $filePath -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
|
|
if ($content) {
|
|
$lines = $content -split "`n"
|
|
$preview = if ($lines.Count -gt 30) { ($lines[0..29] -join "`n") } else { $content }
|
|
|
|
$snippets += @{
|
|
File = $change.Path
|
|
Type = $change.Type
|
|
LinesCount = $lines.Count
|
|
Preview = $preview.Substring(0, [Math]::Min(2000, $preview.Length))
|
|
}
|
|
$count++
|
|
}
|
|
}
|
|
catch {
|
|
# 忽略无法读取的文件
|
|
}
|
|
}
|
|
}
|
|
|
|
return $snippets
|
|
}
|
|
|
|
function Generate-MarkdownReport {
|
|
param([hashtable]$Analysis)
|
|
|
|
$lines = @()
|
|
|
|
# 标题
|
|
$lines += "# Commit 深度分析报告"
|
|
$lines += ""
|
|
$lines += "**提交哈希**: ``$($Analysis.CommitHash)``"
|
|
$lines += "**提交时间**: $($Analysis.Date)"
|
|
$lines += "**作者**: $($Analysis.Author) <$($Analysis.Email)>"
|
|
$lines += "**重要性**: $($Analysis.Importance.ToUpper())"
|
|
$lines += ""
|
|
|
|
# 提交消息
|
|
$lines += "## 提交消息"
|
|
$lines += "``````"
|
|
$lines += $Analysis.Message
|
|
$lines += "``````"
|
|
$lines += ""
|
|
|
|
# 变更统计
|
|
$lines += "## 变更统计"
|
|
$lines += "- **新增文件**: $($Analysis.Stats.Added)"
|
|
$lines += "- **修改文件**: $($Analysis.Stats.Modified)"
|
|
$lines += "- **删除文件**: $($Analysis.Stats.Deleted)"
|
|
$lines += ""
|
|
|
|
# 文件类型分布
|
|
if ($Analysis.FileTypes.Count -gt 0) {
|
|
$lines += "### 文件类型分布"
|
|
$sortedTypes = $Analysis.FileTypes.GetEnumerator() | Sort-Object Value -Descending
|
|
foreach ($ft in $sortedTypes) {
|
|
$lines += "- ``$($ft.Key)``: $($ft.Value) 个文件"
|
|
}
|
|
$lines += ""
|
|
}
|
|
|
|
# 变更文件列表
|
|
if ($Analysis.Changes.Count -gt 0) {
|
|
$lines += "## 变更文件列表"
|
|
$lines += "| 文件路径 | 变更类型 |"
|
|
$lines += "|---------|---------|"
|
|
$typeMap = @{ 'added' = '新增'; 'modified' = '修改'; 'deleted' = '删除' }
|
|
foreach ($change in $Analysis.Changes | Select-Object -First 50) {
|
|
$typeStr = if ($typeMap.ContainsKey($change.Type)) { $typeMap[$change.Type] } else { $change.Type }
|
|
$lines += "| ``$($change.Path)`` | $typeStr |"
|
|
}
|
|
$lines += ""
|
|
}
|
|
|
|
# 影响分析
|
|
if ($Analysis.ImpactAnalysis.Count -gt 0) {
|
|
$lines += "## 影响分析"
|
|
foreach ($impact in $Analysis.ImpactAnalysis) {
|
|
$lines += "- $impact"
|
|
}
|
|
$lines += ""
|
|
}
|
|
|
|
# 代码审查要点
|
|
if ($Analysis.ReviewPoints.Count -gt 0) {
|
|
$lines += "## 代码审查要点"
|
|
foreach ($point in $Analysis.ReviewPoints) {
|
|
$lines += "- ⚠️ $point"
|
|
}
|
|
$lines += ""
|
|
}
|
|
|
|
# 关键代码片段
|
|
if ($Analysis.KeySnippets.Count -gt 0) {
|
|
$lines += "## 关键代码片段"
|
|
foreach ($snippet in $Analysis.KeySnippets | Select-Object -First 5) {
|
|
$lines += "### $($snippet.File)"
|
|
$lines += "- **类型**: $($snippet.Type)"
|
|
$lines += "- **行数**: $($snippet.LinesCount)"
|
|
$lines += ""
|
|
$lines += "``````"
|
|
$lines += $snippet.Preview
|
|
$lines += "``````"
|
|
$lines += ""
|
|
}
|
|
}
|
|
|
|
return $lines -join "`n"
|
|
}
|
|
|
|
# 主逻辑
|
|
Write-Host "Git Commit 深度分析工具" -ForegroundColor Cyan
|
|
Write-Host "======================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# 确保输出目录存在
|
|
$outputPath = Join-Path $RepoPath $OutputDir
|
|
if (-not (Test-Path $outputPath)) {
|
|
New-Item -ItemType Directory -Path $outputPath -Force | Out-Null
|
|
}
|
|
|
|
# 读取 HEAD 日志
|
|
$headLogPath = Join-Path $RepoPath ".git\logs\HEAD"
|
|
if (-not (Test-Path $headLogPath)) {
|
|
Write-Host "错误: 找不到 HEAD 日志文件: $headLogPath" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
# 解析 HEAD 日志
|
|
$commits = @()
|
|
$logContent = Get-Content $headLogPath
|
|
|
|
foreach ($line in $logContent) {
|
|
$line = $line.Trim()
|
|
if ([string]::IsNullOrEmpty($line)) { continue }
|
|
|
|
# 解析日志行
|
|
$parts = $line -split "`t"
|
|
if ($parts.Count -lt 2) { continue }
|
|
|
|
$metaPart = $parts[0]
|
|
$actionPart = $parts[1]
|
|
|
|
$metaTokens = $metaPart -split '\s+'
|
|
if ($metaTokens.Count -lt 5) { continue }
|
|
|
|
$newHash = $metaTokens[1]
|
|
|
|
# 只处理 commit 操作
|
|
if ($actionPart -match 'commit' -or $actionPart -match '^commit:') {
|
|
$message = $actionPart -replace '^commit:\s*', ''
|
|
$commits += @{
|
|
Hash = $newHash
|
|
Message = $message
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Host "找到 $($commits.Count) 个 commit" -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
# 分析每个 commit
|
|
$processed = 0
|
|
$success = 0
|
|
|
|
foreach ($commitInfo in $commits) {
|
|
$commitHash = $commitInfo.Hash
|
|
$shortHash = $commitHash.Substring(0, 7)
|
|
$processed++
|
|
|
|
Write-Host "[$processed/$($commits.Count)] 分析 commit: $shortHash - $($commitInfo.Message.Substring(0, [Math]::Min(50, $commitInfo.Message.Length)))" -NoNewline
|
|
|
|
try {
|
|
# 获取变更
|
|
$changeInfo = Get-CommitChanges -RepoPath $RepoPath -CommitHash $commitHash
|
|
if (-not $changeInfo) {
|
|
Write-Host " [跳过]" -ForegroundColor Yellow
|
|
continue
|
|
}
|
|
|
|
$commit = $changeInfo.Commit
|
|
$changes = $changeInfo.Changes
|
|
$stats = $changeInfo.Stats
|
|
|
|
# 分析
|
|
$importance = Assess-Importance -Message $commit.Message -Changes $changes -Stats $stats
|
|
$fileTypes = Get-FileTypeDistribution -Changes $changes
|
|
$impactAnalysis = Analyze-Impact -Changes $changes -Message $commit.Message
|
|
$reviewPoints = Generate-ReviewPoints -Changes $changes -Message $commit.Message
|
|
$keySnippets = Get-KeySnippets -RepoPath $RepoPath -Changes $changes
|
|
|
|
# 构建分析结果
|
|
$analysis = @{
|
|
CommitHash = $commitHash
|
|
Message = $commit.Message
|
|
Author = $commit.Author
|
|
Email = $commit.Email
|
|
Timestamp = $commit.Timestamp
|
|
Date = (Get-Date -Date ([DateTime]::UnixEpoch.AddSeconds($commit.Timestamp)) -Format 'yyyy-MM-dd HH:mm:ss')
|
|
Stats = $stats
|
|
Changes = $changes
|
|
FileTypes = $fileTypes
|
|
Importance = $importance
|
|
ImpactAnalysis = $impactAnalysis
|
|
ReviewPoints = $reviewPoints
|
|
KeySnippets = $keySnippets
|
|
}
|
|
|
|
# 生成报告
|
|
$report = Generate-MarkdownReport -Analysis $analysis
|
|
|
|
# 保存报告
|
|
$dateStr = Get-Date -Date ([DateTime]::UnixEpoch.AddSeconds($commit.Timestamp)) -Format 'yyyyMMdd'
|
|
$filename = "${dateStr}_${shortHash}_deep_analysis.md"
|
|
$outputFile = Join-Path $outputPath $filename
|
|
|
|
$report | Out-File -FilePath $outputFile -Encoding UTF8
|
|
|
|
Write-Host " [已保存]" -ForegroundColor Green
|
|
$success++
|
|
}
|
|
catch {
|
|
Write-Host " [错误: $_]" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "分析完成! 成功处理 $success / $processed 个 commit" -ForegroundColor Cyan
|