Initial curated RepoPulse source from 2524ccc3c1c27739996dccb2b7992989f88cd26d
This commit is contained in:
commit
0adf8ef5d9
90 files changed
+14315
No files matched your search
@@ -0,0 +1,104 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from collections import Counter
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from .models import GitSignals
|
||||
|
||||
|
||||
def _run(root: Path, *args: str, timeout: int = 8) -> str | None:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(root), *args],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
errors="replace",
|
||||
timeout=timeout,
|
||||
)
|
||||
except (OSError, UnicodeError, subprocess.SubprocessError):
|
||||
return None
|
||||
return result.stdout
|
||||
|
||||
|
||||
def analyze_git(root: Path) -> tuple[GitSignals, dict[str, int]]:
|
||||
top_level = _run(root, "rev-parse", "--show-toplevel")
|
||||
if not top_level:
|
||||
return GitSignals(), {}
|
||||
try:
|
||||
if Path(top_level.strip()).resolve() != root.resolve():
|
||||
return GitSignals(), {}
|
||||
except (OSError, RuntimeError, ValueError):
|
||||
return GitSignals(), {}
|
||||
|
||||
inside = _run(root, "rev-parse", "--is-inside-work-tree")
|
||||
if not inside or inside.strip() != "true":
|
||||
return GitSignals(), {}
|
||||
|
||||
raw_count = _run(root, "rev-list", "--count", "HEAD")
|
||||
raw_latest = _run(root, "log", "-1", "--format=%aI")
|
||||
raw_authors = _run(root, "log", "--format=%aN%x00%aE", "-n", "2000")
|
||||
raw_files = _run(root, "ls-files", "-z")
|
||||
raw_churn = _run(root, "log", "--numstat", "--format=", "--find-renames=20%", "-n", "200")
|
||||
raw_renames = _run(root, "log", "--name-status", "--format=", "--find-renames=20%", "-n", "200")
|
||||
|
||||
contributors: set[str] = set()
|
||||
if raw_authors:
|
||||
for line in raw_authors.splitlines():
|
||||
if line.strip():
|
||||
contributors.add(line.strip())
|
||||
|
||||
churn: Counter[str] = Counter()
|
||||
renamed_files: set[str] = set()
|
||||
rename_map: dict[str, str] = {}
|
||||
if raw_renames:
|
||||
for line in raw_renames.splitlines():
|
||||
parts = line.split("\t")
|
||||
if len(parts) >= 3 and parts[0].startswith("R"):
|
||||
old_path, new_path = parts[-2:]
|
||||
rename_map[old_path] = new_path
|
||||
renamed_files.add(new_path)
|
||||
if raw_churn:
|
||||
for line in raw_churn.splitlines():
|
||||
parts = line.split("\t", 2)
|
||||
if len(parts) != 3:
|
||||
continue
|
||||
additions, deletions, path = parts
|
||||
churn_path = _rename_target(path) if " => " in path else rename_map.get(path, path)
|
||||
if " => " in path:
|
||||
renamed_files.add(churn_path)
|
||||
if additions.isdigit():
|
||||
churn[churn_path] += int(additions)
|
||||
if deletions.isdigit():
|
||||
churn[churn_path] += int(deletions)
|
||||
|
||||
latest_commit_at = None
|
||||
if raw_latest and raw_latest.strip():
|
||||
try:
|
||||
latest_commit_at = datetime.fromisoformat(raw_latest.strip())
|
||||
except ValueError:
|
||||
latest_commit_at = None
|
||||
|
||||
return (
|
||||
GitSignals(
|
||||
available=True,
|
||||
commit_count=int(raw_count.strip()) if raw_count and raw_count.strip().isdigit() else None,
|
||||
contributor_count=len(contributors) if contributors else None,
|
||||
latest_commit_at=latest_commit_at,
|
||||
tracked_files=len([item for item in raw_files.split("\0") if item]) if raw_files is not None else None,
|
||||
renamed_files=sorted(renamed_files),
|
||||
),
|
||||
dict(churn),
|
||||
)
|
||||
|
||||
|
||||
def _rename_target(path: str) -> str:
|
||||
"""Normalize Git's brace-formatted rename path to the new path."""
|
||||
if "{" in path and "}" in path:
|
||||
prefix, remainder = path.split("{", 1)
|
||||
inner, suffix = remainder.split("}", 1)
|
||||
if " => " in inner:
|
||||
return f"{prefix}{inner.split(' => ', 1)[1]}{suffix}"
|
||||
return path.rsplit(" => ", 1)[-1]
|
||||
Reference in new issue
Block a user