Add utils, templates, config, interactive, and github modules
This commit is contained in:
186
src/auto_readme/utils/git_utils.py
Normal file
186
src/auto_readme/utils/git_utils.py
Normal 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)
|
||||
Reference in New Issue
Block a user