half-orm-dev 1.0.0a19__tar.gz → 1.0.0a21__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 (80) hide show
  1. {half_orm_dev-1.0.0a19/half_orm_dev.egg-info → half_orm_dev-1.0.0a21}/PKG-INFO +1 -1
  2. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/hgit.py +12 -2
  3. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/release_manager.py +48 -24
  4. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/repo.py +91 -29
  5. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/.gitignore +3 -1
  6. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/git-hooks/pre-commit +12 -0
  7. half_orm_dev-1.0.0a21/half_orm_dev/templates/git-hooks/pre-push +18 -0
  8. half_orm_dev-1.0.0a21/half_orm_dev/templates/git-hooks/reference-transaction +34 -0
  9. half_orm_dev-1.0.0a21/half_orm_dev/version.txt +1 -0
  10. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21/half_orm_dev.egg-info}/PKG-INFO +1 -1
  11. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/SOURCES.txt +3 -1
  12. half_orm_dev-1.0.0a19/half_orm_dev/version.txt +0 -1
  13. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/AUTHORS +0 -0
  14. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/LICENSE +0 -0
  15. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/README.md +0 -0
  16. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/__init__.py +0 -0
  17. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/bootstrap_manager.py +0 -0
  18. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/__init__.py +0 -0
  19. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/__init__.py +0 -0
  20. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/apply.py +0 -0
  21. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/bootstrap.py +0 -0
  22. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/check.py +0 -0
  23. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/clone.py +0 -0
  24. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/init.py +0 -0
  25. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/migrate.py +0 -0
  26. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/patch.py +0 -0
  27. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/release.py +0 -0
  28. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/restore.py +0 -0
  29. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/revert_migration.py +0 -0
  30. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
  31. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/sync.py +0 -0
  32. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/todo.py +0 -0
  33. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/undo.py +0 -0
  34. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/update.py +0 -0
  35. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/upgrade.py +0 -0
  36. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/main.py +0 -0
  37. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli_extension.py +0 -0
  38. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/database.py +0 -0
  39. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/decorators.py +0 -0
  40. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/file_executor.py +0 -0
  41. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migration_manager.py +0 -0
  42. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
  43. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
  44. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +0 -0
  45. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -0
  46. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
  47. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
  48. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
  49. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
  50. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
  51. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/modules.py +0 -0
  52. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patch_manager.py +0 -0
  53. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patch_validator.py +0 -0
  54. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
  55. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
  56. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
  57. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patches/log +0 -0
  58. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
  59. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/release_file.py +0 -0
  60. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/scripts/repair-metadata.py +0 -0
  61. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/MANIFEST.in +0 -0
  62. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/README +0 -0
  63. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/conftest_template +0 -0
  64. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
  65. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/init_module_template +0 -0
  66. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/module_template_1 +0 -0
  67. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/module_template_2 +0 -0
  68. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/module_template_3 +0 -0
  69. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/pyproject.toml +0 -0
  70. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/relation_test +0 -0
  71. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/sql_adapter +0 -0
  72. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/warning +0 -0
  73. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/utils.py +0 -0
  74. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/dependency_links.txt +0 -0
  75. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/entry_points.txt +0 -0
  76. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/requires.txt +0 -0
  77. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/top_level.txt +0 -0
  78. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/pyproject.toml +0 -0
  79. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/setup.cfg +0 -0
  80. {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 1.0.0a19
3
+ Version: 1.0.0a21
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
@@ -367,7 +367,12 @@ class HGit:
367
367
  # Local git now knows about all remote tags
368
368
  """
369
369
  origin = self.__git_repo.remote('origin')
370
- origin.fetch(tags=True)
370
+ marker = Path(self.__git_repo.working_dir) / '.hop' / '.fetching'
371
+ try:
372
+ marker.touch()
373
+ origin.fetch(tags=True)
374
+ finally:
375
+ marker.unlink(missing_ok=True)
371
376
 
372
377
  def tag_exists(self, tag_name: str) -> bool:
373
378
  """
@@ -443,7 +448,12 @@ class HGit:
443
448
  # Stale remote refs (deleted branches on remote) are removed
444
449
  """
445
450
  origin = self.__git_repo.remote('origin')
446
- origin.fetch(prune=True)
451
+ marker = Path(self.__git_repo.working_dir) / '.hop' / '.fetching'
452
+ try:
453
+ marker.touch()
454
+ origin.fetch(prune=True)
455
+ finally:
456
+ marker.unlink(missing_ok=True)
447
457
 
448
458
  def delete_local_branch(self, branch_name: str) -> None:
449
459
  """
@@ -1232,6 +1232,52 @@ class ReleaseManager:
1232
1232
  # Best effort - don't fail if checkout back fails
1233
1233
  pass
1234
1234
 
1235
+ def ensure_ho_current(self) -> bool:
1236
+ """
1237
+ Set up ho-current branch for production servers if not already done.
1238
+
1239
+ Creates ho-current from the current production version's immutable tag
1240
+ and checks it out. Local-only, never pushed to origin.
1241
+
1242
+ Returns:
1243
+ True if ho-current was created and checked out, False otherwise.
1244
+
1245
+ Raises:
1246
+ ReleaseManagerError: If the required version tag is not found locally.
1247
+ """
1248
+ if not (
1249
+ self._repo.production
1250
+ and self._repo.hgit.branch == 'ho-prod'
1251
+ and not self._repo.hgit.branch_exists('ho-current')
1252
+ ):
1253
+ return False
1254
+
1255
+ try:
1256
+ current_version = self._repo.database.last_release_s
1257
+ except Exception as e:
1258
+ raise ReleaseManagerError(
1259
+ f"Cannot read current production version from database: {e}"
1260
+ )
1261
+
1262
+ current_tag = f'v{current_version}'
1263
+ tag_exists = any(
1264
+ t.name == current_tag
1265
+ for t in self._repo.hgit._HGit__git_repo.tags
1266
+ )
1267
+ if not tag_exists:
1268
+ raise ReleaseManagerError(
1269
+ f"Cannot set up ho-current: tag {current_tag} not found locally.\n"
1270
+ f"Run 'hop update' to fetch tags first."
1271
+ )
1272
+
1273
+ self._repo.hgit.create_branch_from_tag('ho-current', current_tag)
1274
+ self._repo.hgit._HGit__git_repo.heads['ho-current'].checkout()
1275
+ click.echo(
1276
+ f" ℹ ho-current branch created from {current_tag} (local only).\n"
1277
+ f" Production servers use ho-current instead of ho-prod."
1278
+ )
1279
+ return True
1280
+
1235
1281
  def update_production(self) -> dict:
1236
1282
  """
1237
1283
  Fetch tags and list available releases for production upgrade (read-only).
@@ -1290,30 +1336,8 @@ class ReleaseManager:
1290
1336
  f"Cannot read current production version from database: {e}"
1291
1337
  )
1292
1338
 
1293
- # Migration: production servers that still track ho-prod instead of
1294
- # ho-current. Create ho-current from the current version's immutable tag
1295
- # and switch to it — local-only, no push to origin.
1296
- if (
1297
- self._repo.production
1298
- and self._repo.hgit.branch == 'ho-prod'
1299
- and not self._repo.hgit.branch_exists('ho-current')
1300
- ):
1301
- current_tag = f'v{current_version}'
1302
- tag_exists = any(
1303
- t.name == current_tag
1304
- for t in self._repo.hgit._HGit__git_repo.tags
1305
- )
1306
- if not tag_exists:
1307
- raise ReleaseManagerError(
1308
- f"Cannot migrate to ho-current: tag {current_tag} not found locally.\n"
1309
- f"Run 'hop update' again after ensuring tags are fetched."
1310
- )
1311
- self._repo.hgit.create_branch_from_tag('ho-current', current_tag)
1312
- self._repo.hgit._HGit__git_repo.heads['ho-current'].checkout()
1313
- click.echo(
1314
- f" ℹ Migrated to ho-current (created from {current_tag}, local only).\n"
1315
- f" Production servers should now use ho-current instead of ho-prod."
1316
- )
1339
+ # Migration: production servers that still track ho-prod instead of ho-current.
1340
+ self.ensure_ho_current()
1317
1341
 
1318
1342
  # 3. Build list of available releases with details
1319
1343
  available_releases = []
@@ -491,7 +491,6 @@ class Repo:
491
491
  " Run 'hop migrate' on a development machine first, then deploy."
492
492
  )
493
493
 
494
- self._migration_running = True
495
494
  try:
496
495
  # Create migration manager
497
496
  migration_mgr = MigrationManager(self)
@@ -506,25 +505,31 @@ class Repo:
506
505
 
507
506
  result['migration_needed'] = True
508
507
 
509
- # Migration must be run on ho-prod branch
510
- # If not on ho-prod, switch automatically if working directory is clean
511
508
  current_branch = self.hgit.branch if self.hgit else 'unknown'
512
509
  switched_branch = False
513
510
 
511
+ # Dirty check done ONCE here, before any operation.
512
+ # After this point _migration_running disables further dirty checks:
513
+ # all modifications are the migration's responsibility and will be committed.
514
+ if self.hgit and self.hgit.git_repo.is_dirty(untracked_files=False):
515
+ config_version = self.__config.hop_version if hasattr(self, '_Repo__config') else '0.0.0'
516
+ status = self.hgit.git_repo.git.status('--short')
517
+ raise RepoError(
518
+ f"Repository migration required but working directory has uncommitted changes.\n\n"
519
+ f" Repository version: {config_version}\n"
520
+ f" Installed version: {current_version}\n"
521
+ f" Current branch: {current_branch}\n\n"
522
+ f" Please commit or stash your changes:\n"
523
+ f" git stash\n"
524
+ f" OR\n"
525
+ f" git add . && git commit -m \"your message\"\n"
526
+ f"Dirty files:\n{status}"
527
+ )
528
+
529
+ # From here, all modifications are made by the migration itself.
530
+ self._migration_running = True
531
+
514
532
  if not self.hgit or self.hgit.branch != 'ho-prod':
515
- # Check if working directory is clean
516
- if self.hgit and self.hgit.git_repo.is_dirty(untracked_files=False):
517
- config_version = self.__config.hop_version if hasattr(self, '_Repo__config') else '0.0.0'
518
- raise RepoError(
519
- f"Repository migration required but working directory has uncommitted changes.\n\n"
520
- f" Repository version: {config_version}\n"
521
- f" Installed version: {current_version}\n"
522
- f" Current branch: {current_branch}\n\n"
523
- f" Please commit or stash your changes:\n"
524
- f" git stash\n"
525
- f" OR\n"
526
- f" git add . && git commit -m \"your message\"\n"
527
- )
528
533
 
529
534
  # Working directory is clean, switch to ho-prod
530
535
  try:
@@ -896,17 +901,21 @@ class Repo:
896
901
  git_repo = self.hgit.git_repo
897
902
  current_branch = git_repo.active_branch.name
898
903
 
899
- # Check if working directory is clean
900
- if git_repo.is_dirty(untracked_files=False):
901
- status = git_repo.git.status('--short')
902
- raise RepoError(
903
- f"Working directory has uncommitted changes.\n"
904
- f"Please commit or stash your changes before running this command:\n"
905
- f" git stash\n"
906
- f" OR\n"
907
- f" git add . && git commit -m \"your message\"\n"
908
- f"Dirty files:\n{status}"
909
- )
904
+ # Check if working directory is clean.
905
+ # Skipped during migration: _migration_running means the dirty check
906
+ # was already done once at the start of run_migrations_if_needed(),
907
+ # and all subsequent modifications are committed by the migration itself.
908
+ if not getattr(self, '_migration_running', False):
909
+ if git_repo.is_dirty(untracked_files=False):
910
+ status = git_repo.git.status('--short')
911
+ raise RepoError(
912
+ f"Working directory has uncommitted changes.\n"
913
+ f"Please commit or stash your changes before running this command:\n"
914
+ f" git stash\n"
915
+ f" OR\n"
916
+ f" git add . && git commit -m \"your message\"\n"
917
+ f"Dirty files:\n{status}"
918
+ )
910
919
 
911
920
  # Switch to ho-prod temporarily
912
921
  git_repo.heads['ho-prod'].checkout()
@@ -1391,6 +1400,30 @@ class Repo:
1391
1400
  # step 12: Protect ho-prod from direct commits
1392
1401
  self.install_git_hooks()
1393
1402
 
1403
+ def stage_maintenance_file(self, relative_path: str) -> None:
1404
+ """Stage a file modified by an automated maintenance operation."""
1405
+ if not hasattr(self, '_maintenance_files'):
1406
+ self._maintenance_files = []
1407
+ abs_path = os.path.join(self.__base_dir, relative_path)
1408
+ if os.path.exists(abs_path):
1409
+ self.hgit.git_repo.index.add([relative_path])
1410
+ if relative_path not in self._maintenance_files:
1411
+ self._maintenance_files.append(relative_path)
1412
+
1413
+ def commit_maintenance_files(self, message: str = 'update maintenance files') -> bool:
1414
+ """Commit all staged maintenance files in a single [HOP] commit (skip hooks)."""
1415
+ if not getattr(self, '_maintenance_files', None):
1416
+ return False
1417
+ try:
1418
+ self.hgit.git_repo.index.commit(
1419
+ f'[HOP] {message}',
1420
+ skip_hooks=True,
1421
+ )
1422
+ self._maintenance_files = []
1423
+ return True
1424
+ except Exception:
1425
+ return False
1426
+
1394
1427
  def install_git_hooks(self, force: bool = False) -> dict:
1395
1428
  """
1396
1429
  Install or update Git hooks from templates.
@@ -1457,6 +1490,18 @@ class Repo:
1457
1490
  if action == 'installed' or overall_action == 'skipped':
1458
1491
  overall_action = action
1459
1492
 
1493
+ # Ensure production-specific entries are in .gitignore (idempotent).
1494
+ gitignore_path = Path(self.__base_dir) / '.gitignore'
1495
+ if gitignore_path.exists():
1496
+ content = gitignore_path.read_text()
1497
+ lines = content.splitlines()
1498
+ missing = [e for e in ('.hop/production', '.hop/.fetching')
1499
+ if e not in lines]
1500
+ if missing:
1501
+ with gitignore_path.open('a') as f:
1502
+ f.write('\n' + '\n'.join(missing) + '\n')
1503
+ self.stage_maintenance_file('.gitignore')
1504
+
1460
1505
  return {
1461
1506
  'installed': any_installed,
1462
1507
  'action': overall_action
@@ -1632,6 +1677,7 @@ class Repo:
1632
1677
  # 1. Check and update Git hooks
1633
1678
  if not dry_run:
1634
1679
  result['hooks'] = self.install_git_hooks()
1680
+ self.commit_maintenance_files('update git hooks and .gitignore')
1635
1681
  else:
1636
1682
  # Dry run: just check if update would be needed
1637
1683
  hooks_source_dir = os.path.join(TEMPLATE_DIRS, 'git-hooks')
@@ -2941,9 +2987,14 @@ Each script is executed only once unless `--force` is used.
2941
2987
  )
2942
2988
 
2943
2989
  # Step 3: Clone repository
2990
+ # Production clones fetch ho-prod only — dev branches are irrelevant on production.
2991
+ clone_cmd = ["git", "clone"]
2992
+ if connection_options.get('production'):
2993
+ clone_cmd += ["--single-branch", "--branch", "ho-prod"]
2994
+ clone_cmd += [git_origin, str(dest_path)]
2944
2995
  try:
2945
2996
  result = subprocess.run(
2946
- ["git", "clone", git_origin, str(dest_path)],
2997
+ clone_cmd,
2947
2998
  capture_output=True,
2948
2999
  text=True,
2949
3000
  check=True,
@@ -3016,5 +3067,16 @@ Each script is executed only once unless `--force` is used.
3016
3067
  f"Failed to restore database from schema: {e}"
3017
3068
  ) from e
3018
3069
 
3019
- # Step 9: Install Git hooks
3070
+ # Step 9: Set up ho-current for production servers
3071
+ if connection_options.get('production'):
3072
+ # Mark this clone as production (read-only): blocks git commit/push via hooks.
3073
+ production_marker = Path(dest_path) / '.hop' / 'production'
3074
+ production_marker.parent.mkdir(parents=True, exist_ok=True)
3075
+ production_marker.touch()
3076
+ try:
3077
+ repo.release_manager.ensure_ho_current()
3078
+ except Exception as e:
3079
+ print(f" ⚠️ Warning: Could not set up ho-current: {e}", file=sys.stderr)
3080
+
3081
+ # Step 10: Install Git hooks
3020
3082
  repo.install_git_hooks()
@@ -14,4 +14,6 @@ Backups
14
14
  __pycache__
15
15
  .hop/alt_config
16
16
  .hop/local_config
17
- .hop/backups/
17
+ .hop/backups/
18
+ .hop/production
19
+ .hop/.fetching
@@ -1,11 +1,23 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
3
  # Half-ORM pre-commit hook
4
+ # 0. Blocks commits on production servers (read-only)
4
5
  # 1. Checks if current ho-* branch exists on remote origin
5
6
  # 2. Protects ho-prod branch from direct commits
6
7
  # 3. Optionally calls pre-commit-custom if it exists
7
8
  # Generated by half_orm_dev
8
9
 
10
+ # =============================================================================
11
+ # PRODUCTION SERVER GUARD
12
+ # =============================================================================
13
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
14
+ if [ -f "${REPO_ROOT}/.hop/production" ]; then
15
+ echo "ERROR: git commit is not allowed on a production server." >&2
16
+ echo " This repository is read-only (production mode)." >&2
17
+ echo " Changes are deployed via 'hop upgrade', never committed directly." >&2
18
+ exit 1
19
+ fi
20
+
9
21
  # Get current branch
10
22
  CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
11
23
 
@@ -0,0 +1,18 @@
1
+ #!/bin/sh
2
+
3
+ # Half-ORM pre-push hook
4
+ # Blocks git push on production servers (read-only mode).
5
+ # Generated by half_orm_dev
6
+
7
+ # =============================================================================
8
+ # PRODUCTION SERVER GUARD
9
+ # =============================================================================
10
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
11
+ if [ -f "${REPO_ROOT}/.hop/production" ]; then
12
+ echo "ERROR: git push is not allowed on a production server." >&2
13
+ echo " This repository is read-only (production mode)." >&2
14
+ echo " Changes are deployed via 'hop upgrade', never pushed directly." >&2
15
+ exit 1
16
+ fi
17
+
18
+ exit 0
@@ -0,0 +1,34 @@
1
+ #!/bin/sh
2
+
3
+ # Half-ORM reference-transaction hook
4
+ # Blocks tag creation on production servers (read-only mode).
5
+ # Generated by half_orm_dev
6
+
7
+ # Only act on the "prepared" phase (before the transaction is committed).
8
+ if [ "$1" != "prepared" ]; then
9
+ exit 0
10
+ fi
11
+
12
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
13
+ if [ ! -f "${REPO_ROOT}/.hop/production" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ # Allow tag updates that originate from an internal hop fetch operation.
18
+ # hop sets .hop/.fetching before calling git fetch and removes it after.
19
+ if [ -f "${REPO_ROOT}/.hop/.fetching" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ # Block any local tag creation.
24
+ while read -r _ _ ref_name; do
25
+ case "$ref_name" in
26
+ refs/tags/*)
27
+ echo "ERROR: git tag is not allowed on a production server." >&2
28
+ echo " This repository is read-only (production mode)." >&2
29
+ exit 1
30
+ ;;
31
+ esac
32
+ done
33
+
34
+ exit 0
@@ -0,0 +1 @@
1
+ 1.0.0-a21
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 1.0.0a19
3
+ Version: 1.0.0a21
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
@@ -72,4 +72,6 @@ half_orm_dev/templates/relation_test
72
72
  half_orm_dev/templates/sql_adapter
73
73
  half_orm_dev/templates/warning
74
74
  half_orm_dev/templates/git-hooks/pre-commit
75
- half_orm_dev/templates/git-hooks/prepare-commit-msg
75
+ half_orm_dev/templates/git-hooks/pre-push
76
+ half_orm_dev/templates/git-hooks/prepare-commit-msg
77
+ half_orm_dev/templates/git-hooks/reference-transaction
@@ -1 +0,0 @@
1
- 1.0.0-a19
File without changes
File without changes