half-orm-dev 0.17.0a1__tar.gz → 0.17.0a2__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 (67) hide show
  1. {half_orm_dev-0.17.0a1/half_orm_dev.egg-info → half_orm_dev-0.17.0a2}/PKG-INFO +2 -2
  2. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/README.md +1 -1
  3. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/__init__.py +3 -0
  4. half_orm_dev-0.17.0a2/half_orm_dev/cli/commands/check.py +221 -0
  5. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/patch.py +0 -1
  6. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/release.py +27 -33
  7. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/main.py +3 -3
  8. half_orm_dev-0.17.0a2/half_orm_dev/decorators.py +49 -0
  9. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/hgit.py +468 -3
  10. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/patch_manager.py +33 -30
  11. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/patch_validator.py +3 -3
  12. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/release_manager.py +1112 -1262
  13. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/repo.py +223 -5
  14. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/conftest_template +3 -8
  15. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/utils.py +13 -0
  16. half_orm_dev-0.17.0a2/half_orm_dev/version.txt +1 -0
  17. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2/half_orm_dev.egg-info}/PKG-INFO +2 -2
  18. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev.egg-info/SOURCES.txt +2 -4
  19. half_orm_dev-0.17.0a1/half_orm_dev/cli/commands/prepare.py +0 -21
  20. half_orm_dev-0.17.0a1/half_orm_dev/cli/commands/prepare_release.py +0 -119
  21. half_orm_dev-0.17.0a1/half_orm_dev/cli/commands/promote_to.py +0 -127
  22. half_orm_dev-0.17.0a1/half_orm_dev/templates/pre-commit +0 -59
  23. half_orm_dev-0.17.0a1/half_orm_dev/version.txt +0 -1
  24. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/AUTHORS +0 -0
  25. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/LICENSE +0 -0
  26. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/__init__.py +0 -0
  27. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/__init__.py +0 -0
  28. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/apply.py +0 -0
  29. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/clone.py +0 -0
  30. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/init.py +0 -0
  31. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/new.py +0 -0
  32. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/restore.py +0 -0
  33. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/sync.py +0 -0
  34. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/todo.py +0 -0
  35. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/undo.py +0 -0
  36. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/update.py +0 -0
  37. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli/commands/upgrade.py +0 -0
  38. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/cli_extension.py +0 -0
  39. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/database.py +0 -0
  40. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/hop.py +0 -0
  41. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/manifest.py +0 -0
  42. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/modules.py +0 -0
  43. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/patch.py +0 -0
  44. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
  45. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
  46. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
  47. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/patches/log +0 -0
  48. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
  49. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/.gitignore +0 -0
  50. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/MANIFEST.in +0 -0
  51. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/Pipfile +0 -0
  52. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/README +0 -0
  53. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/init_module_template +0 -0
  54. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/module_template_1 +0 -0
  55. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/module_template_2 +0 -0
  56. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/module_template_3 +0 -0
  57. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/relation_test +0 -0
  58. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/setup.py +0 -0
  59. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/sql_adapter +0 -0
  60. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev/templates/warning +0 -0
  61. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev.egg-info/dependency_links.txt +0 -0
  62. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev.egg-info/requires.txt +0 -0
  63. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/half_orm_dev.egg-info/top_level.txt +0 -0
  64. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/setup.cfg +0 -0
  65. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/setup.py +0 -0
  66. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/tests/__init__.py +0 -0
  67. {half_orm_dev-0.17.0a1 → half_orm_dev-0.17.0a2}/tests/conftest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 0.17.0a1
3
+ Version: 0.17.0a2
4
4
  Summary: half_orm development Framework.
5
5
  Home-page: https://github.com/collorg/halfORM_dev
6
6
  Author: Joël Maïzi
@@ -928,7 +928,7 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
928
928
  ---
929
929
 
930
930
  **Version**: 0.17.0
931
- **halfORM**: Compatible with halfORM 0.16.x
931
+ **halfORM**: Compatible with halfORM 0.17.x
932
932
  **Python**: 3.8+
