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
« prev ^ index » next coverage.py v7.3.2, created at 2025-04-08 21:20 -0700
1from __future__ import annotations
3import os
4from pathlib import Path
5from typing import Any
7import sh
9from .status import get_status
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()
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
31 if not (self.__path / ".git").is_dir():
32 msg = "Specified path must be part of a Git repo."
33 raise ValueError(msg)
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()
44 repo_path.mkdir(parents=True, exist_ok=True)
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 )
56 return Repo(repo_path, **kwargs)
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)
64 return not (status["files"]["staged"] or status["files"]["unstaged"])
66 def stage(self, *files: str) -> None:
67 args: list[str] = ["--", *files] if files else ["."]
69 self.__git(
70 "add", *args,
71 )
73 def reset(self) -> None:
74 self.__git(
75 "reset",
76 )
78 def commit(self, message: str, *, stage_all: bool = True) -> None:
79 args: list[str] = ["--all"] if stage_all else []
81 self.__git(
82 "commit", *args, f"--message={message}",
83 )
85 def pull(self, *, rebase: bool = True) -> None:
86 if not self.is_clean():
87 msg = "Repo is not clean."
88 raise RuntimeError(msg)
90 args: list[str] = ["--rebase=true"] if rebase else []
92 self.__git(
93 "pull", *args,
94 )
96 def push(self) -> None:
97 if not self.is_clean():
98 msg = "Repo is not clean."
99 raise RuntimeError(msg)
101 self.__git(
102 "push",
103 )