Coverage for src/extratools_gittools/repo.py: 0%

48 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2025-04-08 21:20 -0700

1from __future__ import annotations 

2 

3import os 

4from pathlib import Path 

5from typing import Any 

6 

7import sh 

8 

9from .status import get_status 

10 

11 

12class Repo: 

13 def __init__( 

14 self, path: Path | str, 

15 *, 

16 user_name: str, 

17 user_email: str, 

18 ) -> None: 

19 self.__path: Path = Path(path).expanduser() 

20 

21 self.__git = sh.bake( 

22 _cwd=self.__path, 

23 _env={ 

24 "GIT_AUTHOR_NAME": user_name, 

25 "GIT_AUTHOR_EMAIL": user_email, 

26 "GIT_COMMITTER_NAME": user_name, 

27 "GIT_COMMITTER_EMAIL": user_email, 

28 } | os.environ, 

29 ).git 

30 

31 if not (self.__path / ".git").is_dir(): 

32 msg = "Specified path must be part of a Git repo." 

33 raise ValueError(msg) 

34 

35 @staticmethod 

36 def init( 

37 path: Path | str, 

38 *, 

39 exist_ok: bool = True, 

40 **kwargs: Any, 

41 ) -> Repo: 

42 repo_path: Path = Path(path).expanduser() 

43 

44 repo_path.mkdir(parents=True, exist_ok=True) 

45 

46 if (repo_path / ".git").exists(): 

47 if not exist_ok: 

48 msg = "Specified path is already a Git repo." 

49 raise RuntimeError(msg) 

50 else: 

51 sh.git( 

52 "init", 

53 _cwd=repo_path, 

54 ) 

55 

56 return Repo(repo_path, **kwargs) 

57 

58 def is_clean(self) -> bool: 

59 status: dict[str, Any] | None = get_status(str(self.__path)) 

60 if not status: 

61 msg = "Cannot get status of Git repo." 

62 raise RuntimeError(msg) 

63 

64 return not (status["files"]["staged"] or status["files"]["unstaged"]) 

65 

66 def stage(self, *files: str) -> None: 

67 args: list[str] = ["--", *files] if files else ["."] 

68 

69 self.__git( 

70 "add", *args, 

71 ) 

72 

73 def reset(self) -> None: 

74 self.__git( 

75 "reset", 

76 ) 

77 

78 def commit(self, message: str, *, stage_all: bool = True) -> None: 

79 args: list[str] = ["--all"] if stage_all else [] 

80 

81 self.__git( 

82 "commit", *args, f"--message={message}", 

83 ) 

84 

85 def pull(self, *, rebase: bool = True) -> None: 

86 if not self.is_clean(): 

87 msg = "Repo is not clean." 

88 raise RuntimeError(msg) 

89 

90 args: list[str] = ["--rebase=true"] if rebase else [] 

91 

92 self.__git( 

93 "pull", *args, 

94 ) 

95 

96 def push(self) -> None: 

97 if not self.is_clean(): 

98 msg = "Repo is not clean." 

99 raise RuntimeError(msg) 

100 

101 self.__git( 

102 "push", 

103 )