Coverage for src/extratools_gittools/status.py: 87%
46 statements
« prev ^ index » next coverage.py v7.3.2, created at 2025-03-27 20:04 -0700
« prev ^ index » next coverage.py v7.3.2, created at 2025-03-27 20:04 -0700
1from pathlib import Path
2from typing import Any
4import sh
7def get_status(path: str = '.') -> dict[str, Any] | None:
8 try:
9 output: str = str(sh.git(
10 "status", "--short", "--branch", "--porcelain=2",
11 _cwd=Path(path).expanduser(),
12 ))
13 except Exception:
14 return None
16 return {
17 "path": path,
18 **parse_status(output),
19 }
22def parse_status(output: str) -> dict[str, Any]:
23 oid: str | None = None
25 head: str | None = None
26 upstream: str | None = None
28 ahead: int = 0
29 behind: int = 0
31 staged: list[str] = []
32 unstaged: list[str] = []
33 untracked: list[str] = []
35 for line in output.rstrip('\n').splitlines():
36 if line.startswith('#'):
37 if line.startswith("# branch.oid "):
38 oid = line.rsplit(' ', 1)[1]
39 if line.startswith("# branch.head "):
40 branch = line.rsplit(' ', 1)[1]
41 if branch != "(detached)":
42 head = branch
43 elif line.startswith("# branch.upstream "):
44 branch = line.rsplit(' ', 1)[1]
45 if branch != "(detached)":
46 upstream = branch
47 elif line.startswith("# branch.ab "):
48 ahead, behind = [abs(int(x)) for x in line.rsplit(' ', 2)[1:]]
49 elif line.startswith('?'):
50 untracked.append(line.rsplit(' ', -1)[1])
51 elif not line.startswith('!'):
52 vals: list[str] = line.split(' ')
54 path: str = vals[-1]
56 submodule_flags: str = vals[2]
57 if submodule_flags[0] == 'S' and submodule_flags[3] == 'U':
58 untracked.append(path)
60 stage_flags: str = vals[1]
61 if stage_flags[0] != '.':
62 staged.append(path)
63 if stage_flags[1] != '.':
64 unstaged.append(path)
66 return {
67 "oid": oid,
68 "branch": {
69 "head": head,
70 "upstream": upstream,
71 },
72 "commits": {
73 "ahead": ahead,
74 "behind": behind,
75 },
76 "files": {
77 "staged": staged,
78 "unstaged": unstaged,
79 "untracked": untracked,
80 },
81 }