half-orm-dev 1.0.0a23__tar.gz → 1.0.0a25__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.0a23/half_orm_dev.egg-info → half_orm_dev-1.0.0a25}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/release.py +4 -0
- half_orm_dev-1.0.0a25/half_orm_dev/cli/commands/upgrade.py +198 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/database.py +1 -1
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migration_manager.py +6 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/modules.py +32 -22
- half_orm_dev-1.0.0a25/half_orm_dev/py.typed +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/release_manager.py +24 -21
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/module_template_1 +2 -0
- half_orm_dev-1.0.0a25/half_orm_dev/version.txt +1 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25/half_orm_dev.egg-info}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev.egg-info/SOURCES.txt +1 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/pyproject.toml +1 -0
- half_orm_dev-1.0.0a23/half_orm_dev/cli/commands/upgrade.py +0 -191
- half_orm_dev-1.0.0a23/half_orm_dev/version.txt +0 -1
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/AUTHORS +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/LICENSE +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/README.md +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/bootstrap_manager.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/bootstrap.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/revert_migration.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/file_executor.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/1/0/0/a20/01_update_gitignore.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/patch_manager.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/repo.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/scripts/repair-metadata.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/git-hooks/pre-push +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/git-hooks/reference-transaction +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev.egg-info/entry_points.txt +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev.egg-info/requires.txt +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/setup.cfg +0 -0
- {half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/setup.py +0 -0
|
@@ -526,6 +526,10 @@ def release_attach_patch(patch_id: str, force: bool) -> None:
|
|
|
526
526
|
|
|
527
527
|
version = current_branch.replace('ho-release/', '')
|
|
528
528
|
|
|
529
|
+
# Accept full branch name or short patch ID
|
|
530
|
+
if patch_id.startswith('ho-patch/'):
|
|
531
|
+
patch_id = patch_id[len('ho-patch/'):]
|
|
532
|
+
|
|
529
533
|
# Confirmation
|
|
530
534
|
if not force:
|
|
531
535
|
click.echo(f"Attaching patch '{patch_id}' to release {version}")
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Upgrade command - Apply releases sequentially to production database.
|
|
3
|
+
|
|
4
|
+
Equivalent to 'apt upgrade' - applies available releases incrementally
|
|
5
|
+
to existing production database without data destruction.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from half_orm_dev.repo import Repo
|
|
10
|
+
from half_orm_dev.release_manager import ReleaseManagerError
|
|
11
|
+
from half_orm import utils
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command()
|
|
15
|
+
@click.option(
|
|
16
|
+
'--to-release', '-t',
|
|
17
|
+
type=str,
|
|
18
|
+
default=None,
|
|
19
|
+
help='Stop at specific version (e.g., 1.3.7). Default: choose interactively'
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
'--dry-run', '-d',
|
|
23
|
+
is_flag=True,
|
|
24
|
+
help='Simulate upgrade without making changes'
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
'--force',
|
|
28
|
+
is_flag=True,
|
|
29
|
+
help='Overwrite existing backup without confirmation'
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
'--skip-backup',
|
|
33
|
+
is_flag=True,
|
|
34
|
+
help='Skip backup creation (DANGEROUS - for testing only)'
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
'--yes', '-y',
|
|
38
|
+
is_flag=True,
|
|
39
|
+
help='Skip confirmation prompt'
|
|
40
|
+
)
|
|
41
|
+
def upgrade(to_release, dry_run, force, skip_backup, yes):
|
|
42
|
+
"""
|
|
43
|
+
Apply releases sequentially to production database.
|
|
44
|
+
|
|
45
|
+
Fetches available releases, lets you choose a target version interactively,
|
|
46
|
+
then upgrades the production database incrementally without data destruction.
|
|
47
|
+
Creates automatic backup before any changes.
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
# Interactive: choose target from list
|
|
51
|
+
half_orm dev upgrade
|
|
52
|
+
|
|
53
|
+
# Upgrade to specific version (no prompt)
|
|
54
|
+
half_orm dev upgrade --to-release=1.3.7
|
|
55
|
+
|
|
56
|
+
# Simulate upgrade (no changes, no prompt)
|
|
57
|
+
half_orm dev upgrade --dry-run
|
|
58
|
+
|
|
59
|
+
# Apply all without confirmation
|
|
60
|
+
half_orm dev upgrade --yes
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
repo = Repo()
|
|
64
|
+
|
|
65
|
+
# === Fetch and display available releases ===
|
|
66
|
+
click.echo("🔄 Fetching available releases...\n")
|
|
67
|
+
update_info = repo.release_manager.update_production()
|
|
68
|
+
|
|
69
|
+
current = update_info['current_version']
|
|
70
|
+
click.echo(f"Current version: {utils.Color.bold(current)}")
|
|
71
|
+
|
|
72
|
+
if not update_info['has_updates']:
|
|
73
|
+
click.echo(f"\n✓ {utils.Color.green('Production is already at latest version.')}")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
available = update_info['available_releases']
|
|
77
|
+
upgrade_path = update_info['upgrade_path']
|
|
78
|
+
latest = upgrade_path[-1]
|
|
79
|
+
|
|
80
|
+
click.echo(f"\nAvailable releases:")
|
|
81
|
+
for rel in available:
|
|
82
|
+
patch_count = len(rel['patches'])
|
|
83
|
+
patches_label = f"{patch_count} patch{'es' if patch_count != 1 else ''}"
|
|
84
|
+
click.echo(f" • {utils.Color.bold(rel['version'])} ({patches_label})")
|
|
85
|
+
|
|
86
|
+
# === Determine target version ===
|
|
87
|
+
if to_release is None and not dry_run and not yes:
|
|
88
|
+
path_str = " → ".join([current] + upgrade_path)
|
|
89
|
+
click.echo(f"\nUpgrade path: {path_str}\n")
|
|
90
|
+
|
|
91
|
+
raw = click.prompt(
|
|
92
|
+
"Target version",
|
|
93
|
+
default=latest,
|
|
94
|
+
).strip()
|
|
95
|
+
|
|
96
|
+
if raw not in upgrade_path:
|
|
97
|
+
click.echo(
|
|
98
|
+
f"\n❌ '{raw}' is not in the upgrade path.\n"
|
|
99
|
+
f" Available: {', '.join(upgrade_path)}",
|
|
100
|
+
err=True,
|
|
101
|
+
)
|
|
102
|
+
raise click.Abort()
|
|
103
|
+
to_release = raw if raw != latest else None # None means "all"
|
|
104
|
+
|
|
105
|
+
# === Confirmation (unless --dry-run or --yes) ===
|
|
106
|
+
if not dry_run and not yes:
|
|
107
|
+
apply_path = upgrade_path
|
|
108
|
+
if to_release:
|
|
109
|
+
apply_path = upgrade_path[:upgrade_path.index(to_release) + 1]
|
|
110
|
+
|
|
111
|
+
click.echo(f"\nWill apply: {utils.Color.bold(' → '.join(apply_path))}")
|
|
112
|
+
if not skip_backup:
|
|
113
|
+
click.echo("Will create backup before starting.")
|
|
114
|
+
|
|
115
|
+
if not click.confirm("\nProceed?", default=True):
|
|
116
|
+
click.echo("\nUpgrade cancelled.")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
click.echo()
|
|
120
|
+
|
|
121
|
+
# === Run upgrade (pass pre-fetched update_info to avoid double git fetch) ===
|
|
122
|
+
result = repo.release_manager.upgrade_production(
|
|
123
|
+
to_version=to_release,
|
|
124
|
+
dry_run=dry_run,
|
|
125
|
+
force_backup=force,
|
|
126
|
+
skip_backup=skip_backup,
|
|
127
|
+
update_info=update_info,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
_display_upgrade_results(result)
|
|
131
|
+
|
|
132
|
+
except ReleaseManagerError as e:
|
|
133
|
+
click.echo(f"\n❌ {utils.Color.red('Upgrade failed:')}")
|
|
134
|
+
click.echo(f" {str(e)}\n")
|
|
135
|
+
raise click.Abort()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _display_upgrade_results(result):
|
|
139
|
+
"""Format and display upgrade results."""
|
|
140
|
+
if result.get('dry_run'):
|
|
141
|
+
click.echo(f"{utils.Color.bold('DRY RUN')} - Simulation only, no changes made\n")
|
|
142
|
+
|
|
143
|
+
current = result['current_version']
|
|
144
|
+
click.echo(f"Current version: {utils.Color.bold(current)}")
|
|
145
|
+
|
|
146
|
+
if not result.get('releases_would_apply'):
|
|
147
|
+
click.echo(f"\n✓ {utils.Color.green('Already at latest version')}")
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
click.echo(f"\nWould create backup: {utils.Color.bold(result.get('backup_would_be_created', ''))}")
|
|
151
|
+
click.echo(f"\nWould apply releases:")
|
|
152
|
+
for version in result['releases_would_apply']:
|
|
153
|
+
patches = result['patches_would_apply'][version]
|
|
154
|
+
click.echo(f" → {utils.Color.bold(version)} - {len(patches)} patches")
|
|
155
|
+
for patch_id in patches:
|
|
156
|
+
click.echo(f" • {patch_id}")
|
|
157
|
+
|
|
158
|
+
final = result['final_version']
|
|
159
|
+
click.echo(f"\nWould upgrade: {current} → {utils.Color.green(final)}")
|
|
160
|
+
click.echo(f"\n{utils.Color.bold('To apply this upgrade, run without --dry-run')}")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
current = result['current_version']
|
|
164
|
+
|
|
165
|
+
if result.get('backup_created'):
|
|
166
|
+
click.echo(f"✓ Backup created: {utils.Color.bold(result['backup_created'])}")
|
|
167
|
+
elif result.get('snapshot_used'):
|
|
168
|
+
click.echo(f"✓ Snapshot created: {utils.Color.bold(result['snapshot_used'])}")
|
|
169
|
+
elif result.get('releases_applied'):
|
|
170
|
+
click.echo(f"⚠️ {utils.Color.bold('No backup created (--skip-backup used)')}")
|
|
171
|
+
|
|
172
|
+
if not result['releases_applied']:
|
|
173
|
+
click.echo(f"\n✓ {utils.Color.green('Production already at latest version')}")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
click.echo(f"\n{utils.Color.green('Applied releases:')}")
|
|
177
|
+
for version in result['releases_applied']:
|
|
178
|
+
patches = result['patches_applied'][version]
|
|
179
|
+
if patches:
|
|
180
|
+
click.echo(f" ✓ {utils.Color.bold(version)} - {len(patches)} patches")
|
|
181
|
+
for patch_id in patches:
|
|
182
|
+
click.echo(f" • {patch_id}")
|
|
183
|
+
else:
|
|
184
|
+
click.echo(f" ✓ {utils.Color.bold(version)} - (empty release)")
|
|
185
|
+
|
|
186
|
+
final = result['final_version']
|
|
187
|
+
click.echo(f"\n{utils.Color.green('✓ Upgrade complete!')}")
|
|
188
|
+
click.echo(f" {current} → {utils.Color.bold(utils.Color.green(final))}")
|
|
189
|
+
|
|
190
|
+
if result.get('target_version'):
|
|
191
|
+
click.echo(f"\n📝 Partial upgrade to {result['target_version']} complete.")
|
|
192
|
+
click.echo(f" To upgrade further, run: half_orm dev upgrade")
|
|
193
|
+
else:
|
|
194
|
+
click.echo(f"\n📝 Production is now at latest version.")
|
|
195
|
+
|
|
196
|
+
if result.get('backup_created'):
|
|
197
|
+
click.echo(f"\n💡 To rollback if needed:")
|
|
198
|
+
click.echo(f" psql -d {result.get('db_name', 'DATABASE')} -f {result['backup_created']}")
|
|
@@ -149,7 +149,7 @@ class Database:
|
|
|
149
149
|
|
|
150
150
|
def __init_db(self):
|
|
151
151
|
"""Tries to connect to the database. If unsuccessful, creates the
|
|
152
|
-
database
|
|
152
|
+
database and initializes it with half_orm_meta.
|
|
153
153
|
"""
|
|
154
154
|
try:
|
|
155
155
|
self.__model = Model(self.__name)
|
|
@@ -590,6 +590,12 @@ class MigrationManager:
|
|
|
590
590
|
Stale local branches (no longer on remote) are skipped to avoid pre-commit
|
|
591
591
|
hook failures.
|
|
592
592
|
"""
|
|
593
|
+
if self._repo.production:
|
|
594
|
+
raise MigrationManagerError(
|
|
595
|
+
"PRODUCTION SAFETY: _regenerate_modules_after_migration() is forbidden "
|
|
596
|
+
"on a production server.\nModule regeneration (which includes database "
|
|
597
|
+
"restoration) must never run in production."
|
|
598
|
+
)
|
|
593
599
|
import re as _re
|
|
594
600
|
from half_orm_dev import modules as _modules
|
|
595
601
|
|
|
@@ -173,9 +173,11 @@ def __get_field_desc(field_name, field):
|
|
|
173
173
|
field_desc = f'{field_desc.__module__}.{ext}'
|
|
174
174
|
else:
|
|
175
175
|
field_desc = field_desc.__name__
|
|
176
|
-
value = 'dataclasses.field(default=None)'
|
|
177
176
|
if field._metadata['fieldtype'][0] == '_':
|
|
178
177
|
value = 'dataclasses.field(default_factory=list)'
|
|
178
|
+
else:
|
|
179
|
+
value = 'dataclasses.field(default=None)'
|
|
180
|
+
field_desc = f'Optional[{field_desc}]'
|
|
179
181
|
field_desc = f'{field_desc} = {value}'
|
|
180
182
|
field_desc = f" {field_name}: {field_desc}"
|
|
181
183
|
error = utils.check_attribute_name(field_name)
|
|
@@ -188,10 +190,10 @@ def __gen_dataclass(relation, fkeys):
|
|
|
188
190
|
rel = relation()
|
|
189
191
|
dc_name = relation._ho_dataclass_name()
|
|
190
192
|
fields = []
|
|
191
|
-
post_init = [' def __post_init__(self):']
|
|
193
|
+
post_init = [' def __post_init__(self) -> None:']
|
|
192
194
|
for field_name, field in rel._ho_fields.items():
|
|
193
195
|
fields.append(__get_field_desc(field_name, field))
|
|
194
|
-
post_init.append(f' self.{field_name}: Field = None')
|
|
196
|
+
post_init.append(f' self.{field_name}: Optional[Field] = None')
|
|
195
197
|
|
|
196
198
|
# Invert user-defined aliases: constraint_name → alias
|
|
197
199
|
aliases = {constraint: alias for alias, constraint in fkeys.items() if alias != ''}
|
|
@@ -518,15 +520,22 @@ def __update_this_module(
|
|
|
518
520
|
fields = []
|
|
519
521
|
kwargs = []
|
|
520
522
|
arg_names = []
|
|
523
|
+
type_import_modules: set = set()
|
|
521
524
|
for key, value in rel._ho_fields.items():
|
|
522
525
|
error = utils.check_attribute_name(key)
|
|
523
526
|
if not error:
|
|
524
527
|
fields.append(f"self.{key}: Field = None")
|
|
525
528
|
kwarg_type = 'typing.Any'
|
|
526
529
|
if hasattr(value.py_type, '__name__'):
|
|
527
|
-
|
|
528
|
-
|
|
530
|
+
mod = getattr(value.py_type, '__module__', 'builtins')
|
|
531
|
+
if mod and mod != 'builtins':
|
|
532
|
+
type_import_modules.add(mod)
|
|
533
|
+
kwarg_type = f'{mod}.{value.py_type.__name__}'
|
|
534
|
+
else:
|
|
535
|
+
kwarg_type = str(value.py_type.__name__)
|
|
536
|
+
kwargs.append(f"{key}: 'typing.Optional[{kwarg_type}]'=None")
|
|
529
537
|
arg_names.append(f'{key}={key}')
|
|
538
|
+
type_imports = '\n'.join(f'import {m}' for m in sorted(type_import_modules))
|
|
530
539
|
fields = "\n ".join(fields)
|
|
531
540
|
kwargs.append('**kwargs')
|
|
532
541
|
kwargs = ", ".join(kwargs)
|
|
@@ -575,6 +584,7 @@ def __update_this_module(
|
|
|
575
584
|
fqtn=fqtn,
|
|
576
585
|
kwargs=kwargs,
|
|
577
586
|
arg_names=arg_names,
|
|
587
|
+
type_imports=type_imports,
|
|
578
588
|
warning=WARNING_TEMPLATE.format(package_name=package_name)))
|
|
579
589
|
|
|
580
590
|
# Generate test file in tests/ directory structure
|
|
@@ -641,7 +651,7 @@ def __gen_dc_relation() -> tuple:
|
|
|
641
651
|
if is_classmethod:
|
|
642
652
|
block.append(' @classmethod')
|
|
643
653
|
prefix = ' async def' if is_async else ' def'
|
|
644
|
-
block.append(f'{prefix} {name}{sig_str}:')
|
|
654
|
+
block.append(f'{prefix} {name}{sig_str}: # type: ignore[empty-body]')
|
|
645
655
|
if doc:
|
|
646
656
|
block.append(_fmt_doc(doc))
|
|
647
657
|
block.append(' ...')
|
|
@@ -670,29 +680,29 @@ def __gen_baseclass(relation, fkeys) -> str:
|
|
|
670
680
|
|
|
671
681
|
lines = [
|
|
672
682
|
f"class {bc_name}(",
|
|
673
|
-
f" MODEL.get_relation_class('{fqtn}', fields_aliases=None),",
|
|
683
|
+
f" MODEL.get_relation_class('{fqtn}', fields_aliases=None), # type: ignore[misc]",
|
|
674
684
|
f" {dc_name}",
|
|
675
685
|
f"):",
|
|
676
686
|
f" def __iter__(self) -> Iterator[{d}]:",
|
|
677
|
-
f" return super().__iter__()",
|
|
687
|
+
f" return super().__iter__() # type: ignore[return-value]",
|
|
678
688
|
f"",
|
|
679
|
-
f" def ho_select(self, *args, distinct: bool = False, order_by: str = None, limit: int = None, offset: int = None, json_agg=None) -> Iterator[{d}]:",
|
|
680
|
-
f" return super().ho_select(*args, distinct=distinct, order_by=order_by, limit=limit, offset=offset, json_agg=json_agg)",
|
|
689
|
+
f" def ho_select(self, *args, distinct: bool = False, order_by: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None, json_agg=None) -> Iterator[{d}]:",
|
|
690
|
+
f" return super().ho_select(*args, distinct=distinct, order_by=order_by, limit=limit, offset=offset, json_agg=json_agg) # type: ignore[return-value]",
|
|
681
691
|
f"",
|
|
682
|
-
f" def ho_get(self, *args) -> {d}:",
|
|
683
|
-
f" return super().ho_get(*args)",
|
|
692
|
+
f" def ho_get(self, *args) -> {d}: # type: ignore[override]",
|
|
693
|
+
f" return super().ho_get(*args) # type: ignore[return-value]",
|
|
684
694
|
f"",
|
|
685
|
-
f" def ho_insert(self, *args, upsert: Optional[bool] = False) -> {d}:",
|
|
686
|
-
f" return super().ho_insert(*args, upsert=upsert)",
|
|
695
|
+
f" def ho_insert(self, *args, upsert: Optional[bool] = False) -> {d}: # type: ignore[override]",
|
|
696
|
+
f" return super().ho_insert(*args, upsert=upsert) # type: ignore[return-value]",
|
|
687
697
|
f"",
|
|
688
|
-
f" async def ho_aselect(self, *args, distinct: bool = False, order_by: str = None, limit: int = None, offset: int = None) -> List[{d}]:",
|
|
689
|
-
f" return await super().ho_aselect(*args, distinct=distinct, order_by=order_by, limit=limit, offset=offset)",
|
|
698
|
+
f" async def ho_aselect(self, *args, distinct: bool = False, order_by: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None) -> List[{d}]:",
|
|
699
|
+
f" return await super().ho_aselect(*args, distinct=distinct, order_by=order_by, limit=limit, offset=offset) # type: ignore[return-value]",
|
|
690
700
|
f"",
|
|
691
|
-
f" async def ho_aget(self, *args) -> {d}:",
|
|
692
|
-
f" return await super().ho_aget(*args)",
|
|
701
|
+
f" async def ho_aget(self, *args) -> {d}: # type: ignore[override]",
|
|
702
|
+
f" return await super().ho_aget(*args) # type: ignore[return-value]",
|
|
693
703
|
f"",
|
|
694
|
-
f" async def ho_ainsert(self, *args, upsert: bool = False) -> {d}:",
|
|
695
|
-
f" return await super().ho_ainsert(*args, upsert=upsert)",
|
|
704
|
+
f" async def ho_ainsert(self, *args, upsert: bool = False) -> {d}: # type: ignore[override]",
|
|
705
|
+
f" return await super().ho_ainsert(*args, upsert=upsert) # type: ignore[return-value]",
|
|
696
706
|
]
|
|
697
707
|
return '\n'.join(lines)
|
|
698
708
|
|
|
@@ -716,10 +726,10 @@ def __gen_baseclasses(package_dir, package_name):
|
|
|
716
726
|
typing_names = sorted(dc_typing | {'Iterator', 'List', 'Optional', 'TYPE_CHECKING'})
|
|
717
727
|
file_.write(f"from typing import {', '.join(typing_names)}\n")
|
|
718
728
|
file_.write("import dataclasses\n")
|
|
719
|
-
file_.write("from half_orm.field import Field\n")
|
|
729
|
+
file_.write("from half_orm.field import Field # type: ignore[import-not-found]\n")
|
|
720
730
|
for mod in sorted(HO_DATACLASSES_IMPORTS):
|
|
721
731
|
file_.write(f"import {mod}\n")
|
|
722
|
-
file_.write(f"from {package_name} import MODEL\n")
|
|
732
|
+
file_.write(f"from {package_name} import MODEL # type: ignore[import-not-found]\n")
|
|
723
733
|
if HO_BASECLASSES_DICT_NAMES:
|
|
724
734
|
file_.write("if TYPE_CHECKING:\n")
|
|
725
735
|
file_.write(f" from {package_name}.ho_typeddicts import (\n")
|
|
File without changes
|
|
@@ -1518,7 +1518,8 @@ class ReleaseManager:
|
|
|
1518
1518
|
to_version: Optional[str] = None,
|
|
1519
1519
|
dry_run: bool = False,
|
|
1520
1520
|
force_backup: bool = False,
|
|
1521
|
-
skip_backup: bool = False
|
|
1521
|
+
skip_backup: bool = False,
|
|
1522
|
+
update_info: Optional[dict] = None,
|
|
1522
1523
|
) -> dict:
|
|
1523
1524
|
"""
|
|
1524
1525
|
Upgrade production database to target version.
|
|
@@ -1610,10 +1611,30 @@ class ReleaseManager:
|
|
|
1610
1611
|
# 'message': 'Production already at latest version'
|
|
1611
1612
|
# }
|
|
1612
1613
|
"""
|
|
1614
|
+
assert self._repo.production
|
|
1613
1615
|
# Get current version
|
|
1614
1616
|
current_version = self._repo.database.last_release_s
|
|
1615
1617
|
|
|
1616
|
-
# === 1.
|
|
1618
|
+
# === 1. Get available releases (before any destructive operation) ===
|
|
1619
|
+
if update_info is None:
|
|
1620
|
+
update_info = self.update_production()
|
|
1621
|
+
|
|
1622
|
+
# Check if already up to date — exit before creating any snapshot/backup
|
|
1623
|
+
if not update_info['has_updates']:
|
|
1624
|
+
return {
|
|
1625
|
+
'status': 'success',
|
|
1626
|
+
'dry_run': False,
|
|
1627
|
+
'backup_created': None,
|
|
1628
|
+
'snapshot_used': None,
|
|
1629
|
+
'current_version': current_version,
|
|
1630
|
+
'target_version': to_version,
|
|
1631
|
+
'releases_applied': [],
|
|
1632
|
+
'patches_applied': {},
|
|
1633
|
+
'final_version': current_version,
|
|
1634
|
+
'message': 'Production already at latest version'
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
# === 2. SNAPSHOT OR BACKUP (unless dry_run or skip_backup) ===
|
|
1617
1638
|
# Preferred: instant snapshot via CREATE DATABASE ... TEMPLATE (requires CREATEDB).
|
|
1618
1639
|
# Fallback: full pg_dump.
|
|
1619
1640
|
# Connections are terminated before the snapshot — this is intentional:
|
|
@@ -1639,27 +1660,9 @@ class ReleaseManager:
|
|
|
1639
1660
|
force=force_backup
|
|
1640
1661
|
)
|
|
1641
1662
|
|
|
1642
|
-
# ===
|
|
1663
|
+
# === 3. Validate environment ===
|
|
1643
1664
|
self._validate_production_upgrade()
|
|
1644
1665
|
|
|
1645
|
-
# === 3. Get available releases ===
|
|
1646
|
-
update_info = self.update_production()
|
|
1647
|
-
|
|
1648
|
-
# Check if already up to date
|
|
1649
|
-
if not update_info['has_updates']:
|
|
1650
|
-
return {
|
|
1651
|
-
'status': 'success',
|
|
1652
|
-
'dry_run': False,
|
|
1653
|
-
'backup_created': backup_path,
|
|
1654
|
-
'snapshot_used': snapshot_name,
|
|
1655
|
-
'current_version': current_version,
|
|
1656
|
-
'target_version': to_version,
|
|
1657
|
-
'releases_applied': [],
|
|
1658
|
-
'patches_applied': {},
|
|
1659
|
-
'final_version': current_version,
|
|
1660
|
-
'message': 'Production already at latest version'
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
1666
|
# === 4. Calculate upgrade path ===
|
|
1664
1667
|
if to_version:
|
|
1665
1668
|
# Upgrade to specific version
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0-a25
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Upgrade command - Apply releases sequentially to production database.
|
|
3
|
-
|
|
4
|
-
Equivalent to 'apt upgrade' - applies available releases incrementally
|
|
5
|
-
to existing production database without data destruction.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import click
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from half_orm_dev.repo import Repo
|
|
11
|
-
from half_orm_dev.release_manager import ReleaseManagerError
|
|
12
|
-
from half_orm import utils
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@click.command()
|
|
16
|
-
@click.option(
|
|
17
|
-
'--to-release', '-t',
|
|
18
|
-
type=str,
|
|
19
|
-
default=None,
|
|
20
|
-
help='Stop at specific version (e.g., 1.3.7). Default: upgrade to latest'
|
|
21
|
-
)
|
|
22
|
-
@click.option(
|
|
23
|
-
'--dry-run', '-d',
|
|
24
|
-
is_flag=True,
|
|
25
|
-
help='Simulate upgrade without making changes'
|
|
26
|
-
)
|
|
27
|
-
@click.option(
|
|
28
|
-
'--force',
|
|
29
|
-
is_flag=True,
|
|
30
|
-
help='Overwrite existing backup without confirmation'
|
|
31
|
-
)
|
|
32
|
-
@click.option(
|
|
33
|
-
'--skip-backup',
|
|
34
|
-
is_flag=True,
|
|
35
|
-
help='Skip backup creation (DANGEROUS - for testing only)'
|
|
36
|
-
)
|
|
37
|
-
def upgrade(to_release, dry_run, force, skip_backup):
|
|
38
|
-
"""
|
|
39
|
-
Apply releases sequentially to production database.
|
|
40
|
-
|
|
41
|
-
Upgrades production database by applying releases incrementally
|
|
42
|
-
to existing data. NEVER destroys or recreates the database.
|
|
43
|
-
Creates automatic backup before any changes.
|
|
44
|
-
|
|
45
|
-
CRITICAL: This command works on EXISTING production database.
|
|
46
|
-
It does NOT use restore operations that would destroy data.
|
|
47
|
-
|
|
48
|
-
Must be run from ho-prod branch.
|
|
49
|
-
|
|
50
|
-
Workflow:
|
|
51
|
-
1. CREATE BACKUP (backups/{version}.sql) - FIRST ACTION
|
|
52
|
-
2. Validate environment (ho-prod branch, clean repo)
|
|
53
|
-
3. Apply releases sequentially on existing database
|
|
54
|
-
4. Update database version after each release
|
|
55
|
-
|
|
56
|
-
Examples:
|
|
57
|
-
# Upgrade to latest (all available releases)
|
|
58
|
-
half_orm dev upgrade
|
|
59
|
-
|
|
60
|
-
# Upgrade to specific version
|
|
61
|
-
half_orm dev upgrade --to-release=1.3.7
|
|
62
|
-
|
|
63
|
-
# Simulate upgrade (no changes)
|
|
64
|
-
half_orm dev upgrade --dry-run
|
|
65
|
-
|
|
66
|
-
# Force overwrite existing backup
|
|
67
|
-
half_orm dev upgrade --force
|
|
68
|
-
|
|
69
|
-
Options:
|
|
70
|
-
--to-release=VERSION Stop at specific version
|
|
71
|
-
--dry-run Simulate without changes
|
|
72
|
-
--force Overwrite existing backup
|
|
73
|
-
--skip-backup Skip backup (DANGEROUS)
|
|
74
|
-
|
|
75
|
-
Requires:
|
|
76
|
-
- Current branch: ho-prod
|
|
77
|
-
- Repository: clean (no uncommitted changes)
|
|
78
|
-
- Permissions: Database write access
|
|
79
|
-
"""
|
|
80
|
-
try:
|
|
81
|
-
# Get repository instance
|
|
82
|
-
repo = Repo()
|
|
83
|
-
|
|
84
|
-
# Delegate to ReleaseManager
|
|
85
|
-
click.echo("🔄 Starting production upgrade...\n")
|
|
86
|
-
|
|
87
|
-
result = repo.release_manager.upgrade_production(
|
|
88
|
-
to_version=to_release,
|
|
89
|
-
dry_run=dry_run,
|
|
90
|
-
force_backup=force,
|
|
91
|
-
skip_backup=skip_backup
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
# Display results
|
|
95
|
-
_display_upgrade_results(result)
|
|
96
|
-
|
|
97
|
-
except ReleaseManagerError as e:
|
|
98
|
-
click.echo(f"\n❌ {utils.Color.red('Upgrade failed:')}")
|
|
99
|
-
click.echo(f" {str(e)}\n")
|
|
100
|
-
raise click.Abort()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _display_upgrade_results(result):
|
|
104
|
-
"""
|
|
105
|
-
Format and display upgrade results to user.
|
|
106
|
-
|
|
107
|
-
Args:
|
|
108
|
-
result: Dict from ReleaseManager.upgrade_production()
|
|
109
|
-
"""
|
|
110
|
-
# === DRY RUN MODE ===
|
|
111
|
-
if result.get('dry_run'):
|
|
112
|
-
click.echo(f"{utils.Color.bold('DRY RUN')} - Simulation only, no changes made\n")
|
|
113
|
-
|
|
114
|
-
current = result['current_version']
|
|
115
|
-
click.echo(f"Current version: {utils.Color.bold(current)}")
|
|
116
|
-
|
|
117
|
-
if not result['releases_would_apply']:
|
|
118
|
-
click.echo(f"\n✓ {utils.Color.green('Already at latest version')}")
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
# Show what would happen
|
|
122
|
-
click.echo(f"\nWould create backup: {utils.Color.bold(result['backup_would_be_created'])}")
|
|
123
|
-
|
|
124
|
-
click.echo(f"\nWould apply releases:")
|
|
125
|
-
for version in result['releases_would_apply']:
|
|
126
|
-
patches = result['patches_would_apply'][version]
|
|
127
|
-
patch_count = len(patches)
|
|
128
|
-
click.echo(f" → {utils.Color.bold(version)} - {patch_count} patches")
|
|
129
|
-
for patch_id in patches:
|
|
130
|
-
click.echo(f" • {patch_id}")
|
|
131
|
-
|
|
132
|
-
final = result['final_version']
|
|
133
|
-
click.echo(f"\nWould upgrade: {current} → {utils.Color.green(final)}")
|
|
134
|
-
|
|
135
|
-
click.echo(f"\n{utils.Color.bold('To apply this upgrade, run without --dry-run')}")
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
# === ACTUAL UPGRADE ===
|
|
139
|
-
|
|
140
|
-
current = result['current_version']
|
|
141
|
-
|
|
142
|
-
# Backup confirmation
|
|
143
|
-
if result['backup_created']:
|
|
144
|
-
backup_path = result['backup_created']
|
|
145
|
-
click.echo(f"✓ Backup created: {utils.Color.bold(backup_path)}")
|
|
146
|
-
elif result.get('message') and 'already at latest' in result['message'].lower():
|
|
147
|
-
# Up to date scenario
|
|
148
|
-
pass
|
|
149
|
-
else:
|
|
150
|
-
click.echo(f"⚠️ {utils.Color.bold('No backup created (--skip-backup used)')}")
|
|
151
|
-
|
|
152
|
-
click.echo(f"\nCurrent version: {utils.Color.bold(current)}")
|
|
153
|
-
|
|
154
|
-
# Check if already up to date
|
|
155
|
-
if not result['releases_applied']:
|
|
156
|
-
click.echo(f"\n✓ {utils.Color.green('Production already at latest version')}")
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
# Show applied releases
|
|
160
|
-
click.echo(f"\n{utils.Color.green('Applied releases:')}")
|
|
161
|
-
|
|
162
|
-
for version in result['releases_applied']:
|
|
163
|
-
patches = result['patches_applied'][version]
|
|
164
|
-
patch_count = len(patches)
|
|
165
|
-
|
|
166
|
-
if patch_count == 0:
|
|
167
|
-
click.echo(f" ✓ {utils.Color.bold(version)} - (empty release)")
|
|
168
|
-
else:
|
|
169
|
-
click.echo(f" ✓ {utils.Color.bold(version)} - {patch_count} patches")
|
|
170
|
-
for patch_id in patches:
|
|
171
|
-
click.echo(f" • {patch_id}")
|
|
172
|
-
|
|
173
|
-
# Final status
|
|
174
|
-
final = result['final_version']
|
|
175
|
-
click.echo(f"\n{utils.Color.green('✓ Upgrade complete!')}")
|
|
176
|
-
click.echo(f" {current} → {utils.Color.bold(utils.Color.green(final))}")
|
|
177
|
-
|
|
178
|
-
# Next steps
|
|
179
|
-
if result['target_version']:
|
|
180
|
-
# Partial upgrade
|
|
181
|
-
click.echo(f"\n📝 Partial upgrade to {result['target_version']} complete.")
|
|
182
|
-
click.echo(f" To upgrade further, run: half_orm dev upgrade")
|
|
183
|
-
else:
|
|
184
|
-
# Full upgrade
|
|
185
|
-
click.echo(f"\n📝 Production is now at latest version.")
|
|
186
|
-
|
|
187
|
-
# Rollback information
|
|
188
|
-
if result['backup_created']:
|
|
189
|
-
backup_path = result['backup_created']
|
|
190
|
-
click.echo(f"\n💡 To rollback if needed:")
|
|
191
|
-
click.echo(f" psql -d {result.get('db_name', 'DATABASE')} -f {backup_path}")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.0-a23
|
|
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.0a23 → half_orm_dev-1.0.0a25}/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
|
{half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a23 → half_orm_dev-1.0.0a25}/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
|