half-orm-dev 1.0.0a31__tar.gz → 1.0.0a32__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.
Files changed (83) hide show
  1. {half_orm_dev-1.0.0a31/half_orm_dev.egg-info → half_orm_dev-1.0.0a32}/PKG-INFO +22 -18
  2. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/README.md +19 -17
  3. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/__init__.py +0 -3
  4. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/check.py +1 -1
  5. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/recover.py +1 -1
  6. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/main.py +1 -1
  7. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/file_executor.py +48 -46
  8. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/hgit.py +43 -0
  9. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migration_manager.py +1 -0
  10. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +3 -3
  11. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/patch_manager.py +231 -180
  12. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/release_file.py +3 -4
  13. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/release_manager.py +37 -70
  14. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/repo.py +157 -114
  15. half_orm_dev-1.0.0a32/half_orm_dev/version.txt +1 -0
  16. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32/half_orm_dev.egg-info}/PKG-INFO +22 -18
  17. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev.egg-info/SOURCES.txt +0 -3
  18. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev.egg-info/requires.txt +2 -0
  19. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/setup.py +2 -0
  20. half_orm_dev-1.0.0a31/half_orm_dev/bootstrap_manager.py +0 -392
  21. half_orm_dev-1.0.0a31/half_orm_dev/cli/commands/bootstrap.py +0 -139
  22. half_orm_dev-1.0.0a31/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -103
  23. half_orm_dev-1.0.0a31/half_orm_dev/version.txt +0 -1
  24. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/AUTHORS +0 -0
  25. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/LICENSE +0 -0
  26. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/__init__.py +0 -0
  27. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/__init__.py +0 -0
  28. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/apply.py +0 -0
  29. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/clone.py +0 -0
  30. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/init.py +0 -0
  31. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/migrate.py +0 -0
  32. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/patch.py +0 -0
  33. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/release.py +0 -0
  34. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/restore.py +0 -0
  35. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/revert_migration.py +0 -0
  36. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/rollback.py +0 -0
  37. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
  38. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/sync.py +0 -0
  39. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/todo.py +0 -0
  40. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/undo.py +0 -0
  41. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli/commands/upgrade.py +0 -0
  42. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/cli_extension.py +0 -0
  43. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/database.py +0 -0
  44. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/decorators.py +0 -0
  45. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
  46. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
  47. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
  48. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
  49. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
  50. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
  51. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/1/0/0/a20/01_update_gitignore.py +0 -0
  52. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
  53. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/modules.py +0 -0
  54. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/patch_validator.py +0 -0
  55. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
  56. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
  57. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
  58. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/patches/log +0 -0
  59. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
  60. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/py.typed +0 -0
  61. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/scripts/repair-metadata.py +0 -0
  62. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/.gitignore +0 -0
  63. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/MANIFEST.in +0 -0
  64. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/README +0 -0
  65. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/conftest_template +0 -0
  66. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
  67. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/git-hooks/pre-push +0 -0
  68. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
  69. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/git-hooks/reference-transaction +0 -0
  70. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/init_module_template +0 -0
  71. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/module_template_1 +0 -0
  72. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/module_template_2 +0 -0
  73. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/module_template_3 +0 -0
  74. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/pyproject.toml +0 -0
  75. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/relation_test +0 -0
  76. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/sql_adapter +0 -0
  77. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/templates/warning +0 -0
  78. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev/utils.py +0 -0
  79. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev.egg-info/dependency_links.txt +0 -0
  80. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev.egg-info/entry_points.txt +0 -0
  81. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/half_orm_dev.egg-info/top_level.txt +0 -0
  82. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/pyproject.toml +0 -0
  83. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a32}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 1.0.0a31
3
+ Version: 1.0.0a32
4
4
  Summary: half_orm development Framework.
5
5
  Author-email: Joël Maïzi <joel.maizi@collorg.org>
6
6
  License-Expression: GPL-3.0-or-later
@@ -21,8 +21,10 @@ License-File: LICENSE
21
21
  License-File: AUTHORS
22
22
  Requires-Dist: GitPython
23
23
  Requires-Dist: click
24
+ Requires-Dist: packaging
24
25
  Requires-Dist: pydash
25
26
  Requires-Dist: pytest
27
+ Requires-Dist: pytest-asyncio
26
28
  Requires-Dist: half_orm<1.1.0,>=1.0.0a1
27
29
  Requires-Dist: tomli>=2.0.0; python_version < "3.11"
28
30
  Requires-Dist: tomli_w>=1.0.0
@@ -384,29 +386,31 @@ half_orm dev upgrade [--to-release X.Y.Z]
384
386
  half_orm dev upgrade --dry-run
