Initial upload: DevDash CLI with TUI dashboard
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
96
src/git/status.py
Normal file
96
src/git/status.py
Normal file
@@ -0,0 +1,96 @@
|
||||
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
|
||||
Reference in New Issue
Block a user