| name | Git分析器 |
| description | 当分析Git仓库、检查提交历史、理解项目演进或查找问题时,分析Git历史和提交。 |
| license | MIT |
Git分析器技能
概述
Git历史是项目的时间线。正确读取它以了解代码如何演进。误读历史会在调试时浪费时间。
核心原则: Git告诉你什么改变了以及为什么改变。
何时使用
始终:
- 理解代码演进
- 查找错误何时引入
- 跟踪功能开发
- 理解重构
- 代码审查
- 性能分析
触发短语:
- "分析Git历史"
- "查找错误引入点"
- "代码演进分析"
- "Git提交历史"
- "分支分析"
- "代码回溯"
Git分析功能
历史分析
- 提交历史查看
- 分支演进跟踪
- 合并历史分析
- 标签历史查看
- 作者贡献统计
代码分析
- 文件变更历史
- 代码行数统计
- 热点文件识别
- 代码复杂度分析
- 技术债务检测
团队分析
- 开发者活跃度
- 贡献分布统计
- 协作模式分析
- 代码审查效率
- 工作时间分析
常见Git问题
提交信息不规范
问题:
提交信息不清晰或不规范
错误示例:
fix bug
update
temp commit
wip
解决方案:
使用规范的提交格式:
feat: 添加用户登录功能
fix: 修复登录验证错误
docs: 更新API文档
refactor: 重构用户服务模块
分支管理混乱
问题:
分支命名不规范,合并策略混乱
错误示例:
- feature1
- bug-fix-2
- test-branch
- master分支直接开发
解决方案:
- main/master: 主分支
- develop: 开发分支
- feature/xxx: 功能分支
- hotfix/xxx: 热修复分支
- release/xxx: 发布分支
合并冲突频发
问题:
频繁的合并冲突
原因:
- 功能分支过长时间不合并
- 多人修改同一文件
- 缺乏代码审查
解决方案:
1. 定期合并主分支
2. 小步提交,频繁集成
3. 建立代码审查流程
4. 使用功能开关
代码实现示例
Git分析器
import subprocess
import json
import re
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass
from datetime import datetime
from collections import defaultdict, Counter
import os
@dataclass
class GitCommit:
"""Git提交信息"""
hash: str
author: str
email: str
date: datetime
message: str
files_changed: List[str]
insertions: int
deletions: int
branches: List[str]
@dataclass
class GitFile:
"""Git文件信息"""
path: str
size: int
last_modified: datetime
last_author: str
change_count: int
total_insertions: int
total_deletions: int
@dataclass
class GitAuthor:
"""Git作者信息"""
name: str
email: str
commits: int
insertions: int
deletions:
files_touched:
first_commit: datetime
last_commit: datetime
active_days:
:
total_commits:
total_authors:
total_files:
date_range: [datetime, datetime]
top_authors: [GitAuthor]
hot_files: [GitFile]
commit_frequency: [, ]
:
():
.repo_path = repo_path
.git_cmd = [, , repo_path]
() -> :
:
result = subprocess.run(
.git_cmd + args,
capture_output=,
text=,
check=
)
result.stdout.strip()
subprocess.CalledProcessError e:
Exception()
() -> [GitCommit]:
args = [
,
,
,
,
,
]
since:
args.append()
output = ._run_git_command(args)
._parse_commits(output)
() -> [GitCommit]:
commits = []
lines = output.split()
i =
i < (lines):
lines[i]:
i +=
header = lines[i].split()
(header) >= :
commit_hash = header[]
author = header[]
email = header[]
date = datetime.fromisoformat(header[].replace(, ))
message = .join(header[:])
i +=
insertions =
deletions =
files_changed = []
i < (lines) lines[i]:
line = lines[i]
line:
parts = line.split()
(parts) == :
:
ins = (parts[]) parts[] !=
dels = (parts[]) parts[] !=
file_path = parts[]
insertions += ins
deletions += dels
files_changed.append(file_path)
ValueError:
i +=
branches = ._get_commit_branches(commit_hash)
commits.append(GitCommit(
=commit_hash,
author=author,
email=email,
date=date,
message=message,
files_changed=files_changed,
insertions=insertions,
deletions=deletions,
branches=branches
))
:
i +=
commits
() -> []:
:
output = ._run_git_command([
, , commit_hash
])
branches = []
line output.split():
branch = line.strip().replace(, )
branch branch != :
branches.append(branch)
branches
:
[]
() -> [GitCommit]:
output = ._run_git_command([
,
,
,
,
file_path
])
._parse_commits(output)
() -> [GitAuthor]:
output = ._run_git_command([
,
,
,
])
author_stats = defaultdict(: {
: ,
: ,
: ,
: (),
: [],
: ,
:
})
lines = output.split()
i =
i < (lines):
lines[i]:
i +=
lines[i]:
parts = lines[i].split()
(parts) >= :
author = parts[]
email = parts[]
date = datetime.fromisoformat(parts[].replace(, ))
stats = author_stats[author]
stats[] +=
stats[].append(date)
stats[] date < stats[]:
stats[] = date
stats[] date > stats[]:
stats[] = date
i +=
i < (lines) lines[i] lines[i]:
file_parts = lines[i].split()
(file_parts) >= :
file_path = file_parts[]
stats[].add(file_path)
:
ins = (file_parts[]) file_parts[] !=
dels = (file_parts[]) file_parts[] !=
stats[] += ins
stats[] += dels
ValueError:
i +=
:
i +=
authors = []
author_name, stats author_stats.items():
active_days = ((d.date() d stats[]))
authors.append(GitAuthor(
name=author_name,
email=,
commits=stats[],
insertions=stats[],
deletions=stats[],
files_touched=(stats[]),
first_commit=stats[],
last_commit=stats[],
active_days=active_days
))
(authors, key= x: x.commits, reverse=)
() -> [GitFile]:
output = ._run_git_command([
,
,
])
file_stats = defaultdict(: {
: ,
: ,
: ,
: ,
:
})
lines = output.split()
current_commit =
line lines:
line.strip():
line.startswith():
current_commit = line
:
file_path = line.strip()
file_path file_path != :
stats = file_stats[file_path]
stats[] +=
hot_files = []
file_path, stats file_stats.items():
:
full_path = os.path.join(.repo_path, file_path)
os.path.exists(full_path):
size = os.path.getsize(full_path)
:
size =
last_commit_info = ._get_last_file_commit(file_path)
hot_files.append(GitFile(
path=file_path,
size=size,
last_modified=last_commit_info.get(, datetime.now()),
last_author=last_commit_info.get(, ),
change_count=stats[],
total_insertions=stats[],
total_deletions=stats[]
))
:
(hot_files, key= x: x.change_count, reverse=)[:limit]
() -> [, ]:
:
output = ._run_git_command([
,
,
,
,
file_path
])
output:
parts = output.split()
{
: parts[] (parts) > ,
: datetime.fromisoformat(parts[].replace(, )) (parts) > datetime.now()
}
:
{: , : datetime.now()}
() -> GitAnalysis:
total_commits = (._run_git_command([, , ]))
authors = .get_authors()
total_authors = (authors)
output = ._run_git_command([])
total_files = (output.split()) output
first_date_str = ._run_git_command([, , , ]).split()[]
last_date_str = ._run_git_command([, , , ])
first_date = datetime.fromisoformat(first_date_str.replace(, ))
last_date = datetime.fromisoformat(last_date_str.replace(, ))
hot_files = .get_hot_files()
commit_frequency = ._get_commit_frequency()
GitAnalysis(
total_commits=total_commits,
total_authors=total_authors,
total_files=total_files,
date_range=(first_date, last_date),
top_authors=authors[:],
hot_files=hot_files,
commit_frequency=commit_frequency
)
() -> [, ]:
output = ._run_git_command([
,
,
])
dates = output.split()
(Counter(dates))
() -> [GitCommit]:
commits = .get_commits(limit=)
suspicious_commits = []
commit commits:
(keyword commit.message.lower() keyword [, , , ]):
suspicious_commits.append(commit)
suspicious_commits
() -> :
report = []
report.append()
report.append()
report.append()
report.append()
report.append()
project_age = (analysis.date_range[] - analysis.date_range[]).days
report.append()
project_age > :
avg_commits_per_day = analysis.total_commits / project_age
report.append()
report.append()
report.append()
i, author (analysis.top_authors[:], ):
report.append()
report.append()
report.append()
report.append()
report.append()
report.append()
i, file (analysis.hot_files[:], ):
report.append()
report.append()
report.append()
report.append()
report.append()
report.append()
recent_dates = (analysis.commit_frequency.items(), reverse=)[:]
date, count recent_dates:
report.append()
.join(report)
():
analyzer = GitAnalyzer()
analysis = analyzer.analyze_repository()
report = analyzer.generate_analysis_report(analysis)
(report)
bug_commits = analyzer.find_bug_introduction()
()
__name__ == :
main()
Git代码审查工具
import re
from typing import List, Dict, Any
class GitCodeReviewer:
"""Git代码审查工具"""
def __init__(self, repo_path: str = "."):
self.analyzer = GitAnalyzer(repo_path)
self.review_rules = self._initialize_review_rules()
def review_commit(self, commit_hash: str) -> Dict[str, Any]:
"""审查单个提交"""
commits = self.analyzer.get_commits(limit=1)
if not commits or commits[0].hash != commit_hash:
output = self.analyzer._run_git_command([
"show",
"--pretty=format:%H|%an|%ae|%ad|%s",
"--date=iso",
"--numstat",
commit_hash
])
commits = self.analyzer._parse_commits(output)
if not commits:
return {"error": "提交不存在"}
commit = commits[0]
message_issues = ._review_commit_message(commit.message)
code_issues = ._review_code_changes(commit)
file_issues = ._review_file_changes(commit)
{
: commit,
: {
: message_issues,
: code_issues,
: file_issues
},
: ._calculate_review_score(message_issues, code_issues, file_issues)
}
() -> []:
issues = []
(message) > :
issues.append()
re.(, message):
issues.append()
(message) < :
issues.append()
sensitive_patterns = [
,
,
,
,
]
pattern sensitive_patterns:
re.search(pattern, message, re.IGNORECASE):
issues.append()
issues
() -> []:
issues = []
total_changes = commit.insertions + commit.deletions
total_changes > :
issues.append()
commit.deletions > commit.insertions * :
issues.append()
(commit.files_changed) > :
issues.append()
issues
() -> []:
issues = []
sensitive_files = [
,
,
,
,
]
file_path commit.files_changed:
sensitive sensitive_files:
sensitive file_path:
issues.append()
test_files = [f f commit.files_changed f.lower()]
test_files (commit.files_changed) > :
issues.append()
issues
() -> :
total_issues = (message_issues) + (code_issues) + (file_issues)
score = (, - total_issues * )
score
() -> [, ]:
output = .analyzer._run_git_command([
,
,
])
commit_hashes = output.split() output []
branch_reviews = []
total_score =
commit_hash commit_hashes:
commit_hash.strip():
review = .review_commit(commit_hash.strip())
branch_reviews.append(review)
total_score += review.get(, )
avg_score = total_score / (branch_reviews) branch_reviews
{
: branch_name,
: branch_reviews,
: (branch_reviews),
: avg_score,
: ._get_branch_recommendation(avg_score)
}
() -> :
score >= :
score >= :
:
() -> [, ]:
{
: ,
: ,
: ,
: ,
: [
, , , ,
]
}
():
reviewer = GitCodeReviewer()
latest_commit = reviewer.analyzer._run_git_command([, ])
review_result = reviewer.review_commit(latest_commit)
()
()
()
()
review_result[][]:
()
issue review_result[][]:
()
review_result[][]:
()
issue review_result[][]:
()
review_result[][]:
()
issue review_result[][]:
()
__name__ == :
main()
Git最佳实践
提交规范
- 原子提交: 每个提交只做一件事
- 清晰信息: 使用描述性的提交信息
- 格式统一: 遵循团队约定的格式
- 及时提交: 频繁提交,小步快跑
分支策略
- 主分支保护: 主分支只接受合并
- 功能分支: 每个功能使用独立分支
- 定期同步: 定期合并主分支更新
- 清理分支: 及时删除已合并的分支
代码审查
- 强制审查: 所有代码必须经过审查
- 审查清单: 使用标准化的审查清单
- 及时反馈: 快速响应审查请求
- 建设性意见: 提供具体的改进建议
相关技能
- code-review - 代码审查
- version-control - 版本控制
- project-management - 项目管理
- team-collaboration - 团队协作