half-orm-dev 0.16.0a9__tar.gz → 0.17.0a1__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-0.16.0a9/half_orm_dev.egg-info → half_orm_dev-0.17.0a1}/PKG-INFO +3 -3
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/README.md +1 -1
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/init.py +1 -1
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/database.py +1 -1
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/patch_manager.py +22 -13
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/release_manager.py +146 -116
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/repo.py +9 -1
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/conftest_template +8 -3
- half_orm_dev-0.17.0a1/half_orm_dev/templates/pre-commit +59 -0
- half_orm_dev-0.17.0a1/half_orm_dev/version.txt +1 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1/half_orm_dev.egg-info}/PKG-INFO +3 -3
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev.egg-info/SOURCES.txt +1 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev.egg-info/requires.txt +1 -1
- half_orm_dev-0.16.0a9/half_orm_dev/version.txt +0 -1
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/AUTHORS +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/LICENSE +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/new.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/prepare.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/prepare_release.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/promote_to.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/hop.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/manifest.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/modules.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/patch.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/Pipfile +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/module_template_1 +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/setup.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/setup.cfg +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/setup.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/tests/__init__.py +0 -0
- {half_orm_dev-0.16.0a9 → half_orm_dev-0.17.0a1}/tests/conftest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: half_orm_dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0a1
|
|
4
4
|
Summary: half_orm development Framework.
|
|
5
5
|
Home-page: https://github.com/collorg/halfORM_dev
|
|
6
6
|
Author: Joël Maïzi
|
|
@@ -24,7 +24,7 @@ License-File: AUTHORS
|
|
|
24
24
|
Requires-Dist: GitPython
|
|
25
25
|
Requires-Dist: click
|
|
26
26
|
Requires-Dist: pydash
|
|
27
|
-
Requires-Dist: half_orm<0.
|
|
27
|
+
Requires-Dist: half_orm<0.18.0,>=0.17.0
|
|
28
28
|
Requires-Dist: pytest
|
|
29
29
|
Dynamic: author
|
|
30
30
|
Dynamic: author-email
|
|
@@ -927,7 +927,7 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
|
|
|
927
927
|
|
|
928
928
|
---
|
|
929
929
|
|
|
930
|
-
**Version**: 0.
|
|
930
|
+
**Version**: 0.17.0
|
|
931
931
|
**halfORM**: Compatible with halfORM 0.16.x
|
|
932
932
|
**Python**: 3.8+
|
|
933
933
|
**PostgreSQL**: Tested with 13+ (might work with earlier versions)
|
|
@@ -887,7 +887,7 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
|
|
|
887
887
|
|
|
888
888
|
---
|
|
889
889
|
|
|
890
|
-
**Version**: 0.
|
|
890
|
+
**Version**: 0.17.0
|
|
891
891
|
**halfORM**: Compatible with halfORM 0.16.x
|
|
892
892
|
**Python**: 3.8+
|
|
893
893
|
**PostgreSQL**: Tested with 13+ (might work with earlier versions)
|
|
@@ -33,7 +33,7 @@ class ProjectDirectoryExistsError(Exception):
|
|
|
33
33
|
@click.option('--production', is_flag=True, help='Mark as production environment (default: False)')
|
|
34
34
|
@click.option('--force-sync-only', is_flag=True, help='Skip metadata installation, force sync-only mode')
|
|
35
35
|
@click.option('--create-db', is_flag=False, default=True)
|
|
36
|
-
@click.option('--docker', default=
|
|
36
|
+
@click.option('--docker', default='', help='Docker container name for PostgreSQL')
|
|
37
37
|
def init(project_name, host, port, user, password, git_origin, production, force_sync_only, create_db, docker):
|
|
38
38
|
"""
|
|
39
39
|
Initialize a new half_orm_dev project with database and code structure.
|
|
@@ -820,7 +820,7 @@ class Database:
|
|
|
820
820
|
... )
|
|
821
821
|
"""
|
|
822
822
|
# Detect execution mode based on docker_container presence
|
|
823
|
-
docker_container = connection_params.get('docker_container'
|
|
823
|
+
docker_container = connection_params.get('docker_container')
|
|
824
824
|
|
|
825
825
|
if docker_container:
|
|
826
826
|
# Docker mode: Execute command inside Docker container
|
|
@@ -1235,16 +1235,18 @@ class PatchManager:
|
|
|
1235
1235
|
2. Validates repository is clean
|
|
1236
1236
|
3. Validates git remote is configured
|
|
1237
1237
|
4. Validates and normalizes patch ID format
|
|
1238
|
-
5.
|
|
1239
|
-
|
|
1240
|
-
6.
|
|
1241
|
-
7.
|
|
1242
|
-
8.
|
|
1243
|
-
9.
|
|
1244
|
-
10.
|
|
1245
|
-
11.
|
|
1246
|
-
12.
|
|
1247
|
-
13.
|
|
1238
|
+
5. **ACQUIRES DISTRIBUTED LOCK on ho-prod** (30min timeout)
|
|
1239
|
+
6. Fetches all references from remote (branches + tags) - with lock
|
|
1240
|
+
6.5 Validates ho-prod is synced with origin/ho-prod
|
|
1241
|
+
7. Checks patch number available via tag lookup (with up-to-date state)
|
|
1242
|
+
8. Creates Patches/PATCH_ID/ directory (on ho-prod)
|
|
1243
|
+
9. Commits directory on ho-prod "Add Patches/{patch_id} directory"
|
|
1244
|
+
10. Creates local tag ho-patch/{number} (points to commit on ho-prod)
|
|
1245
|
+
11. **Pushes tag to reserve number globally** ← POINT OF NO RETURN
|
|
1246
|
+
12. Creates ho-patch/PATCH_ID branch from current commit
|
|
1247
|
+
13. Pushes branch to remote (with retry)
|
|
1248
|
+
14. **RELEASES LOCK** (always, even on error)
|
|
1249
|
+
15. Checkouts to new patch branch
|
|
1248
1250
|
|
|
1249
1251
|
Transactional guarantees:
|
|
1250
1252
|
- Failure before step 10 (tag push): Complete rollback to initial state
|
|
@@ -1291,13 +1293,16 @@ class PatchManager:
|
|
|
1291
1293
|
except Exception as e:
|
|
1292
1294
|
raise PatchManagerError(f"Invalid patch ID: {e}")
|
|
1293
1295
|
|
|
1294
|
-
# Step 5:
|
|
1296
|
+
# Step 5: ACQUIRE LOCK on ho-prod (with 30 min timeout for stale locks)
|
|
1297
|
+
lock_tag = self._repo.hgit.acquire_branch_lock("ho-prod", timeout_minutes=30)
|
|
1298
|
+
|
|
1299
|
+
# Step 6: Fetch all references from remote (branches + tags) - with lock held
|
|
1295
1300
|
self._fetch_from_remote()
|
|
1296
1301
|
|
|
1297
|
-
# Step
|
|
1302
|
+
# Step 6.5: Validate ho-prod is synced with origin
|
|
1298
1303
|
self._validate_ho_prod_synced_with_origin()
|
|
1299
1304
|
|
|
1300
|
-
# Step
|
|
1305
|
+
# Step 7: Check patch number available (via tag, with up-to-date state)
|
|
1301
1306
|
branch_name = f"ho-patch/{normalized_id}"
|
|
1302
1307
|
self._check_patch_id_available(normalized_id)
|
|
1303
1308
|
|
|
@@ -1360,6 +1365,10 @@ class PatchManager:
|
|
|
1360
1365
|
)
|
|
1361
1366
|
raise PatchManagerError(f"Patch creation failed: {e}")
|
|
1362
1367
|
|
|
1368
|
+
finally:
|
|
1369
|
+
# ALWAYS release lock (even on error)
|
|
1370
|
+
self._repo.hgit.release_branch_lock(lock_tag)
|
|
1371
|
+
|
|
1363
1372
|
# Step 13: Checkout to new branch (non-critical, warn if fails)
|
|
1364
1373
|
try:
|
|
1365
1374
|
self._checkout_branch(branch_name)
|
|
@@ -143,6 +143,7 @@ class ReleaseManager:
|
|
|
143
143
|
origin, and pushes to reserve version globally.
|
|
144
144
|
|
|
145
145
|
Workflow:
|
|
146
|
+
0. Acquire lock tag
|
|
146
147
|
1. Validate on ho-prod branch
|
|
147
148
|
2. Validate repository is clean
|
|
148
149
|
3. Fetch from origin
|
|
@@ -153,6 +154,7 @@ class ReleaseManager:
|
|
|
153
154
|
8. Create empty stage file
|
|
154
155
|
9. Commit with message "Prepare release X.Y.Z-stage"
|
|
155
156
|
10. Push to origin (global reservation)
|
|
157
|
+
11. Release lock tag
|
|
156
158
|
|
|
157
159
|
Branch requirements:
|
|
158
160
|
- Must be on ho-prod branch
|
|
@@ -197,76 +199,84 @@ class ReleaseManager:
|
|
|
197
199
|
except ReleaseManagerError as e:
|
|
198
200
|
print(f"Failed: {e}")
|
|
199
201
|
"""
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
f"Must be on ho-prod branch to prepare release.\n"
|
|
204
|
-
f"Current branch: {self._repo.hgit.branch}\n"
|
|
205
|
-
f"Switch to ho-prod: git checkout ho-prod"
|
|
206
|
-
)
|
|
202
|
+
try:
|
|
203
|
+
# 0. ACQUIRE LOCK on ho-prod (with 30 min timeout for stale locks)
|
|
204
|
+
lock_tag = self._repo.hgit.acquire_branch_lock("ho-prod", timeout_minutes=30)
|
|
207
205
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
206
|
+
# 1. Validate on ho-prod branch
|
|
207
|
+
if self._repo.hgit.branch != 'ho-prod':
|
|
208
|
+
raise ReleaseManagerError(
|
|
209
|
+
f"Must be on ho-prod branch to prepare release.\n"
|
|
210
|
+
f"Current branch: {self._repo.hgit.branch}\n"
|
|
211
|
+
f"Switch to ho-prod: git checkout ho-prod"
|
|
212
|
+
)
|
|
216
213
|
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
# 2. Validate repository is clean
|
|
215
|
+
if not self._repo.hgit.repos_is_clean():
|
|
216
|
+
raise ReleaseManagerError(
|
|
217
|
+
"Repository has uncommitted changes.\n"
|
|
218
|
+
"Commit or stash changes before preparing release:\n"
|
|
219
|
+
" git status\n"
|
|
220
|
+
" git add . && git commit"
|
|
221
|
+
)
|
|
219
222
|
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
# 3. Fetch from origin
|
|
224
|
+
self._repo.hgit.fetch_from_origin()
|
|
222
225
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
226
|
+
# 4. Synchronize with origin
|
|
227
|
+
is_synced, status = self._repo.hgit.is_branch_synced("ho-prod")
|
|
228
|
+
|
|
229
|
+
if status == "behind":
|
|
230
|
+
# Pull automatically
|
|
231
|
+
self._repo.hgit.pull()
|
|
232
|
+
elif status == "diverged":
|
|
233
|
+
raise ReleaseManagerError(
|
|
234
|
+
"ho-prod has diverged from origin/ho-prod.\n"
|
|
235
|
+
"Manual resolution required:\n"
|
|
236
|
+
" git pull --rebase origin ho-prod\n"
|
|
237
|
+
" or\n"
|
|
238
|
+
" git merge origin/ho-prod"
|
|
239
|
+
)
|
|
240
|
+
# If "synced" or "ahead", continue
|
|
235
241
|
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
# 5. Read production version from model/schema.sql
|
|
243
|
+
prod_version_str = self._get_production_version()
|
|
238
244
|
|
|
239
|
-
|
|
240
|
-
|
|
245
|
+
# Parse into Version object for calculation
|
|
246
|
+
prod_version = self.parse_version_from_filename(f"{prod_version_str}.txt")
|
|
241
247
|
|
|
242
|
-
|
|
243
|
-
|
|
248
|
+
# 6. Calculate next version
|
|
249
|
+
next_version = self.calculate_next_version(prod_version, increment_type)
|
|
244
250
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
# 7. Verify stage file doesn't exist
|
|
252
|
+
stage_file = self._releases_dir / f"{next_version}-stage.txt"
|
|
253
|
+
if stage_file.exists():
|
|
254
|
+
raise ReleaseFileError(
|
|
255
|
+
f"Stage file already exists: {stage_file}\n"
|
|
256
|
+
f"Version {next_version} is already in development.\n"
|
|
257
|
+
f"To continue with this version, use existing stage file."
|
|
258
|
+
)
|
|
253
259
|
|
|
254
|
-
|
|
255
|
-
|
|
260
|
+
# 8. Create empty stage file
|
|
261
|
+
stage_file.touch()
|
|
256
262
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
263
|
+
# 9. Commit
|
|
264
|
+
self._repo.hgit.add(str(stage_file))
|
|
265
|
+
self._repo.hgit.commit("-m", f"Prepare release {next_version}-stage")
|
|
260
266
|
|
|
261
|
-
|
|
262
|
-
|
|
267
|
+
# 10. Push to origin (global reservation)
|
|
268
|
+
self._repo.hgit.push()
|
|
269
|
+
# Return result
|
|
270
|
+
return {
|
|
271
|
+
'version': next_version,
|
|
272
|
+
'file': str(stage_file),
|
|
273
|
+
'previous_version': prod_version_str
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
finally:
|
|
277
|
+
# 11. ALWAYS release lock (even on error)
|
|
278
|
+
self._repo.hgit.release_branch_lock(lock_tag)
|
|
263
279
|
|
|
264
|
-
# Return result
|
|
265
|
-
return {
|
|
266
|
-
'version': next_version,
|
|
267
|
-
'file': str(stage_file),
|
|
268
|
-
'previous_version': prod_version_str
|
|
269
|
-
}
|
|
270
280
|
|
|
271
281
|
def _get_production_version(self) -> str:
|
|
272
282
|
"""
|
|
@@ -698,21 +708,24 @@ class ReleaseManager:
|
|
|
698
708
|
|
|
699
709
|
Complete workflow with distributed lock to prevent race conditions:
|
|
700
710
|
1. Pre-lock validations (branch, clean, patch exists)
|
|
701
|
-
2.
|
|
702
|
-
3.
|
|
703
|
-
4.
|
|
704
|
-
5. Sync with origin (
|
|
705
|
-
6.
|
|
706
|
-
7.
|
|
707
|
-
8.
|
|
708
|
-
9.
|
|
709
|
-
10.
|
|
710
|
-
11.
|
|
711
|
-
12.
|
|
712
|
-
13.
|
|
713
|
-
14.
|
|
714
|
-
|
|
715
|
-
|
|
711
|
+
2. Pre-lock check: detect stage file and verify patch not already in release (local state, fail-fast)
|
|
712
|
+
3. **ACQUIRE LOCK on ho-prod** (prevents concurrent modifications)
|
|
713
|
+
4. Fetch from origin (get latest state)
|
|
714
|
+
5. Sync with origin/ho-prod (pull if behind)
|
|
715
|
+
6. Re-detect target stage file (may have changed after sync)
|
|
716
|
+
7. Re-check patch not already in release (may have changed after sync)
|
|
717
|
+
8. Ensure patch branch synced with ho-prod
|
|
718
|
+
9. Create temporary validation branch FROM ho-prod
|
|
719
|
+
10. Merge ALL patches already in release (from ho-release/X.Y.Z/* branches)
|
|
720
|
+
11. Merge new patch branch (from ho-patch/{patch_id})
|
|
721
|
+
12. Add patch to stage file on temp branch + commit
|
|
722
|
+
13. Run validation tests (with ALL patches integrated)
|
|
723
|
+
14. If tests fail: cleanup temp branch, release lock, exit with error
|
|
724
|
+
15. If tests pass: return to ho-prod, delete temp branch
|
|
725
|
+
16. Add patch to stage file on ho-prod + commit (file change only)
|
|
726
|
+
17. Push ho-prod to origin
|
|
727
|
+
18. Archive patch branch to ho-release/{version}/{patch_id}
|
|
728
|
+
19. **RELEASE LOCK** (in finally block, always executed)
|
|
716
729
|
|
|
717
730
|
CRITICAL: ho-prod NEVER contains patch code directly. It only contains
|
|
718
731
|
the releases/*.txt files that list which patches are in each release.
|
|
@@ -811,10 +824,8 @@ class ReleaseManager:
|
|
|
811
824
|
f"Checkout branch first: git checkout ho-patch/{patch_id}"
|
|
812
825
|
)
|
|
813
826
|
|
|
814
|
-
# 2.
|
|
827
|
+
# 2. Pre-lock validations on local state (fail-fast before acquiring lock)
|
|
815
828
|
target_version, stage_file = self._detect_target_stage_file(to_version)
|
|
816
|
-
|
|
817
|
-
# 3. Check patch not already in release
|
|
818
829
|
existing_patches = self.read_release_patches(stage_file)
|
|
819
830
|
if patch_id in existing_patches:
|
|
820
831
|
raise ReleaseManagerError(
|
|
@@ -822,10 +833,39 @@ class ReleaseManager:
|
|
|
822
833
|
f"Nothing to do."
|
|
823
834
|
)
|
|
824
835
|
|
|
825
|
-
#
|
|
836
|
+
# 3. ACQUIRE LOCK on ho-prod (with 30 min timeout for stale locks)
|
|
826
837
|
lock_tag = self._repo.hgit.acquire_branch_lock("ho-prod", timeout_minutes=30)
|
|
827
838
|
|
|
828
839
|
try:
|
|
840
|
+
# 4. Fetch from origin to get latest state
|
|
841
|
+
self._repo.hgit.fetch_from_origin()
|
|
842
|
+
|
|
843
|
+
# 5. Sync with origin/ho-prod
|
|
844
|
+
is_synced, sync_status = self._repo.hgit.is_branch_synced("ho-prod")
|
|
845
|
+
if not is_synced:
|
|
846
|
+
if sync_status == "behind":
|
|
847
|
+
self._repo.hgit.pull()
|
|
848
|
+
elif sync_status == "diverged":
|
|
849
|
+
raise ReleaseManagerError(
|
|
850
|
+
"ho-prod has diverged from origin/ho-prod.\n"
|
|
851
|
+
"Manual resolution required:\n"
|
|
852
|
+
" git pull --rebase origin ho-prod\n"
|
|
853
|
+
" or\n"
|
|
854
|
+
" git merge origin/ho-prod"
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
# 6. Re-detect target stage file (may have changed after sync)
|
|
858
|
+
target_version, stage_file = self._detect_target_stage_file(to_version)
|
|
859
|
+
|
|
860
|
+
# 7. Re-check patch not already in release (may have changed after sync)
|
|
861
|
+
existing_patches = self.read_release_patches(stage_file)
|
|
862
|
+
if patch_id in existing_patches:
|
|
863
|
+
raise ReleaseManagerError(
|
|
864
|
+
f"Patch {patch_id} already in release {target_version}-stage.\n"
|
|
865
|
+
f"Nothing to do."
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
# 8. Ensure patch branch is synced with ho-prod
|
|
829
869
|
sync_result = self._ensure_patch_branch_synced(patch_id)
|
|
830
870
|
|
|
831
871
|
if sync_result['strategy'] != 'already-synced':
|
|
@@ -837,30 +877,12 @@ class ReleaseManager:
|
|
|
837
877
|
file=sys.stderr
|
|
838
878
|
)
|
|
839
879
|
|
|
840
|
-
|
|
841
|
-
# Manual resolution required - release lock and exit
|
|
842
|
-
# Lock will be released in finally block
|
|
843
|
-
raise
|
|
844
|
-
|
|
845
|
-
temp_branch = f"temp-valid-{target_version}"
|
|
846
|
-
|
|
847
|
-
try:
|
|
848
|
-
# 5. Sync with origin (now that we have lock)
|
|
849
|
-
self._repo.hgit.fetch_from_origin()
|
|
850
|
-
is_synced, status = self._repo.hgit.is_branch_synced("ho-prod")
|
|
851
|
-
|
|
852
|
-
if status == "behind":
|
|
853
|
-
self._repo.hgit.pull()
|
|
854
|
-
elif status == "diverged":
|
|
855
|
-
raise ReleaseManagerError(
|
|
856
|
-
"Branch ho-prod has diverged from origin.\n"
|
|
857
|
-
"Manual merge or rebase required."
|
|
858
|
-
)
|
|
880
|
+
temp_branch = f"temp-valid-{target_version}"
|
|
859
881
|
|
|
860
|
-
#
|
|
882
|
+
# 9. Create temporary validation branch FROM ho-prod
|
|
861
883
|
self._repo.hgit.checkout("-b", temp_branch)
|
|
862
884
|
|
|
863
|
-
#
|
|
885
|
+
# 10. Merge ALL existing patches in the release (already validated)
|
|
864
886
|
for existing_patch_id in existing_patches:
|
|
865
887
|
archived_branch = f"ho-release/{target_version}/{existing_patch_id}"
|
|
866
888
|
if self._repo.hgit.branch_exists(archived_branch):
|
|
@@ -1397,24 +1419,13 @@ class ReleaseManager:
|
|
|
1397
1419
|
"Commit or stash changes before promoting."
|
|
1398
1420
|
)
|
|
1399
1421
|
|
|
1400
|
-
# 2.
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
source_type = 'stage'
|
|
1408
|
-
else: # target == 'prod'
|
|
1409
|
-
# Production: stage optional, sequential version
|
|
1410
|
-
stage_path = Path(self._releases_dir) / f"{version}-stage.txt"
|
|
1411
|
-
if stage_path.exists():
|
|
1412
|
-
stage_file = f"{version}-stage.txt"
|
|
1413
|
-
source_type = 'stage'
|
|
1414
|
-
else:
|
|
1415
|
-
stage_file = None
|
|
1416
|
-
source_type = 'empty'
|
|
1417
|
-
target_file = f"{version}.txt"
|
|
1422
|
+
# 2. Pre-lock validation: check stage exists (preliminary check on local state)
|
|
1423
|
+
# This allows fast failure before acquiring lock
|
|
1424
|
+
try:
|
|
1425
|
+
version, stage_file = self._detect_stage_to_promote()
|
|
1426
|
+
except ReleaseManagerError:
|
|
1427
|
+
# No stage found locally - fail fast before lock
|
|
1428
|
+
raise
|
|
1418
1429
|
|
|
1419
1430
|
# 3. Acquire distributed lock
|
|
1420
1431
|
lock_tag = None
|
|
@@ -1434,6 +1445,25 @@ class ReleaseManager:
|
|
|
1434
1445
|
"Resolve conflicts manually: git pull origin ho-prod"
|
|
1435
1446
|
)
|
|
1436
1447
|
|
|
1448
|
+
# 5. Re-detect source and target (with up-to-date state after sync)
|
|
1449
|
+
version, stage_file = self._detect_stage_to_promote()
|
|
1450
|
+
if target != 'prod':
|
|
1451
|
+
# RC: stage required, validate single active RC rule
|
|
1452
|
+
self._validate_single_active_rc(version)
|
|
1453
|
+
rc_number = self._determine_rc_number(version)
|
|
1454
|
+
target_file = f"{version}-{target}{rc_number}.txt"
|
|
1455
|
+
source_type = 'stage'
|
|
1456
|
+
else: # target == 'prod'
|
|
1457
|
+
# Production: stage optional, sequential version
|
|
1458
|
+
stage_path = Path(self._releases_dir) / f"{version}-stage.txt"
|
|
1459
|
+
if stage_path.exists():
|
|
1460
|
+
stage_file = f"{version}-stage.txt"
|
|
1461
|
+
source_type = 'stage'
|
|
1462
|
+
else:
|
|
1463
|
+
stage_file = None
|
|
1464
|
+
source_type = 'empty'
|
|
1465
|
+
target_file = f"{version}.txt"
|
|
1466
|
+
|
|
1437
1467
|
# 5. Apply patches to database (prod only)
|
|
1438
1468
|
patches_applied = []
|
|
1439
1469
|
if target == 'prod':
|
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
from configparser import ConfigParser
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
import shutil
|
|
10
11
|
import subprocess
|
|
11
12
|
from typing import Optional
|
|
12
13
|
from psycopg2 import OperationalError
|
|
@@ -595,6 +596,13 @@ class Repo:
|
|
|
595
596
|
# Step 11: Initialize Git repository with ho-prod branch
|
|
596
597
|
self._initialize_git_repository()
|
|
597
598
|
|
|
599
|
+
# step 12: Protect ho-prod from direct commits
|
|
600
|
+
hook_source = os.path.join(TEMPLATE_DIRS, 'pre-commit')
|
|
601
|
+
hook_dest = os.path.join(self.__base_dir, '.git', 'hooks', 'pre-commit')
|
|
602
|
+
shutil.copy(hook_source, hook_dest)
|
|
603
|
+
# Make hook executable
|
|
604
|
+
os.chmod(hook_dest, 0o755)
|
|
605
|
+
|
|
598
606
|
|
|
599
607
|
def _validate_package_name(self, package_name):
|
|
600
608
|
"""
|
|
@@ -830,7 +838,7 @@ class Repo:
|
|
|
830
838
|
# Creates .hop/config:
|
|
831
839
|
# [halfORM]
|
|
832
840
|
# package_name = my_blog
|
|
833
|
-
# hop_version = 0.
|
|
841
|
+
# hop_version = 0.17.0
|
|
834
842
|
# devel = True
|
|
835
843
|
# git_origin = https://github.com/user/my_blog.git
|
|
836
844
|
"""
|
|
@@ -7,9 +7,13 @@ conftest.py files in the appropriate subdirectories.
|
|
|
7
7
|
Generated by half_orm v{hop_release}
|
|
8
8
|
"""
|
|
9
9
|
import pytest
|
|
10
|
-
from
|
|
10
|
+
from {package_name} import MODEL
|
|
11
11
|
from half_orm.relation import Relation
|
|
12
12
|
|
|
13
|
+
try:
|
|
14
|
+
from base_conftest import *
|
|
15
|
+
except ImportError:
|
|
16
|
+
pass
|
|
13
17
|
|
|
14
18
|
@pytest.fixture(scope="session")
|
|
15
19
|
def database_model():
|
|
@@ -24,7 +28,8 @@ def database_model():
|
|
|
24
28
|
users = database_model.get_relation_class('public.users')
|
|
25
29
|
assert users is not None
|
|
26
30
|
"""
|
|
27
|
-
|
|
31
|
+
MODEL.sql_trace = True
|
|
32
|
+
return MODEL
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
@pytest.fixture
|
|
@@ -39,4 +44,4 @@ def relation_class():
|
|
|
39
44
|
from {package_name}.public.users import Users
|
|
40
45
|
assert issubclass(Users, relation_class)
|
|
41
46
|
"""
|
|
42
|
-
return Relation
|
|
47
|
+
return Relation
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Half-ORM pre-commit hook
|
|
4
|
+
# Protects ho-prod branch from direct commits
|
|
5
|
+
# Generated by half_orm_dev
|
|
6
|
+
|
|
7
|
+
# Get current branch
|
|
8
|
+
CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
|
|
9
|
+
|
|
10
|
+
# Check if we're on ho-prod branch
|
|
11
|
+
if [ "$CURRENT_BRANCH" != "ho-prod" ]; then
|
|
12
|
+
# Not on ho-prod, allow commit
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Check if this is a temporary validation tag commit (allows automated promotion)
|
|
17
|
+
CURRENT_TAG=$(git describe --exact-match --tags HEAD 2>/dev/null | grep -E '^temp-valid-')
|
|
18
|
+
|
|
19
|
+
if [ -n "$CURRENT_TAG" ]; then
|
|
20
|
+
# This is an automated promotion commit with temp tag, allow it
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Check if there's an active lock on ho-prod
|
|
25
|
+
# Lock tags follow pattern: lock-ho-prod-{timestamp}
|
|
26
|
+
LOCK_TAG=$(git tag -l 'lock-ho-prod-*' 2>/dev/null | head -n 1)
|
|
27
|
+
|
|
28
|
+
if [ -n "$LOCK_TAG" ]; then
|
|
29
|
+
# Lock is active, allow commit from Half-ORM workflow
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Direct commit on ho-prod is not allowed
|
|
34
|
+
cat << 'EOF'
|
|
35
|
+
❌ Direct commits on 'ho-prod' are not allowed
|
|
36
|
+
|
|
37
|
+
The ho-prod branch is a protected production branch. Changes must be
|
|
38
|
+
promoted through the official Half-ORM release workflow.
|
|
39
|
+
|
|
40
|
+
To create a patch:
|
|
41
|
+
$ half_orm dev patch new <patch_id>
|
|
42
|
+
|
|
43
|
+
To add a patch to a release:
|
|
44
|
+
$ half_orm dev patch <patch_id> add-to-release [release]
|
|
45
|
+
|
|
46
|
+
To promote a stage release to rc/production:
|
|
47
|
+
$ half_orm dev release promote <rc|prod>
|
|
48
|
+
|
|
49
|
+
This workflow ensures:
|
|
50
|
+
• All patches are validated and tested
|
|
51
|
+
• Code is properly merged from patch branches
|
|
52
|
+
• Active patch branches are notified to rebase
|
|
53
|
+
• Production deploys are tracked and auditable
|
|
54
|
+
|
|
55
|
+
For more information, see the Half-ORM documentation on release management.
|
|
56
|
+
|
|
57
|
+
EOF
|
|
58
|
+
|
|
59
|
+
exit 1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.17.0-a1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: half_orm_dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0a1
|
|
4
4
|
Summary: half_orm development Framework.
|
|
5
5
|
Home-page: https://github.com/collorg/halfORM_dev
|
|
6
6
|
Author: Joël Maïzi
|
|
@@ -24,7 +24,7 @@ License-File: AUTHORS
|
|
|
24
24
|
Requires-Dist: GitPython
|
|
25
25
|
Requires-Dist: click
|
|
26
26
|
Requires-Dist: pydash
|
|
27
|
-
Requires-Dist: half_orm<0.
|
|
27
|
+
Requires-Dist: half_orm<0.18.0,>=0.17.0
|
|
28
28
|
Requires-Dist: pytest
|
|
29
29
|
Dynamic: author
|
|
30
30
|
Dynamic: author-email
|
|
@@ -927,7 +927,7 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
|
|
|
927
927
|
|
|
928
928
|
---
|
|
929
929
|
|
|
930
|
-
**Version**: 0.
|
|
930
|
+
**Version**: 0.17.0
|
|
931
931
|
**halfORM**: Compatible with halfORM 0.16.x
|
|
932
932
|
**Python**: 3.8+
|
|
933
933
|
**PostgreSQL**: Tested with 13+ (might work with earlier versions)
|
|
@@ -53,6 +53,7 @@ half_orm_dev/templates/init_module_template
|
|
|
53
53
|
half_orm_dev/templates/module_template_1
|
|
54
54
|
half_orm_dev/templates/module_template_2
|
|
55
55
|
half_orm_dev/templates/module_template_3
|
|
56
|
+
half_orm_dev/templates/pre-commit
|
|
56
57
|
half_orm_dev/templates/relation_test
|
|
57
58
|
half_orm_dev/templates/setup.py
|
|
58
59
|
half_orm_dev/templates/sql_adapter
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.16.0-a9
|
|
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-0.16.0a9 → half_orm_dev-0.17.0a1}/half_orm_dev/cli/commands/prepare_release.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
|
|
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
|