rebake 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rebake-0.0.1/.github/workflows/release.yml +35 -0
- rebake-0.0.1/.github/workflows/test.yml +78 -0
- rebake-0.0.1/.gitignore +1 -0
- rebake-0.0.1/.python-version +1 -0
- rebake-0.0.1/PKG-INFO +108 -0
- rebake-0.0.1/README.md +96 -0
- rebake-0.0.1/pyproject.toml +49 -0
- rebake-0.0.1/src/rebake/__init__.py +0 -0
- rebake-0.0.1/src/rebake/check.py +21 -0
- rebake-0.0.1/src/rebake/cli.py +45 -0
- rebake-0.0.1/src/rebake/config.py +44 -0
- rebake-0.0.1/src/rebake/py.typed +0 -0
- rebake-0.0.1/src/rebake/update.py +90 -0
- rebake-0.0.1/src/rebake/utils/__init__.py +0 -0
- rebake-0.0.1/src/rebake/utils/git.py +172 -0
- rebake-0.0.1/src/rebake/utils/template.py +20 -0
- rebake-0.0.1/src/rebake/utils/variables.py +31 -0
- rebake-0.0.1/tests/__init__.py +0 -0
- rebake-0.0.1/tests/e2e/__init__.py +0 -0
- rebake-0.0.1/tests/e2e/conftest.py +74 -0
- rebake-0.0.1/tests/e2e/fixtures/simple_template/cookiecutter.json +1 -0
- rebake-0.0.1/tests/e2e/fixtures/simple_template/{{cookiecutter.project_name}}/CONTRIBUTING.md +1 -0
- rebake-0.0.1/tests/e2e/fixtures/simple_template/{{cookiecutter.project_name}}/README.md +1 -0
- rebake-0.0.1/tests/e2e/test_check_e2e.py +34 -0
- rebake-0.0.1/tests/e2e/test_update_e2e.py +42 -0
- rebake-0.0.1/tests/test_check.py +63 -0
- rebake-0.0.1/tests/test_config.py +87 -0
- rebake-0.0.1/tests/test_update.py +106 -0
- rebake-0.0.1/tests/test_variables.py +51 -0
- rebake-0.0.1/uv.lock +588 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- '*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
deploy:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
environment:
|
|
14
|
+
name: pypi
|
|
15
|
+
url: https://pypi.org/p/rebake
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- name: Set up the latest version of uv
|
|
22
|
+
uses: astral-sh/setup-uv@v7
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
- name: Build
|
|
26
|
+
run: uv build
|
|
27
|
+
|
|
28
|
+
- name: upload dists
|
|
29
|
+
uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: release-dists
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
- name: Publish package distributions to PyPI
|
|
35
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test Python ${{ matrix.python-version }}
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v5
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync --all-extras
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: uv run pytest
|
|
34
|
+
|
|
35
|
+
lint:
|
|
36
|
+
name: Lint
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- name: Set up Python
|
|
43
|
+
uses: actions/setup-python@v5
|
|
44
|
+
with:
|
|
45
|
+
python-version: "3.13"
|
|
46
|
+
|
|
47
|
+
- name: Install uv
|
|
48
|
+
uses: astral-sh/setup-uv@v5
|
|
49
|
+
|
|
50
|
+
- name: Install dependencies
|
|
51
|
+
run: uv sync --all-extras
|
|
52
|
+
|
|
53
|
+
- name: Run ruff check
|
|
54
|
+
run: uv run ruff check
|
|
55
|
+
|
|
56
|
+
- name: Run ruff format check
|
|
57
|
+
run: uv run ruff format --check
|
|
58
|
+
|
|
59
|
+
type-check:
|
|
60
|
+
name: Type Check
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Set up Python
|
|
67
|
+
uses: actions/setup-python@v5
|
|
68
|
+
with:
|
|
69
|
+
python-version: "3.13"
|
|
70
|
+
|
|
71
|
+
- name: Install uv
|
|
72
|
+
uses: astral-sh/setup-uv@v5
|
|
73
|
+
|
|
74
|
+
- name: Install dependencies
|
|
75
|
+
run: uv sync --all-extras
|
|
76
|
+
|
|
77
|
+
- name: Run type check
|
|
78
|
+
run: uv run pyrefly check
|
rebake-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__pycache__/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
rebake-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rebake
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A spiritual successor to cruft for managing cookiecutter projects
|
|
5
|
+
Author-email: Ryo Kitagawa <kitadrum50@gmail.com>
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: cookiecutter>=2.6.0
|
|
8
|
+
Requires-Dist: gitpython>=3.1.44
|
|
9
|
+
Requires-Dist: rich>=13.9.4
|
|
10
|
+
Requires-Dist: typer>=0.15.1
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# rebake
|
|
14
|
+
|
|
15
|
+
A spiritual successor to [cruft](https://github.com/cruft/cruft) for managing [cookiecutter](https://github.com/cookiecutter/cookiecutter) projects.
|
|
16
|
+
|
|
17
|
+
rebake improves on cruft in two key areas:
|
|
18
|
+
|
|
19
|
+
1. **Better conflict UX** — uses `git apply -3` to produce inline conflict markers instead of `.rej` files
|
|
20
|
+
2. **New variable detection** — prompts for variables added to the template since the project was last updated
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
- Python 3.12+
|
|
25
|
+
- [uv](https://docs.astral.sh/uv/)
|
|
26
|
+
- Git
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv tool install rebake
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or add it to a project:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv add rebake
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### `rebake check`
|
|
43
|
+
|
|
44
|
+
Check whether the project is up-to-date with its template.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
rebake check [PROJECT_DIR]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Exit codes:
|
|
51
|
+
- `0` — up-to-date
|
|
52
|
+
- `1` — outdated
|
|
53
|
+
- `2` — error (e.g. `.cruft.json` not found)
|
|
54
|
+
|
|
55
|
+
### `rebake update`
|
|
56
|
+
|
|
57
|
+
Apply the latest template changes to the project.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
rebake update [PROJECT_DIR]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
rebake will:
|
|
64
|
+
1. Abort if there are uncommitted changes (commit or stash first)
|
|
65
|
+
2. Detect new variables added to the template and prompt for their values
|
|
66
|
+
3. Generate a diff between the old and new rendered templates
|
|
67
|
+
4. Apply the diff with `git apply -3` — conflicts appear as inline markers
|
|
68
|
+
5. Update `.cruft.json` with the new commit hash and any newly added variables
|
|
69
|
+
|
|
70
|
+
## Migrating from cruft
|
|
71
|
+
|
|
72
|
+
rebake reads `.cruft.json` as-is. No migration needed — just replace `cruft` with `rebake` in your commands.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# before
|
|
76
|
+
cruft check
|
|
77
|
+
cruft update
|
|
78
|
+
|
|
79
|
+
# after
|
|
80
|
+
rebake check
|
|
81
|
+
rebake update
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## `.cruft.json` format
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"template": "https://github.com/owner/template",
|
|
89
|
+
"commit": "abc123...",
|
|
90
|
+
"checkout": "main",
|
|
91
|
+
"context": {
|
|
92
|
+
"cookiecutter": {
|
|
93
|
+
"project_name": "my-project",
|
|
94
|
+
"author": "Jane Doe"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"skip": ["go.sum", "*.lock"]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/kitagry/rebake
|
|
105
|
+
cd rebake
|
|
106
|
+
uv sync
|
|
107
|
+
uv run pytest
|
|
108
|
+
```
|
rebake-0.0.1/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# rebake
|
|
2
|
+
|
|
3
|
+
A spiritual successor to [cruft](https://github.com/cruft/cruft) for managing [cookiecutter](https://github.com/cookiecutter/cookiecutter) projects.
|
|
4
|
+
|
|
5
|
+
rebake improves on cruft in two key areas:
|
|
6
|
+
|
|
7
|
+
1. **Better conflict UX** — uses `git apply -3` to produce inline conflict markers instead of `.rej` files
|
|
8
|
+
2. **New variable detection** — prompts for variables added to the template since the project was last updated
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
- Python 3.12+
|
|
13
|
+
- [uv](https://docs.astral.sh/uv/)
|
|
14
|
+
- Git
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install rebake
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or add it to a project:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv add rebake
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### `rebake check`
|
|
31
|
+
|
|
32
|
+
Check whether the project is up-to-date with its template.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
rebake check [PROJECT_DIR]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Exit codes:
|
|
39
|
+
- `0` — up-to-date
|
|
40
|
+
- `1` — outdated
|
|
41
|
+
- `2` — error (e.g. `.cruft.json` not found)
|
|
42
|
+
|
|
43
|
+
### `rebake update`
|
|
44
|
+
|
|
45
|
+
Apply the latest template changes to the project.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
rebake update [PROJECT_DIR]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
rebake will:
|
|
52
|
+
1. Abort if there are uncommitted changes (commit or stash first)
|
|
53
|
+
2. Detect new variables added to the template and prompt for their values
|
|
54
|
+
3. Generate a diff between the old and new rendered templates
|
|
55
|
+
4. Apply the diff with `git apply -3` — conflicts appear as inline markers
|
|
56
|
+
5. Update `.cruft.json` with the new commit hash and any newly added variables
|
|
57
|
+
|
|
58
|
+
## Migrating from cruft
|
|
59
|
+
|
|
60
|
+
rebake reads `.cruft.json` as-is. No migration needed — just replace `cruft` with `rebake` in your commands.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# before
|
|
64
|
+
cruft check
|
|
65
|
+
cruft update
|
|
66
|
+
|
|
67
|
+
# after
|
|
68
|
+
rebake check
|
|
69
|
+
rebake update
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## `.cruft.json` format
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"template": "https://github.com/owner/template",
|
|
77
|
+
"commit": "abc123...",
|
|
78
|
+
"checkout": "main",
|
|
79
|
+
"context": {
|
|
80
|
+
"cookiecutter": {
|
|
81
|
+
"project_name": "my-project",
|
|
82
|
+
"author": "Jane Doe"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"skip": ["go.sum", "*.lock"]
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/kitagry/rebake
|
|
93
|
+
cd rebake
|
|
94
|
+
uv sync
|
|
95
|
+
uv run pytest
|
|
96
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rebake"
|
|
3
|
+
description = "A spiritual successor to cruft for managing cookiecutter projects"
|
|
4
|
+
readme = "README.md"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Ryo Kitagawa", email = "kitadrum50@gmail.com" }
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
dynamic = ["version"]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cookiecutter>=2.6.0",
|
|
12
|
+
"typer>=0.15.1",
|
|
13
|
+
"gitpython>=3.1.44",
|
|
14
|
+
"rich>=13.9.4",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
rebake = "rebake.cli:app"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=8.3.5",
|
|
23
|
+
"pytest-mock>=3.14.0",
|
|
24
|
+
"ruff>=0.9.0",
|
|
25
|
+
"pyrefly>=0.17.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
markers = ["e2e: end-to-end tests using real git and cookiecutter"]
|
|
30
|
+
|
|
31
|
+
[tool.ruff]
|
|
32
|
+
target-version = "py312"
|
|
33
|
+
line-length = 120
|
|
34
|
+
|
|
35
|
+
[tool.ruff.lint]
|
|
36
|
+
select = ["E", "F", "I"]
|
|
37
|
+
|
|
38
|
+
[tool.hatch.version]
|
|
39
|
+
source = "uv-dynamic-versioning"
|
|
40
|
+
|
|
41
|
+
[tool.uv-dynamic-versioning]
|
|
42
|
+
enable = true
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/rebake"]
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["hatchling", "uv-dynamic-versioning"]
|
|
49
|
+
build-backend = "hatchling.build"
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rebake.config import CruftConfig
|
|
7
|
+
from rebake.utils.git import get_template_head_commit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CheckResult(Enum):
|
|
11
|
+
UP_TO_DATE = "up-to-date"
|
|
12
|
+
OUTDATED = "outdated"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_up_to_date(project_dir: Path = Path(".")) -> CheckResult:
|
|
16
|
+
"""Check whether the project is up-to-date with its template."""
|
|
17
|
+
config = CruftConfig.load(project_dir)
|
|
18
|
+
head_commit = get_template_head_commit(config.template, checkout=config.checkout)
|
|
19
|
+
if config.commit == head_commit:
|
|
20
|
+
return CheckResult.UP_TO_DATE
|
|
21
|
+
return CheckResult.OUTDATED
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from rebake.check import CheckResult, is_up_to_date
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="A spiritual successor to cruft for managing cookiecutter projects.")
|
|
11
|
+
console = Console()
|
|
12
|
+
err_console = Console(stderr=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def check(
|
|
17
|
+
project_dir: Path = typer.Argument(Path("."), help="Path to the project directory"),
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Check if the project is up-to-date with its template."""
|
|
20
|
+
try:
|
|
21
|
+
result = is_up_to_date(project_dir)
|
|
22
|
+
except FileNotFoundError as e:
|
|
23
|
+
err_console.print(f"[red]Error:[/red] {e}")
|
|
24
|
+
raise typer.Exit(code=2)
|
|
25
|
+
|
|
26
|
+
if result == CheckResult.UP_TO_DATE:
|
|
27
|
+
console.print("[green]✓[/green] Project is up-to-date.")
|
|
28
|
+
raise typer.Exit(code=0)
|
|
29
|
+
else:
|
|
30
|
+
console.print("[yellow]![/yellow] Project is outdated.")
|
|
31
|
+
raise typer.Exit(code=1)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command()
|
|
35
|
+
def update(
|
|
36
|
+
project_dir: Path = typer.Argument(Path("."), help="Path to the project directory"),
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Apply the latest template changes to the project."""
|
|
39
|
+
from rebake.update import run_update
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
run_update(project_dir)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
err_console.print(f"[red]Error:[/red] {e}")
|
|
45
|
+
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
CRUFT_FILE = ".cruft.json"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class CruftConfig:
|
|
13
|
+
template: str
|
|
14
|
+
commit: str
|
|
15
|
+
context: dict[str, Any]
|
|
16
|
+
checkout: str | None = None
|
|
17
|
+
skip: list[str] = field(default_factory=list)
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def load(cls, project_dir: Path = Path(".")) -> "CruftConfig":
|
|
21
|
+
cruft_file = project_dir / CRUFT_FILE
|
|
22
|
+
if not cruft_file.exists():
|
|
23
|
+
raise FileNotFoundError(f"{CRUFT_FILE} not found in {project_dir}")
|
|
24
|
+
data = json.loads(cruft_file.read_text())
|
|
25
|
+
return cls(
|
|
26
|
+
template=data["template"],
|
|
27
|
+
commit=data["commit"],
|
|
28
|
+
context=data.get("context", {}),
|
|
29
|
+
checkout=data.get("checkout"),
|
|
30
|
+
skip=data.get("skip", []),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def save(self, project_dir: Path = Path(".")) -> None:
|
|
34
|
+
cruft_file = project_dir / CRUFT_FILE
|
|
35
|
+
data: dict[str, Any] = {
|
|
36
|
+
"template": self.template,
|
|
37
|
+
"commit": self.commit,
|
|
38
|
+
"context": self.context,
|
|
39
|
+
}
|
|
40
|
+
if self.checkout is not None:
|
|
41
|
+
data["checkout"] = self.checkout
|
|
42
|
+
if self.skip:
|
|
43
|
+
data["skip"] = self.skip
|
|
44
|
+
cruft_file.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
|
File without changes
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from rebake.config import CruftConfig
|
|
9
|
+
from rebake.utils.git import (
|
|
10
|
+
apply_patch,
|
|
11
|
+
clone_at_commit,
|
|
12
|
+
generate_diff,
|
|
13
|
+
get_template_head_commit,
|
|
14
|
+
is_working_tree_clean,
|
|
15
|
+
)
|
|
16
|
+
from rebake.utils.template import render_template
|
|
17
|
+
from rebake.utils.variables import detect_new_variables, prompt_new_variables
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run_update(project_dir: Path = Path(".")) -> None:
|
|
23
|
+
"""Apply the latest template changes to the project.
|
|
24
|
+
|
|
25
|
+
Raises RuntimeError when the working tree has uncommitted changes.
|
|
26
|
+
"""
|
|
27
|
+
# Resolve to absolute path before any subprocess/cookiecutter calls that may change CWD
|
|
28
|
+
project_dir = project_dir.resolve()
|
|
29
|
+
|
|
30
|
+
if not is_working_tree_clean(project_dir):
|
|
31
|
+
raise RuntimeError("Project has uncommitted changes. Please commit or stash them before updating.")
|
|
32
|
+
|
|
33
|
+
config = CruftConfig.load(project_dir)
|
|
34
|
+
old_commit = config.commit
|
|
35
|
+
new_commit = get_template_head_commit(config.template, checkout=config.checkout)
|
|
36
|
+
|
|
37
|
+
console.print(f"Updating from [cyan]{old_commit[:8]}[/cyan] → [cyan]{new_commit[:8]}[/cyan]")
|
|
38
|
+
|
|
39
|
+
old_context = config.context.get("cookiecutter", {})
|
|
40
|
+
|
|
41
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
42
|
+
tmp = Path(tmpdir)
|
|
43
|
+
|
|
44
|
+
# Clone template at old and new commits to compute the diff
|
|
45
|
+
old_template_dir = tmp / "old_template"
|
|
46
|
+
new_template_dir = tmp / "new_template"
|
|
47
|
+
clone_at_commit(config.template, old_commit, old_template_dir)
|
|
48
|
+
clone_at_commit(config.template, new_commit, new_template_dir)
|
|
49
|
+
|
|
50
|
+
# Detect variables added in the new template and prompt the user
|
|
51
|
+
new_vars = detect_new_variables(new_template_dir, old_context)
|
|
52
|
+
extra_context = {}
|
|
53
|
+
if new_vars:
|
|
54
|
+
console.print("[yellow]New template variables detected. Please provide values:[/yellow]")
|
|
55
|
+
extra_context = prompt_new_variables(new_vars)
|
|
56
|
+
|
|
57
|
+
merged_context = {**old_context, **extra_context}
|
|
58
|
+
|
|
59
|
+
# Render both template versions with the merged context
|
|
60
|
+
old_output = tmp / "old_output"
|
|
61
|
+
new_output = tmp / "new_output"
|
|
62
|
+
old_output.mkdir()
|
|
63
|
+
new_output.mkdir()
|
|
64
|
+
old_rendered = render_template(old_template_dir, merged_context, old_output)
|
|
65
|
+
new_rendered = render_template(new_template_dir, merged_context, new_output)
|
|
66
|
+
|
|
67
|
+
patch = generate_diff(old_rendered, new_rendered)
|
|
68
|
+
|
|
69
|
+
if patch:
|
|
70
|
+
success, stderr = apply_patch(patch, project_dir)
|
|
71
|
+
if not success:
|
|
72
|
+
rej_files = sorted(project_dir.rglob("*.rej"))
|
|
73
|
+
console.print("[yellow]![/yellow] Some hunks could not be applied.")
|
|
74
|
+
if rej_files:
|
|
75
|
+
console.print("Resolve conflicts and delete the following [bold].rej[/bold] files:")
|
|
76
|
+
for f in rej_files:
|
|
77
|
+
console.print(f" [bold]{f.relative_to(project_dir)}[/bold]")
|
|
78
|
+
if stderr:
|
|
79
|
+
console.print(stderr)
|
|
80
|
+
else:
|
|
81
|
+
console.print("[green]✓[/green] Patch applied successfully.")
|
|
82
|
+
else:
|
|
83
|
+
console.print("[green]✓[/green] No changes to apply.")
|
|
84
|
+
|
|
85
|
+
# Persist the new commit hash and any newly prompted variables.
|
|
86
|
+
# Save even on partial apply so the next run starts from the new baseline
|
|
87
|
+
# rather than re-applying the same diff.
|
|
88
|
+
config.commit = new_commit
|
|
89
|
+
config.context["cookiecutter"] = merged_context
|
|
90
|
+
config.save(project_dir)
|
|
File without changes
|