Files
LanMountainDesktop/scripts/Analyze-GitCommits.ps1
lincube d8f75e86be Add IPC backoff/retries and safer disposal
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.
2026-05-07 21:39:21 +08:00

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