From c82277d6656adf423f838b6d7e5fe3a520be549f Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 24 Jul 2020 14:03:23 +0200 Subject: [PATCH] Windows fixes - Ignore errors when executing `shutil.rmtree()`, because it seems like it's common to fail when deleting git repos on Windows, and since these are temp files, we don't really care that much there's garbage left. Any good OS should clean the temp folders automatically. - Always find Jinja templates in `PosixPath` mode. - Ignore `OSError` when trying to enter a possibly git root directory. This is yielded by Windows when the path is a URL and we don't really care about it. - Fix some tests with non-windows haradcoded stuff. - Fix a test that was using a Bash script. Modified to be Python, which should work fine cross-system. - Do not modify EOL in CI. - Use python executable in tests instead of python3. - Update pre-commit versions to include https://github.com/pre-commit/pre-commit-hooks/pull/509. --- .github/workflows/ci.yml | 8 ++++++++ .pre-commit-config.yaml | 4 ++-- copier/main.py | 2 +- copier/tools.py | 6 ++---- copier/vcs.py | 6 ++---- tests/demo_migrations/copier.yaml | 6 +++--- tests/demo_migrations/migrations.py | 2 +- tests/demo_migrations/tasks.py | 13 +++++++++++++ tests/demo_migrations/tasks.sh | 4 ---- tests/test_config.py | 6 +++--- tests/test_migrations.py | 6 +++--- tests/test_output.py | 8 ++++---- tests/test_vcs.py | 2 +- 13 files changed, 43 insertions(+), 30 deletions(-) create mode 100755 tests/demo_migrations/tasks.py delete mode 100755 tests/demo_migrations/tasks.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b792e8fb..8c2db686b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: branches: [master] release: types: [created] +env: + LANG: "en_US.utf-8" + LC_ALL: "en_US.utf-8" + PYTHONIOENCODING: "UTF-8" jobs: build: @@ -45,6 +49,10 @@ jobs: shell: bash - run: git config --global user.email copier@copier shell: bash + - run: git config --global core.autocrlf false + shell: bash + - run: git config --global core.eol lf + shell: bash - run: python -m pip install poetry poetry-dynamic-versioning shell: bash - run: poetry install diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f5b233a0..b68bf0315 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: # isorting our imports - repo: https://github.com/timothycrosley/isort - rev: 4.3.21 + rev: 5.2.2 hooks: - id: isort additional_dependencies: @@ -52,7 +52,7 @@ repos: # miscellaneous hooks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.1.0 + rev: v3.2.0 hooks: - id: check-added-large-files - id: check-ast diff --git a/copier/main.py b/copier/main.py index 9ef7e1212..e3f5eb7e1 100644 --- a/copier/main.py +++ b/copier/main.py @@ -148,7 +148,7 @@ def copy( raise finally: if is_update: - shutil.rmtree(conf.src_path) + shutil.rmtree(conf.src_path, ignore_errors=True) def copy_local(conf: ConfigData) -> None: diff --git a/copier/tools.py b/copier/tools.py index 4e3ddf006..36512ab20 100644 --- a/copier/tools.py +++ b/copier/tools.py @@ -156,10 +156,8 @@ def __init__(self, conf: ConfigData) -> None: ) def __call__(self, fullpath: StrOrPath) -> str: - relpath = ( - str(fullpath).replace(str(self.conf.src_path), "", 1).lstrip(os.path.sep) - ) - tmpl = self.env.get_template(relpath) + relpath = Path(fullpath).relative_to(self.conf.src_path).as_posix() + tmpl = self.env.get_template(str(relpath)) return tmpl.render(**self.data) def string(self, string: StrOrPath) -> str: diff --git a/copier/vcs.py b/copier/vcs.py index d24004a30..aaf66e054 100644 --- a/copier/vcs.py +++ b/copier/vcs.py @@ -1,7 +1,6 @@ """Utilities related to VCS.""" import re -import shutil import tempfile from pathlib import Path @@ -27,8 +26,8 @@ def is_git_repo_root(path: Path) -> bool: """Indicate if a given path is a git repo root directory.""" try: with local.cwd(path / ".git"): - return bool(git("rev-parse", "--is-inside-git-dir") == "true\n") - except (FileNotFoundError, NotADirectoryError): + return bool(git("rev-parse", "--is-inside-git-dir").strip() == "true") + except OSError: return False @@ -74,7 +73,6 @@ def checkout_latest_tag(local_repo: StrOrPath) -> str: def clone(url: str, ref: str = "HEAD") -> str: location = tempfile.mkdtemp(prefix=f"{__name__}.clone.") - shutil.rmtree(location) # Path must not exist git("clone", "--no-checkout", url, location) with local.cwd(location): git("checkout", ref) diff --git a/tests/demo_migrations/copier.yaml b/tests/demo_migrations/copier.yaml index 0e9211804..80dc5944b 100644 --- a/tests/demo_migrations/copier.yaml +++ b/tests/demo_migrations/copier.yaml @@ -1,11 +1,11 @@ _exclude: - - tasks.sh + - tasks.py - migrations.py - .git _tasks: - - "[[ _copier_conf.src_path / 'tasks.sh' ]] 1" - - ["[[ _copier_conf.src_path / 'tasks.sh' ]]", 2] + - "python [[ _copier_conf.src_path / 'tasks.py' ]] 1" + - [python, "[[ _copier_conf.src_path / 'tasks.py' ]]", 2] _migrations: # This migration is never executed because it's the 1st version copied, and diff --git a/tests/demo_migrations/migrations.py b/tests/demo_migrations/migrations.py index 6c498e0a0..9d34c3338 100755 --- a/tests/demo_migrations/migrations.py +++ b/tests/demo_migrations/migrations.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import json import os import sys diff --git a/tests/demo_migrations/tasks.py b/tests/demo_migrations/tasks.py new file mode 100755 index 000000000..9e7b10285 --- /dev/null +++ b/tests/demo_migrations/tasks.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +import os +import os.path +import sys +from contextlib import suppress + +from plumbum.cmd import git + +with open("created-with-tasks.txt", "a", newline="\n") as cwt: + cwt.write(" ".join([os.environ["STAGE"]] + sys.argv[1:]) + "\n") + git("init") + with suppress(FileNotFoundError): + os.unlink("delete-in-tasks.txt") diff --git a/tests/demo_migrations/tasks.sh b/tests/demo_migrations/tasks.sh deleted file mode 100755 index 50b26976b..000000000 --- a/tests/demo_migrations/tasks.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -echo $STAGE "$@" >> created-with-tasks.txt -git init -rm -f delete-in-tasks.txt diff --git a/tests/test_config.py b/tests/test_config.py index 1262d5016..705f729e6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,4 +1,3 @@ -import re from pathlib import Path import pytest @@ -45,11 +44,12 @@ def test_read_data(tmp_path, template): def test_invalid_yaml(capsys): - conf_path = Path("tests/demo_invalid/copier.yml") + conf_path = Path("tests", "demo_invalid", "copier.yml") with pytest.raises(InvalidConfigFileError): load_yaml_data(conf_path) out, _ = capsys.readouterr() - assert re.search(r"INVALID.*tests/demo_invalid/copier\.yml", out) + assert "INVALID CONFIG FILE" in out + assert str(conf_path) in out @pytest.mark.parametrize( diff --git a/tests/test_migrations.py b/tests/test_migrations.py index 258315e89..eee41e20b 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -35,7 +35,7 @@ def test_migrations_and_tasks(tmpdir: py.path.local): assert not (tmp_path / "delete-in-tasks.txt").exists() assert (tmp_path / "delete-in-migration-v2.txt").isfile() assert not (tmp_path / "migrations.py").exists() - assert not (tmp_path / "tasks.sh").exists() + assert not (tmp_path / "tasks.py").exists() assert not glob(str(tmp_path / "*-before.txt")) assert not glob(str(tmp_path / "*-after.txt")) answers = yaml.safe_load((tmp_path / ".copier-answers.yml").read()) @@ -53,7 +53,7 @@ def test_migrations_and_tasks(tmpdir: py.path.local): assert not (tmp_path / "delete-in-tasks.txt").exists() assert not (tmp_path / "delete-in-migration-v2.txt").exists() assert not (tmp_path / "migrations.py").exists() - assert not (tmp_path / "tasks.sh").exists() + assert not (tmp_path / "tasks.py").exists() assert (tmp_path / "v1.0.0-v2-v2.0-before.json").isfile() assert (tmp_path / "v1.0.0-v2-v2.0-after.json").isfile() answers = yaml.safe_load((tmp_path / ".copier-answers.yml").read()) @@ -100,7 +100,7 @@ def test_pre_migration_modifies_answers(tmp_path_factory): _migrations: - version: v2 before: - - - python3 + - - python - -c - | import sys, yaml, pathlib diff --git a/tests/test_output.py b/tests/test_output.py index 7c7b00c84..302d6246a 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -8,7 +8,7 @@ def test_output(capsys, tmp_path): out, _ = capsys.readouterr() assert re.search(r"create[^\s]* config\.py", out) assert re.search(r"create[^\s]* pyproject\.toml", out) - assert re.search(r"create[^\s]* doc/images/nslogo\.gif", out) + assert re.search(r"create[^\s]* doc[/\\]images[/\\]nslogo\.gif", out) def test_output_pretend(capsys, tmp_path): @@ -16,7 +16,7 @@ def test_output_pretend(capsys, tmp_path): out, _ = capsys.readouterr() assert re.search(r"create[^\s]* config\.py", out) assert re.search(r"create[^\s]* pyproject\.toml", out) - assert re.search(r"create[^\s]* doc/images/nslogo\.gif", out) + assert re.search(r"create[^\s]* doc[/\\]images[/\\]nslogo\.gif", out) def test_output_force(capsys, tmp_path): @@ -27,7 +27,7 @@ def test_output_force(capsys, tmp_path): assert re.search(r"conflict[^\s]* config\.py", out) assert re.search(r"force[^\s]* config\.py", out) assert re.search(r"identical[^\s]* pyproject\.toml", out) - assert re.search(r"identical[^\s]* doc/images/nslogo\.gif", out) + assert re.search(r"identical[^\s]* doc[/\\]images[/\\]nslogo\.gif", out) def test_output_skip(capsys, tmp_path): @@ -38,7 +38,7 @@ def test_output_skip(capsys, tmp_path): assert re.search(r"conflict[^\s]* config\.py", out) assert re.search(r"skip[^\s]* config\.py", out) assert re.search(r"identical[^\s]* pyproject\.toml", out) - assert re.search(r"identical[^\s]* doc/images/nslogo\.gif", out) + assert re.search(r"identical[^\s]* doc[/\\]images[/\\]nslogo\.gif", out) def test_output_quiet(capsys, tmp_path): diff --git a/tests/test_vcs.py b/tests/test_vcs.py index 4af0d54bf..9d1776741 100644 --- a/tests/test_vcs.py +++ b/tests/test_vcs.py @@ -45,4 +45,4 @@ def test_clone(): tmp = vcs.clone("https://github.com/copier-org/copier.git") assert tmp assert exists(join(tmp, "README.md")) - shutil.rmtree(tmp) + shutil.rmtree(tmp, ignore_errors=True)