Загрузка данных
#!/usr/bin/env python3
"""
Скрипт сбора статистики по коммитам в Git-репозитории.
Собирает по указанным авторам:
- количество коммитов
- частоту (коммиты в неделю / средний интервал)
- объём затронутого кода в .py файлах (добавлено / удалено строк)
Период по умолчанию: с 2026-03-01 по сегодня.
Авторы: Владимиров Никита Михайлович, Ефимов Александр Сергеевич, OrlovaIU
Запуск:
python git_python_stats.py [путь_к_репозиторию]
(по умолчанию — текущая директория)
"""
import subprocess
import sys
import datetime
from collections import defaultdict
from pathlib import Path
AUTHORS = [
"Владимиров Никита Михайлович",
"Ефимов Александр Сергеевич",
"OrlovaIU",
]
SINCE = "2026-03-01"
UNTIL = datetime.date.today().isoformat() # сегодня
def run_git_log(repo_path: str) -> str:
"""Запускает git log и возвращает сырой вывод."""
cmd = [
"git", "-C", repo_path, "log",
f"--since={SINCE}",
f"--until={UNTIL}",
"--pretty=format:%H|%an|%ae|%ad",
"--date=short",
"--numstat",
"--", "*.py",
]
result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
if result.returncode != 0:
print("Ошибка выполнения git log:", result.stderr, file=sys.stderr)
sys.exit(1)
return result.stdout
def parse_commits(raw: str) -> list[dict]:
"""Парсит вывод git log --numstat."""
commits = []
current = None
for line in raw.splitlines():
line = line.strip()
if not line:
continue
# Заголовок коммита: hash|author|email|date
if line.count("|") >= 3:
parts = line.split("|", 3)
if len(parts) >= 4:
current = {
"hash": parts[0],
"author": parts[1],
"email": parts[2],
"date": parts[3],
"added": 0,
"deleted": 0,
}
commits.append(current)
continue
# Строка numstat: added\tdeleted\tfilename
if current and "\t" in line:
parts = line.split("\t")
if len(parts) >= 2:
try:
added = int(parts[0]) if parts[0] != "-" else 0
deleted = int(parts[1]) if parts[1] != "-" else 0
current["added"] += added
current["deleted"] += deleted
except ValueError:
pass
return commits
def matches_author(commit: dict, authors: list[str]) -> bool:
"""Проверяет, относится ли коммит к одному из интересующих авторов."""
text = f"{commit['author']} {commit['email']}".lower()
return any(a.lower() in text for a in authors)
def compute_frequency(dates: list[str]) -> dict:
"""Вычисляет простые метрики частоты по списку дат коммитов (YYYY-MM-DD)."""
if not dates:
return {"weeks": 0, "commits_per_week": 0.0, "avg_days_between": None}
unique_dates = sorted(set(dates))
start = datetime.date.fromisoformat(unique_dates[0])
end = datetime.date.fromisoformat(unique_dates[-1])
days = (end - start).days + 1
weeks = max(days / 7.0, 1 / 7.0)
# Средний интервал между коммитами (по всем датам, не только уникальным)
if len(dates) > 1:
sorted_all = sorted(datetime.date.fromisoformat(d) for d in dates)
intervals = [(sorted_all[i] - sorted_all[i - 1]).days for i in range(1, len(sorted_all))]
avg_interval = sum(intervals) / len(intervals) if intervals else None
else:
avg_interval = None
return {
"span_days": days,
"weeks": round(weeks, 1),
"commits_per_week": round(len(dates) / weeks, 2),
"avg_days_between": round(avg_interval, 1) if avg_interval is not None else None,
"active_days": len(unique_dates),
}
def main():
repo = sys.argv[1] if len(sys.argv) > 1 else "."
repo_path = Path(repo).resolve()
if not (repo_path / ".git").exists():
print(f"Не найден git-репозиторий по пути: {repo_path}", file=sys.stderr)
sys.exit(1)
print(f"Анализ репозитория: {repo_path}")
print(f"Период: {SINCE} — {UNTIL}")
print(f"Авторы: {', '.join(AUTHORS)}")
print("-" * 70)
raw = run_git_log(str(repo_path))
all_commits = parse_commits(raw)
filtered = [c for c in all_commits if matches_author(c, AUTHORS)]
# Группировка по реальному имени автора
stats = defaultdict(lambda: {
"commits": 0,
"added": 0,
"deleted": 0,
"dates": [],
"emails": set(),
})
for c in filtered:
key = c["author"]
stats[key]["commits"] += 1
stats[key]["added"] += c["added"]
stats[key]["deleted"] += c["deleted"]
stats[key]["dates"].append(c["date"])
stats[key]["emails"].add(c["email"])
if not stats:
print("Коммитов по указанным авторам за период не найдено.")
return
# Вывод
total_commits = 0
total_added = 0
total_deleted = 0
for author, data in sorted(stats.items(), key=lambda x: -x[1]["commits"]):
freq = compute_frequency(data["dates"])
net = data["added"] - data["deleted"]
total_commits += data["commits"]
total_added += data["added"]
total_deleted += data["deleted"]
print(f"\nАвтор: {author}")
print(f" Email(ы): {', '.join(sorted(data['emails']))}")
print(f" Коммитов: {data['commits']}")
print(f" Строк в .py: +{data['added']} / -{data['deleted']} (чистые: {net:+d})")
print(f" Частота: {freq['commits_per_week']} коммитов/неделю "
f"(период активности {freq['span_days']} дн., {freq['active_days']} активных дней)")
if freq["avg_days_between"] is not None:
print(f" Средний интервал между коммитами: {freq['avg_days_between']} дней")
print("\n" + "=" * 70)
print(f"ИТОГО по всем авторам:")
print(f" Коммитов: {total_commits}")
print(f" Строк в .py: +{total_added} / -{total_deleted} "
f"(чистые: {total_added - total_deleted:+d})")
if __name__ == "__main__":
main()