Add utils, templates, config, interactive, and github modules
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / release (push) Has been cancelled

This commit is contained in:
2026-02-05 08:45:41 +00:00
parent f2d988ce97
commit 1f5d2ea540

View File

@@ -0,0 +1,186 @@
"""Git utilities for the Auto README Generator."""
import subprocess
from pathlib import Path
from typing import Optional
from ..models import GitInfo
class GitUtils:
"""Utility class for git operations."""
@classmethod
def is_git_repo(cls, path: Path) -> bool:
"""Check if the path is a git repository."""
try:
result = subprocess.run(
["git", "rev-parse", "--git-dir"],
cwd=path,
capture_output=True,
text=True,
timeout=5,
)
return result.returncode == 0
except (subprocess.SubprocessError, FileNotFoundError):
return False
@classmethod
def get_remote_url(cls, path: Path) -> Optional[str]:
"""Get the remote URL for the origin remote."""
try:
result = subprocess.run(
["git", "remote", "get-url", "origin"],
cwd=path,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
return result.stdout.strip()
except (subprocess.SubprocessError, FileNotFoundError):
pass
return None
@classmethod
def get_current_branch(cls, path: Path) -> Optional[str]:
"""Get the current branch name."""
try:
result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=path,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
return result.stdout.strip()
except (subprocess.SubprocessError, FileNotFoundError):
pass
return None
@classmethod
def get_commit_sha(cls, path: Path, short: bool = True) -> Optional[str]:
"""Get the current commit SHA."""
try:
flag = "--short" if short else ""
result = subprocess.run(
["git", "rev-parse", flag, "HEAD"],
cwd=path,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
return result.stdout.strip()
except (subprocess.SubprocessError, FileNotFoundError):
pass
return None
@classmethod
def get_repo_info(cls, remote_url: Optional[str]) -> tuple[Optional[str], Optional[str]]:
"""Extract owner and repo name from remote URL."""
if not remote_url:
return None, None
import re
patterns = [
r"github\.com[:/](/[^/]+)/([^/]+)\.git",
r"github\.com/([^/]+)/([^/]+)",
r"git@github\.com:([^/]+)/([^/]+)\.git",
r"git@github\.com:([^/]+)/([^/]+)",
]
for pattern in patterns:
match = re.search(pattern, remote_url)
if match:
owner, repo = match.groups()
repo = repo.replace(".git", "")
return owner, repo
return None, None
@classmethod
def get_git_info(cls, path: Path) -> GitInfo:
"""Get complete git information for a repository."""
is_repo = cls.is_git_repo(path)
if not is_repo:
return GitInfo()
remote_url = cls.get_remote_url(path)
branch = cls.get_current_branch(path)
commit_sha = cls.get_commit_sha(path)
owner, repo_name = cls.get_repo_info(remote_url)
return GitInfo(
remote_url=remote_url,
branch=branch,
commit_sha=commit_sha,
is_repo=True,
repo_name=repo_name,
owner=owner,
)
@classmethod
def get_last_commit_message(cls, path: Path) -> Optional[str]:
"""Get the last commit message."""
try:
result = subprocess.run(
["git", "log", "-1", "--format=%s"],
cwd=path,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
return result.stdout.strip()
except (subprocess.SubprocessError, FileNotFoundError):
pass
return None
@classmethod
def get_commit_count(cls, path: Path) -> Optional[int]:
"""Get the total number of commits."""
try:
result = subprocess.run(
["git", "rev-list", "--count", "HEAD"],
cwd=path,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
return int(result.stdout.strip())
except (subprocess.SubprocessError, FileNotFoundError, ValueError):
pass
return None
@classmethod
def get_contributors(cls, path: Path) -> list[str]:
"""Get list of contributors."""
try:
result = subprocess.run(
["git", "log", "--format=%an", "--reverse"],
cwd=path,
capture_output=True,
text=True,
timeout=10,
)
if result.returncode == 0:
contributors = []
seen = set()
for line in result.stdout.strip().split("\n"):
if line and line not in seen:
seen.add(line)
contributors.append(line)
return contributors
except (subprocess.SubprocessError, FileNotFoundError):
pass
return []
def get_git_info(path: Path) -> GitInfo:
"""Get complete git information for a repository."""
return GitUtils.get_git_info(path)