385
387
  ```
386
388
 
387
- ### Data Bootstrap
389
+ ### Bootstrap - Data Initialization
388
390
 
389
- Mark patch files with `-- @HOP:bootstrap` (SQL) or `# @HOP:bootstrap` (Python) to declare reference data.
390
- The marker **must be on the first line** of the file:
391
+ Bootstrap scripts initialize application data on empty databases. Place SQL and Python files in the `bootstrap/` directory:
391
392
 
392
- ```sql
393
- -- @HOP:bootstrap
394
- INSERT INTO roles (name) VALUES ('admin'), ('user') ON CONFLICT DO NOTHING;
395
393
  ```
396
-
397
- ```python
398
- # @HOP:bootstrap
399
- # (no shebang — the file is executed directly by half-orm-dev)
400
- MyModel(field='value').ho_insert()
394
+ bootstrap/
395
+ ├── 01-init-roles.sql
396
+ ├── 02-seed-config.py
397
+ └── 03-reference-data.sql
401
398
  ```
402
399
 
403
- These files are automatically:
404
- - Copied to `bootstrap/` during `patch merge`
405
- - Executed during production `upgrade`
406
- - Tracked in database (each script runs once)
400
+ Files are executed **alphabetically** during:
401
+ - **Development**: Each `patch apply` (allows iteration on bootstrap scripts)
402
+ - **Production**: Initial `clone` only (one-time initialization)
403
+
404
+ For production data changes, use **patches** (not bootstrap).
407
405
 
408
- > **Note:** If the marker is not on the first line it is silently ignored.
409
- > A warning is displayed during `patch apply` to help catch this mistake.
406
+ **Python files** can define a `run(model)` function to share the database connection:
407
+
408
+ ```python
409
+ def run(model):
410
+ # model is the halfORM Model instance with active connection
411
+ MyModel = model.get_relation_class('schema.table')
412
+ MyModel(field='value').ho_insert()
413
+ ```
410
414
 
411
415
  **Note:** Use `half_orm dev <command> --help` for detailed help on each command.
412
416
 
@@ -353,29 +353,31 @@ half_orm dev upgrade [--to-release X.Y.Z]
353
353
  half_orm dev upgrade --dry-run
354
354
  ```
355
355
 
356
- ### Data Bootstrap
356
+ ### Bootstrap - Data Initialization
357
357
 
358
- Mark patch files with `-- @HOP:bootstrap` (SQL) or `# @HOP:bootstrap` (Python) to declare reference data.
359
- The marker **must be on the first line** of the file:
358
+ Bootstrap scripts initialize application data on empty databases. Place SQL and Python files in the `bootstrap/` directory:
360
359
 
361
- ```sql
362
- -- @HOP:bootstrap
363
- INSERT INTO roles (name) VALUES ('admin'), ('user') ON CONFLICT DO NOTHING;
364
360
  ```
365
-
366
- ```python
367
- # @HOP:bootstrap
368
- # (no shebang — the file is executed directly by half-orm-dev)
369
- MyModel(field='value').ho_insert()
361
+ bootstrap/
362
+ ├── 01-init-roles.sql
363
+ ├── 02-seed-config.py
364
+ └── 03-reference-data.sql
370
365
  ```
371
366
 
372
- These files are automatically:
373
- - Copied to `bootstrap/` during `patch merge`
374
- - Executed during production `upgrade`
375
- - Tracked in database (each script runs once)
367
+ Files are executed **alphabetically** during:
368
+ - **Development**: Each `patch apply` (allows iteration on bootstrap scripts)
369
+ - **Production**: Initial `clone` only (one-time initialization)
370
+
371
+ For production data changes, use **patches** (not bootstrap).
376
372
 
377
- > **Note:** If the marker is not on the first line it is silently ignored.
378
- > A warning is displayed during `patch apply` to help catch this mistake.
373
+ **Python files** can define a `run(model)` function to share the database connection:
374
+
375
+ ```python
376
+ def run(model):
377
+ # model is the halfORM Model instance with active connection
378
+ MyModel = model.get_relation_class('schema.table')
379
+ MyModel(field='value').ho_insert()
380
+ ```
379
381
 
380
382
  **Note:** Use `half_orm dev <command> --help` for detailed help on each command.
381
383
 
@@ -15,7 +15,6 @@ from .check import check
15
15
  from .set_git_origin import set_git_origin
16
16
  from .migrate import migrate
17
17
  from .revert_migration import revert_migration
18
- from .bootstrap import bootstrap
19
18
  from .rollback import rollback
20
19
  from .recover import recover
21
20
  from .todo import apply_release
