Skip to content

Commit 84d4105

Browse files
authored
Merge pull request #25
develop
2 parents 4be472b + bfc177d commit 84d4105

27 files changed

+426
-87
lines changed

hooks/post_gen_project.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88

99

1010
REMOVE_PATHS: list[str] = [
11-
"{% if cookiecutter.platform_provider != github %}.github{% endif %}",
12-
"{% if cookiecutter.platform_provider != gitlab %}.gitlab-ci.yml{% endif %}",
13-
"{% if cookiecutter.platform_provider != bitbucket %}.bitbucket-pipelines.yml{% endif %}",
11+
"{% if not cookiecutter.add_rust_extension %}rust{% endif %}",
12+
"{% if not cookiecutter.add_rust_extension %}.github/workflows/lint-rust.yml{% endif %}",
13+
"{% if not cookiecutter.add_rust_extension %}.github/workflows/build-rust.yml{% endif %}",
14+
"{% if not cookiecutter.add_rust_extension %}.github/workflows/test-rust.yml{% endif %}",
15+
"{% if cookiecutter.repository_provider != 'github' %}.github{% endif %}",
16+
"{% if cookiecutter.repository_provider != 'gitlab' %}.gitlab-ci.yml{% endif %}",
17+
"{% if cookiecutter.repository_provider != 'bitbucket' %}bitbucket-pipelines.yml{% endif %}",
1418
]
1519

1620

@@ -50,7 +54,7 @@ def remove_undesired_files() -> None:
5054
if path.is_dir():
5155
shutil.rmtree(path, onerror=remove_readonly)
5256
else:
53-
path.unlink()
57+
path.unlink(missing_ok=True)
5458

5559

5660
def remove_readonly(func: Callable[[str], Any], path: str, _: Any) -> None:
@@ -63,4 +67,4 @@ def remove_readonly(func: Callable[[str], Any], path: str, _: Any) -> None:
6367

6468

6569
if __name__ == "__main__":
66-
reindent_cookiecutter_json()
70+
post_gen_project()

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def test(session: Session) -> None:
122122
session.run("pytest", "tests")
123123

124124

125-
@nox.parametrize(arg_names="add_rust_extension", arg_values_list=[False], ids=["no-rust"])
125+
@nox.parametrize(arg_names="add_rust_extension", arg_values_list=[False, True], ids=["no-rust", "rust"])
126126
@nox.session(python=DEFAULT_TEMPLATE_PYTHON_VERSION, name="update-demo")
127127
def update_demo(session: Session, add_rust_extension: bool) -> None:
128128
session.log("Installing script dependencies for updating generated project demos...")

scripts/lint-from-demo.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import sys
21
from pathlib import Path
32
from typing import Annotated
43

@@ -9,6 +8,14 @@
98
from util import git
109
from util import FolderOption
1110
from util import in_new_demo
11+
from util import require_clean_and_up_to_date_repo
12+
13+
14+
# These still may need linted, but retrocookie shouldn't be used on them
15+
IGNORED_FILES: list[str] = [
16+
"pyproject.toml",
17+
"uv.lock",
18+
]
1219

1320

1421
cli: typer.Typer = typer.Typer()
@@ -21,20 +28,22 @@ def lint_from_demo(
2128
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False
2229
) -> None:
2330
"""Runs precommit in a generated project and matches the template to the results."""
24-
try:
25-
with in_new_demo(
26-
demos_cache_folder=demos_cache_folder,
27-
add_rust_extension=add_rust_extension,
28-
no_cache=no_cache
29-
) as demo_path:
30-
pre_commit.main.main(["run", "--all-files", "--hook-stage=manual", "--show-diff-on-failure"])
31-
try:
32-
retrocookie(instance_path=demo_path, commits=["HEAD"])
33-
finally:
34-
git("checkout", "HEAD", "--", "{{cookiecutter.project_name}}/pyproject.toml")
35-
except Exception as error:
36-
typer.secho(f"error: {error}", fg="red")
37-
sys.exit(1)
31+
with in_new_demo(
32+
demos_cache_folder=demos_cache_folder,
33+
add_rust_extension=add_rust_extension,
34+
no_cache=no_cache
35+
) as demo_path:
36+
require_clean_and_up_to_date_repo()
37+
git("checkout", "develop")
38+
git("branch", "-D", "temp/lint-from-demo", ignore_error=True)
39+
git("checkout", "-b", "temp/lint-from-demo", "develop")
40+
pre_commit.main.main(["run", "--all-files", "--show-diff-on-failure"])
41+
42+
for path in IGNORED_FILES:
43+
git("checkout", "HEAD", "--", path)
44+
git("add", ".")
45+
git("commit", "-m", "meta: lint-from-demo", "--no-verify")
46+
retrocookie(instance_path=demo_path, commits=["develop..temp/lint-from-demo"])
3847

