Files
LanMountainDesktop/scripts/analyze_git_commits.py
lincube 7a70476ce8 合并对设置系统的更新 (#11)
* Add Windows system chrome patchers (Harmony)

Introduce support for toggling the system chrome on Windows using Harmony patchers. Adds Lib.Harmony.Thin to package props and project, new patcher infrastructure (ChromePatchState, PatcherEntrance) and two Harmony patches that disable FluentAvalonia's Windows chrome when configured. Program.cs now loads the chrome setting and installs patchers conditionally on Windows/x86-x64. Settings viewmodel and view updated: expose IsWindowsOs, require restart on appearance changes, migrate SettingsWindow to FAAppWindow and adapt titlebar/layout (include Windows caption placeholder and footer menu items). Also add a .gitkeep and a build log file.

* Refactor settings window UI and theming

Improve theming and layout for the Settings window and related services.

- MaterialSurfaceService: add special material parameters for SettingsWindowBackground (lower alpha, no blur) and avoid hot-switching real backdrops for non-settings windows.
- GlassEffectService: add AdaptiveSettingsWindowTintBrush + ResolveSettingsWindowTintAlpha to provide optional content tinting tied to system material mode.
- SettingsWindowService: refactor theme application into ApplyThemeVariantAndResources, ensure settings window material is applied at show/activate times, and tidy theme/resource application flow.
- SettingsWindow.axaml / .axaml.cs: restructure title bar (separate Grid.Row=0 border) and FANavigationView host, add pane-footer toggle button for :minimal layout, use dynamic corner radius resource, and update toggle/visibility/icon logic and responsive layout code.
- SettingsPages: remove some IconText usages and adjust margins; use DesignCornerRadiusLg for update card corner radius.
- Add NuGet.Config to set local globalPackagesFolder and ignore .nuget/packages in .gitignore.

These changes aim to improve visuals, avoid backdrop overdraw, and make the settings window behavior consistent across themes and layouts.

* Add localization and localize settings pages

Add many new localization keys (en-US and zh-CN) for notifications, developer tools, about page, status bar, and video wallpaper. Update Notification, Dev, About and StatusBar view models to use LocalizationService, expose localized ObservableProperties, and refresh localized text at construction. Localize selection options and test notification texts, and fix notification severity handling. Wire up XAML to the new localized properties (About/Dev/StatusBar pages) and update the settings page title for notifications. Also adjust copyright line generation and replace hardcoded placeholders with bound Watermark properties.

* Redesign settings window with fluent shell & search

Rebuild the settings window as a Fluent shell: adds a custom 48-DIP titlebar with Back, pane toggle, icon/title, search box, restart/more menu, and caption-button spacer; moves compact pane toggle into the titlebar and preserves FANavigationView as the primary navigation surface. Introduces a SettingsSearchService (with UI AutoComplete integration, search indexing, navigation-by-result, and search result highlighting) plus focused tests for search filtering and theme material normalization. Adds navigation history/back stack, updates SettingsViewModels for new bindings and localization keys, and updates General/Apearance pages to expose new strings and options. Implements an "auto" system material mode: default in AppSettingsSnapshot, new MaterialAuto constants and normalization/resolution logic in ThemeAppearanceValues, WindowMaterialService and MaterialSurfaceService adjustments to prefer Mica on Win11 and Acrylic on Win10 using TransparencyLevelHint. GlassEffectService and AppearanceThemeService updated to use effective material mode and to track live theme state changes. Adds localization entries (en-US, zh-CN), spec/tasks docs, and other UI/style tweaks to support the redesign.

* fix.修折叠与展开按钮

* Add OOBE startup presentation and settings merge

Introduce a new OOBE step for "Startup & Presentation" that exposes startup and UI preferences in OobeWindow (toggles for taskbar, slide/fade transitions, fused popup, and autostart). Add HostAppSettingsOobeMerger to read/write Host settings.json (PascalCase fields) and MergeStartupPresentation behavior, plus LauncherWindowsStartupService to sync the current Launcher into the Windows Run key on Windows. Wire UI handlers, persist choices on Next, and load defaults when entering the step. Include unit tests for the merger, adjust SettingsWindow navigation pane/toggle handling, and update docs/LAUNCHER.md to describe the new OOBE step and implementation files.

* Move whiteboard persistence to file storage

Switch whiteboard note storage from legacy DB rows to per-note JSON files and add migration support. Update WhiteboardNoteSnapshot schema (version bump, viewport, canvas, expires, PathSvgData) and change IWhiteboardNotePersistenceService.SaveNote to return bool to surface write failures (e.g. read-only files). Implement file-based WhiteboardNotePersistenceService with legacy DB migration/cleanup, retention handling, and logging. Add comprehensive unit tests for persistence, stroke path builder, SVG import and viewport helper. Also add ThirdParty/DotNetCampus.InkCanvas project and reference it in the main csproj, and bump PostHog package to 2.6.0.

* Introduce render gate and chart caching

Replace UI DispatcherTimer polling with a StudySnapshotRenderGate across multiple widgets to queue and apply only the latest analytics snapshot; components updated include StudyDeductionReasonsWidget, StudyEnvironmentWidget, StudyInterruptDensityWidget, StudyNoiseCurveWidget. Add StudySnapshotRenderGate implementation to coordinate rendering and monitoring leases and update subscription/lease lifecycle handling (subscribe/unsubscribe, Acquire/Dispose leases, Clear/Dispose gate). Rewrite chart controls (StudyNoiseCurveChartControl and StudyNoiseDistributionScatterChartControl) to use stable logical-time origins, split series into static vs dynamic tails, add geometry/sample caching, stable jitter/coordinate mapping helpers, and expose internal helpers & counts for testing. Add unit tests (StudyComponentRenderingTests) covering the render gate and chart behaviors (layer counts, logical X mapping, stable jitter, cache rebuild). These changes improve rendering correctness and performance by avoiding redundant renders and enabling deterministic chart layout.

* Use MaterialColorSnapshot in appearance flow

Introduce unified material/color spec and tests, and refactor appearance plumbing to use MaterialColorSnapshot as the single source of truth. Add .trae material-color-service spec/checklist/tasks and integration/unit tests for plugin mapping and appearance VM behavior. AppearanceChangedEvent extended with new appearance change flags and HasChanged logic. ComponentEditorMaterialThemeAdapter rewritten to accept MaterialColorSnapshot and derive palette from snapshot data. Simplify AppearanceSettingsPageViewModel and related view code: remove legacy preview/custom-seed UI logic, preserve material/color fields when updating theme or corner radius, and update save calls to use with-expressions. Update ComponentEditorWindow to use adapter-provided OnPrimary brush and minor docs updates.

* Add material color services, plugin DTOs, and tests

Introduce IPC wire-format appearance DTOs (PluginIsolation.Contracts) and clarify they are distinct from the runtime PluginSdk snapshot. Update PluginSdk comments to document the runtime-facing snapshot shape. Change ComponentColorSchemeHelper to use the HostMaterialColorProvider and add an overload that accepts a MaterialColorSnapshot. Add new services and pipelines (MaterialColorService, MaterialSurfaceService, WindowMaterialService, WallpaperColorPipeline) and refactor AppearanceThemeService to depend on MaterialColorService while removing legacy internal implementations. Add multiple unit tests (ComponentColorSchemeHelper, PluginAppearanceBoundary, SettingsCatalogService, WallpaperSettingsPageViewModel) and update localization resources with new material_color and wallpaper keys.

* Add CODE_WIKI and update localization

Add a comprehensive CODE_WIKI.md documenting project architecture, modules, startup flow, plugin system, testing and developer workflows. Update localization resources (en-US.json, zh-CN.json) with new/translated keys for wallpaper controls (custom color UI), material & color settings (semantic roles, surfaces, refresh/polling state), appearance (corner radius), status bar font size options, privacy policy text, component library labels, clock settings, and new language entry (Korean). Also modify settings-related ViewModels and Settings page views to surface these new features and texts (MaterialColorSettingsPageViewModel.cs, SettingsViewModels.cs, WallpaperSettingsPageViewModel.cs, MainWindow.SettingsHardCut.Stubs.cs, ComponentsSettingsPage.axaml, WallpaperSettingsPage.axaml).

* Add Data settings page and storage scanner

Introduce a new "Data" settings page to visualize and manage local app storage. Adds DataStorageService (scanning, disk info, clean operations), DataSettingsPageViewModel, XAML view and code-behind, and HexToColor/HexToBrush converters; registers converters in App.axaml. Also update localization strings for the new page and add icon mapping so the settings entry uses the Database icon. Enables per-category and global cleaning workflows and formatted size display.

* 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.

* Add preview controls and settings UI tweaks

Introduce GridPreviewControl and CornerRadiusPreviewControl for visual previews and wire them into the Components settings (add ScreenAspectRatio, CornerRadiusPreviewValue, and screen aspect init). Refactor ComponentsSettingsPage UI to show live previews. Improve DataSettingsPage layout and storage bar logic (use item percentages directly, include remaining segment, adjust visuals and visibility triggers). Simplify LauncherSettingsPage header/appearance layout. Add SECURITY_AUDIT_REPORT.md, analysis summary, mockup HTML, and a local .claude settings file.

* Add install checkpoint/resume and DDSS workflows

Introduce install checkpoint support and resume logic for updates, plus related locking and validation. Adds InstallCheckpoint model, AppJsonContext serialization, and UpdatePaths helpers for deployment lock, apply-in-progress lock and install-checkpoint path. UpdateEngineService gains checkpoint load/save/delete, incoming-state validation, resume logic for PLONDS and legacy updates, apply lock handling, and safer cleanup; ApplyPendingPlondsUpdateAsync and ApplyPendingUpdate flow updated accordingly. Add DeploymentLock contract and extend UpdateState with pause/resume/cancel helpers. Tests updated to cover stale/valid checkpoint resume and legacy/PLONDS flows. CI: enhance ddss-publish to detect release channel, validate S3 assets, prepare and atomically publish channel pointer; add ddss-rollback workflow to publish rollbacks; adjust plonds-build concurrency and release events.

* changed.更了好多

* fix.消息盒子媒体播放器组件服务修复

* change.重做天气,为回到系统提供自定义功能。

* feat.airapp与融合桌面

* feat.动画优化与更新界面

* feat.数字时钟,白板功能修复

* feat.完善了时钟轻应用,为启动器提供了多语言支持

* feat.发布与打包优化

* changed.天气选项卡更新
2026-05-19 07:55:21 +08:00

601 lines
20 KiB
Python

#!/usr/bin/env python3
"""
Git Commit 深度分析工具
用于解析 Git 对象文件并生成详细的代码变更分析报告
"""
import zlib
import os
import re
import json
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Any
from dataclasses import dataclass, field
from collections import defaultdict
@dataclass
class GitObject:
"""Git 对象基类"""
obj_type: str
content: bytes
raw_data: bytes
@dataclass
class CommitInfo:
"""提交信息"""
hash: str
parent: Optional[str]
tree: str
author: str
email: str
timestamp: int
timezone: str
message: str
changes: List[Dict] = field(default_factory=list)
stats: Dict = field(default_factory=dict)
@dataclass
class FileChange:
"""文件变更信息"""
path: str
change_type: str # added, modified, deleted, renamed
old_path: Optional[str] = None
additions: int = 0
deletions: int = 0
diff_content: str = ""
class GitObjectParser:
"""Git 对象解析器"""
def __init__(self, repo_path: str):
self.repo_path = Path(repo_path)
self.objects_path = self.repo_path / ".git" / "objects"
self.commit_cache: Dict[str, CommitInfo] = {}
self.tree_cache: Dict[str, Dict[str, str]] = {}
def read_object(self, obj_hash: str) -> Optional[GitObject]:
"""读取并解压缩 Git 对象"""
if len(obj_hash) < 4:
return None
obj_dir = obj_hash[:2]
obj_file = obj_hash[2:]
obj_path = self.objects_path / obj_dir / obj_file
if not obj_path.exists():
return None
try:
with open(obj_path, 'rb') as f:
compressed_data = f.read()
# 解压缩 zlib
decompressed = zlib.decompress(compressed_data)
# 解析对象头和内容
null_idx = decompressed.index(b'\x00')
header = decompressed[:null_idx].decode('utf-8')
content = decompressed[null_idx + 1:]
obj_type = header.split()[0]
return GitObject(obj_type=obj_type, content=content, raw_data=decompressed)
except Exception as e:
print(f"Error reading object {obj_hash}: {e}")
return None
def parse_commit(self, commit_hash: str) -> Optional[CommitInfo]:
"""解析 commit 对象"""
if commit_hash in self.commit_cache:
return self.commit_cache[commit_hash]
obj = self.read_object(commit_hash)
if not obj or obj.obj_type != 'commit':
return None
try:
content = obj.content.decode('utf-8', errors='replace')
lines = content.split('\n')
parent = None
tree = None
author = None
email = None
timestamp = None
timezone = None
message_lines = []
in_message = False
for line in lines:
if in_message:
message_lines.append(line)
elif line.startswith('tree '):
tree = line[5:].strip()
elif line.startswith('parent '):
parent = line[7:].strip()
elif line.startswith('author '):
# author name <email> timestamp timezone
match = re.match(r'author (.+) <(.+)> (\d+) ([+-]\d+)', line)
if match:
author = match.group(1)
email = match.group(2)
timestamp = int(match.group(3))
timezone = match.group(4)
elif line == '':
in_message = True
message = '\n'.join(message_lines).strip()
commit_info = CommitInfo(
hash=commit_hash,
parent=parent,
tree=tree,
author=author or "Unknown",
email=email or "",
timestamp=timestamp or 0,
timezone=timezone or "",
message=message
)
self.commit_cache[commit_hash] = commit_info
return commit_info
except Exception as e:
print(f"Error parsing commit {commit_hash}: {e}")
return None
def parse_tree(self, tree_hash: str) -> Dict[str, str]:
"""解析 tree 对象,返回文件路径到 blob hash 的映射"""
if tree_hash in self.tree_cache:
return self.tree_cache[tree_hash]
obj = self.read_object(tree_hash)
if not obj or obj.obj_type != 'tree':
return {}
entries = {}
content = obj.content
idx = 0
while idx < len(content):
# 查找空格分隔符
space_idx = content.find(b' ', idx)
if space_idx == -1:
break
mode = content[idx:space_idx].decode('utf-8')
# 查找 null 分隔符
null_idx = content.find(b'\x00', space_idx)
if null_idx == -1:
break
name = content[space_idx + 1:null_idx].decode('utf-8', errors='replace')
# 读取 20 字节的 SHA
sha_start = null_idx + 1
sha_end = sha_start + 20
if sha_end > len(content):
break
sha = content[sha_start:sha_end].hex()
entries[name] = sha
idx = sha_end
self.tree_cache[tree_hash] = entries
return entries
def get_blob_content(self, blob_hash: str) -> Optional[str]:
"""获取 blob 对象的内容"""
obj = self.read_object(blob_hash)
if not obj or obj.obj_type != 'blob':
return None
try:
return obj.content.decode('utf-8', errors='replace')
except:
return None
def compare_trees(self, old_tree: str, new_tree: str) -> List[FileChange]:
"""比较两个 tree 对象,返回文件变更列表"""
old_files = self.parse_tree(old_tree) if old_tree else {}
new_files = self.parse_tree(new_tree) if new_tree else {}
changes = []
# 查找新增和修改的文件
for path, new_hash in new_files.items():
if path not in old_files:
changes.append(FileChange(path=path, change_type='added'))
elif old_files[path] != new_hash:
changes.append(FileChange(path=path, change_type='modified'))
# 查找删除的文件
for path in old_files:
if path not in new_files:
changes.append(FileChange(path=path, change_type='deleted'))
return changes
def get_commit_changes(self, commit_hash: str) -> Tuple[List[FileChange], Dict]:
"""获取提交的所有变更"""
commit = self.parse_commit(commit_hash)
if not commit:
return [], {}
# 获取当前提交的 tree
current_tree = self.parse_tree(commit.tree)
# 获取父提交的 tree
parent_tree = {}
if commit.parent:
parent_commit = self.parse_commit(commit.parent)
if parent_commit:
parent_tree = self.parse_tree(parent_commit.tree)
changes = []
stats = {'added': 0, 'modified': 0, 'deleted': 0, 'total_additions': 0, 'total_deletions': 0}
# 比较 tree
all_paths = set(current_tree.keys()) | set(parent_tree.keys())
for path in all_paths:
if path in current_tree and path not in parent_tree:
# 新增文件
changes.append(FileChange(path=path, change_type='added'))
stats['added'] += 1
elif path not in current_tree and path in parent_tree:
# 删除文件
changes.append(FileChange(path=path, change_type='deleted'))
stats['deleted'] += 1
elif current_tree.get(path) != parent_tree.get(path):
# 修改文件
changes.append(FileChange(path=path, change_type='modified'))
stats['modified'] += 1
return changes, stats
class CommitAnalyzer:
"""提交分析器"""
def __init__(self, repo_path: str):
self.parser = GitObjectParser(repo_path)
self.repo_path = Path(repo_path)
def analyze_commit(self, commit_hash: str) -> Dict[str, Any]:
"""分析单个提交"""
commit = self.parser.parse_commit(commit_hash)
if not commit:
return {}
changes, stats = self.parser.get_commit_changes(commit_hash)
# 分析文件类型
file_types = defaultdict(int)
for change in changes:
ext = Path(change.path).suffix or 'no_extension'
file_types[ext] += 1
# 分析变更的重要性
importance = self._assess_importance(commit.message, changes, stats)
# 提取关键代码片段
key_snippets = self._extract_key_snippets(changes)
return {
'commit_hash': commit_hash,
'message': commit.message,
'author': commit.author,
'email': commit.email,
'timestamp': commit.timestamp,
'date': datetime.fromtimestamp(commit.timestamp).strftime('%Y-%m-%d %H:%M:%S'),
'parent': commit.parent,
'changes': [
{
'path': c.path,
'type': c.change_type,
'additions': c.additions,
'deletions': c.deletions
}
for c in changes
],
'stats': stats,
'file_types': dict(file_types),
'importance': importance,
'key_snippets': key_snippets,
'impact_analysis': self._analyze_impact(changes, commit.message),
'review_points': self._generate_review_points(changes, commit.message)
}
def _assess_importance(self, message: str, changes: List[FileChange], stats: Dict) -> str:
"""评估提交的重要性"""
message_lower = message.lower()
# 检查关键关键词
critical_keywords = ['fix', 'bug', 'security', 'crash', 'memory leak', 'deadlock']
feature_keywords = ['feat', 'feature', 'add', 'implement', 'new']
refactor_keywords = ['refactor', 'restructure', 'cleanup', 'optimize']
if any(kw in message_lower for kw in critical_keywords):
return 'critical'
elif any(kw in message_lower for kw in feature_keywords):
return 'feature'
elif stats.get('added', 0) + stats.get('modified', 0) + stats.get('deleted', 0) > 20:
return 'major'
elif any(kw in message_lower for kw in refactor_keywords):
return 'refactor'
else:
return 'minor'
def _extract_key_snippets(self, changes: List[FileChange]) -> List[Dict]:
"""提取关键代码片段"""
snippets = []
for change in changes[:10]: # 限制分析的文件数量
if change.change_type == 'deleted':
continue
# 尝试读取文件内容
file_path = self.repo_path / change.path
if file_path.exists() and file_path.is_file():
try:
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
# 提取文件的基本信息
lines = content.split('\n')
snippet = {
'file': change.path,
'type': change.change_type,
'lines_count': len(lines),
'preview': '\n'.join(lines[:30]) if len(lines) > 30 else content
}
snippets.append(snippet)
except Exception:
pass
return snippets
def _analyze_impact(self, changes: List[FileChange], message: str) -> List[str]:
"""分析变更对项目的影响"""
impacts = []
# 分析受影响的模块
affected_modules = set()
for change in changes:
parts = change.path.split('/')
if len(parts) > 1:
affected_modules.add(parts[0])
if affected_modules:
impacts.append(f"受影响的模块: {', '.join(sorted(affected_modules))}")
# 分析文件类型影响
file_types = defaultdict(int)
for change in changes:
ext = Path(change.path).suffix
if ext:
file_types[ext] += 1
if '.cs' in file_types:
impacts.append(f"涉及 {file_types['.cs']} 个 C# 文件变更")
if '.axaml' in file_types or '.xaml' in file_types:
impacts.append("涉及 UI/XAML 文件变更")
if '.md' in file_types:
impacts.append("涉及文档更新")
# 根据提交消息分析
message_lower = message.lower()
if 'fix' in message_lower:
impacts.append("这是一个修复性提交,可能解决现有问题")
if 'feat' in message_lower or 'feature' in message_lower:
impacts.append("这是一个功能新增提交,扩展了项目能力")
if 'refactor' in message_lower:
impacts.append("这是一个重构提交,改善了代码结构")
if 'test' in message_lower:
impacts.append("涉及测试相关变更")
return impacts
def _generate_review_points(self, changes: List[FileChange], message: str) -> List[str]:
"""生成代码审查要点"""
points = []
# 检查大文件变更
large_files = [c for c in changes if c.additions + c.deletions > 100]
if large_files:
points.append(f"注意: 有 {len(large_files)} 个文件变更超过 100 行,需要仔细审查")
# 检查关键文件
critical_patterns = ['Program.cs', 'App.axaml', 'MainWindow', 'Core', 'Service']
for change in changes:
for pattern in critical_patterns:
if pattern in change.path:
points.append(f"关键文件变更: {change.path} - 需要特别关注")
break
# 检查提交消息质量
if len(message) < 10:
points.append("提交消息较短,建议提供更详细的变更说明")
if 'wip' in message.lower() or 'todo' in message.lower():
points.append("提交包含 WIP/TODO 标记,确认是否已完成")
# 检查文件删除
deleted = [c for c in changes if c.change_type == 'deleted']
if deleted:
points.append(f"删除了 {len(deleted)} 个文件,确认是否有其他代码依赖这些文件")
return points
def generate_markdown_report(analysis: Dict[str, Any]) -> str:
"""生成 Markdown 格式的分析报告"""
lines = []
# 标题
lines.append(f"# Commit 深度分析报告")
lines.append(f"")
lines.append(f"**提交哈希**: `{analysis['commit_hash']}`")
lines.append(f"**提交时间**: {analysis['date']}")
lines.append(f"**作者**: {analysis['author']} <{analysis['email']}>")
lines.append(f"**重要性**: {analysis['importance'].upper()}")
lines.append(f"")
# 提交消息
lines.append(f"## 提交消息")
lines.append(f"```")
lines.append(analysis['message'])
lines.append(f"```")
lines.append(f"")
# 变更统计
lines.append(f"## 变更统计")
stats = analysis['stats']
lines.append(f"- **新增文件**: {stats.get('added', 0)}")
lines.append(f"- **修改文件**: {stats.get('modified', 0)}")
lines.append(f"- **删除文件**: {stats.get('deleted', 0)}")
lines.append(f"")
# 文件类型分布
if analysis.get('file_types'):
lines.append(f"### 文件类型分布")
for ext, count in sorted(analysis['file_types'].items(), key=lambda x: -x[1]):
lines.append(f"- `{ext}`: {count} 个文件")
lines.append(f"")
# 变更文件列表
if analysis.get('changes'):
lines.append(f"## 变更文件列表")
lines.append(f"| 文件路径 | 变更类型 |")
lines.append(f"|---------|---------|")
type_map = {'added': '新增', 'modified': '修改', 'deleted': '删除'}
for change in analysis['changes'][:50]: # 限制显示数量
change_type = type_map.get(change['type'], change['type'])
lines.append(f"| `{change['path']}` | {change_type} |")
lines.append(f"")
# 影响分析
if analysis.get('impact_analysis'):
lines.append(f"## 影响分析")
for impact in analysis['impact_analysis']:
lines.append(f"- {impact}")
lines.append(f"")
# 代码审查要点
if analysis.get('review_points'):
lines.append(f"## 代码审查要点")
for point in analysis['review_points']:
lines.append(f"- ⚠️ {point}")
lines.append(f"")
# 关键代码片段
if analysis.get('key_snippets'):
lines.append(f"## 关键代码片段")
for snippet in analysis['key_snippets'][:5]:
lines.append(f"### {snippet['file']}")
lines.append(f"- **类型**: {snippet['type']}")
lines.append(f"- **行数**: {snippet['lines_count']}")
lines.append(f"")
lines.append(f"```")
lines.append(snippet['preview'][:2000]) # 限制预览长度
lines.append(f"```")
lines.append(f"")
return '\n'.join(lines)
def main():
"""主函数"""
repo_path = r"d:\github\LanMountainDesktop"
output_dir = Path(repo_path) / "docs" / "auto_commit_md"
# 确保输出目录存在
output_dir.mkdir(parents=True, exist_ok=True)
# 读取 HEAD 日志
head_log_path = Path(repo_path) / ".git" / "logs" / "HEAD"
if not head_log_path.exists():
print(f"错误: 找不到 HEAD 日志文件: {head_log_path}")
return
# 解析 HEAD 日志获取所有 commit
commits = []
with open(head_log_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
# 解析日志行
# 格式: old_hash new_hash name <email> timestamp timezone\taction: message
parts = line.split('\t')
if len(parts) < 2:
continue
meta_part = parts[0]
action_part = parts[1]
meta_tokens = meta_part.split()
if len(meta_tokens) < 5:
continue
new_hash = meta_tokens[1]
# 只处理 commit 操作
if 'commit' in action_part or action_part.startswith('commit:'):
message = action_part.replace('commit:', '').strip()
commits.append({
'hash': new_hash,
'message': message
})
print(f"找到 {len(commits)} 个 commit")
# 初始化分析器
analyzer = CommitAnalyzer(repo_path)
# 分析每个 commit
for i, commit_info in enumerate(commits):
commit_hash = commit_info['hash']
short_hash = commit_hash[:7]
print(f"[{i+1}/{len(commits)}] 分析 commit: {short_hash} - {commit_info['message'][:50]}")
try:
# 分析提交
analysis = analyzer.analyze_commit(commit_hash)
if not analysis:
print(f" 跳过: 无法解析 commit {short_hash}")
continue
# 生成报告
report = generate_markdown_report(analysis)
# 保存报告
date_str = datetime.fromtimestamp(analysis['timestamp']).strftime('%Y%m%d')
filename = f"{date_str}_{short_hash}_deep_analysis.md"
output_path = output_dir / filename
with open(output_path, 'w', encoding='utf-8') as f:
f.write(report)
print(f" 已保存: {filename}")
except Exception as e:
print(f" 错误: 分析 commit {short_hash} 时出错: {e}")
import traceback
traceback.print_exc()
print("\n分析完成!")
if __name__ == "__main__":
main()