Coverage for src/extratools_gittools/status.py: 7%

46 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-03-27 18:06 -0700

1import os 

2from typing import Any, Optional 

3 

4import sh 

5 

6 

7def get_status(path: str = '.') -> Optional[dict[str, Any]]: 

8 try: 

9 output: str = str(sh.git( 

10 "status", "--short", "--branch", "--porcelain=2", 

11 _cwd=os.path.expanduser(path) 

12 )) 

13 except Exception: 

14 return None 

15 

16 return { 

17 "path": path, 

18 **parse_status(output), 

19 } 

20 

21 

22def parse_status(output: str) -> dict[str, Any]: 

23 oid: Optional[str] = None 

24 

25 head: Optional[str] = None 

26 upstream: Optional[str] = None 

27 

28 ahead: int = 0 

29 behind: int = 0 

30 

31 staged: list[str] = [] 

32 unstaged: list[str] = [] 

33 untracked: list[str] = [] 

34 

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(' ') 

53 

54 path: str = vals[-1] 

55 

56 submodule_flags: str = vals[2] 

57 if submodule_flags[0] == 'S' and submodule_flags[3] == 'U': 

58 untracked.append(path) 

59 

60 stage_flags: str = vals[1] 

61 if stage_flags[0] != '.': 

62 staged.append(path) 

63 if stage_flags[1] != '.': 

64 unstaged.append(path) 

65 

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 }