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