97 lines
2.5 KiB
Python
97 lines
2.5 KiB
Python
from dataclasses import dataclass
|
|
from typing import List, Optional
|
|
import subprocess
|
|
|
|
|
|
class GitStatusError(Exception):
|
|
pass
|
|
|
|
|
|
def _run_git_command(args: List[str], cwd: str = None) -> str:
|
|
try:
|
|
result = subprocess.run(
|
|
["git"] + args,
|
|
cwd=cwd,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
return result.stdout.strip()
|
|
except subprocess.CalledProcessError as e:
|
|
raise GitStatusError(f"Git command failed: {e}")
|
|
|
|
|
|
def _get_current_branch(cwd: str = None) -> str:
|
|
try:
|
|
return _run_git_command(["rev-parse", "--abbrev-ref", "HEAD"], cwd)
|
|
except GitStatusError:
|
|
return "HEAD"
|
|
|
|
|
|
def _get_commit_hash(cwd: str = None) -> str:
|
|
return _run_git_command(["rev-parse", "SHORT"], cwd)
|
|
|
|
|
|
def _is_detached(cwd: str = None) -> bool:
|
|
branch = _get_current_branch(cwd)
|
|
return branch == "HEAD"
|
|
|
|
|
|
def _get_staged_count(cwd: str = None) -> int:
|
|
output = _run_git_command(["diff", "--staged", "--name-only"], cwd)
|
|
return len(output.splitlines()) if output else 0
|
|
|
|
|
|
def _get_unstaged_count(cwd: str = None) -> int:
|
|
output = _run_git_command(["diff", "--name-only"], cwd)
|
|
return len(output.splitlines()) if output else 0
|
|
|
|
|
|
def _get_untracked_count(cwd: str = None) -> int:
|
|
output = _run_git_command(["ls-files", "--others", "--exclude-standard"], cwd)
|
|
return len(output.splitlines()) if output else 0
|
|
|
|
|
|
@dataclass
|
|
class GitStatus:
|
|
branch: str
|
|
commit: str
|
|
staged: int = 0
|
|
unstaged: int = 0
|
|
untracked: int = 0
|
|
ahead: int = 0
|
|
behind: int = 0
|
|
is_detached: bool = False
|
|
is_clean: bool = True
|
|
|
|
|
|
def get_git_status(cwd: str = None) -> GitStatus:
|
|
try:
|
|
is_detached_head = _is_detached(cwd)
|
|
branch = "(detached)" if is_detached_head else _get_current_branch(cwd)
|
|
commit = _get_commit_hash(cwd)
|
|
staged = _get_staged_count(cwd)
|
|
unstaged = _get_unstaged_count(cwd)
|
|
untracked = _get_untracked_count(cwd)
|
|
is_clean = (staged == 0 and unstaged == 0 and untracked == 0)
|
|
|
|
return GitStatus(
|
|
branch=branch,
|
|
commit=commit,
|
|
staged=staged,
|
|
unstaged=unstaged,
|
|
untracked=untracked,
|
|
is_detached=is_detached_head,
|
|
is_clean=is_clean,
|
|
)
|
|
except GitStatusError:
|
|
raise GitStatusError("Failed to get git status")
|
|
|
|
|
|
def is_git_repo(cwd: str = None) -> bool:
|
|
try:
|
|
_run_git_command(["rev-parse", "--git-dir"], cwd)
|
|
return True
|
|
except GitStatusError:
|
|
return False
|