@@ -36,7 +35,6 @@ ALL_COMMANDS = {
36
35
  'set-git-origin': set_git_origin, # Update git remote origin URL
37
36
  'migrate': migrate, # Repository migration after upgrade
38
37
  'revert-migration': revert_migration, # Revert last migration
39
- 'bootstrap': bootstrap, # Execute data initialization scripts
40
38
  # 🚧 (stubs)
41
39
  'apply_release': apply_release,
42
40
 
@@ -58,7 +56,6 @@ __all__ = [
58
56
  'upgrade',
59
57
  'check',
60
58
  'migrate',
61
- 'bootstrap',
62
59
  'rollback',
63
60
  'recover',
64
61
  # Adapted commands
@@ -162,7 +162,7 @@ def _display_check_results(repo, result: dict, dry_run: bool, verbose: bool):
162
162
  click.echo(f"\n🔧 {utils.Color.bold('Orphaned patches')} ({len(orphaned_patches)}):")
163
163
  for patch_id in sorted(orphaned_patches):
164
164
  click.echo(f" • {patch_id}")
165
- click.echo(f" {utils.Color.blue('(Use \"half_orm dev release attach-patch <id>\" to reattach)')}")
165
+ click.echo(f""" {utils.Color.blue('(Use "half_orm dev release attach-patch <id>" to reattach)')}""")
166
166
 
167
167
  # Show standalone patch branches (not in candidates/stage)
168
168
  standalone_patches = [b for b in patch_branches
@@ -41,6 +41,6 @@ def recover() -> None:
41
41
 
42
42
  if result['errors']:
43
43
  for error in result['errors']:
44
- click.echo(utils.Color.yellow(f"Warning: {error}"), err=True)
44
+ click.echo(utils.Color.bold(f"Warning: {error}"), err=True)
45
45
 
46
46
  click.echo(utils.Color.green("Recovery complete."))
@@ -60,7 +60,7 @@ class Hop:
60
60
  return ['sync-package', 'check']
61
61
 
62
62
  # DEVELOPMENT ENVIRONMENT - Patch development
63
- return ['patch', 'release', 'check', 'bootstrap', 'set-git-origin',
63
+ return ['patch', 'release', 'check', 'set-git-origin',
64
64
  'revert-migration', 'recover']
65
65
 
66
66
  @property
@@ -1,13 +1,12 @@
1
1
  """
2
2
  Shared utilities for executing SQL and Python files.
3
3
 
4
- This module provides common file execution functionality used by both
5
- PatchManager and BootstrapManager.
4
+ This module provides common file execution functionality for patch application
5
+ and bootstrap initialization.
6
6
  """
7
7
 
8
8
  import ast
9
9
  import importlib.util
10
- import re
11
10
  import subprocess
12
11
  import sys
13
12
  from pathlib import Path
@@ -166,54 +165,57 @@ def execute_python_bootstrap(file_path: Path, model, cwd: Optional[Path] = None)
166
165
  sys.modules.pop(module_name, None)
167
166
 
168
167
 
169
- _HOP_MARKER = re.compile(r"(--|#)\s*@hop:(bootstrap|data)")
170
-
171
-
172
- def is_bootstrap_file(file_path: Path) -> bool:
168
+ def execute_bootstrap_files(bootstrap_dir: Path, model) -> None:
173
169
  """
174
- Check if file has @HOP:bootstrap or @HOP:data marker on first line.
175
-
176
- The marker must be on the first line of the file:
177
- - SQL files: -- @HOP:bootstrap or -- @HOP:data
178
- - Python files: # @HOP:bootstrap or # @HOP:data
170
+ Execute all bootstrap files in alphabetic order.
179
171
 
180
- Note: @HOP:data is supported as an alias for backwards compatibility.
172
+ Bootstrap files are SQL and Python files in the bootstrap/ directory that
173
+ initialize application data on empty databases. They are executed in
174
+ alphabetic order (no numeric parsing needed).
181
175
 
182
176
  Args:
183
- file_path: Path to file to check
184
-
185
- Returns:
186
- True if file has bootstrap marker on first line, False otherwise
187
- """
188
- try:
189
- with file_path.open('r', encoding='utf-8') as f:
190
- first_line = f.readline().strip().lower()
191
- return _HOP_MARKER.match(first_line) is not None
192
- except OSError:
193
- return False
194
-
195
-
196
- def has_misplaced_bootstrap_marker(file_path: Path) -> bool:
197
- """
198
- Check if file has a @HOP:bootstrap or @HOP:data marker NOT on the first line.
177
+ bootstrap_dir: Path to bootstrap directory
178
+ model: halfORM Model instance (shared database connection)
199
179
 
200
- Used to warn the user during patch apply when the marker is present in the
201
- file but will be ignored because it is not on the first line.
180
+ Raises:
181
+ FileExecutionError: If any file execution fails
202
182
 
203
- Args:
204
- file_path: Path to file to check
183
+ Example:
184
+ bootstrap_dir = Path('/path/to/project/bootstrap')
185
+ execute_bootstrap_files(bootstrap_dir, model)
205
186
 
206
- Returns:
207
- True if a misplaced marker is found, False otherwise
187
+ # Executes files in order:
188
+ # - 01-init-users.sql
189
+ # - 02-seed-config.py
190
+ # - 03-reference-data.sql
208
191
  """
209
- if is_bootstrap_file(file_path):
210
- return False
211
- try:
212
- with file_path.open('r', encoding='utf-8') as f:
213
- f.readline() # skip first line
214
- for line in f:
215
- if _HOP_MARKER.search(line.strip().lower()):
216
- return True
217
- except OSError:
218
- pass
219
- return False
192
+ if not bootstrap_dir.exists():
193
+ return
194
+
195
+ # Collect all SQL and Python files
196
+ files = []
197
+ for file_path in bootstrap_dir.iterdir():
198
+ if file_path.is_file() and file_path.suffix in ('.sql', '.py'):
199
+ files.append(file_path)
200
+
201
+ if not files:
202
+ return
203
+
204
+ # Sort alphabetically by filename
205
+ files.sort(key=lambda f: f.name)
206
+
207
+ # Execute each file
208
+ for file_path in files:
209
+ try:
210
+ if file_path.suffix == '.sql':
211
+ execute_sql_file(file_path, model)
212
+ elif file_path.suffix == '.py':
213
+ execute_python_bootstrap(file_path, model, cwd=bootstrap_dir)
214
+ except FileExecutionError:
215
+ # Re-raise FileExecutionError as-is (already has good error message)
216
+ raise
217
+ except Exception as e:
218
+ # Wrap unexpected errors
219
+ raise FileExecutionError(
220
+ f"Failed to execute bootstrap file {file_path.name}: {e}"
221
+ ) from e
@@ -560,6 +560,49 @@ class HGit:
560
560
  finally:
561
561
  marker.unlink(missing_ok=True)
562
562
 
563
+ def setup_production_branches(self) -> None:
564
+ """
565
+ Create local tracking branches for all remote ho-prod-* branches.
566
+
567
+ This ensures production servers have local access to all versioned
568
+ production branches (ho-prod, ho-prod-X.Y.Z) for rollback support.
569
+
570
+ Workflow:
571
+ 1. List all remote branches matching origin/ho-prod*
572
+ 2. For each remote branch, create local tracking branch if missing
573
+ 3. Skip if local branch already exists
574
+
575
+ Used in:
576
+ - Production clone: after initial checkout
577
+ - Production upgrade: after fetch to get new releases
578
+
579
+ Examples:
580
+ # After clone or fetch
581
+ hgit.setup_production_branches()
582
+ # → Creates ho-prod-0.1.0, ho-prod-0.1.1, etc. from origin
583
+ """
584
+ # Get all remote branches matching ho-prod*
585
+ remote_branches = []
586
+ for ref in self.__git_repo.remote('origin').refs:
587
+ branch_name = ref.name.replace('origin/', '')
588
+ if branch_name.startswith('ho-prod'):
589
+ remote_branches.append(branch_name)
590
+
591
+ # Create local tracking branches for each remote ho-prod* branch
592
+ for branch_name in remote_branches:
593
+ try:
594
+ # Check if local branch already exists
595
+ if branch_name in [b.name for b in self.__git_repo.branches]:
596
+ continue
597
+
598
+ # Create local tracking branch
599
+ remote_ref = self.__git_repo.remote('origin').refs[branch_name]
600
+ self.__git_repo.create_head(branch_name, remote_ref)
601
+
602
+ except Exception:
603
+ # Skip branches that can't be created (shouldn't happen)
604
+ pass
605
+
563
606
  def delete_local_branch(self, branch_name: str) -> None:
564
607
  """
565
608
  Delete local branch.
@@ -543,6 +543,7 @@ class MigrationManager:
543
543
  else:
544
544
  repo.restore_database_from_schema(skip_bootstrap=True)
545
545
  else:
546
+ #XXX EST-CE QUE POUR ho-patch/* on ne devrait pas utiliser from_release_schema ?
546
547
  # ho-prod and ho-patch/*: use production schema
547
548
  repo.restore_database_from_schema(skip_bootstrap=True)
548
549
 
@@ -18,10 +18,10 @@ from pathlib import Path
18
18
  import subprocess
19
19
  import sys
20
20
 
21
- try:
21
+ if sys.version_info >= (3, 11):
22
+ import tomllib as tomli
23
+ else:
22
24
  import tomli
23
- except ImportError:
24
- import tomllib as tomli
25
25
 
26
26
  try:
27
27
  import tomli_w