diff --git a/pyproject.toml b/pyproject.toml index d210231..c53c049 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ authors = [ requires-python = ">=3.10" dependencies = [ - "gitpython>=3.1.52", "pyyaml>=6.0.3", "httpx[socks]>=0.28.1", "packaging>=26.2", diff --git a/src/composekit/generate.py b/src/composekit/generate.py index 261307f..449aeec 100755 --- a/src/composekit/generate.py +++ b/src/composekit/generate.py @@ -265,9 +265,9 @@ def main(args: argparse.Namespace) -> None: if args.commit: repo = open_repo(reset=False) - repo.git.add(".") - staged_count = len(repo.index.diff(repo.head.commit)) + repo.add(".") + staged_count = repo.staged_count() if staged_count > 0: - repo.index.commit( + repo.commit( f"chore(composes): update {staged_count} compose file(s)" ) diff --git a/src/composekit/sort.py b/src/composekit/sort.py index 10af990..a93b6f4 100644 --- a/src/composekit/sort.py +++ b/src/composekit/sort.py @@ -9,9 +9,8 @@ try: import yaml - from git import Repo - from composekit.utils import iter_container_files, open_repo + from composekit.utils import Repository, iter_container_files, open_repo except ImportError as err: raise RuntimeError( "ERROR: Missing required packages. See the README." @@ -20,7 +19,7 @@ async def process_file( path: Path, - repo: Repo | None, + repo: Repository | None, git_lock: asyncio.Lock, ) -> None: with open(path) as file: @@ -41,8 +40,8 @@ async def process_file( yaml.dump_all(sorted_containers, file, sort_keys=False) if repo is not None: - repo.index.add(path) - repo.index.commit(f"chore({path.stem}): sort keys") + repo.add(path) + repo.commit(f"chore({path.stem}): sort keys") def main(args: argparse.Namespace) -> None: diff --git a/src/composekit/update.py b/src/composekit/update.py index adaf4b3..300433b 100755 --- a/src/composekit/update.py +++ b/src/composekit/update.py @@ -11,12 +11,16 @@ try: import httpx import yaml - from git import Repo from packaging.version import InvalidVersion, Version from composekit.container import Container, load_containers from composekit.utils import Config as _Config - from composekit.utils import iter_container_files, list_tags, open_repo + from composekit.utils import ( + Repository, + iter_container_files, + list_tags, + open_repo, + ) except ImportError as err: raise RuntimeError( "ERROR: Missing required packages. See the README." @@ -202,7 +206,7 @@ async def process_file( path: Path, client: httpx.AsyncClient, config: Config, - repo: Repo | None, + repo: Repository | None, git_lock: asyncio.Lock, ) -> None: with open(path) as file: @@ -224,8 +228,8 @@ async def process_file( ) if repo is not None: - repo.index.add(path) - repo.index.commit( + repo.add(path) + repo.commit( f"chore({path.stem}): update {image} to {newest_version}" ) diff --git a/src/composekit/utils/__init__.py b/src/composekit/utils/__init__.py index 8fbb0ad..5d424ec 100644 --- a/src/composekit/utils/__init__.py +++ b/src/composekit/utils/__init__.py @@ -1,6 +1,12 @@ from .config import Config from .file import iter_container_files -from .git import open_repo +from .git import Repository, open_repo from .oci_api import list_tags -__all__ = ("Config", "iter_container_files", "list_tags", "open_repo") +__all__ = ( + "Config", + "Repository", + "iter_container_files", + "list_tags", + "open_repo", +) diff --git a/src/composekit/utils/git.py b/src/composekit/utils/git.py index 28aab1f..0f10453 100644 --- a/src/composekit/utils/git.py +++ b/src/composekit/utils/git.py @@ -1,15 +1,46 @@ -try: - from git import Repo -except ImportError as err: - raise RuntimeError( - "ERROR: Missing required packages. See the README." - ) from err +import shutil +import subprocess +from dataclasses import dataclass +from pathlib import Path +_git = shutil.which("git") +if _git is None: + raise RuntimeError("ERROR: Git is required but was not found in PATH.") -def open_repo(reset: bool = True) -> Repo: - repo = Repo(".", search_parent_directories=True) +GIT: str = _git + + +@dataclass(frozen=True) +class Repository: + root: Path + + def _run(self, *args: str) -> str: + result = subprocess.check_output( # noqa: S603 + [GIT, "-C", self.root, *args], text=True + ) + return result + + def add(self, path: str | Path) -> None: + self._run("add", "--", str(path)) + + def commit(self, message: str) -> None: + self._run("commit", "-m", message) + + def reset(self) -> None: + self._run("reset", "--hard", "HEAD") + + def staged_count(self) -> int: + output = self._run("diff", "--cached", "--name-only", "-z") + return len([path for path in output.split("\0") if path]) + + +def open_repo(reset: bool = True) -> Repository: + result = subprocess.check_output( # noqa: S603 + [GIT, "rev-parse", "--show-toplevel"], + text=True, + ) + repo = Repository(Path(result.strip())) if reset: - # Discard any changes - repo.index.reset(working_tree=True) + repo.reset() return repo diff --git a/uv.lock b/uv.lock index faef136..6385fba 100644 --- a/uv.lock +++ b/uv.lock @@ -48,7 +48,6 @@ name = "composekit" version = "1.0.2" source = { editable = "." } dependencies = [ - { name = "gitpython" }, { name = "httpx", extra = ["socks"] }, { name = "packaging" }, { name = "pyyaml" }, @@ -66,7 +65,6 @@ dev = [ [package.metadata] requires-dist = [ - { name = "gitpython", specifier = ">=3.1.52" }, { name = "httpx", extras = ["socks"], specifier = ">=0.28.1" }, { name = "packaging", specifier = ">=26.2" }, { name = "pyrefly", marker = "extra == 'dev'", specifier = ">=1.1.1" }, @@ -91,30 +89,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] -[[package]] -name = "gitdb" -version = "4.0.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "smmap" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, -] - -[[package]] -name = "gitpython" -version = "3.1.52" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "gitdb" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e5/fd/df0bafa4eb5ea2f51e1adee9f7a94c8e62c5d180e65117045dfca3439c8a/gitpython-3.1.52.tar.gz", hash = "sha256:de0a8ad86274c6e75ae8b37dd055ba68f19818c813108642263227b20775b48e", size = 223726, upload-time = "2026-07-16T03:15:59.599Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/90/04dff7c1e176bb1c3011ef1647393d368790da710d8dde1cdcfad301f45a/gitpython-3.1.52-py3-none-any.whl", hash = "sha256:79a36ee1f83523214a3f72d56cf1c4e490d577dc61af77e43dfe5862bd9da01a", size = 215366, upload-time = "2026-07-16T03:15:58.239Z" }, -] - [[package]] name = "h11" version = "0.16.0" @@ -355,15 +329,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, ] -[[package]] -name = "smmap" -version = "5.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, -] - [[package]] name = "socksio" version = "1.0.0"