diff --git a/pyproject.toml b/pyproject.toml index 225ec7e2a0..ac7416475f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ name = "dycw-utilities" readme = "README.md" requires-python = ">= 3.12" - version = "0.174.17" + version = "0.174.18" [project.entry-points.pytest11] pytest-randomly = "utilities.pytest_plugins.pytest_randomly" @@ -135,7 +135,7 @@ # bump-my-version [tool.bumpversion] allow_dirty = true - current_version = "0.174.17" + current_version = "0.174.18" [[tool.bumpversion.files]] filename = "src/utilities/__init__.py" diff --git a/src/tests/test_subprocess.py b/src/tests/test_subprocess.py index 1b52890194..a9db25c059 100644 --- a/src/tests/test_subprocess.py +++ b/src/tests/test_subprocess.py @@ -33,6 +33,7 @@ cp_cmd, echo_cmd, expand_path, + git_clone, git_clone_cmd, git_hard_reset_cmd, maybe_parent, @@ -60,6 +61,7 @@ tee_cmd, touch_cmd, uv_run_cmd, + yield_git_repo, yield_ssh_temp_dir, ) from utilities.tempfile import TemporaryDirectory, TemporaryFile @@ -233,14 +235,21 @@ def test_subs(self) -> None: assert result == expected +class TestGitClone: + @throttle(delta=5 * MINUTE) + def test_main(self, *, tmp_path: Path) -> None: + git_clone("https://github.com/dycw/template-generic", tmp_path) + assert (tmp_path / ".bumpversion.toml").is_file() + + class TestGitCloneCmd: def test_main(self) -> None: - result = git_clone_cmd("https://github.com/foo/bar", "path") + result = git_clone_cmd("https://github.com/dycw/template-generic", "path") expected = [ "git", "clone", "--recurse-submodules", - "https://github.com/foo/bar", + "https://github.com/dycw/template-generic", "path", ] assert result == expected @@ -1187,6 +1196,13 @@ def test_args(self) -> None: assert result == expected +class TestYieldGitRepo: + @throttle(delta=5 * MINUTE) + def test_main(self) -> None: + with yield_git_repo("https://github.com/dycw/template-generic") as temp: + assert (temp / ".bumpversion.toml").is_file() + + class TestYieldSSHTempDir: @skipif_ci @throttle(delta=5 * MINUTE) diff --git a/src/utilities/__init__.py b/src/utilities/__init__.py index 88301dd685..ee4f5e6a7a 100644 --- a/src/utilities/__init__.py +++ b/src/utilities/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "0.174.17" +__version__ = "0.174.18" diff --git a/src/utilities/docker.py b/src/utilities/docker.py index 549075e930..6d153138b6 100644 --- a/src/utilities/docker.py +++ b/src/utilities/docker.py @@ -269,6 +269,7 @@ def yield_docker_temp_dir( logger: LoggerLike | None = None, keep: bool = False, ) -> Iterator[Path]: + """Yield a temporary directory in a Docker container.""" path = Path( # skipif-ci docker_exec( container, diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index b2864aff71..91538051f1 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -221,6 +221,16 @@ def expand_path( ## +def git_clone( + url: str, path: PathLike, /, *, sudo: bool = False, branch: str | None = None +) -> None: + """Clone a repository.""" + rm(path, sudo=sudo) + run(*maybe_sudo_cmd(*git_clone_cmd(url, path), sudo=sudo)) + if branch is not None: + run(*maybe_sudo_cmd(*git_hard_reset_cmd(branch=branch), sudo=sudo), cwd=path) + + def git_clone_cmd(url: str, path: PathLike, /) -> list[str]: """Command to use 'git clone' to clone a repository.""" return ["git", "clone", "--recurse-submodules", url, str(path)] @@ -1089,14 +1099,26 @@ def tee_cmd(path: PathLike, /, *, append: bool = False) -> list[str]: ## +def touch(path: PathLike, /, *, sudo: bool = False) -> None: + """Change file access and modification times.""" + run(*maybe_sudo_cmd(*touch_cmd(path), sudo=sudo)) + + def touch_cmd(path: PathLike, /) -> list[str]: + """Command to use 'touch' to change file access and modification times.""" return ["touch", str(path)] ## +def uv_run(module: str, /, *args: str) -> None: + """Run a command or script.""" + run(*uv_run_cmd(module, *args)) # pragma: no cover + + def uv_run_cmd(module: str, /, *args: str) -> list[str]: + """Command to use 'uv' to run a command or script.""" return [ "uv", "run", @@ -1114,6 +1136,17 @@ def uv_run_cmd(module: str, /, *args: str) -> list[str]: ## +@contextmanager +def yield_git_repo(url: str, /, *, branch: str | None = None) -> Iterator[Path]: + """Yield a temporary git repository.""" + with TemporaryDirectory() as temp_dir: + git_clone(url, temp_dir, branch=branch) + yield temp_dir + + +## + + @contextmanager def yield_ssh_temp_dir( user: str, @@ -1124,6 +1157,7 @@ def yield_ssh_temp_dir( logger: LoggerLike | None = None, keep: bool = False, ) -> Iterator[Path]: + """Yield a temporary directory on a remote machine.""" path = Path( # skipif-ci ssh(user, hostname, *MKTEMP_DIR_CMD, return_=True, retry=retry, logger=logger) ) @@ -1160,6 +1194,7 @@ def yield_ssh_temp_dir( "cp_cmd", "echo_cmd", "expand_path", + "git_clone", "git_clone_cmd", "git_hard_reset_cmd", "maybe_parent", @@ -1183,7 +1218,10 @@ def yield_ssh_temp_dir( "symlink", "symlink_cmd", "tee_cmd", + "touch", "touch_cmd", + "uv_run", "uv_run_cmd", + "yield_git_repo", "yield_ssh_temp_dir", ] diff --git a/uv.lock b/uv.lock index 87f8606f03..c0f758a86d 100644 --- a/uv.lock +++ b/uv.lock @@ -634,7 +634,7 @@ wheels = [ [[package]] name = "dycw-utilities" -version = "0.174.17" +version = "0.174.18" source = { editable = "." } dependencies = [ { name = "atomicwrites" },