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.
- {half_orm_dev-1.0.0a19/half_orm_dev.egg-info → half_orm_dev-1.0.0a21}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/hgit.py +12 -2
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/release_manager.py +48 -24
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/repo.py +91 -29
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/.gitignore +3 -1
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/git-hooks/pre-commit +12 -0
- half_orm_dev-1.0.0a21/half_orm_dev/templates/git-hooks/pre-push +18 -0
- half_orm_dev-1.0.0a21/half_orm_dev/templates/git-hooks/reference-transaction +34 -0
- half_orm_dev-1.0.0a21/half_orm_dev/version.txt +1 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21/half_orm_dev.egg-info}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/SOURCES.txt +3 -1
- half_orm_dev-1.0.0a19/half_orm_dev/version.txt +0 -1
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/AUTHORS +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/LICENSE +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/README.md +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/bootstrap_manager.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/bootstrap.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/revert_migration.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/database.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/file_executor.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migration_manager.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/modules.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patch_manager.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patch_validator.py +0 -0
- {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
- {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
- {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
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/scripts/repair-metadata.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/module_template_1 +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/entry_points.txt +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/requires.txt +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/setup.cfg +0 -0
- {half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/setup.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
-
|
|
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:
|
|
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()
|
|
@@ -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
|
|
@@ -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/
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/cli/commands/revert_migration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a19 → half_orm_dev-1.0.0a21}/half_orm_dev/templates/git-hooks/prepare-commit-msg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|