3948

4049
if __name__ == '__main__':

scripts/update-demo.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from util import git
1111
from util import FolderOption
1212
from util import REPO_FOLDER
13+
from util import require_clean_and_up_to_date_repo
1314

1415

1516
cli: typer.Typer = typer.Typer()
@@ -26,7 +27,8 @@ def update_demo(
2627
demo_path: Path = demos_cache_folder / demo_name
2728
typer.secho(f"Updating demo project at {demo_path=}.", fg="yellow")
2829
with work_in(demo_path):
29-
git("status", "--porcelain")
30+
require_clean_and_up_to_date_repo()
31+
git("checkout", "develop")
3032
cruft.update(
3133
project_dir=demo_path,
3234
template_path=REPO_FOLDER,

scripts/util.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
from typing import Any
1111
from typing import Callable
1212
from typing import Generator
13+
from typing import Literal
14+
from typing import Optional
15+
from typing import overload
1316

1417
import cruft
1518
import typer
1619
from cookiecutter.utils import work_in
17-
from pygments.lexers import q
1820
from typer.models import OptionInfo
1921

2022

@@ -35,12 +37,24 @@ def remove_readonly(func: Callable[[str], Any], path: str, _: Any) -> None:
3537
func(path)
3638

3739

38-
def run_command(command: str, *args: str) -> subprocess.CompletedProcess:
40+
@overload
41+
def run_command(command: str, *args: str, ignore_error: Literal[True]) -> Optional[subprocess.CompletedProcess]:
42+
...
43+
44+
45+
@overload
46+
def run_command(command: str, *args: str, ignore_error: Literal[False] = ...) -> subprocess.CompletedProcess:
47+
...
48+
49+
50+
def run_command(command: str, *args: str, ignore_error: bool = False) -> Optional[subprocess.CompletedProcess]:
3951
"""Runs the provided command in a subprocess."""
4052
try:
4153
process = subprocess.run([command, *args], check=True, capture_output=True, text=True)
4254
return process
4355
except subprocess.CalledProcessError as error:
56+
if ignore_error:
57+
return None
4458
print(error.stdout, end="")
4559
print(error.stderr, end="", file=sys.stderr)
4660
raise
@@ -50,6 +64,28 @@ def run_command(command: str, *args: str) -> subprocess.CompletedProcess:
5064
uv: partial[subprocess.CompletedProcess] = partial(run_command, "uv")
5165

5266

67+
def require_clean_and_up_to_date_repo() -> None:
68+
"""Checks if the repo is clean and up to date with any important branches."""
69+
git("fetch")
70+
git("status", "--porcelain")
71+
if not is_branch_synced_with_remote("develop"):
72+
raise ValueError("develop is not synced with origin/develop")
73+
if not is_branch_synced_with_remote("main"):
74+
raise ValueError("main is not synced with origin/main")
75+
if not is_ancestor("main", "develop"):
76+
raise ValueError("main is not an ancestor of develop")
77+
78+
79+
def is_branch_synced_with_remote(branch: str) -> bool:
80+
"""Checks if the branch is synced with its remote."""
81+
return is_ancestor(branch, f"origin/{branch}") and is_ancestor(f"origin/{branch}", branch)
82+
83+
84+
def is_ancestor(ancestor: str, descendent: str) -> bool:
85+
"""Checks if the branch is synced with its remote."""
86+
return git("merge-base", "--is-ancestor", ancestor, descendent).returncode == 0
87+
88+
5389
@contextmanager
5490
def in_new_demo(
5591
demos_cache_folder: Path,

tests/conftest.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import subprocess
44
from pathlib import Path
55
from typing import Any
6+
from typing import Literal
67

78
import pytest
89
import toml
@@ -84,10 +85,15 @@ def robust_demo__name(robust_demo__add_rust_extension: str, robust_demo__is_setu
8485

8586

8687
@pytest.fixture(scope="session")
87-
def robust_demo__extra_context(robust_demo__name: str, robust_demo__add_rust_extension: bool) -> dict[str, Any]:
88+
def robust_demo__extra_context(
89+
robust_demo__name: str,
90+
robust_demo__add_rust_extension: bool,
91+
robust_demo__repository_provider: Literal["github", "gitlab", "bitbucket"]
92+
) -> dict[str, Any]:
8893
return {
8994
"project_name": robust_demo__name,
90-
"add_rust_extension": robust_demo__add_rust_extension
95+
"add_rust_extension": robust_demo__add_rust_extension,
96+
"repository_provider": robust_demo__repository_provider,
9197
}
9298

9399

@@ -96,6 +102,11 @@ def robust_demo__add_rust_extension(request: FixtureRequest) -> bool:
96102
return getattr(request, "param", False)
97103

98104

105+
@pytest.fixture(scope="session")
106+
def robust_demo__repository_provider(request: FixtureRequest) -> Literal["github", "gitlab", "bitbucket"]:
107+
return getattr(request, "param", "github")
108+
109+
99110
@pytest.fixture(scope="session")
100111
def robust_demo__is_setup(request: FixtureRequest) -> bool:
101112
return getattr(request, "param", True)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
6+
@pytest.mark.parametrize(
7+
argnames="removed_relative_path",
8+
argvalues=[".gitlab-ci.yml", "bitbucket-pipelines.yml"]
9+
)
10+
@pytest.mark.parametrize(
11+
argnames="robust_demo__repository_provider",
12+
argvalues=["github"],
13+
indirect=True
14+
)
15+
def test_files_removed_for_github(robust_demo: Path, removed_relative_path: str) -> None:
16+
path: Path = robust_demo / removed_relative_path
17+
assert not path.exists()
18+
19+
20+
@pytest.mark.parametrize(
21+
argnames="removed_relative_path",
22+
argvalues=[".github", "bitbucket-pipelines.yml"]
23+
)
24+
@pytest.mark.parametrize(
25+
argnames="robust_demo__repository_provider",
26+
argvalues=["gitlab"],
27+
indirect=True
28+
)
29+
def test_files_removed_for_gitlab(robust_demo: Path, removed_relative_path: str) -> None:
30+
path: Path = robust_demo / removed_relative_path
31+
assert not path.exists()
32+
33+
34+
@pytest.mark.parametrize(
35+
argnames="removed_relative_path",
36+
argvalues=[".github", ".gitlab-ci.yml"]
37+
)
38+
@pytest.mark.parametrize(
39+
argnames="robust_demo__repository_provider",
40+
argvalues=["bitbucket"],
41+
indirect=True
42+
)
43+
def test_files_removed_for_bitbucket(robust_demo: Path, removed_relative_path: str) -> None:
44+
path: Path = robust_demo / removed_relative_path
45+
assert not path.exists()
46+
47+
48+
@pytest.mark.parametrize(
49+
argnames="removed_relative_path",
50+
argvalues=[
51+
"rust",
52+
".github/workflows/lint-rust.yml",
53+
".github/workflows/build-rust.yml",
54+
".github/workflows/test-rust.yml"
55+
]
56+
)
57+
@pytest.mark.parametrize(
58+
argnames="robust_demo__add_rust_extension",
59+
argvalues=[False],
60+
indirect=True,
61+
ids=["no-rust"]
62+
)
63+
@pytest.mark.parametrize(
64+
argnames="robust_demo__repository_provider",
65+
argvalues=["github"],
66+
indirect=True,
67+
)
68+
def test_files_removed_for_no_rust_extension(robust_demo: Path, removed_relative_path: str) -> None:
69+
path: Path = robust_demo / removed_relative_path
70+
assert not path.exists()

{{cookiecutter.project_name}}/.github/workflows/build-docs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ on:
1111
- "noxfile.py"
1212
- "pyproject.toml"
1313
- ".github/workflows/build-docs.yml"
14-
- ".ruff.toml" # Affects docstrings via linting
15-
- "pyrightconfig.json" # Affects type hints in docs
14+
- ".ruff.toml" # Affects docstrings via linting
15+
- "pyrightconfig.json" # Affects type hints in docs
1616

1717
push:
1818
branches:

{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} renamed to {{cookiecutter.project_name}}/.github/workflows/lint-rust.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
name: Lint Rust Code
32

43
on:

{{cookiecutter.project_name}}/.github/workflows/release-python.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ jobs:
3333
id: current_version
3434
run: echo "CURRENT_VERSION=v$(uvx --from commitizen cz version -p)" >> $GITHUB_OUTPUT
3535

36-
3736
build_and_testpypi:
3837
name: Build & Publish to TestPyPI
3938
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)