rhiza 0.8.8__py3-none-any.whl → 0.9.1__py3-none-any.whl
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.
- rhiza/__init__.py +4 -4
- rhiza/__main__.py +1 -1
- rhiza/cli.py +122 -49
- rhiza/commands/__init__.py +7 -5
- rhiza/commands/init.py +34 -11
- rhiza/commands/materialize.py +48 -23
- rhiza/commands/migrate.py +1 -1
- rhiza/commands/summarise.py +416 -0
- rhiza/commands/uninstall.py +2 -1
- rhiza/commands/validate.py +8 -7
- rhiza/commands/welcome.py +2 -2
- rhiza/models.py +28 -6
- {rhiza-0.8.8.dist-info → rhiza-0.9.1.dist-info}/METADATA +46 -26
- rhiza-0.9.1.dist-info/RECORD +21 -0
- rhiza-0.8.8.dist-info/RECORD +0 -20
- {rhiza-0.8.8.dist-info → rhiza-0.9.1.dist-info}/WHEEL +0 -0
- {rhiza-0.8.8.dist-info → rhiza-0.9.1.dist-info}/entry_points.txt +0 -0
- {rhiza-0.8.8.dist-info → rhiza-0.9.1.dist-info}/licenses/LICENSE +0 -0
rhiza/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Rhiza — Manage reusable configuration templates for Python projects.
|
|
2
2
|
|
|
3
|
-
Rhiza is a command
|
|
3
|
+
Rhiza is a command-line interface (CLI) that helps you maintain consistent
|
|
4
4
|
configuration across multiple Python projects using templates stored in a
|
|
5
5
|
central repository. It can initialize projects with standard configuration,
|
|
6
6
|
materialize (inject) template files into a target repository, and validate the
|
|
@@ -11,8 +11,8 @@ template configuration.
|
|
|
11
11
|
- Template initialization for new or existing projects.
|
|
12
12
|
- Template materialization with selective include/exclude support.
|
|
13
13
|
- Configuration validation (syntax and basic semantics).
|
|
14
|
-
- Multi
|
|
15
|
-
- Non
|
|
14
|
+
- Multi-host support (GitHub and GitLab).
|
|
15
|
+
- Non-destructive updates by default, with an explicit `--force` flag.
|
|
16
16
|
|
|
17
17
|
## Quick start
|
|
18
18
|
|
|
@@ -29,7 +29,7 @@ Validate your configuration:
|
|
|
29
29
|
rhiza validate
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Customize `.
|
|
32
|
+
Customize `.rhiza/template.yml`, then materialize templates into your project:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
rhiza materialize
|
rhiza/__main__.py
CHANGED
|
@@ -11,7 +11,7 @@ import typer
|
|
|
11
11
|
from rhiza.cli import app
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def load_plugins(app: typer.Typer):
|
|
14
|
+
def load_plugins(app: typer.Typer) -> None:
|
|
15
15
|
"""Load plugins from entry points."""
|
|
16
16
|
# 'rhiza.plugins' matches the group we defined in rhiza-tools
|
|
17
17
|
plugin_entries = entry_points(group="rhiza.plugins")
|
rhiza/cli.py
CHANGED
|
@@ -5,6 +5,7 @@ Commands are thin wrappers around implementations in `rhiza.commands.*`.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Annotated
|
|
8
9
|
|
|
9
10
|
import typer
|
|
10
11
|
|
|
@@ -13,6 +14,7 @@ from rhiza.commands import init as init_cmd
|
|
|
13
14
|
from rhiza.commands import materialize as materialize_cmd
|
|
14
15
|
from rhiza.commands import validate as validate_cmd
|
|
15
16
|
from rhiza.commands.migrate import migrate as migrate_cmd
|
|
17
|
+
from rhiza.commands.summarise import summarise as summarise_cmd
|
|
16
18
|
from rhiza.commands.uninstall import uninstall as uninstall_cmd
|
|
17
19
|
from rhiza.commands.welcome import welcome as welcome_cmd
|
|
18
20
|
|
|
@@ -21,14 +23,14 @@ app = typer.Typer(
|
|
|
21
23
|
"""
|
|
22
24
|
Rhiza - Manage reusable configuration templates for Python projects
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
https://jebel-quant.github.io/rhiza-cli/
|
|
25
27
|
"""
|
|
26
28
|
),
|
|
27
29
|
add_completion=True,
|
|
28
30
|
)
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
def version_callback(value: bool):
|
|
33
|
+
def version_callback(value: bool) -> None:
|
|
32
34
|
"""Print version information and exit.
|
|
33
35
|
|
|
34
36
|
Args:
|
|
@@ -52,7 +54,7 @@ def main(
|
|
|
52
54
|
callback=version_callback,
|
|
53
55
|
is_eager=True,
|
|
54
56
|
),
|
|
55
|
-
):
|
|
57
|
+
) -> None:
|
|
56
58
|
"""Rhiza CLI main callback.
|
|
57
59
|
|
|
58
60
|
This callback is executed before any command. It handles global options
|
|
@@ -65,13 +67,15 @@ def main(
|
|
|
65
67
|
|
|
66
68
|
@app.command()
|
|
67
69
|
def init(
|
|
68
|
-
target:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
target: Annotated[
|
|
71
|
+
Path,
|
|
72
|
+
typer.Argument(
|
|
73
|
+
exists=True,
|
|
74
|
+
file_okay=False,
|
|
75
|
+
dir_okay=True,
|
|
76
|
+
help="Target directory (defaults to current directory)",
|
|
77
|
+
),
|
|
78
|
+
] = Path("."),
|
|
75
79
|
project_name: str = typer.Option(
|
|
76
80
|
None,
|
|
77
81
|
"--project-name",
|
|
@@ -93,10 +97,20 @@ def init(
|
|
|
93
97
|
help="Target Git hosting platform (github or gitlab). Determines which CI/CD files to include. "
|
|
94
98
|
"If not provided, will prompt interactively.",
|
|
95
99
|
),
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
template_repository: str = typer.Option(
|
|
101
|
+
None,
|
|
102
|
+
"--template-repository",
|
|
103
|
+
help="Custom template repository (format: owner/repo). Defaults to 'jebel-quant/rhiza'.",
|
|
104
|
+
),
|
|
105
|
+
template_branch: str = typer.Option(
|
|
106
|
+
None,
|
|
107
|
+
"--template-branch",
|
|
108
|
+
help="Custom template branch. Defaults to 'main'.",
|
|
109
|
+
),
|
|
110
|
+
) -> None:
|
|
111
|
+
r"""Initialize or validate .rhiza/template.yml.
|
|
98
112
|
|
|
99
|
-
Creates a default `.
|
|
113
|
+
Creates a default `.rhiza/template.yml` configuration file if one
|
|
100
114
|
doesn't exist, or validates the existing configuration.
|
|
101
115
|
|
|
102
116
|
The default template includes common Python project files.
|
|
@@ -108,6 +122,8 @@ def init(
|
|
|
108
122
|
rhiza init
|
|
109
123
|
rhiza init --git-host github
|
|
110
124
|
rhiza init --git-host gitlab
|
|
125
|
+
rhiza init --template-repository myorg/my-templates
|
|
126
|
+
rhiza init --template-repository myorg/my-templates --template-branch develop
|
|
111
127
|
rhiza init /path/to/project
|
|
112
128
|
rhiza init ..
|
|
113
129
|
"""
|
|
@@ -117,18 +133,22 @@ def init(
|
|
|
117
133
|
package_name=package_name,
|
|
118
134
|
with_dev_dependencies=with_dev_dependencies,
|
|
119
135
|
git_host=git_host,
|
|
136
|
+
template_repository=template_repository,
|
|
137
|
+
template_branch=template_branch,
|
|
120
138
|
)
|
|
121
139
|
|
|
122
140
|
|
|
123
141
|
@app.command()
|
|
124
142
|
def materialize(
|
|
125
|
-
target:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
143
|
+
target: Annotated[
|
|
144
|
+
Path,
|
|
145
|
+
typer.Argument(
|
|
146
|
+
exists=True,
|
|
147
|
+
file_okay=False,
|
|
148
|
+
dir_okay=True,
|
|
149
|
+
help="Target git repository (defaults to current directory)",
|
|
150
|
+
),
|
|
151
|
+
] = Path("."),
|
|
132
152
|
branch: str = typer.Option("main", "--branch", "-b", help="Rhiza branch to use"),
|
|
133
153
|
target_branch: str = typer.Option(
|
|
134
154
|
None,
|
|
@@ -137,13 +157,13 @@ def materialize(
|
|
|
137
157
|
help="Create and checkout a new branch in the target repository for changes",
|
|
138
158
|
),
|
|
139
159
|
force: bool = typer.Option(False, "--force", "-y", help="Overwrite existing files"),
|
|
140
|
-
):
|
|
160
|
+
) -> None:
|
|
141
161
|
r"""Inject Rhiza configuration templates into a target repository.
|
|
142
162
|
|
|
143
163
|
Materializes configuration files from the template repository specified
|
|
144
|
-
in .
|
|
164
|
+
in .rhiza/template.yml into your project. This command:
|
|
145
165
|
|
|
146
|
-
- Reads .
|
|
166
|
+
- Reads .rhiza/template.yml configuration
|
|
147
167
|
- Performs a sparse clone of the template repository
|
|
148
168
|
- Copies specified files/directories to your project
|
|
149
169
|
- Respects exclusion patterns defined in the configuration
|
|
@@ -161,17 +181,19 @@ def materialize(
|
|
|
161
181
|
|
|
162
182
|
@app.command()
|
|
163
183
|
def validate(
|
|
164
|
-
target:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
184
|
+
target: Annotated[
|
|
185
|
+
Path,
|
|
186
|
+
typer.Argument(
|
|
187
|
+
exists=True,
|
|
188
|
+
file_okay=False,
|
|
189
|
+
dir_okay=True,
|
|
190
|
+
help="Target git repository (defaults to current directory)",
|
|
191
|
+
),
|
|
192
|
+
] = Path("."),
|
|
193
|
+
) -> None:
|
|
172
194
|
r"""Validate Rhiza template configuration.
|
|
173
195
|
|
|
174
|
-
Validates the .
|
|
196
|
+
Validates the .rhiza/template.yml file to ensure it is syntactically
|
|
175
197
|
correct and semantically valid.
|
|
176
198
|
|
|
177
199
|
Performs comprehensive validation:
|
|
@@ -196,14 +218,16 @@ def validate(
|
|
|
196
218
|
|
|
197
219
|
@app.command()
|
|
198
220
|
def migrate(
|
|
199
|
-
target:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
)
|
|
221
|
+
target: Annotated[
|
|
222
|
+
Path,
|
|
223
|
+
typer.Argument(
|
|
224
|
+
exists=True,
|
|
225
|
+
file_okay=False,
|
|
226
|
+
dir_okay=True,
|
|
227
|
+
help="Target git repository (defaults to current directory)",
|
|
228
|
+
),
|
|
229
|
+
] = Path("."),
|
|
230
|
+
) -> None:
|
|
207
231
|
r"""Migrate project to the new .rhiza folder structure.
|
|
208
232
|
|
|
209
233
|
This command helps transition projects to use the new `.rhiza/` folder
|
|
@@ -229,7 +253,7 @@ def migrate(
|
|
|
229
253
|
|
|
230
254
|
|
|
231
255
|
@app.command()
|
|
232
|
-
def welcome():
|
|
256
|
+
def welcome() -> None:
|
|
233
257
|
r"""Display a friendly welcome message and explain what Rhiza is.
|
|
234
258
|
|
|
235
259
|
Shows a welcome message, explains Rhiza's purpose, key features,
|
|
@@ -243,20 +267,22 @@ def welcome():
|
|
|
243
267
|
|
|
244
268
|
@app.command()
|
|
245
269
|
def uninstall(
|
|
246
|
-
target:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
270
|
+
target: Annotated[
|
|
271
|
+
Path,
|
|
272
|
+
typer.Argument(
|
|
273
|
+
exists=True,
|
|
274
|
+
file_okay=False,
|
|
275
|
+
dir_okay=True,
|
|
276
|
+
help="Target git repository (defaults to current directory)",
|
|
277
|
+
),
|
|
278
|
+
] = Path("."),
|
|
253
279
|
force: bool = typer.Option(
|
|
254
280
|
False,
|
|
255
281
|
"--force",
|
|
256
282
|
"-y",
|
|
257
283
|
help="Skip confirmation prompt and proceed with deletion",
|
|
258
284
|
),
|
|
259
|
-
):
|
|
285
|
+
) -> None:
|
|
260
286
|
r"""Remove all Rhiza-managed files from the repository.
|
|
261
287
|
|
|
262
288
|
Reads the `.rhiza.history` file and removes all files that were
|
|
@@ -280,3 +306,50 @@ def uninstall(
|
|
|
280
306
|
rhiza uninstall /path/to/project -y
|
|
281
307
|
"""
|
|
282
308
|
uninstall_cmd(target, force)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@app.command()
|
|
312
|
+
def summarise(
|
|
313
|
+
target: Annotated[
|
|
314
|
+
Path,
|
|
315
|
+
typer.Argument(
|
|
316
|
+
exists=True,
|
|
317
|
+
file_okay=False,
|
|
318
|
+
dir_okay=True,
|
|
319
|
+
help="Target git repository (defaults to current directory)",
|
|
320
|
+
),
|
|
321
|
+
] = Path("."),
|
|
322
|
+
output: Annotated[
|
|
323
|
+
Path | None,
|
|
324
|
+
typer.Option(
|
|
325
|
+
"--output",
|
|
326
|
+
"-o",
|
|
327
|
+
help="Output file path (defaults to stdout)",
|
|
328
|
+
),
|
|
329
|
+
] = None,
|
|
330
|
+
) -> None:
|
|
331
|
+
r"""Generate a summary of staged changes for PR descriptions.
|
|
332
|
+
|
|
333
|
+
Analyzes staged git changes and generates a structured PR description
|
|
334
|
+
that includes:
|
|
335
|
+
|
|
336
|
+
- Summary statistics (files added/modified/deleted)
|
|
337
|
+
- Changes categorized by type (workflows, configs, docs, tests, etc.)
|
|
338
|
+
- Template repository information
|
|
339
|
+
- Last sync date
|
|
340
|
+
|
|
341
|
+
This is useful when creating pull requests after running `rhiza materialize`
|
|
342
|
+
to provide reviewers with a clear overview of what changed.
|
|
343
|
+
|
|
344
|
+
Examples:
|
|
345
|
+
rhiza summarise
|
|
346
|
+
rhiza summarise --output pr-description.md
|
|
347
|
+
rhiza summarise /path/to/project -o description.md
|
|
348
|
+
|
|
349
|
+
Typical workflow:
|
|
350
|
+
rhiza materialize
|
|
351
|
+
git add .
|
|
352
|
+
rhiza summarise --output pr-body.md
|
|
353
|
+
gh pr create --title "chore: Sync with rhiza" --body-file pr-body.md
|
|
354
|
+
"""
|
|
355
|
+
summarise_cmd(target, output)
|
rhiza/commands/__init__.py
CHANGED
|
@@ -8,7 +8,7 @@ configuration templates for Python projects.
|
|
|
8
8
|
|
|
9
9
|
### init
|
|
10
10
|
|
|
11
|
-
Initialize or validate `.
|
|
11
|
+
Initialize or validate `.rhiza/template.yml` in a target directory.
|
|
12
12
|
|
|
13
13
|
Creates a default configuration file if it doesn't exist, or validates
|
|
14
14
|
an existing one. The default configuration includes common Python project
|
|
@@ -29,7 +29,7 @@ is used.
|
|
|
29
29
|
|
|
30
30
|
Validate Rhiza template configuration.
|
|
31
31
|
|
|
32
|
-
Validates the `.
|
|
32
|
+
Validates the `.rhiza/template.yml` file to ensure it is syntactically
|
|
33
33
|
correct and semantically valid. Performs comprehensive validation including
|
|
34
34
|
YAML syntax checking, required field verification, field type validation,
|
|
35
35
|
and repository format verification.
|
|
@@ -50,6 +50,8 @@ For more detailed usage examples and workflows, see the USAGE.md guide
|
|
|
50
50
|
or try rhiza <command> --help
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
|
-
from .init import init
|
|
54
|
-
from .materialize import materialize
|
|
55
|
-
from .validate import validate
|
|
53
|
+
from .init import init
|
|
54
|
+
from .materialize import materialize
|
|
55
|
+
from .validate import validate
|
|
56
|
+
|
|
57
|
+
__all__ = ["init", "materialize", "validate"]
|
rhiza/commands/init.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Command to initialize or validate .
|
|
1
|
+
"""Command to initialize or validate .rhiza/template.yml.
|
|
2
2
|
|
|
3
3
|
This module provides the init command that creates or validates the
|
|
4
|
-
.
|
|
4
|
+
.rhiza/template.yml file, which defines where templates come from
|
|
5
5
|
and what paths are governed by Rhiza.
|
|
6
6
|
"""
|
|
7
7
|
|
|
@@ -52,7 +52,7 @@ def _validate_git_host(git_host: str | None) -> str | None:
|
|
|
52
52
|
git_host = git_host.lower()
|
|
53
53
|
if git_host not in ["github", "gitlab"]:
|
|
54
54
|
logger.error(f"Invalid git-host: {git_host}. Must be 'github' or 'gitlab'")
|
|
55
|
-
raise ValueError(f"Invalid git-host: {git_host}. Must be 'github' or 'gitlab'")
|
|
55
|
+
raise ValueError(f"Invalid git-host: {git_host}. Must be 'github' or 'gitlab'") # noqa: TRY003
|
|
56
56
|
return git_host
|
|
57
57
|
|
|
58
58
|
|
|
@@ -81,7 +81,7 @@ def _prompt_git_host() -> str:
|
|
|
81
81
|
git_host = "github"
|
|
82
82
|
logger.debug("Non-interactive mode detected, defaulting to github")
|
|
83
83
|
|
|
84
|
-
return git_host
|
|
84
|
+
return str(git_host)
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def _get_include_paths_for_host(git_host: str) -> list[str]:
|
|
@@ -124,12 +124,19 @@ def _get_include_paths_for_host(git_host: str) -> list[str]:
|
|
|
124
124
|
]
|
|
125
125
|
|
|
126
126
|
|
|
127
|
-
def _create_template_file(
|
|
127
|
+
def _create_template_file(
|
|
128
|
+
target: Path,
|
|
129
|
+
git_host: str,
|
|
130
|
+
template_repository: str | None = None,
|
|
131
|
+
template_branch: str | None = None,
|
|
132
|
+
) -> None:
|
|
128
133
|
"""Create default template.yml file.
|
|
129
134
|
|
|
130
135
|
Args:
|
|
131
136
|
target: Target repository path.
|
|
132
137
|
git_host: Git hosting platform.
|
|
138
|
+
template_repository: Custom template repository (format: owner/repo).
|
|
139
|
+
template_branch: Custom template branch.
|
|
133
140
|
"""
|
|
134
141
|
rhiza_dir = target / ".rhiza"
|
|
135
142
|
template_file = rhiza_dir / "template.yml"
|
|
@@ -141,9 +148,20 @@ def _create_template_file(target: Path, git_host: str) -> None:
|
|
|
141
148
|
logger.debug("Using default template configuration")
|
|
142
149
|
|
|
143
150
|
include_paths = _get_include_paths_for_host(git_host)
|
|
151
|
+
|
|
152
|
+
# Use custom template repository/branch if provided, otherwise use defaults
|
|
153
|
+
repo = template_repository or "jebel-quant/rhiza"
|
|
154
|
+
branch = template_branch or "main"
|
|
155
|
+
|
|
156
|
+
# Log when custom values are used
|
|
157
|
+
if template_repository:
|
|
158
|
+
logger.info(f"Using custom template repository: {repo}")
|
|
159
|
+
if template_branch:
|
|
160
|
+
logger.info(f"Using custom template branch: {branch}")
|
|
161
|
+
|
|
144
162
|
default_template = RhizaTemplate(
|
|
145
|
-
template_repository=
|
|
146
|
-
template_branch=
|
|
163
|
+
template_repository=repo,
|
|
164
|
+
template_branch=branch,
|
|
147
165
|
include=include_paths,
|
|
148
166
|
)
|
|
149
167
|
|
|
@@ -243,10 +261,12 @@ def init(
|
|
|
243
261
|
package_name: str | None = None,
|
|
244
262
|
with_dev_dependencies: bool = False,
|
|
245
263
|
git_host: str | None = None,
|
|
246
|
-
|
|
247
|
-
|
|
264
|
+
template_repository: str | None = None,
|
|
265
|
+
template_branch: str | None = None,
|
|
266
|
+
) -> bool:
|
|
267
|
+
"""Initialize or validate .rhiza/template.yml in the target repository.
|
|
248
268
|
|
|
249
|
-
Creates a default .
|
|
269
|
+
Creates a default .rhiza/template.yml file if it doesn't exist,
|
|
250
270
|
or validates an existing one.
|
|
251
271
|
|
|
252
272
|
Args:
|
|
@@ -256,6 +276,9 @@ def init(
|
|
|
256
276
|
with_dev_dependencies: Include development dependencies in pyproject.toml.
|
|
257
277
|
git_host: Target Git hosting platform ("github" or "gitlab"). Determines which
|
|
258
278
|
CI/CD configuration files to include. If None, will prompt user interactively.
|
|
279
|
+
template_repository: Custom template repository (format: owner/repo).
|
|
280
|
+
Defaults to 'jebel-quant/rhiza'.
|
|
281
|
+
template_branch: Custom template branch. Defaults to 'main'.
|
|
259
282
|
|
|
260
283
|
Returns:
|
|
261
284
|
bool: True if validation passes, False otherwise.
|
|
@@ -275,7 +298,7 @@ def init(
|
|
|
275
298
|
git_host = _prompt_git_host()
|
|
276
299
|
|
|
277
300
|
# Create template file
|
|
278
|
-
_create_template_file(target, git_host)
|
|
301
|
+
_create_template_file(target, git_host, template_repository, template_branch)
|
|
279
302
|
|
|
280
303
|
# Bootstrap Python project structure
|
|
281
304
|
if project_name is None:
|
rhiza/commands/materialize.py
CHANGED
|
@@ -8,7 +8,7 @@ into the target Git repository, and records managed files in
|
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
10
|
import shutil
|
|
11
|
-
import subprocess
|
|
11
|
+
import subprocess # nosec B404
|
|
12
12
|
import sys
|
|
13
13
|
import tempfile
|
|
14
14
|
from pathlib import Path
|
|
@@ -20,6 +20,22 @@ from rhiza.models import RhizaTemplate
|
|
|
20
20
|
from rhiza.subprocess_utils import get_git_executable
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
def _log_git_stderr_errors(stderr: str | None) -> None:
|
|
24
|
+
"""Extract and log only relevant error messages from git stderr.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
stderr: Git command stderr output.
|
|
28
|
+
"""
|
|
29
|
+
if stderr:
|
|
30
|
+
# Extract relevant error message from git stderr
|
|
31
|
+
stderr_lines = stderr.strip().split("\n")
|
|
32
|
+
# Show only the most relevant error lines, skip verbose git output
|
|
33
|
+
for line in stderr_lines:
|
|
34
|
+
line = line.strip()
|
|
35
|
+
if line and (line.startswith("fatal:") or line.startswith("error:")):
|
|
36
|
+
logger.error(line)
|
|
37
|
+
|
|
38
|
+
|
|
23
39
|
def _handle_target_branch(
|
|
24
40
|
target: Path, target_branch: str | None, git_executable: str, git_env: dict[str, str]
|
|
25
41
|
) -> None:
|
|
@@ -37,7 +53,7 @@ def _handle_target_branch(
|
|
|
37
53
|
logger.info(f"Creating/checking out target branch: {target_branch}")
|
|
38
54
|
try:
|
|
39
55
|
# Check if branch already exists using git rev-parse
|
|
40
|
-
result = subprocess.run(
|
|
56
|
+
result = subprocess.run( # nosec B603
|
|
41
57
|
[git_executable, "rev-parse", "--verify", target_branch],
|
|
42
58
|
cwd=target,
|
|
43
59
|
capture_output=True,
|
|
@@ -48,23 +64,29 @@ def _handle_target_branch(
|
|
|
48
64
|
if result.returncode == 0:
|
|
49
65
|
# Branch exists, switch to it
|
|
50
66
|
logger.info(f"Branch '{target_branch}' exists, checking out...")
|
|
51
|
-
subprocess.run(
|
|
67
|
+
subprocess.run( # nosec B603
|
|
52
68
|
[git_executable, "checkout", target_branch],
|
|
53
69
|
cwd=target,
|
|
54
70
|
check=True,
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True,
|
|
55
73
|
env=git_env,
|
|
56
74
|
)
|
|
57
75
|
else:
|
|
58
76
|
# Branch doesn't exist, create it from current HEAD
|
|
59
77
|
logger.info(f"Creating new branch '{target_branch}'...")
|
|
60
|
-
subprocess.run(
|
|
78
|
+
subprocess.run( # nosec B603
|
|
61
79
|
[git_executable, "checkout", "-b", target_branch],
|
|
62
80
|
cwd=target,
|
|
63
81
|
check=True,
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True,
|
|
64
84
|
env=git_env,
|
|
65
85
|
)
|
|
66
86
|
except subprocess.CalledProcessError as e:
|
|
67
|
-
logger.error(f"Failed to create/checkout branch '{target_branch}'
|
|
87
|
+
logger.error(f"Failed to create/checkout branch '{target_branch}'")
|
|
88
|
+
_log_git_stderr_errors(e.stderr)
|
|
89
|
+
logger.error("Please ensure you have no uncommitted changes or conflicts")
|
|
68
90
|
sys.exit(1)
|
|
69
91
|
|
|
70
92
|
|
|
@@ -91,6 +113,9 @@ def _validate_and_load_template(target: Path, branch: str) -> tuple[RhizaTemplat
|
|
|
91
113
|
|
|
92
114
|
# Extract template configuration settings
|
|
93
115
|
rhiza_repo = template.template_repository
|
|
116
|
+
if not rhiza_repo:
|
|
117
|
+
logger.error("template-repository is not configured in template.yml")
|
|
118
|
+
raise RuntimeError("template-repository is required") # noqa: TRY003
|
|
94
119
|
rhiza_branch = template.template_branch or branch
|
|
95
120
|
include_paths = template.include
|
|
96
121
|
excluded_paths = template.exclude
|
|
@@ -99,7 +124,7 @@ def _validate_and_load_template(target: Path, branch: str) -> tuple[RhizaTemplat
|
|
|
99
124
|
if not include_paths:
|
|
100
125
|
logger.error("No include paths found in template.yml")
|
|
101
126
|
logger.error("Add at least one path to the 'include' list in template.yml")
|
|
102
|
-
raise RuntimeError("No include paths found in template.yml")
|
|
127
|
+
raise RuntimeError("No include paths found in template.yml") # noqa: TRY003
|
|
103
128
|
|
|
104
129
|
# Log the paths we'll be including
|
|
105
130
|
logger.info("Include paths:")
|
|
@@ -136,7 +161,7 @@ def _construct_git_url(rhiza_repo: str, rhiza_host: str) -> str:
|
|
|
136
161
|
else:
|
|
137
162
|
logger.error(f"Unsupported template-host: {rhiza_host}")
|
|
138
163
|
logger.error("template-host must be 'github' or 'gitlab'")
|
|
139
|
-
raise ValueError(f"Unsupported template-host: {rhiza_host}. Must be 'github' or 'gitlab'.")
|
|
164
|
+
raise ValueError(f"Unsupported template-host: {rhiza_host}. Must be 'github' or 'gitlab'.") # noqa: TRY003
|
|
140
165
|
return git_url
|
|
141
166
|
|
|
142
167
|
|
|
@@ -161,7 +186,7 @@ def _clone_template_repository(
|
|
|
161
186
|
# Clone the repository using sparse checkout
|
|
162
187
|
try:
|
|
163
188
|
logger.debug("Executing git clone with sparse checkout")
|
|
164
|
-
subprocess.run(
|
|
189
|
+
subprocess.run( # nosec B603
|
|
165
190
|
[
|
|
166
191
|
git_executable,
|
|
167
192
|
"clone",
|
|
@@ -181,16 +206,18 @@ def _clone_template_repository(
|
|
|
181
206
|
)
|
|
182
207
|
logger.debug("Git clone completed successfully")
|
|
183
208
|
except subprocess.CalledProcessError as e:
|
|
184
|
-
logger.error(f"Failed to clone repository
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
logger.error(
|
|
188
|
-
|
|
209
|
+
logger.error(f"Failed to clone repository from {git_url}")
|
|
210
|
+
_log_git_stderr_errors(e.stderr)
|
|
211
|
+
logger.error("Please check that:")
|
|
212
|
+
logger.error(" - The repository exists and is accessible")
|
|
213
|
+
logger.error(f" - Branch '{rhiza_branch}' exists in the repository")
|
|
214
|
+
logger.error(" - You have network access to the git hosting service")
|
|
215
|
+
sys.exit(1)
|
|
189
216
|
|
|
190
217
|
# Initialize sparse checkout in cone mode
|
|
191
218
|
try:
|
|
192
219
|
logger.debug("Initializing sparse checkout")
|
|
193
|
-
subprocess.run(
|
|
220
|
+
subprocess.run( # nosec B603
|
|
194
221
|
[git_executable, "sparse-checkout", "init", "--cone"],
|
|
195
222
|
cwd=tmp_dir,
|
|
196
223
|
check=True,
|
|
@@ -200,15 +227,14 @@ def _clone_template_repository(
|
|
|
200
227
|
)
|
|
201
228
|
logger.debug("Sparse checkout initialized")
|
|
202
229
|
except subprocess.CalledProcessError as e:
|
|
203
|
-
logger.error(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
raise
|
|
230
|
+
logger.error("Failed to initialize sparse checkout")
|
|
231
|
+
_log_git_stderr_errors(e.stderr)
|
|
232
|
+
sys.exit(1)
|
|
207
233
|
|
|
208
234
|
# Set sparse checkout paths
|
|
209
235
|
try:
|
|
210
236
|
logger.debug(f"Setting sparse checkout paths: {include_paths}")
|
|
211
|
-
subprocess.run(
|
|
237
|
+
subprocess.run( # nosec B603
|
|
212
238
|
[git_executable, "sparse-checkout", "set", "--skip-checks", *include_paths],
|
|
213
239
|
cwd=tmp_dir,
|
|
214
240
|
check=True,
|
|
@@ -218,10 +244,9 @@ def _clone_template_repository(
|
|
|
218
244
|
)
|
|
219
245
|
logger.debug("Sparse checkout paths configured")
|
|
220
246
|
except subprocess.CalledProcessError as e:
|
|
221
|
-
logger.error(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
raise
|
|
247
|
+
logger.error("Failed to configure sparse checkout paths")
|
|
248
|
+
_log_git_stderr_errors(e.stderr)
|
|
249
|
+
sys.exit(1)
|
|
225
250
|
|
|
226
251
|
|
|
227
252
|
def _copy_files_to_target(
|
rhiza/commands/migrate.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This module implements the `migrate` command. It helps transition projects to use
|
|
4
4
|
the new `.rhiza/` folder structure for storing Rhiza state and configuration files,
|
|
5
|
-
separate from `.github
|
|
5
|
+
separate from `.github/` which contains GitHub-specific configurations.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import shutil
|