half-orm-dev 0.16.0a9__py3-none-any.whl
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/__init__.py +1 -0
- half_orm_dev/cli/__init__.py +9 -0
- half_orm_dev/cli/commands/__init__.py +56 -0
- half_orm_dev/cli/commands/apply.py +13 -0
- half_orm_dev/cli/commands/clone.py +102 -0
- half_orm_dev/cli/commands/init.py +331 -0
- half_orm_dev/cli/commands/new.py +15 -0
- half_orm_dev/cli/commands/patch.py +317 -0
- half_orm_dev/cli/commands/prepare.py +21 -0
- half_orm_dev/cli/commands/prepare_release.py +119 -0
- half_orm_dev/cli/commands/promote_to.py +127 -0
- half_orm_dev/cli/commands/release.py +344 -0
- half_orm_dev/cli/commands/restore.py +14 -0
- half_orm_dev/cli/commands/sync.py +13 -0
- half_orm_dev/cli/commands/todo.py +73 -0
- half_orm_dev/cli/commands/undo.py +17 -0
- half_orm_dev/cli/commands/update.py +73 -0
- half_orm_dev/cli/commands/upgrade.py +191 -0
- half_orm_dev/cli/main.py +103 -0
- half_orm_dev/cli_extension.py +38 -0
- half_orm_dev/database.py +1389 -0
- half_orm_dev/hgit.py +1025 -0
- half_orm_dev/hop.py +167 -0
- half_orm_dev/manifest.py +43 -0
- half_orm_dev/modules.py +456 -0
- half_orm_dev/patch.py +281 -0
- half_orm_dev/patch_manager.py +1694 -0
- half_orm_dev/patch_validator.py +335 -0
- half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +34 -0
- half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +2 -0
- half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +3 -0
- half_orm_dev/patches/log +2 -0
- half_orm_dev/patches/sql/half_orm_meta.sql +208 -0
- half_orm_dev/release_manager.py +2841 -0
- half_orm_dev/repo.py +1562 -0
- half_orm_dev/templates/.gitignore +15 -0
- half_orm_dev/templates/MANIFEST.in +1 -0
- half_orm_dev/templates/Pipfile +13 -0
- half_orm_dev/templates/README +25 -0
- half_orm_dev/templates/conftest_template +42 -0
- half_orm_dev/templates/init_module_template +10 -0
- half_orm_dev/templates/module_template_1 +12 -0
- half_orm_dev/templates/module_template_2 +6 -0
- half_orm_dev/templates/module_template_3 +3 -0
- half_orm_dev/templates/relation_test +23 -0
- half_orm_dev/templates/setup.py +81 -0
- half_orm_dev/templates/sql_adapter +9 -0
- half_orm_dev/templates/warning +12 -0
- half_orm_dev/utils.py +49 -0
- half_orm_dev/version.txt +1 -0
- half_orm_dev-0.16.0a9.dist-info/METADATA +935 -0
- half_orm_dev-0.16.0a9.dist-info/RECORD +58 -0
- half_orm_dev-0.16.0a9.dist-info/WHEEL +5 -0
- half_orm_dev-0.16.0a9.dist-info/licenses/AUTHORS +3 -0
- half_orm_dev-0.16.0a9.dist-info/licenses/LICENSE +14 -0
- half_orm_dev-0.16.0a9.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/conftest.py +329 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Patch command group - Unified patch development and management.
|
|
3
|
+
|
|
4
|
+
Groups all patch-related commands under 'half_orm dev patch':
|
|
5
|
+
- patch new: Create new patch branch and directory
|
|
6
|
+
- patch apply: Apply current patch files to database
|
|
7
|
+
- patch add: Add patch to stage release with validation
|
|
8
|
+
|
|
9
|
+
Replaces legacy commands:
|
|
10
|
+
- create-patch → patch new
|
|
11
|
+
- apply-patch → patch apply
|
|
12
|
+
- add-to-release → patch add
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from half_orm_dev.repo import Repo
|
|
19
|
+
from half_orm_dev.patch_manager import PatchManagerError
|
|
20
|
+
from half_orm_dev.release_manager import ReleaseManagerError
|
|
21
|
+
from half_orm import utils
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group()
|
|
25
|
+
def patch():
|
|
26
|
+
"""
|
|
27
|
+
Patch development and management commands.
|
|
28
|
+
|
|
29
|
+
Create, apply, and integrate patches into releases with this
|
|
30
|
+
unified command group.
|
|
31
|
+
|
|
32
|
+
\b
|
|
33
|
+
Common workflow:
|
|
34
|
+
1. half_orm dev patch new <patch_id>
|
|
35
|
+
2. half_orm dev patch apply
|
|
36
|
+
3. half_orm dev patch add <patch_id>
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@patch.command('new')
|
|
42
|
+
@click.argument('patch_id', type=str)
|
|
43
|
+
@click.option(
|
|
44
|
+
'--description', '-d',
|
|
45
|
+
type=str,
|
|
46
|
+
default=None,
|
|
47
|
+
help='Optional description for the patch'
|
|
48
|
+
)
|
|
49
|
+
def patch_new(patch_id: str, description: Optional[str] = None) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Create new patch branch and directory structure.
|
|
52
|
+
|
|
53
|
+
Creates a new ho-patch/PATCH_ID branch from ho-prod and sets up the
|
|
54
|
+
corresponding Patches/PATCH_ID/ directory structure for schema changes.
|
|
55
|
+
|
|
56
|
+
This command must be run from the ho-prod branch. All business logic
|
|
57
|
+
is delegated to PatchManager.
|
|
58
|
+
|
|
59
|
+
\b
|
|
60
|
+
Args:
|
|
61
|
+
patch_id: Patch identifier (e.g., "456" or "456-user-authentication")
|
|
62
|
+
description: Optional description to include in patch README
|
|
63
|
+
|
|
64
|
+
\b
|
|
65
|
+
Examples:
|
|
66
|
+
Create patch with numeric ID:
|
|
67
|
+
$ half_orm dev patch new 456
|
|
68
|
+
|
|
69
|
+
Create patch with full ID and description:
|
|
70
|
+
$ half_orm dev patch new 456-user-auth -d "Add user authentication"
|
|
71
|
+
|
|
72
|
+
\b
|
|
73
|
+
Raises:
|
|
74
|
+
click.ClickException: If validation fails or creation errors occur
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
# Get repository instance
|
|
78
|
+
repo = Repo()
|
|
79
|
+
|
|
80
|
+
# Delegate to PatchManager
|
|
81
|
+
result = repo.patch_manager.create_patch(patch_id, description)
|
|
82
|
+
|
|
83
|
+
# Display success message
|
|
84
|
+
click.echo(f"✓ Created patch branch: {utils.Color.bold(result['branch_name'])}")
|
|
85
|
+
click.echo(f"✓ Created patch directory: {utils.Color.bold(str(result['patch_dir']))}")
|
|
86
|
+
click.echo(f"✓ Switched to branch: {utils.Color.bold(result['on_branch'])}")
|
|
87
|
+
click.echo()
|
|
88
|
+
click.echo("📝 Next steps:")
|
|
89
|
+
click.echo(f" 1. Add SQL/Python files to {result['patch_dir']}/")
|
|
90
|
+
click.echo(f" 2. Run: {utils.Color.bold('half_orm dev patch apply')}")
|
|
91
|
+
click.echo(" 3. Test your changes")
|
|
92
|
+
click.echo(f" 4. Run: {utils.Color.bold('half_orm dev patch add')} {patch_id}")
|
|
93
|
+
|
|
94
|
+
except PatchManagerError as e:
|
|
95
|
+
raise click.ClickException(str(e))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@patch.command('apply')
|
|
99
|
+
def patch_apply() -> None:
|
|
100
|
+
"""
|
|
101
|
+
Apply current patch files to database.
|
|
102
|
+
|
|
103
|
+
Must be run from ho-patch/PATCH_ID branch. Automatically detects
|
|
104
|
+
patch from current branch name and executes complete workflow:
|
|
105
|
+
database restoration, patch application, and code generation.
|
|
106
|
+
|
|
107
|
+
This command has no parameters - patch detection is automatic from
|
|
108
|
+
the current Git branch. All business logic is delegated to
|
|
109
|
+
PatchManager.apply_patch_complete_workflow().
|
|
110
|
+
|
|
111
|
+
\b
|
|
112
|
+
Workflow:
|
|
113
|
+
1. Validate current branch is ho-patch/*
|
|
114
|
+
2. Extract patch_id from branch name
|
|
115
|
+
3. Restore database from model/schema.sql
|
|
116
|
+
4. Apply patch SQL/Python files in lexicographic order
|
|
117
|
+
5. Generate halfORM Python code via modules.py
|
|
118
|
+
6. Display detailed report with next steps
|
|
119
|
+
|
|
120
|
+
\b
|
|
121
|
+
Branch Requirements:
|
|
122
|
+
- Must be on ho-patch/PATCH_ID branch
|
|
123
|
+
- Branch name format: ho-patch/456 or ho-patch/456-description
|
|
124
|
+
- Corresponding Patches/PATCH_ID/ directory must exist
|
|
125
|
+
|
|
126
|
+
\b
|
|
127
|
+
Examples:
|
|
128
|
+
On branch ho-patch/456-user-auth:
|
|
129
|
+
$ half_orm dev patch apply
|
|
130
|
+
|
|
131
|
+
\b
|
|
132
|
+
Output:
|
|
133
|
+
✓ Current branch: ho-patch/456-user-auth
|
|
134
|
+
✓ Detected patch: 456-user-auth
|
|
135
|
+
✓ Database restored from model/schema.sql
|
|
136
|
+
✓ Applied 2 patch file(s):
|
|
137
|
+
• 01_create_users.sql
|
|
138
|
+
• 02_add_indexes.sql
|
|
139
|
+
✓ Generated 3 Python file(s):
|
|
140
|
+
• mydb/mydb/public/user.py
|
|
141
|
+
• mydb/mydb/public/user_session.py
|
|
142
|
+
• tests/mydb/public/test_user.py
|
|
143
|
+
|
|
144
|
+
\b
|
|
145
|
+
📝 Next steps:
|
|
146
|
+
1. Review generated code in mydb/mydb/
|
|
147
|
+
2. Implement business logic stubs
|
|
148
|
+
3. Run: half_orm dev test
|
|
149
|
+
4. Commit: git add . && git commit -m 'Implement business logic'
|
|
150
|
+
|
|
151
|
+
\b
|
|
152
|
+
Raises:
|
|
153
|
+
click.ClickException: If branch validation fails or application errors occur
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
# Get repository instance
|
|
157
|
+
repo = Repo()
|
|
158
|
+
|
|
159
|
+
# Get current branch
|
|
160
|
+
current_branch = repo.hgit.branch
|
|
161
|
+
|
|
162
|
+
# Validate branch format
|
|
163
|
+
if not current_branch.startswith('ho-patch/'):
|
|
164
|
+
raise click.ClickException(
|
|
165
|
+
f"Must be on ho-patch/* branch. Current branch: {current_branch}\n"
|
|
166
|
+
f"Use: half_orm dev patch new <patch_id>"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Extract patch_id from branch name
|
|
170
|
+
patch_id = current_branch.replace('ho-patch/', '')
|
|
171
|
+
|
|
172
|
+
# Display current context
|
|
173
|
+
click.echo(f"✓ Current branch: {utils.Color.bold(current_branch)}")
|
|
174
|
+
click.echo(f"✓ Detected patch: {utils.Color.bold(patch_id)}")
|
|
175
|
+
click.echo()
|
|
176
|
+
|
|
177
|
+
# Delegate to PatchManager
|
|
178
|
+
click.echo("Applying patch...")
|
|
179
|
+
result = repo.patch_manager.apply_patch_complete_workflow(patch_id)
|
|
180
|
+
|
|
181
|
+
# Display success
|
|
182
|
+
click.echo(f"✓ {utils.Color.green('Patch applied successfully!')}")
|
|
183
|
+
click.echo(f"✓ Database restored from model/schema.sql")
|
|
184
|
+
click.echo()
|
|
185
|
+
|
|
186
|
+
# Display applied files
|
|
187
|
+
applied_files = result.get('applied_release_files', []) + result.get('applied_current_files', [])
|
|
188
|
+
if applied_files:
|
|
189
|
+
click.echo(f"✓ Applied {len(applied_files)} patch file(s):")
|
|
190
|
+
for filename in applied_files:
|
|
191
|
+
click.echo(f" • {filename}")
|
|
192
|
+
click.echo()
|
|
193
|
+
else:
|
|
194
|
+
click.echo("ℹ No patch files to apply (empty patch)")
|
|
195
|
+
click.echo()
|
|
196
|
+
|
|
197
|
+
# Display generated files
|
|
198
|
+
if result['generated_files']:
|
|
199
|
+
click.echo(f"✓ Generated {len(result['generated_files'])} Python file(s):")
|
|
200
|
+
for filepath in result['generated_files']:
|
|
201
|
+
click.echo(f" • {filepath}")
|
|
202
|
+
click.echo()
|
|
203
|
+
else:
|
|
204
|
+
click.echo("ℹ No Python files generated (no schema changes)")
|
|
205
|
+
click.echo()
|
|
206
|
+
|
|
207
|
+
# Display next steps
|
|
208
|
+
click.echo("📝 Next steps:")
|
|
209
|
+
click.echo(" 1. Review generated code")
|
|
210
|
+
click.echo(" 2. Implement business logic stubs")
|
|
211
|
+
click.echo(f" 3. Run: {utils.Color.bold('half_orm dev test')}")
|
|
212
|
+
click.echo(f""" 4. Commit: {utils.Color.bold('git add . && git commit -m "Implement business logic"')}""")
|
|
213
|
+
click.echo()
|
|
214
|
+
|
|
215
|
+
except PatchManagerError as e:
|
|
216
|
+
raise click.ClickException(str(e))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@patch.command('add')
|
|
220
|
+
@click.argument('patch_id', type=str)
|
|
221
|
+
@click.option(
|
|
222
|
+
'--to-version', '-v',
|
|
223
|
+
type=str,
|
|
224
|
+
default=None,
|
|
225
|
+
help='Target release version (required if multiple stage releases exist)'
|
|
226
|
+
)
|
|
227
|
+
def patch_add(patch_id: str, to_version: Optional[str] = None) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Add patch to stage release file with validation.
|
|
230
|
+
|
|
231
|
+
Integrates developed patch into a stage release for deployment.
|
|
232
|
+
Must be run from ho-prod branch. All business logic is delegated
|
|
233
|
+
to ReleaseManager with distributed lock for safe concurrent operations.
|
|
234
|
+
|
|
235
|
+
\b
|
|
236
|
+
Complete workflow:
|
|
237
|
+
1. Acquire exclusive lock on ho-prod (via Git tag)
|
|
238
|
+
2. Create temporary validation branch
|
|
239
|
+
3. Apply all release patches + current patch
|
|
240
|
+
4. Run pytest validation tests
|
|
241
|
+
5. If tests pass: integrate to ho-prod
|
|
242
|
+
6. If tests fail: cleanup and exit with error
|
|
243
|
+
7. Send resync notifications to other patch branches
|
|
244
|
+
8. Archive patch branch to ho-release/{version}/ namespace
|
|
245
|
+
9. Cleanup patch branch
|
|
246
|
+
10. Release lock
|
|
247
|
+
|
|
248
|
+
\b
|
|
249
|
+
Args:
|
|
250
|
+
patch_id: Patch identifier to add (e.g., "456-user-auth")
|
|
251
|
+
to_version: Target release version (auto-detected if single stage exists)
|
|
252
|
+
|
|
253
|
+
\b
|
|
254
|
+
Branch Requirements:
|
|
255
|
+
- Must be on ho-prod branch
|
|
256
|
+
- Repository must be clean (no uncommitted changes)
|
|
257
|
+
- Must be synced with origin/ho-prod
|
|
258
|
+
- Patch branch ho-patch/PATCH_ID must exist
|
|
259
|
+
- At least one stage release file must exist
|
|
260
|
+
|
|
261
|
+
\b
|
|
262
|
+
Examples:
|
|
263
|
+
Add patch to auto-detected stage release:
|
|
264
|
+
$ half_orm dev patch add 456-user-auth
|
|
265
|
+
|
|
266
|
+
Add patch to specific version:
|
|
267
|
+
$ half_orm dev patch add 456-user-auth --to-version 1.3.6
|
|
268
|
+
|
|
269
|
+
\b
|
|
270
|
+
Output:
|
|
271
|
+
✓ Detected stage release: 1.3.6-stage.txt
|
|
272
|
+
✓ Validated patch 456-user-auth
|
|
273
|
+
✓ All tests passed
|
|
274
|
+
✓ Integrated to ho-prod
|
|
275
|
+
✓ Archived branch: ho-release/1.3.6/456-user-auth
|
|
276
|
+
✓ Notified 2 active patch branches
|
|
277
|
+
|
|
278
|
+
📝 Next steps:
|
|
279
|
+
1. Other developers: git pull && git rebase ho-prod
|
|
280
|
+
2. Continue development: half_orm dev patch new <next_patch_id>
|
|
281
|
+
3. Promote to RC: half_orm dev release promote rc
|
|
282
|
+
|
|
283
|
+
\b
|
|
284
|
+
Raises:
|
|
285
|
+
click.ClickException: If validation fails or integration errors occur
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
# Get repository instance
|
|
289
|
+
repo = Repo()
|
|
290
|
+
|
|
291
|
+
# Display context
|
|
292
|
+
click.echo(f"Adding patch {utils.Color.bold(patch_id)} to stage release...")
|
|
293
|
+
click.echo()
|
|
294
|
+
|
|
295
|
+
# Delegate to ReleaseManager
|
|
296
|
+
result = repo.release_manager.add_patch_to_release(patch_id, to_version)
|
|
297
|
+
|
|
298
|
+
# Display success message
|
|
299
|
+
click.echo(f"✓ {utils.Color.green('Patch added to release successfully!')}")
|
|
300
|
+
click.echo()
|
|
301
|
+
click.echo(f" Stage file: {utils.Color.bold(result['stage_file'])}")
|
|
302
|
+
click.echo(f" Patch added: {utils.Color.bold(result['patch_id'])}")
|
|
303
|
+
click.echo(f" Tests passed: {utils.Color.green('✓')}")
|
|
304
|
+
click.echo(f" Archived branch: {utils.Color.bold(result['archived_branch'])}")
|
|
305
|
+
|
|
306
|
+
if result.get('notified_branches'):
|
|
307
|
+
click.echo(f" Notified: {len(result['notified_branches'])} active branch(es)")
|
|
308
|
+
|
|
309
|
+
click.echo()
|
|
310
|
+
click.echo("📝 Next steps:")
|
|
311
|
+
click.echo(f" • Other developers: {utils.Color.bold('git pull && git rebase ho-prod')}")
|
|
312
|
+
click.echo(f" • Continue development: {utils.Color.bold('half_orm dev patch new <next_patch_id>')}")
|
|
313
|
+
click.echo(f" • Promote to RC: {utils.Color.bold('half_orm dev release promote rc')}")
|
|
314
|
+
click.echo()
|
|
315
|
+
|
|
316
|
+
except ReleaseManagerError as e:
|
|
317
|
+
raise click.ClickException(str(e))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prepare command - Prepares the next release
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import click
|
|
7
|
+
from half_orm_dev.repo import Repo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command()
|
|
11
|
+
@click.option(
|
|
12
|
+
'-l', '--level',
|
|
13
|
+
type=click.Choice(['patch', 'minor', 'major']),
|
|
14
|
+
help="Release level."
|
|
15
|
+
)
|
|
16
|
+
@click.option('-m', '--message', type=str, help="The git commit message")
|
|
17
|
+
def prepare(level, message=None):
|
|
18
|
+
"""Prepares the next release."""
|
|
19
|
+
repo = Repo()
|
|
20
|
+
repo.prepare_release(level, message)
|
|
21
|
+
sys.exit()
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prepare-release command - Prepares the next release stage file
|
|
3
|
+
|
|
4
|
+
Creates releases/X.Y.Z-stage.txt based on production version and increment level.
|
|
5
|
+
Part of Git-centric release workflow (stage → rc → production).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import sys
|
|
10
|
+
from half_orm_dev.repo import Repo
|
|
11
|
+
from half_orm_dev.release_manager import (
|
|
12
|
+
ReleaseManagerError,
|
|
13
|
+
ReleaseFileError,
|
|
14
|
+
ReleaseVersionError
|
|
15
|
+
)
|
|
16
|
+
from half_orm import utils
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.command()
|
|
20
|
+
@click.argument(
|
|
21
|
+
'level',
|
|
22
|
+
type=click.Choice(['patch', 'minor', 'major'], case_sensitive=False)
|
|
23
|
+
)
|
|
24
|
+
def prepare_release(level):
|
|
25
|
+
"""
|
|
26
|
+
Prepare next release stage file.
|
|
27
|
+
|
|
28
|
+
Creates releases/X.Y.Z-stage.txt based on production version and
|
|
29
|
+
semantic versioning increment level.
|
|
30
|
+
|
|
31
|
+
LEVEL: Version increment type (patch, minor, or major)
|
|
32
|
+
|
|
33
|
+
\b
|
|
34
|
+
Semantic versioning rules:
|
|
35
|
+
• patch: Bug fixes, minor changes (1.3.5 → 1.3.6)
|
|
36
|
+
• minor: New features, backward compatible (1.3.5 → 1.4.0)
|
|
37
|
+
• major: Breaking changes (1.3.5 → 2.0.0)
|
|
38
|
+
|
|
39
|
+
\b
|
|
40
|
+
Workflow:
|
|
41
|
+
1. Read production version from model/schema.sql
|
|
42
|
+
2. Calculate next version (patch/minor/major)
|
|
43
|
+
3. Create releases/X.Y.Z-stage.txt
|
|
44
|
+
4. Commit and push to reserve version globally
|
|
45
|
+
|
|
46
|
+
\b
|
|
47
|
+
Requirements:
|
|
48
|
+
• Must be on ho-prod branch
|
|
49
|
+
• Repository must be clean (no uncommitted changes)
|
|
50
|
+
• Must be synced with origin/ho-prod
|
|
51
|
+
|
|
52
|
+
\b
|
|
53
|
+
Examples:
|
|
54
|
+
# Prepare patch release (production 1.3.5 → 1.3.6)
|
|
55
|
+
half_orm dev prepare-release patch
|
|
56
|
+
|
|
57
|
+
# Prepare minor release (production 1.3.5 → 1.4.0)
|
|
58
|
+
half_orm dev prepare-release minor
|
|
59
|
+
|
|
60
|
+
# Prepare major release (production 1.3.5 → 2.0.0)
|
|
61
|
+
half_orm dev prepare-release major
|
|
62
|
+
|
|
63
|
+
\b
|
|
64
|
+
Next steps after prepare-release:
|
|
65
|
+
• Create patches: half_orm dev create-patch <patch_id>
|
|
66
|
+
• Add to release: half_orm dev add-to-release <patch_id>
|
|
67
|
+
• Promote to RC: half_orm dev promote-to rc
|
|
68
|
+
"""
|
|
69
|
+
# Normalize level to lowercase
|
|
70
|
+
level = level.lower()
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
# Get Repo singleton
|
|
74
|
+
repo = Repo()
|
|
75
|
+
|
|
76
|
+
# Get ReleaseManager
|
|
77
|
+
release_mgr = repo.release_manager
|
|
78
|
+
|
|
79
|
+
click.echo(f"Preparing {level} release...")
|
|
80
|
+
click.echo()
|
|
81
|
+
|
|
82
|
+
# Prepare release
|
|
83
|
+
result = release_mgr.prepare_release(level)
|
|
84
|
+
|
|
85
|
+
# Extract result info
|
|
86
|
+
version = result['version']
|
|
87
|
+
stage_file = result['file']
|
|
88
|
+
previous_version = result['previous_version']
|
|
89
|
+
|
|
90
|
+
# Success message
|
|
91
|
+
click.echo(f"✅ {utils.Color.bold('Release prepared successfully!')}")
|
|
92
|
+
click.echo()
|
|
93
|
+
click.echo(f" Previous version: {utils.Color.bold(previous_version)}")
|
|
94
|
+
click.echo(f" New version: {utils.Color.bold(version)}")
|
|
95
|
+
click.echo(f" Stage file: {utils.Color.bold(stage_file)}")
|
|
96
|
+
click.echo()
|
|
97
|
+
click.echo(f"📝 Next steps:")
|
|
98
|
+
click.echo(f" 1. Create patches: {utils.Color.bold(f'half_orm dev create-patch <patch_id>')}")
|
|
99
|
+
click.echo(f" 2. Add to release: {utils.Color.bold(f'half_orm dev add-to-release <patch_id>')}")
|
|
100
|
+
click.echo(f" 3. Promote to RC: {utils.Color.bold('half_orm dev promote-to rc')}")
|
|
101
|
+
click.echo()
|
|
102
|
+
|
|
103
|
+
except ReleaseManagerError as e:
|
|
104
|
+
# Handle validation errors (branch, clean, sync, etc.)
|
|
105
|
+
click.echo(f"❌ {utils.Color.red('Release preparation failed:')}", err=True)
|
|
106
|
+
click.echo(f" {str(e)}", err=True)
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
except ReleaseFileError as e:
|
|
110
|
+
# Handle file errors (missing schema, stage exists, etc.)
|
|
111
|
+
click.echo(f"❌ {utils.Color.red('File error:')}", err=True)
|
|
112
|
+
click.echo(f" {str(e)}", err=True)
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
except ReleaseVersionError as e:
|
|
116
|
+
# Handle version errors (invalid format, calculation, etc.)
|
|
117
|
+
click.echo(f"❌ {utils.Color.red('Version error:')}", err=True)
|
|
118
|
+
click.echo(f" {str(e)}", err=True)
|
|
119
|
+
sys.exit(1)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Promote-to command implementation.
|
|
3
|
+
|
|
4
|
+
Thin CLI layer that delegates to ReleaseManager for business logic.
|
|
5
|
+
Promotes stage release to target ('rc' or 'prod') with code merge and branch cleanup.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from half_orm_dev.repo import Repo
|
|
12
|
+
from half_orm_dev.release_manager import ReleaseManagerError
|
|
13
|
+
from half_orm import utils
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command('promote-to')
|
|
17
|
+
@click.argument('target', type=click.Choice(['rc', 'prod'], case_sensitive=False))
|
|
18
|
+
def promote_to(target: str) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Promote stage release to release candidate.
|
|
21
|
+
|
|
22
|
+
Promotes the smallest stage release to RC (rc1, rc2, etc.), merges all
|
|
23
|
+
archived patch code into ho-prod, and deletes patch branches. Must be
|
|
24
|
+
run from ho-prod branch.
|
|
25
|
+
|
|
26
|
+
Complete workflow:
|
|
27
|
+
1. Detect smallest stage release (sequential promotion)
|
|
28
|
+
2. Validate single active RC rule
|
|
29
|
+
3. Acquire distributed lock on ho-prod
|
|
30
|
+
4. Merge archived patches code into ho-prod
|
|
31
|
+
5. Rename stage file to RC file (git mv)
|
|
32
|
+
6. Commit and push promotion
|
|
33
|
+
7. Send rebase notifications to active branches
|
|
34
|
+
8. Cleanup patch branches
|
|
35
|
+
9. Release lock
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
# Promote smallest stage release
|
|
39
|
+
$ half_orm dev promote-to
|
|
40
|
+
|
|
41
|
+
# Output:
|
|
42
|
+
# ✓ Promoted 1.3.5-stage → 1.3.5-rc1
|
|
43
|
+
# ✓ Merged 3 patches into ho-prod
|
|
44
|
+
# ✓ Deleted 3 patch branches
|
|
45
|
+
# ✓ Notified 2 active branches
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
click.ClickException: If validations fail or workflow errors occur
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
# Get repository instance
|
|
52
|
+
repo = Repo()
|
|
53
|
+
|
|
54
|
+
# Delegate to ReleaseManager
|
|
55
|
+
click.echo(f"Promoting stage release to {target.upper()}...")
|
|
56
|
+
click.echo()
|
|
57
|
+
|
|
58
|
+
result = repo.release_manager.promote_to(target.lower())
|
|
59
|
+
|
|
60
|
+
# Display success message
|
|
61
|
+
click.echo(f"✓ {utils.Color.green('Success!')}")
|
|
62
|
+
click.echo()
|
|
63
|
+
click.echo(f"Promoted: {utils.Color.bold(result['from_file'])} → {utils.Color.bold(result['to_file'])}")
|
|
64
|
+
click.echo(f"Version: {utils.Color.bold(result['version'])}")
|
|
65
|
+
if result.get('rc_number'):
|
|
66
|
+
click.echo(f"RC number: {utils.Color.bold(str(result['rc_number']))}")
|
|
67
|
+
click.echo()
|
|
68
|
+
|
|
69
|
+
# Display merged patches
|
|
70
|
+
if result['patches_merged']:
|
|
71
|
+
click.echo(f"✓ Merged {len(result['patches_merged'])} patches into ho-prod:")
|
|
72
|
+
for patch in result['patches_merged']:
|
|
73
|
+
click.echo(f" • {patch}")
|
|
74
|
+
click.echo()
|
|
75
|
+
else:
|
|
76
|
+
click.echo("✓ No patches to merge (empty stage)")
|
|
77
|
+
click.echo()
|
|
78
|
+
|
|
79
|
+
# Display deleted branches
|
|
80
|
+
if result['branches_deleted']:
|
|
81
|
+
click.echo(f"✓ Deleted {len(result['branches_deleted'])} patch branches")
|
|
82
|
+
click.echo()
|
|
83
|
+
|
|
84
|
+
# Display notifications
|
|
85
|
+
if result.get('notifications_sent'):
|
|
86
|
+
click.echo(f"✓ Notified {len(result['notifications_sent'])} active patch branches:")
|
|
87
|
+
for branch in result['notifications_sent']:
|
|
88
|
+
click.echo(f" • {branch}")
|
|
89
|
+
click.echo()
|
|
90
|
+
|
|
91
|
+
# Display commit info
|
|
92
|
+
click.echo(f"Commit: {utils.Color.bold(result['commit_sha'][:8])}")
|
|
93
|
+
click.echo(f"Lock: {result['lock_tag']}")
|
|
94
|
+
click.echo()
|
|
95
|
+
|
|
96
|
+
# Next steps
|
|
97
|
+
click.echo(f"{utils.Color.bold('📝 Next steps:')}")
|
|
98
|
+
click.echo(f""" 1. Test RC: {utils.Color.bold(f'half_orm dev apply-release {result["to_file"].replace(".txt", "")}')}""")
|
|
99
|
+
click.echo(f" 2. If tests pass: {utils.Color.bold('half_orm dev promote-to prod')}")
|
|
100
|
+
click.echo(f" 3. If issues found: Create fix patch and add to new RC")
|
|
101
|
+
click.echo()
|
|
102
|
+
|
|
103
|
+
except ReleaseManagerError as e:
|
|
104
|
+
# Handle expected errors
|
|
105
|
+
click.echo(f"❌ {utils.Color.red('Promotion failed:')}", err=True)
|
|
106
|
+
click.echo(f" {str(e)}", err=True)
|
|
107
|
+
click.echo()
|
|
108
|
+
|
|
109
|
+
# Provide helpful hints based on error type
|
|
110
|
+
error_msg = str(e).lower()
|
|
111
|
+
|
|
112
|
+
if "must be on ho-prod" in error_msg:
|
|
113
|
+
click.echo("💡 Switch to ho-prod: git checkout ho-prod", err=True)
|
|
114
|
+
elif "uncommitted changes" in error_msg:
|
|
115
|
+
click.echo("💡 Commit or stash changes first", err=True)
|
|
116
|
+
elif "no stage releases" in error_msg:
|
|
117
|
+
click.echo("💡 Create a stage release: half_orm dev prepare-release", err=True)
|
|
118
|
+
elif "rc" in error_msg and "must be deployed" in error_msg:
|
|
119
|
+
click.echo("💡 Deploy existing RC to production first", err=True)
|
|
120
|
+
elif "lock held" in error_msg or "lock" in error_msg:
|
|
121
|
+
click.echo("💡 Another operation in progress, retry later", err=True)
|
|
122
|
+
elif "merge conflict" in error_msg:
|
|
123
|
+
click.echo("💡 Resolve conflicts manually and retry", err=True)
|
|
124
|
+
elif "diverged" in error_msg:
|
|
125
|
+
click.echo("💡 Resolve ho-prod divergence: git pull origin ho-prod", err=True)
|
|
126
|
+
|
|
127
|
+
sys.exit(1)
|