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