933
933
  **PostgreSQL**: Tested with 13+ (might work with earlier versions)
934
934
 
@@ -888,7 +888,7 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
888
888
  ---
889
889
 
890
890
  **Version**: 0.17.0
891
- **halfORM**: Compatible with halfORM 0.16.x
891
+ **halfORM**: Compatible with halfORM 0.17.x
892
892
  **Python**: 3.8+
893
893
  **PostgreSQL**: Tested with 13+ (might work with earlier versions)
894
894
 
@@ -12,6 +12,7 @@ from .patch import patch
12
12
  from .release import release
13
13
  from .update import update
14
14
  from .upgrade import upgrade
15
+ from .check import check
15
16
  from .todo import apply_release
16
17
  from .todo import create_hotfix
17
18
  from .todo import rollback
@@ -29,6 +30,7 @@ ALL_COMMANDS = {
29
30
  'release': release,
30
31
  'update': update, # Adapted for production
31
32
  'upgrade': upgrade, # Adapted for production
33
+ 'check': check, # Project health check and updates
32
34
  # 🚧 (stubs)
33
35
  'apply_release': apply_release,
34
36
 
@@ -48,6 +50,7 @@ __all__ = [
48
50
  'patch',
49
51
  'release',
50
52
  'upgrade',
53
+ 'check',
51
54
  'create_hotfix',
52
55
  'rollback',
53
56
  # Adapted commands
@@ -0,0 +1,221 @@
1
+ """
2
+ Check command - Verify and update project configuration.
3
+
4
+ Checks project health and updates components as needed:
5
+ - Git hooks (pre-commit)
6
+ - Configuration files
7
+ - Template files
8
+ - Clean up stale branches
9
+ """
10
+
11
+ import click
12
+ from half_orm_dev.repo import Repo
13
+ from half_orm import utils
14
+
15
+
16
+ @click.command()
17
+ @click.option(
18
+ '--prune-branches', '-p',
19
+ is_flag=True,
20
+ help='Also clean up local branches that no longer exist on remote'
21
+ )
22
+ @click.option(
23
+ '--dry-run',
24
+ is_flag=True,
25
+ help='Show what would be done without making changes'
26
+ )
27
+ @click.option(
28
+ '--verbose', '-v',
29
+ is_flag=True,
30
+ help='Show detailed information'
31
+ )
32
+ def check(prune_branches: bool, dry_run: bool, verbose: bool) -> None:
33
+ """
34
+ Verify and update project configuration.
35
+
36
+ Checks project health and updates components as needed. This command
37
+ is also run automatically at the start of other commands.
38
+
39
+ Checks performed:
40
+ • Git hooks are up to date (pre-commit)
41
+ • Repository is properly configured
42
+ • Optionally: Clean up stale local branches
43
+
44
+ Examples:
45
+ # Basic check and update
46
+ half_orm dev check
47
+
48
+ # Check and clean up stale branches
49
+ half_orm dev check --prune-branches
50
+
51
+ # Preview what would be done
52
+ half_orm dev check --dry-run
53
+ """
54
+ try:
55
+ repo = Repo()
56
+
57
+ # Perform check (delegates to Repo)
58
+ result = repo.check_and_update(
59
+ prune_branches=prune_branches,
60
+ dry_run=dry_run,
61
+ silent=False # Show messages
62
+ )
63
+
64
+ # Display results
65
+ _display_check_results(result, dry_run, prune_branches, verbose)
66
+
67
+ except Exception as e:
68
+ click.echo(utils.Color.red(f"❌ Error: {e}"), err=True)
69
+ if verbose:
70
+ import traceback
71
+ traceback.print_exc()
72
+ raise click.Abort()
73
+
74
+
75
+ def _display_check_results(result: dict, dry_run: bool, prune_branches: bool, verbose: bool):
76
+ """Display check results to user."""
77
+ # Hooks
78
+ hooks = result.get('hooks', {})
79
+ if hooks.get('installed'):
80
+ if hooks['action'] == 'updated':
81
+ click.echo(f"✓ {utils.Color.green('Pre-commit hook updated')}")
82
+ elif hooks['action'] == 'installed':
83
+ click.echo(f"✓ {utils.Color.green('Pre-commit hook installed')}")
84
+ elif verbose:
85
+ click.echo(f"✓ {utils.Color.green('Pre-commit hook up to date')}")
86
+
87
+ # Active branches
88
+ active = result.get('active_branches', {})
89
+ patch_branches = active.get('patch_branches', [])
90
+ release_branches = active.get('release_branches', [])
91
+
92
+ # Show current branch
93
+ current = active.get('current_branch')
94
+ if current:
95
+ click.echo(f"\n📍 {utils.Color.bold('Current branch:')} {current}")
96
+
97
+ # Show patch branches
98
+ if patch_branches:
99
+ click.echo(f"\n🔧 {utils.Color.bold('Patch branches')} ({len(patch_branches)}):")
100
+ for branch_info in patch_branches:
101
+ _display_branch_info(branch_info, verbose)
102
+ elif verbose:
103
+ click.echo(f"\n🔧 {utils.Color.bold('Patch branches:')} None")
104
+
105
+ # Show patch branches in stage release (grouped by version)
106
+ active_release = [b for b in release_branches if b.get('in_stage_file', False)]
107
+ if active_release:
108
+ click.echo(f"\n📦 {utils.Color.bold('Patch branches in stage release:')}")
109
+ _display_release_branches_grouped(active_release, verbose)
110
+ elif verbose:
111
+ click.echo(f"\n📦 {utils.Color.bold('Patch branches in stage release:')} None")
112
+
113
+ # Show stale release branches (exist locally but not in stage)
114
+ stale_release = [b for b in release_branches if not b.get('in_stage_file', False)]
115
+ if stale_release and verbose:
116
+ click.echo(f"\n⚠️ {utils.Color.blue('Stale release branches')} ({len(stale_release)}):")
117
+ for branch_info in stale_release[:5]:
118
+ click.echo(f" • {branch_info['name']}")
119
+ if not branch_info['exists_on_remote']:
120
+ click.echo(f" {utils.Color.red('⚠ Not on remote - can be deleted')}")
121
+ if len(stale_release) > 5:
122
+ click.echo(f" ... and {len(stale_release) - 5} more")
123
+
124
+ # Prune results
125
+ if prune_branches:
126
+ branches = result.get('branches', {})
127
+ deleted = branches.get('deleted', [])
128
+
129
+ if deleted:
130
+ click.echo()
131
+ if dry_run:
132
+ click.echo(f"○ {utils.Color.blue(f'Would delete {len(deleted)} stale branch(es)')}")
133
+ else:
134
+ click.echo(f"✓ {utils.Color.green(f'Deleted {len(deleted)} stale branch(es)')}")
135
+
136
+ if verbose:
137
+ for branch in deleted[:10]:
138
+ symbol = "○" if dry_run else "✓"
139
+ click.echo(f" {symbol} {branch}")
140
+ if len(deleted) > 10:
141
+ click.echo(f" ... and {len(deleted) - 10} more")
142
+
143
+ if branches.get('errors'):
144
+ click.echo(f"⚠ {utils.Color.red('Some errors occurred during cleanup')}")
145
+ if verbose:
146
+ for branch, error in branches['errors'][:3]:
147
+ click.echo(f" {branch}: {error}")
148
+
149
+
150
+ def _display_release_branches_grouped(branches: list, verbose: bool):
151
+ """Display release branches grouped by version and sorted by order."""
152
+ from collections import defaultdict
153
+
154
+ # Group branches by version
155
+ by_version = defaultdict(list)
156
+ for branch_info in branches:
157
+ name = branch_info['name']
158
+ # Extract version from ho-release/{version}/{patch_id}
159
+ parts = name.split('/')
160
+ if len(parts) >= 3 and parts[0] == 'ho-release':
161
+ version = parts[1]
162
+ patch_id = '/'.join(parts[2:]) # Handle patch IDs with slashes
163
+ by_version[version].append((patch_id, branch_info))
164
+
165
+ # Display each version group
166
+ for version in sorted(by_version.keys()):
167
+ patches = by_version[version]
168
+
169
+ # Sort patches by their order in the stage file
170
+ patches_sorted = sorted(patches, key=lambda x: x[1].get('order', 999))
171
+
172
+ click.echo(f"\n {utils.Color.bold(f'Release {version}')} ({len(patches)} patch{'es' if len(patches) > 1 else ''}):")
173
+ for patch_id, branch_info in patches_sorted:
174
+ _display_branch_info(branch_info, verbose, indent=" ", show_patch_id_only=True)
175
+
176
+
177
+ def _display_branch_info(branch_info: dict, verbose: bool, indent: str = " ", show_patch_id_only: bool = False):
178
+ """Display information about a single branch.
179
+
180
+ Args:
181
+ branch_info: Branch information dict
182
+ verbose: Show verbose output
183
+ indent: Indentation prefix
184
+ show_patch_id_only: If True, show only patch_id instead of full branch name
185
+ """
186
+ name = branch_info['name']
187
+ is_current = branch_info.get('is_current', False)
188
+ exists_on_remote = branch_info.get('exists_on_remote', False)
189
+ sync_status = branch_info.get('sync_status', 'unknown')
190
+ ahead = branch_info.get('ahead', 0)
191
+ behind = branch_info.get('behind', 0)
192
+
193
+ # Extract display name
194
+ if show_patch_id_only:
195
+ # Extract patch_id from ho-release/{version}/{patch_id}
196
+ parts = name.split('/')
197
+ if len(parts) >= 3:
198
+ display_name = '/'.join(parts[2:])
199
+ else:
200
+ display_name = name
201
+ else:
202
+ display_name = name
203
+
204
+ # Symbol for current branch
205
+ marker = "→ " if is_current else ""
206
+
207
+ # Status symbol and text
208
+ if not exists_on_remote:
209
+ status = utils.Color.red("⚠ no remote")
210
+ elif sync_status == 'synced':
211
+ status = utils.Color.green("✓ synced")
212
+ elif sync_status == 'ahead':
213
+ status = utils.Color.blue(f"↑ {ahead} ahead")
214
+ elif sync_status == 'behind':
215
+ status = utils.Color.blue(f"↓ {behind} behind")
216
+ elif sync_status == 'diverged':
217
+ status = utils.Color.red(f"⚠ diverged (↑{ahead} ↓{behind})")
218
+ else:
219
+ status = "?"
220
+
221
+ click.echo(f"{indent}{marker}• {display_name} - {status}")
@@ -301,7 +301,6 @@ def patch_add(patch_id: str, to_version: Optional[str] = None) -> None:
301
301
  click.echo(f" Stage file: {utils.Color.bold(result['stage_file'])}")
302
302
  click.echo(f" Patch added: {utils.Color.bold(result['patch_id'])}")
303
303
  click.echo(f" Tests passed: {utils.Color.green('✓')}")
304
- click.echo(f" Archived branch: {utils.Color.bold(result['archived_branch'])}")
305
304
 
306
305
  if result.get('notified_branches'):
307
306
  click.echo(f" Notified: {len(result['notified_branches'])} active branch(es)")
@@ -101,29 +101,31 @@ def release_new(level: str) -> None:
101
101
  # Get ReleaseManager
102
102
  release_mgr = repo.release_manager
103
103
 
104
- click.echo(f"Preparing {level} release...")
104
+ click.echo(f"Creating {level} release with integration branch...")
105
105
  click.echo()
106
106
 
107
- # Prepare release
108
- result = release_mgr.prepare_release(level)
107
+ # Create new release with integration branch
108
+ result = release_mgr.new_release(level)
109
109
 
110
110
  # Extract result info
111
111
  version = result['version']
112
- stage_file = result['file']
113
- previous_version = result['previous_version']
112
+ branch = result['branch']
113
+ stage_file = result['stage_file']
114
114
 
115
115
  # Success message
116
- click.echo(f"✅ {utils.Color.bold('Release prepared successfully!')}")
116
+ click.echo(f"✅ {utils.Color.bold('Release created successfully!')}")
117
117
  click.echo()
118
- click.echo(f" Previous version: {utils.Color.bold(previous_version)}")
119
- click.echo(f" New version: {utils.Color.bold(version)}")
118
+ click.echo(f" Version: {utils.Color.bold(version)}")
119
+ click.echo(f" Release branch: {utils.Color.bold(branch)}")
120
120
  click.echo(f" Stage file: {utils.Color.bold(stage_file)}")
121
121
  click.echo()
122
122
  click.echo(f"📝 Next steps:")
123
123
  click.echo(f" 1. Create patches: {utils.Color.bold(f'half_orm dev patch new <patch_id>')}")
124
- click.echo(f" 2. Add to release: {utils.Color.bold(f'half_orm dev patch add <patch_id>')}")
124
+ click.echo(f" 2. Add to release: {utils.Color.bold(f'half_orm dev patch add <patch_id> --to-version={version}')}")
125
125
  click.echo(f" 3. Promote to RC: {utils.Color.bold('half_orm dev release promote rc')}")
126
126
  click.echo()
127
+ click.echo(f"ℹ️ Patches will be merged into {utils.Color.bold(branch)} for integration testing")
128
+ click.echo()
127
129
 
128
130
  except ReleaseManagerError as e:
129
131
  # Handle validation errors (branch, clean, sync, etc.)
@@ -222,12 +224,17 @@ def release_promote(target: str) -> None:
222
224
  try:
223
225
  # Get repository instance
224
226
  repo = Repo()
227
+ release_mgr = repo.release_manager
225
228
 
226
229
  # Delegate to ReleaseManager
227
230
  click.echo(f"Promoting release to {target.upper()}...")
228
231
  click.echo()
229
232
 
230
- result = repo.release_manager.promote_to(target.lower())
233
+ # ReleaseManager auto-detects which version to promote
234
+ if target.lower() == 'rc':
235
+ result = release_mgr.promote_to_rc()
236
+ else: # prod
237
+ result = release_mgr.promote_to_prod()
231
238
 
232
239
  # Display success message
233
240
  click.echo(f"✓ {utils.Color.green('Success!')}")
@@ -236,38 +243,25 @@ def release_promote(target: str) -> None:
236
243
  # Target-specific output
237
244
  if target.lower() == 'rc':
238
245
  # RC promotion output
239
- click.echo(f" Promoted: {utils.Color.bold(result['from_file'])} → {utils.Color.bold(result['to_file'])}")
240
- patches = result.get('patches_merged')
241
- if patches:
242
- click.echo(f" Patches merged: {utils.Color.bold(str(len(patches)))} patch(es)")
243
- click.echo(f" Branches cleaned: {utils.Color.bold(str(len(result['branches_deleted'])))} branch(es)")
244
-
245
- if result.get('notified_branches'):
246
- click.echo(f" Notified: {len(result['notified_branches'])} active branch(es)")
247
-
246
+ click.echo(f" Version: {utils.Color.bold(result['version'])}")
247
+ click.echo(f" Tag: {utils.Color.bold(result['tag'])}")
248
+ click.echo(f" Branch: {utils.Color.bold(result['branch'])}")
248
249
  click.echo()
249
250
  click.echo("📝 Next steps:")
250
251
  click.echo(f" • Test RC thoroughly")
251
- click.echo(f" • Fix issues: Create patch, add to new stage, promote again")
252
252
  click.echo(f" • Deploy to production: {utils.Color.bold('half_orm dev release promote prod')}")
253
253
 
254
254
  else:
255
255
  # Production promotion output
256
- click.echo(f" Promoted: {utils.Color.bold(result['from_file'])} → {utils.Color.bold(result['to_file'])}")
257
- click.echo(f" Version: {utils.Color.bold(result['version'])}")
258
-
259
- if result.get('schema_file'):
260
- click.echo(f" Schema: {utils.Color.bold(result['schema_file'])}")
261
- if result.get('metadata_file'):
262
- click.echo(f" Metadata: {utils.Color.bold(result['metadata_file'])}")
263
- if result.get('symlink_updated'):
264
- click.echo(f" Symlink: schema.sql → {utils.Color.bold(result['schema_file'])}")
265
-
256
+ click.echo(f" Version: {utils.Color.bold(result['version'])}")
257
+ click.echo(f" Tag: {utils.Color.bold(result['tag'])}")
258
+ deleted = result.get('deleted_branches', [])
259
+ if deleted:
260
+ click.echo(f" Branches deleted: {utils.Color.bold(str(len(deleted)))}")
266
261
  click.echo()
267
262
  click.echo("📝 Next steps:")
268
- click.echo(f"""Tag release: {utils.Color.bold(f'git tag v{result["version"]}')}""")
269
- click.echo(f" • Deploy to production servers: {utils.Color.bold('half_orm dev db upgrade')}")
270
- click.echo(f" • Start next cycle: {utils.Color.bold('half_orm dev release new patch')}")
263
+ click.echo(f" • Deploy to production servers")
264
+ click.echo(f" • Start next cycle: {utils.Color.bold('half_orm dev release new minor')}")
271
265
 
272
266
  click.echo()
273
267
 
@@ -31,15 +31,15 @@ class Hop:
31
31
  # Inside hop repository
32
32
  if not self.__repo.devel:
33
33
  # Sync-only mode (no metadata)
34
- return ['sync-package']
34
+ return ['sync-package', 'check']
35
35
 
36
36
  # Development mode (metadata present)
37
37
  if self.__repo.database.production:
38
38
  # PRODUCTION ENVIRONMENT - Release deployment only
39
- return ['update', 'upgrade']
39
+ return ['update', 'upgrade', 'check']
40
40
  else:
41
41
  # DEVELOPMENT ENVIRONMENT - Patch development
42
- return ['patch', 'release']
42
+ return ['patch', 'release', 'check']
43
43
 
44
44
  @property
45
45
  def repo_checked(self):
@@ -0,0 +1,49 @@
1
+ """
2
+ Decorators for half-orm-dev.
3
+
4
+ Provides common decorators for ReleaseManager and PatchManager.
5
+ """
6
+
7
+ from functools import wraps
8
+
9
+
10
+ def with_ho_prod_lock(branch: str = "ho-prod", timeout_minutes: int = 30):
11
+ """
12
+ Decorator to protect methods that modify ho-prod with a lock tag.
13
+
14
+ The lock tag allows the pre-commit hook to permit commits on ho-prod
15
+ during the execution of the decorated method.
16
+
17
+ Args:
18
+ branch: Branch to lock (default: "ho-prod")
19
+ timeout_minutes: Lock timeout in minutes (default: 30)
20
+
21
+ Usage:
22
+ @with_ho_prod_lock()
23
+ def my_method(self, ...):
24
+ # Can commit to ho-prod here
25
+ ...
26
+
27
+ Notes:
28
+ - The decorator assumes `self._repo.hgit` has `acquire_branch_lock()`
29
+ and `release_branch_lock()` methods
30
+ - The lock is ALWAYS released in the finally block, even on error
31
+ - If lock acquisition fails, the method is not executed
32
+ """
33
+ def decorator(func):
34
+ @wraps(func)
35
+ def wrapper(self, *args, **kwargs):
36
+ lock_tag = None
37
+ try:
38
+ # Acquire lock
39
+ lock_tag = self._repo.hgit.acquire_branch_lock(branch, timeout_minutes=timeout_minutes)
40
+
41
+ # Execute the method
42
+ return func(self, *args, **kwargs)
43
+ finally:
44
+ # Always release lock (even on error)
45
+ if lock_tag:
46
+ self._repo.hgit.release_branch_lock(lock_tag)
47
+
48
+ return wrapper
49
+ return decorator