half-orm-dev 0.17.2a4__tar.gz → 0.17.2a6__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.17.2a4/half_orm_dev.egg-info → half_orm_dev-0.17.2a6}/PKG-INFO +1 -1
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/__init__.py +3 -0
- half_orm_dev-0.17.2a6/half_orm_dev/cli/commands/migrate.py +125 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/main.py +37 -14
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/migration_manager.py +41 -43
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/repo.py +179 -26
- half_orm_dev-0.17.2a6/half_orm_dev/version.txt +1 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6/half_orm_dev.egg-info}/PKG-INFO +1 -1
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev.egg-info/SOURCES.txt +1 -0
- half_orm_dev-0.17.2a4/half_orm_dev/version.txt +0 -1
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/AUTHORS +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/LICENSE +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/README.md +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/new.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/database.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/modules.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/patch_manager.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/release_manager.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/Pipfile +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/module_template_1 +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev.egg-info/requires.txt +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/pyproject.toml +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/setup.cfg +0 -0
- {half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/setup.py +0 -0
|
@@ -13,6 +13,7 @@ from .release import release
|
|
|
13
13
|
from .update import update
|
|
14
14
|
from .upgrade import upgrade
|
|
15
15
|
from .check import check
|
|
16
|
+
from .migrate import migrate
|
|
16
17
|
from .todo import apply_release
|
|
17
18
|
from .todo import rollback
|
|
18
19
|
|
|
@@ -30,6 +31,7 @@ ALL_COMMANDS = {
|
|
|
30
31
|
'update': update, # Adapted for production
|
|
31
32
|
'upgrade': upgrade, # Adapted for production
|
|
32
33
|
'check': check, # Project health check and updates
|
|
34
|
+
'migrate': migrate, # Repository migration after upgrade
|
|
33
35
|
# 🚧 (stubs)
|
|
34
36
|
'apply_release': apply_release,
|
|
35
37
|
|
|
@@ -49,6 +51,7 @@ __all__ = [
|
|
|
49
51
|
'release',
|
|
50
52
|
'upgrade',
|
|
51
53
|
'check',
|
|
54
|
+
'migrate',
|
|
52
55
|
'rollback',
|
|
53
56
|
# Adapted commands
|
|
54
57
|
'sync_package',
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migrate command - Apply repository migrations after half_orm_dev upgrade.
|
|
3
|
+
|
|
4
|
+
This command runs pending migrations when the installed half_orm_dev version
|
|
5
|
+
is newer than the repository's hop_version in .hop/config.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from half_orm_dev.repo import Repo, RepoError
|
|
10
|
+
from half_orm import utils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.option(
|
|
15
|
+
'--verbose', '-v',
|
|
16
|
+
is_flag=True,
|
|
17
|
+
help='Show detailed migration information'
|
|
18
|
+
)
|
|
19
|
+
def migrate(verbose: bool) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Apply repository migrations after half_orm_dev upgrade.
|
|
22
|
+
|
|
23
|
+
This command updates the repository structure and configuration files
|
|
24
|
+
when you upgrade to a newer version of half_orm_dev.
|
|
25
|
+
|
|
26
|
+
Requirements:
|
|
27
|
+
• Must be on ho-prod branch
|
|
28
|
+
• Repository must be clean (no uncommitted changes)
|
|
29
|
+
|
|
30
|
+
Process:
|
|
31
|
+
1. Detects version mismatch between installed half_orm_dev and repository
|
|
32
|
+
2. Applies any migration scripts for intermediate versions
|
|
33
|
+
3. Updates hop_version in .hop/config
|
|
34
|
+
4. Creates migration commit on ho-prod
|
|
35
|
+
5. Syncs .hop/ directory to all active branches
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
# After upgrading half_orm_dev
|
|
39
|
+
$ pip install --upgrade half_orm_dev
|
|
40
|
+
$ half_orm dev migrate
|
|
41
|
+
⚠️ Migration needed: half_orm_dev 0.17.2 → 0.18.0
|
|
42
|
+
Current branch: ho-prod
|
|
43
|
+
|
|
44
|
+
Running migrations...
|
|
45
|
+
✓ Applied migration: 0.17.2 → 0.18.0
|
|
46
|
+
✓ Updated .hop/config: hop_version = 0.18.0
|
|
47
|
+
✓ Synced .hop/ to active branches
|
|
48
|
+
|
|
49
|
+
# View detailed migration info
|
|
50
|
+
$ half_orm dev migrate --verbose
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
repo = Repo()
|
|
54
|
+
|
|
55
|
+
# Check if we're in a repository
|
|
56
|
+
if not repo.checked:
|
|
57
|
+
click.echo(utils.Color.red("❌ Not in a hop repository"), err=True)
|
|
58
|
+
raise click.Abort()
|
|
59
|
+
|
|
60
|
+
# Get current versions
|
|
61
|
+
from half_orm_dev.utils import hop_version
|
|
62
|
+
installed_version = hop_version()
|
|
63
|
+
config_version = repo._Repo__config.hop_version if hasattr(repo, '_Repo__config') else '0.0.0'
|
|
64
|
+
|
|
65
|
+
# Check if migration is needed
|
|
66
|
+
comparison = repo.compare_versions(installed_version, config_version)
|
|
67
|
+
|
|
68
|
+
if comparison == 0:
|
|
69
|
+
# Versions are equal - no migration needed
|
|
70
|
+
click.echo(f"✓ {utils.Color.green('Repository is up to date')}")
|
|
71
|
+
click.echo(f" Repository version: {config_version}")
|
|
72
|
+
click.echo(f" Installed version: {installed_version}")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
elif comparison < 0:
|
|
76
|
+
# Installed version is OLDER than repository version
|
|
77
|
+
click.echo(f"⚠️ {utils.Color.red('Installed half_orm_dev is older than repository version')}", err=True)
|
|
78
|
+
click.echo(f"\n Repository version: {config_version}", err=True)
|
|
79
|
+
click.echo(f" Installed version: {installed_version}", err=True)
|
|
80
|
+
click.echo(f"\n Please upgrade half_orm_dev:", err=True)
|
|
81
|
+
click.echo(f" pip install --upgrade half_orm_dev\n", err=True)
|
|
82
|
+
raise click.Abort()
|
|
83
|
+
|
|
84
|
+
# Migration needed (comparison > 0)
|
|
85
|
+
click.echo(f"⚠️ {utils.Color.bold('Migration needed:')}")
|
|
86
|
+
click.echo(f" half_orm_dev {config_version} → {installed_version}")
|
|
87
|
+
|
|
88
|
+
# Check current branch
|
|
89
|
+
current_branch = repo.hgit.branch if repo.hgit else 'unknown'
|
|
90
|
+
click.echo(f" Current branch: {current_branch}")
|
|
91
|
+
click.echo()
|
|
92
|
+
|
|
93
|
+
# Run migrations
|
|
94
|
+
click.echo(f" Running migrations...")
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
result = repo.run_migrations_if_needed(silent=False)
|
|
98
|
+
|
|
99
|
+
if result['migration_run']:
|
|
100
|
+
click.echo(f"\n✓ {utils.Color.green('Migration completed successfully')}")
|
|
101
|
+
click.echo(f" Updated .hop/config: hop_version = {installed_version}")
|
|
102
|
+
|
|
103
|
+
if verbose and result.get('errors'):
|
|
104
|
+
click.echo(f"\n⚠️ Warnings during migration:")
|
|
105
|
+
for error in result['errors']:
|
|
106
|
+
click.echo(f" • {error}")
|
|
107
|
+
|
|
108
|
+
click.echo(f"\n✓ Synced .hop/ to active branches")
|
|
109
|
+
else:
|
|
110
|
+
click.echo(f"✓ {utils.Color.green('Repository is up to date')}")
|
|
111
|
+
|
|
112
|
+
except RepoError as e:
|
|
113
|
+
# Migration failed or branch check failed
|
|
114
|
+
click.echo(utils.Color.red(f"\n❌ {e}"), err=True)
|
|
115
|
+
raise click.Abort()
|
|
116
|
+
|
|
117
|
+
except RepoError as e:
|
|
118
|
+
click.echo(utils.Color.red(f"❌ Error: {e}"), err=True)
|
|
119
|
+
raise click.Abort()
|
|
120
|
+
except Exception as e:
|
|
121
|
+
click.echo(utils.Color.red(f"❌ Unexpected error: {e}"), err=True)
|
|
122
|
+
if verbose:
|
|
123
|
+
import traceback
|
|
124
|
+
traceback.print_exc()
|
|
125
|
+
raise click.Abort()
|
|
@@ -28,6 +28,9 @@ class Hop:
|
|
|
28
28
|
# Outside hop repository - commands for project initialization
|
|
29
29
|
return ['init', 'clone']
|
|
30
30
|
|
|
31
|
+
if self.__repo.needs_migration():
|
|
32
|
+
return ['migrate']
|
|
33
|
+
|
|
31
34
|
# Inside hop repository
|
|
32
35
|
if not self.__repo.devel:
|
|
33
36
|
# Sync-only mode (no metadata)
|
|
@@ -73,21 +76,41 @@ def create_cli_group():
|
|
|
73
76
|
if ctx.invoked_subcommand is None:
|
|
74
77
|
# Show repo state when no subcommand is provided
|
|
75
78
|
if hop.repo_checked:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
# Check if migration is needed
|
|
80
|
+
if hop._Hop__repo.needs_migration():
|
|
81
|
+
# Display migration warning
|
|
82
|
+
from half_orm_dev.utils import hop_version
|
|
83
|
+
installed_version = hop_version()
|
|
84
|
+
config_version = hop._Hop__repo._Repo__config.hop_version
|
|
85
|
+
current_branch = hop._Hop__repo.hgit.branch if hop._Hop__repo.hgit else 'unknown'
|
|
86
|
+
|
|
87
|
+
click.echo(f"\n{'='*70}")
|
|
88
|
+
click.echo(f"⚠️ {utils.Color.bold(utils.Color.red('REPOSITORY MIGRATION REQUIRED'))} ⚠️")
|
|
89
|
+
click.echo(f"{'='*70}")
|
|
90
|
+
click.echo(f"\n Repository version: {utils.Color.red(config_version)}")
|
|
91
|
+
click.echo(f" Installed version: {utils.Color.green(installed_version)}")
|
|
92
|
+
click.echo(f" Current branch: {current_branch}")
|
|
93
|
+
click.echo(f"\n {utils.Color.bold('All commands are blocked until migration is complete.')}")
|
|
94
|
+
click.echo(f"\n To apply migration, run:")
|
|
95
|
+
click.echo(f" {utils.Color.bold('half_orm dev migrate')}")
|
|
96
|
+
click.echo(f"\n{'='*70}\n")
|
|
84
97
|
else:
|
|
85
|
-
#
|
|
86
|
-
click.echo(
|
|
87
|
-
click.echo(f"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
# Normal display
|
|
99
|
+
click.echo(hop.state)
|
|
100
|
+
click.echo(f"\n{utils.Color.bold('Available commands:')}")
|
|
101
|
+
|
|
102
|
+
# Adapt displayed commands based on environment
|
|
103
|
+
if hop._Hop__repo.database.production:
|
|
104
|
+
# Production commands
|
|
105
|
+
click.echo(f" • {utils.Color.bold('update')} - Fetch and list available releases")
|
|
106
|
+
click.echo(f" • {utils.Color.bold('upgrade [--to-release=X.Y.Z]')} - Apply releases to production")
|
|
107
|
+
else:
|
|
108
|
+
# Development commands
|
|
109
|
+
click.echo(f" • {utils.Color.bold('patch')}")
|
|
110
|
+
click.echo(f" • {utils.Color.bold('prepare-release <level>')} - Prepare next release stage file (patch/minor/major)")
|
|
111
|
+
click.echo(f" • {utils.Color.bold('promote-to <target>')} - Promote stage to rc or prod")
|
|
112
|
+
|
|
113
|
+
click.echo(f"\nTry {utils.Color.bold('half_orm dev <command> --help')} for more information.\n")
|
|
91
114
|
else:
|
|
92
115
|
click.echo(hop.state)
|
|
93
116
|
click.echo("\nNot in a hop repository.")
|
|
@@ -25,6 +25,7 @@ import re
|
|
|
25
25
|
import subprocess
|
|
26
26
|
import sys
|
|
27
27
|
import importlib.util
|
|
28
|
+
from packaging import version
|
|
28
29
|
from pathlib import Path
|
|
29
30
|
from typing import List, Dict, Optional, Tuple
|
|
30
31
|
from half_orm import utils
|
|
@@ -58,38 +59,6 @@ class MigrationManager:
|
|
|
58
59
|
# Path to migrations directory (in half_orm_dev package)
|
|
59
60
|
self._migrations_root = Path(__file__).parent / 'migrations'
|
|
60
61
|
|
|
61
|
-
def _parse_version(self, version_str: str) -> Tuple[int, int, int]:
|
|
62
|
-
"""
|
|
63
|
-
Parse version string to tuple.
|
|
64
|
-
|
|
65
|
-
Supports version formats:
|
|
66
|
-
- "0.17.1"
|
|
67
|
-
- "0.1.0a1" (ignores suffix)
|
|
68
|
-
- "0.17.1-a1" (ignores suffix)
|
|
69
|
-
- "0.17.1-rc2" (ignores suffix)
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
version_str: Version string like "0.17.1" or "0.17.1-a1"
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
Tuple of (major, minor, patch)
|
|
76
|
-
"""
|
|
77
|
-
# Strip any pre-release suffix (e.g., "-a1", "-rc2")
|
|
78
|
-
base_version = version_str.split('-')[0]
|
|
79
|
-
if not re.match(r"^\d+\.\d+\.\d+$", base_version):
|
|
80
|
-
match = re.match(r"^(\d+\.\d+\.\d+)", base_version)
|
|
81
|
-
if match:
|
|
82
|
-
base_version = match.group(1)
|
|
83
|
-
|
|
84
|
-
parts = base_version.split('.')
|
|
85
|
-
if len(parts) != 3:
|
|
86
|
-
raise MigrationManagerError(f"Invalid version format: {version_str}")
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
return (int(parts[0]), int(parts[1]), int(parts[2]))
|
|
90
|
-
except ValueError as e:
|
|
91
|
-
raise MigrationManagerError(f"Invalid version format: {version_str}") from e
|
|
92
|
-
|
|
93
62
|
def _version_to_path(self, version: Tuple[int, int, int]) -> Path:
|
|
94
63
|
"""
|
|
95
64
|
Convert version tuple to migration directory path.
|
|
@@ -117,8 +86,8 @@ class MigrationManager:
|
|
|
117
86
|
Returns:
|
|
118
87
|
List of (version_str, migration_dir_path) tuples in order
|
|
119
88
|
"""
|
|
120
|
-
current =
|
|
121
|
-
target =
|
|
89
|
+
current = version.parse(current_version).release
|
|
90
|
+
target = version.parse(target_version).release
|
|
122
91
|
|
|
123
92
|
pending = []
|
|
124
93
|
|
|
@@ -298,8 +267,19 @@ class MigrationManager:
|
|
|
298
267
|
) else "0.0.0"
|
|
299
268
|
|
|
300
269
|
# If already at target version, nothing to do
|
|
301
|
-
|
|
302
|
-
|
|
270
|
+
try:
|
|
271
|
+
comparison = self._repo.compare_versions(current_version, target_version)
|
|
272
|
+
|
|
273
|
+
if comparison >= 0:
|
|
274
|
+
# Already at or past target version (0 = equal, 1 = higher)
|
|
275
|
+
return result
|
|
276
|
+
except Exception as e:
|
|
277
|
+
# If version comparison fails (invalid format), log and continue
|
|
278
|
+
# This allows migration to proceed even if version format is unexpected
|
|
279
|
+
result['errors'].append(
|
|
280
|
+
f"Could not compare versions {current_version} and {target_version}: {e}. "
|
|
281
|
+
f"Continuing with migration attempt."
|
|
282
|
+
)
|
|
303
283
|
|
|
304
284
|
# Get pending migrations
|
|
305
285
|
pending = self.get_pending_migrations(current_version, target_version)
|
|
@@ -392,21 +372,39 @@ class MigrationManager:
|
|
|
392
372
|
Check if migration is needed.
|
|
393
373
|
|
|
394
374
|
Compares current tool version with hop_version in .hop/config.
|
|
375
|
+
Properly handles pre-release versions (alpha, beta, rc).
|
|
395
376
|
|
|
396
377
|
Args:
|
|
397
|
-
current_tool_version: Current half_orm_dev version
|
|
378
|
+
current_tool_version: Current half_orm_dev version (e.g., "0.17.2-a5")
|
|
398
379
|
|
|
399
380
|
Returns:
|
|
400
|
-
True if migration is needed
|
|
381
|
+
True if migration/update is needed
|
|
401
382
|
"""
|
|
402
383
|
if not hasattr(self._repo, '_Repo__config'):
|
|
403
384
|
return False
|
|
404
385
|
|
|
405
386
|
config_version = self._repo._Repo__config.hop_version
|
|
406
387
|
|
|
407
|
-
#
|
|
408
|
-
|
|
409
|
-
|
|
388
|
+
# If no hop_version is configured, no migration needed
|
|
389
|
+
if not config_version:
|
|
390
|
+
return False
|
|
410
391
|
|
|
411
|
-
|
|
412
|
-
|
|
392
|
+
try:
|
|
393
|
+
# Use Repo's centralized comparison method
|
|
394
|
+
# Returns: 1 if current > config, 0 if equal, -1 if current < config
|
|
395
|
+
comparison = self._repo.compare_versions(current_tool_version, config_version)
|
|
396
|
+
|
|
397
|
+
# Migration needed if current version is higher
|
|
398
|
+
# This now properly compares: 0.17.2a5 > 0.17.2a3 → returns 1 ✓
|
|
399
|
+
return comparison > 0
|
|
400
|
+
|
|
401
|
+
except Exception as e:
|
|
402
|
+
# If version parsing fails, log warning and don't block
|
|
403
|
+
import warnings
|
|
404
|
+
warnings.warn(
|
|
405
|
+
f"Could not parse versions for migration check: "
|
|
406
|
+
f"current={current_tool_version}, config={config_version}. "
|
|
407
|
+
f"Error: {e}",
|
|
408
|
+
UserWarning
|
|
409
|
+
)
|
|
410
|
+
return False
|
|
@@ -250,15 +250,16 @@ class Repo:
|
|
|
250
250
|
This method is called when no hop config file is provided.
|
|
251
251
|
Returns True if we are in a repo, False otherwise.
|
|
252
252
|
"""
|
|
253
|
-
base_dir =
|
|
253
|
+
base_dir = self._find_base_dir()
|
|
254
254
|
while base_dir:
|
|
255
255
|
if self.__set_base_dir(base_dir):
|
|
256
256
|
self.database = Database(self)
|
|
257
257
|
if self.devel:
|
|
258
258
|
self.hgit = HGit(self)
|
|
259
259
|
self.__checked = True
|
|
260
|
-
#
|
|
261
|
-
|
|
260
|
+
# NOTE: Migration is no longer automatic - user must run `half_orm dev migrate`
|
|
261
|
+
# This prevents implicit changes and gives user control over when migration happens
|
|
262
|
+
return
|
|
262
263
|
par_dir = os.path.split(base_dir)[0]
|
|
263
264
|
if par_dir == base_dir:
|
|
264
265
|
break
|
|
@@ -270,64 +271,216 @@ class Repo:
|
|
|
270
271
|
self.__base_dir = base_dir
|
|
271
272
|
self.__config = Config(base_dir)
|
|
272
273
|
self.__local_config = LocalConfig(base_dir)
|
|
274
|
+
self._validate_version()
|
|
273
275
|
return True
|
|
274
276
|
return False
|
|
275
277
|
|
|
276
|
-
def
|
|
278
|
+
def _validate_version(self):
|
|
279
|
+
"""
|
|
280
|
+
Validate that installed half_orm_dev version meets repository requirement.
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
RepoError: If installed version is lower than required hop_version
|
|
284
|
+
"""
|
|
285
|
+
required_version = self.__config.hop_version
|
|
286
|
+
if not required_version:
|
|
287
|
+
# No version requirement in .hop/config, skip validation
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
installed_version = hop_version()
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
# Use centralized comparison method
|
|
294
|
+
if self.compare_versions(installed_version, required_version) < 0:
|
|
295
|
+
raise RepoError(
|
|
296
|
+
f"Repository requires half_orm_dev >= {required_version} "
|
|
297
|
+
f"but {installed_version} is installed.\n"
|
|
298
|
+
f"Please upgrade: pip install --upgrade half_orm_dev"
|
|
299
|
+
)
|
|
300
|
+
except RepoError as e:
|
|
301
|
+
# If it's a version validation error (not comparison error), re-raise it
|
|
302
|
+
if "requires half_orm_dev" in str(e):
|
|
303
|
+
raise
|
|
304
|
+
# If version parsing fails, log warning but don't block
|
|
305
|
+
warnings.warn(
|
|
306
|
+
f"Could not parse version: installed={installed_version}, "
|
|
307
|
+
f"required={required_version}. Error: {e}",
|
|
308
|
+
UserWarning
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def compare_versions(self, version1: str, version2: str) -> int:
|
|
312
|
+
"""
|
|
313
|
+
Compare two version strings using packaging.version.
|
|
314
|
+
|
|
315
|
+
Properly handles pre-release versions (alpha, beta, rc) according to PEP 440.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
version1: First version string (e.g., "0.17.2-a5")
|
|
319
|
+
version2: Second version string (e.g., "0.17.2-a3")
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
-1 if version1 < version2
|
|
323
|
+
0 if version1 == version2
|
|
324
|
+
1 if version1 > version2
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
RepoError: If either version string is invalid
|
|
328
|
+
|
|
329
|
+
Examples:
|
|
330
|
+
>>> repo.compare_versions("0.17.2-a5", "0.17.2-a3")
|
|
331
|
+
1 # 0.17.2a5 > 0.17.2a3
|
|
332
|
+
>>> repo.compare_versions("0.17.2", "0.17.2-a5")
|
|
333
|
+
1 # 0.17.2 > 0.17.2a5 (release > pre-release)
|
|
334
|
+
>>> repo.compare_versions("0.17.1", "0.17.2")
|
|
335
|
+
-1 # 0.17.1 < 0.17.2
|
|
336
|
+
>>> repo.compare_versions("0.17.2", "0.17.2")
|
|
337
|
+
0 # Equal
|
|
338
|
+
"""
|
|
339
|
+
try:
|
|
340
|
+
v1 = version.parse(version1)
|
|
341
|
+
v2 = version.parse(version2)
|
|
342
|
+
|
|
343
|
+
if v1 < v2:
|
|
344
|
+
return -1
|
|
345
|
+
elif v1 > v2:
|
|
346
|
+
return 1
|
|
347
|
+
else:
|
|
348
|
+
return 0
|
|
349
|
+
|
|
350
|
+
except version.InvalidVersion as e:
|
|
351
|
+
raise RepoError(
|
|
352
|
+
f"Invalid version format: {e}"
|
|
353
|
+
) from e
|
|
354
|
+
|
|
355
|
+
def needs_migration(self) -> bool:
|
|
356
|
+
"""
|
|
357
|
+
Check if repository needs migration.
|
|
358
|
+
|
|
359
|
+
Compares installed half_orm_dev version with repository's hop_version.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
True if installed version > repository version (migration needed)
|
|
363
|
+
False otherwise
|
|
364
|
+
|
|
365
|
+
Examples:
|
|
366
|
+
>>> repo.needs_migration()
|
|
367
|
+
True # Installed 0.18.0, repo at 0.17.2
|
|
368
|
+
"""
|
|
369
|
+
if not hasattr(self, '_Repo__config') or not self.__config:
|
|
370
|
+
return False
|
|
371
|
+
|
|
372
|
+
installed_version = hop_version()
|
|
373
|
+
config_version = self.__config.hop_version
|
|
374
|
+
|
|
375
|
+
if not config_version:
|
|
376
|
+
return False
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
return self.compare_versions(installed_version, config_version) > 0
|
|
380
|
+
except RepoError:
|
|
381
|
+
# If version comparison fails, assume no migration needed
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
def run_migrations_if_needed(self, silent: bool = False) -> dict:
|
|
277
385
|
"""
|
|
278
386
|
Run pending migrations using MigrationManager.
|
|
279
387
|
|
|
280
|
-
|
|
281
|
-
|
|
388
|
+
Detects and runs migrations based on current half_orm_dev version
|
|
389
|
+
vs hop_version in .hop/config.
|
|
282
390
|
|
|
283
391
|
Behavior:
|
|
284
|
-
- On ho-prod: Runs migration with lock, creates commit,
|
|
285
|
-
- On other branches:
|
|
392
|
+
- On ho-prod: Runs migration with lock, creates commit, syncs to active branches
|
|
393
|
+
- On other branches: Raises RepoError directing user to checkout ho-prod
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
silent: If True, suppress informational messages (only show errors)
|
|
286
397
|
|
|
287
|
-
|
|
398
|
+
Returns:
|
|
399
|
+
dict with keys:
|
|
400
|
+
- migration_needed: bool - True if migration was needed
|
|
401
|
+
- migration_run: bool - True if migration was executed
|
|
402
|
+
- target_version: str - Target version migrated to
|
|
403
|
+
- errors: list - Any errors encountered
|
|
404
|
+
|
|
405
|
+
Raises:
|
|
406
|
+
RepoError: If not on ho-prod branch and migration is needed
|
|
407
|
+
|
|
408
|
+
Examples:
|
|
409
|
+
# Run migration (raises if not on ho-prod)
|
|
410
|
+
result = repo.run_migrations_if_needed()
|
|
411
|
+
|
|
412
|
+
# Check result
|
|
413
|
+
if result['migration_run']:
|
|
414
|
+
print(f"Migrated to {result['target_version']}")
|
|
288
415
|
"""
|
|
416
|
+
result = {
|
|
417
|
+
'migration_needed': False,
|
|
418
|
+
'migration_run': False,
|
|
419
|
+
'target_version': None,
|
|
420
|
+
'errors': []
|
|
421
|
+
}
|
|
422
|
+
|
|
289
423
|
try:
|
|
290
424
|
# Create migration manager
|
|
291
425
|
migration_mgr = MigrationManager(self)
|
|
292
426
|
|
|
293
427
|
# Get current half_orm_dev version
|
|
294
428
|
current_version = hop_version()
|
|
429
|
+
result['target_version'] = current_version
|
|
295
430
|
|
|
296
431
|
# Check if migration is needed
|
|
297
432
|
if not migration_mgr.check_migration_needed(current_version):
|
|
298
|
-
return
|
|
433
|
+
return result
|
|
434
|
+
|
|
435
|
+
result['migration_needed'] = True
|
|
299
436
|
|
|
300
437
|
# Only run migrations on ho-prod branch
|
|
301
438
|
if not self.hgit or self.hgit.branch != 'ho-prod':
|
|
302
|
-
#
|
|
439
|
+
# Raise error directing user to checkout ho-prod
|
|
303
440
|
current_branch = self.hgit.branch if self.hgit else 'unknown'
|
|
304
441
|
config_version = self.__config.hop_version if hasattr(self, '_Repo__config') else '0.0.0'
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
442
|
+
raise RepoError(
|
|
443
|
+
f"Repository migration required\n\n"
|
|
444
|
+
f" Repository version: {config_version}\n"
|
|
445
|
+
f" Installed version: {current_version}\n"
|
|
446
|
+
f" Current branch: {current_branch}\n\n"
|
|
447
|
+
f" Please checkout to ho-prod branch and run:\n"
|
|
448
|
+
f" git checkout ho-prod\n"
|
|
449
|
+
f" half_orm dev migrate\n"
|
|
450
|
+
)
|
|
312
451
|
|
|
313
452
|
# Run migrations on ho-prod
|
|
314
453
|
# Branch sync is handled automatically by the decorator
|
|
315
|
-
|
|
454
|
+
migration_result = migration_mgr.run_migrations(
|
|
316
455
|
target_version=current_version,
|
|
317
456
|
create_commit=True
|
|
318
457
|
)
|
|
319
458
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
459
|
+
result['migration_run'] = True
|
|
460
|
+
result['errors'] = migration_result.get('errors', [])
|
|
461
|
+
|
|
462
|
+
# Log success if not silent
|
|
463
|
+
if not silent:
|
|
464
|
+
if migration_result.get('migrations_applied'):
|
|
465
|
+
print(f"✓ Applied {len(migration_result['migrations_applied'])} migration(s)")
|
|
466
|
+
else:
|
|
467
|
+
print(f"✓ Updated repository version to {current_version}")
|
|
324
468
|
|
|
469
|
+
except RepoError:
|
|
470
|
+
# Re-raise RepoError (for branch check)
|
|
471
|
+
raise
|
|
325
472
|
except MigrationManagerError as e:
|
|
326
|
-
# Log migration errors
|
|
327
|
-
|
|
473
|
+
# Log migration errors
|
|
474
|
+
error_msg = f"Migration failed: {e}"
|
|
475
|
+
result['errors'].append(error_msg)
|
|
476
|
+
raise RepoError(error_msg) from e
|
|
328
477
|
except Exception as e:
|
|
329
478
|
# Catch any unexpected errors
|
|
330
|
-
|
|
479
|
+
error_msg = f"Unexpected migration error: {e}"
|
|
480
|
+
result['errors'].append(error_msg)
|
|
481
|
+
raise RepoError(error_msg) from e
|
|
482
|
+
|
|
483
|
+
return result
|
|
331
484
|
|
|
332
485
|
def sync_hop_to_active_branches(self, reason: str = "update") -> dict:
|
|
333
486
|
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.17.2-a6
|
|
@@ -29,6 +29,7 @@ half_orm_dev/cli/commands/apply.py
|
|
|
29
29
|
half_orm_dev/cli/commands/check.py
|
|
30
30
|
half_orm_dev/cli/commands/clone.py
|
|
31
31
|
half_orm_dev/cli/commands/init.py
|
|
32
|
+
half_orm_dev/cli/commands/migrate.py
|
|
32
33
|
half_orm_dev/cli/commands/new.py
|
|
33
34
|
half_orm_dev/cli/commands/patch.py
|
|
34
35
|
half_orm_dev/cli/commands/release.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.17.2-a4
|
|
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-0.17.2a4 → half_orm_dev-0.17.2a6}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/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
|
{half_orm_dev-0.17.2a4 → half_orm_dev-0.17.2a6}/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
|