half-orm-dev 0.17.2a5__tar.gz → 0.17.2a7__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.2a5/half_orm_dev.egg-info → half_orm_dev-0.17.2a7}/PKG-INFO +1 -1
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/__init__.py +3 -0
- half_orm_dev-0.17.2a7/half_orm_dev/cli/commands/migrate.py +125 -0
- half_orm_dev-0.17.2a7/half_orm_dev/cli/main.py +190 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/migration_manager.py +41 -43
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/repo.py +172 -32
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/git-hooks/pre-commit +37 -0
- half_orm_dev-0.17.2a7/half_orm_dev/version.txt +1 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7/half_orm_dev.egg-info}/PKG-INFO +1 -1
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev.egg-info/SOURCES.txt +1 -0
- half_orm_dev-0.17.2a5/half_orm_dev/cli/main.py +0 -103
- half_orm_dev-0.17.2a5/half_orm_dev/version.txt +0 -1
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/AUTHORS +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/LICENSE +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/README.md +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/new.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/database.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/modules.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/patch_manager.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/release_manager.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/Pipfile +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/module_template_1 +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev.egg-info/requires.txt +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/pyproject.toml +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/setup.cfg +0 -0
- {half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/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()
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI module - Creates and configures the CLI group
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import functools
|
|
7
|
+
from half_orm_dev.repo import Repo, OutdatedHalfORMDevError
|
|
8
|
+
from half_orm import utils
|
|
9
|
+
from .commands import ALL_COMMANDS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Hop:
|
|
13
|
+
"""Sets the options available to the hop command"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.__repo: Repo = None
|
|
17
|
+
self.__hop_upgrade_error: OutdatedHalfORMDevError = None
|
|
18
|
+
|
|
19
|
+
# Try to initialize Repo, catch version errors
|
|
20
|
+
try:
|
|
21
|
+
self.__repo = Repo() # Utilise le singleton
|
|
22
|
+
except OutdatedHalfORMDevError as e:
|
|
23
|
+
# Capture the error but don't raise it yet
|
|
24
|
+
self.__hop_upgrade_error = e
|
|
25
|
+
|
|
26
|
+
self.__available_cmds = self._determine_available_commands()
|
|
27
|
+
|
|
28
|
+
def _determine_available_commands(self):
|
|
29
|
+
"""
|
|
30
|
+
Determine which commands are available based on context.
|
|
31
|
+
|
|
32
|
+
Returns different command sets based on:
|
|
33
|
+
- Repository status (checked/unchecked)
|
|
34
|
+
- Development mode (devel flag - metadata presence)
|
|
35
|
+
- Environment (production flag)
|
|
36
|
+
|
|
37
|
+
Note: When needs_hop_upgrade is true, commands will still be added
|
|
38
|
+
but will be blocked by the decorator at execution time.
|
|
39
|
+
"""
|
|
40
|
+
if self.needs_hop_upgrade:
|
|
41
|
+
# Version downgrade detected - return a minimal set of commands
|
|
42
|
+
# Commands will be blocked by decorator, but we need them in the list
|
|
43
|
+
# so Click doesn't show "No such command" error
|
|
44
|
+
return ['check', 'migrate']
|
|
45
|
+
|
|
46
|
+
if not self.repo_checked:
|
|
47
|
+
# Outside hop repository - commands for project initialization
|
|
48
|
+
return ['init', 'clone']
|
|
49
|
+
|
|
50
|
+
if self.__repo.needs_migration():
|
|
51
|
+
return ['migrate']
|
|
52
|
+
|
|
53
|
+
# Inside hop repository
|
|
54
|
+
if not self.__repo.devel:
|
|
55
|
+
# Sync-only mode (no metadata)
|
|
56
|
+
return ['sync-package', 'check']
|
|
57
|
+
|
|
58
|
+
# Development mode (metadata present)
|
|
59
|
+
if self.__repo.database.production:
|
|
60
|
+
# PRODUCTION ENVIRONMENT - Release deployment only
|
|
61
|
+
return ['update', 'upgrade', 'check']
|
|
62
|
+
else:
|
|
63
|
+
# DEVELOPMENT ENVIRONMENT - Patch development
|
|
64
|
+
return ['patch', 'release', 'check']
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def repo_checked(self):
|
|
68
|
+
"""Returns whether we are in a repo or not."""
|
|
69
|
+
return self.__repo and self.__repo.checked
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def needs_hop_upgrade(self):
|
|
73
|
+
"""Returns whether half_orm_dev needs to be upgraded."""
|
|
74
|
+
return self.__hop_upgrade_error is not None
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def hop_upgrade_error(self):
|
|
78
|
+
"""Returns the upgrade error if any."""
|
|
79
|
+
return self.__hop_upgrade_error
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def state(self):
|
|
83
|
+
"""Returns the state of the repo."""
|
|
84
|
+
return self.__repo.state if self.__repo else "Not in a repository"
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def available_commands(self):
|
|
88
|
+
"""Returns the list of available commands."""
|
|
89
|
+
return self.__available_cmds
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def create_cli_group():
|
|
93
|
+
"""
|
|
94
|
+
Creates and returns the CLI group with appropriate commands.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
click.Group: Configured CLI group
|
|
98
|
+
"""
|
|
99
|
+
hop = Hop()
|
|
100
|
+
|
|
101
|
+
def check_version_before_invoke(f, allow_on_downgrade=False):
|
|
102
|
+
"""Decorator to check version before invoking any command.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
f: Function to wrap
|
|
106
|
+
allow_on_downgrade: If True, allow this command even when version is outdated
|
|
107
|
+
"""
|
|
108
|
+
@functools.wraps(f)
|
|
109
|
+
def wrapper(*args, **kwargs):
|
|
110
|
+
if hop.needs_hop_upgrade and not allow_on_downgrade:
|
|
111
|
+
# Display formatted error message for version downgrade
|
|
112
|
+
error = hop.hop_upgrade_error
|
|
113
|
+
click.echo("=" * 70, err=True)
|
|
114
|
+
click.echo(f"{utils.Color.red('❌ OUTDATED half_orm_dev VERSION')}", err=True)
|
|
115
|
+
click.echo("=" * 70, err=True)
|
|
116
|
+
click.echo(f"\n Repository requires: {utils.Color.bold(error.required_version)}", err=True)
|
|
117
|
+
click.echo(f" Installed version: {utils.Color.bold(error.installed_version)}", err=True)
|
|
118
|
+
click.echo(f"\n Your installed version is OLDER than the repository requirement.", err=True)
|
|
119
|
+
click.echo(f" All commands are blocked for safety.", err=True)
|
|
120
|
+
click.echo(f"\n Please upgrade half_orm_dev:", err=True)
|
|
121
|
+
click.echo(f" {utils.Color.bold('pip install --upgrade half_orm_dev')}", err=True)
|
|
122
|
+
click.echo("\n" + "=" * 70 + "\n", err=True)
|
|
123
|
+
raise click.Abort()
|
|
124
|
+
return f(*args, **kwargs)
|
|
125
|
+
return wrapper
|
|
126
|
+
|
|
127
|
+
# Create custom Group that auto-decorates commands
|
|
128
|
+
class VersionCheckGroup(click.Group):
|
|
129
|
+
def add_command(self, cmd, name=None):
|
|
130
|
+
"""Override to decorate all commands with version check."""
|
|
131
|
+
if isinstance(cmd, click.Command) and cmd.callback:
|
|
132
|
+
cmd.callback = check_version_before_invoke(cmd.callback)
|
|
133
|
+
super().add_command(cmd, name)
|
|
134
|
+
|
|
135
|
+
@click.group(cls=VersionCheckGroup, invoke_without_command=True)
|
|
136
|
+
@click.pass_context
|
|
137
|
+
@check_version_before_invoke
|
|
138
|
+
def dev(ctx):
|
|
139
|
+
"""halfORM development tools - Git-centric patch management and database synchronization"""
|
|
140
|
+
if ctx.invoked_subcommand is None:
|
|
141
|
+
# Show repo state when no subcommand is provided
|
|
142
|
+
if hop.repo_checked:
|
|
143
|
+
# Check if migration is needed
|
|
144
|
+
if hop._Hop__repo.needs_migration():
|
|
145
|
+
# Display migration warning
|
|
146
|
+
from half_orm_dev.utils import hop_version
|
|
147
|
+
installed_version = hop_version()
|
|
148
|
+
config_version = hop._Hop__repo._Repo__config.hop_version
|
|
149
|
+
current_branch = hop._Hop__repo.hgit.branch if hop._Hop__repo.hgit else 'unknown'
|
|
150
|
+
|
|
151
|
+
click.echo(f"\n{'='*70}")
|
|
152
|
+
click.echo(f"⚠️ {utils.Color.bold(utils.Color.red('REPOSITORY MIGRATION REQUIRED'))} ⚠️")
|
|
153
|
+
click.echo(f"{'='*70}")
|
|
154
|
+
click.echo(f"\n Repository version: {utils.Color.red(config_version)}")
|
|
155
|
+
click.echo(f" Installed version: {utils.Color.green(installed_version)}")
|
|
156
|
+
click.echo(f" Current branch: {current_branch}")
|
|
157
|
+
click.echo(f"\n {utils.Color.bold('All commands are blocked until migration is complete.')}")
|
|
158
|
+
click.echo(f"\n To apply migration, run:")
|
|
159
|
+
click.echo(f" {utils.Color.bold('half_orm dev migrate')}")
|
|
160
|
+
click.echo(f"\n{'='*70}\n")
|
|
161
|
+
else:
|
|
162
|
+
# Normal display
|
|
163
|
+
click.echo(hop.state)
|
|
164
|
+
click.echo(f"\n{utils.Color.bold('Available commands:')}")
|
|
165
|
+
|
|
166
|
+
# Adapt displayed commands based on environment
|
|
167
|
+
if hop._Hop__repo.database.production:
|
|
168
|
+
# Production commands
|
|
169
|
+
click.echo(f" • {utils.Color.bold('update')} - Fetch and list available releases")
|
|
170
|
+
click.echo(f" • {utils.Color.bold('upgrade [--to-release=X.Y.Z]')} - Apply releases to production")
|
|
171
|
+
else:
|
|
172
|
+
# Development commands
|
|
173
|
+
click.echo(f" • {utils.Color.bold('patch')}")
|
|
174
|
+
click.echo(f" • {utils.Color.bold('prepare-release <level>')} - Prepare next release stage file (patch/minor/major)")
|
|
175
|
+
click.echo(f" • {utils.Color.bold('promote-to <target>')} - Promote stage to rc or prod")
|
|
176
|
+
|
|
177
|
+
click.echo(f"\nTry {utils.Color.bold('half_orm dev <command> --help')} for more information.\n")
|
|
178
|
+
else:
|
|
179
|
+
click.echo(hop.state)
|
|
180
|
+
click.echo("\nNot in a hop repository.")
|
|
181
|
+
click.echo(f"\n{utils.Color.bold('Available commands:')}")
|
|
182
|
+
click.echo(f"\n • {utils.Color.bold('init <package_name>')} - Create new halfORM project.")
|
|
183
|
+
click.echo(f"\n • {utils.Color.bold('clone <git origin>')} - Clone an existing halfORM project.\n")
|
|
184
|
+
|
|
185
|
+
# Add only available commands to the group
|
|
186
|
+
for cmd_name in hop.available_commands:
|
|
187
|
+
if cmd_name in ALL_COMMANDS:
|
|
188
|
+
dev.add_command(ALL_COMMANDS[cmd_name])
|
|
189
|
+
|
|
190
|
+
return dev
|
|
@@ -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
|
|
@@ -37,6 +37,17 @@ from .utils import TEMPLATE_DIRS, hop_version, resolve_database_config_name
|
|
|
37
37
|
class RepoError(Exception):
|
|
38
38
|
pass
|
|
39
39
|
|
|
40
|
+
class OutdatedHalfORMDevError(RepoError):
|
|
41
|
+
"""Raised when installed half_orm_dev version is older than repository requirement."""
|
|
42
|
+
def __init__(self, required_version: str, installed_version: str):
|
|
43
|
+
self.required_version = required_version
|
|
44
|
+
self.installed_version = installed_version
|
|
45
|
+
super().__init__(
|
|
46
|
+
f"Repository requires half_orm_dev >= {required_version} "
|
|
47
|
+
f"but {installed_version} is installed.\n"
|
|
48
|
+
f"Please upgrade: pip install --upgrade half_orm_dev"
|
|
49
|
+
)
|
|
50
|
+
|
|
40
51
|
class Config:
|
|
41
52
|
"""
|
|
42
53
|
"""
|
|
@@ -257,8 +268,12 @@ class Repo:
|
|
|
257
268
|
if self.devel:
|
|
258
269
|
self.hgit = HGit(self)
|
|
259
270
|
self.__checked = True
|
|
260
|
-
#
|
|
261
|
-
|
|
271
|
+
# NOTE: Migration is no longer automatic - user must run `half_orm dev migrate`
|
|
272
|
+
# This prevents implicit changes and gives user control over when migration happens
|
|
273
|
+
|
|
274
|
+
# Automatically check and update hooks/config (silent, uses cache)
|
|
275
|
+
# This ensures Git hooks are always up-to-date for all commands
|
|
276
|
+
self.check_and_update(silent=True)
|
|
262
277
|
return
|
|
263
278
|
par_dir = os.path.split(base_dir)[0]
|
|
264
279
|
if par_dir == base_dir:
|
|
@@ -290,13 +305,13 @@ class Repo:
|
|
|
290
305
|
installed_version = hop_version()
|
|
291
306
|
|
|
292
307
|
try:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
except
|
|
308
|
+
# Use centralized comparison method
|
|
309
|
+
if self.compare_versions(installed_version, required_version) < 0:
|
|
310
|
+
raise OutdatedHalfORMDevError(required_version, installed_version)
|
|
311
|
+
except OutdatedHalfORMDevError:
|
|
312
|
+
# Re-raise downgrade errors immediately
|
|
313
|
+
raise
|
|
314
|
+
except RepoError as e:
|
|
300
315
|
# If version parsing fails, log warning but don't block
|
|
301
316
|
warnings.warn(
|
|
302
317
|
f"Could not parse version: installed={installed_version}, "
|
|
@@ -304,61 +319,179 @@ class Repo:
|
|
|
304
319
|
UserWarning
|
|
305
320
|
)
|
|
306
321
|
|
|
307
|
-
def
|
|
322
|
+
def compare_versions(self, version1: str, version2: str) -> int:
|
|
323
|
+
"""
|
|
324
|
+
Compare two version strings using packaging.version.
|
|
325
|
+
|
|
326
|
+
Properly handles pre-release versions (alpha, beta, rc) according to PEP 440.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
version1: First version string (e.g., "0.17.2-a5")
|
|
330
|
+
version2: Second version string (e.g., "0.17.2-a3")
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
-1 if version1 < version2
|
|
334
|
+
0 if version1 == version2
|
|
335
|
+
1 if version1 > version2
|
|
336
|
+
|
|
337
|
+
Raises:
|
|
338
|
+
RepoError: If either version string is invalid
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
>>> repo.compare_versions("0.17.2-a5", "0.17.2-a3")
|
|
342
|
+
1 # 0.17.2a5 > 0.17.2a3
|
|
343
|
+
>>> repo.compare_versions("0.17.2", "0.17.2-a5")
|
|
344
|
+
1 # 0.17.2 > 0.17.2a5 (release > pre-release)
|
|
345
|
+
>>> repo.compare_versions("0.17.1", "0.17.2")
|
|
346
|
+
-1 # 0.17.1 < 0.17.2
|
|
347
|
+
>>> repo.compare_versions("0.17.2", "0.17.2")
|
|
348
|
+
0 # Equal
|
|
349
|
+
"""
|
|
350
|
+
try:
|
|
351
|
+
v1 = version.parse(version1)
|
|
352
|
+
v2 = version.parse(version2)
|
|
353
|
+
|
|
354
|
+
if v1 < v2:
|
|
355
|
+
return -1
|
|
356
|
+
elif v1 > v2:
|
|
357
|
+
return 1
|
|
358
|
+
else:
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
except version.InvalidVersion as e:
|
|
362
|
+
raise RepoError(
|
|
363
|
+
f"Invalid version format: {e}"
|
|
364
|
+
) from e
|
|
365
|
+
|
|
366
|
+
def needs_migration(self) -> bool:
|
|
367
|
+
"""
|
|
368
|
+
Check if repository needs migration.
|
|
369
|
+
|
|
370
|
+
Compares installed half_orm_dev version with repository's hop_version.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
True if installed version > repository version (migration needed)
|
|
374
|
+
False otherwise
|
|
375
|
+
|
|
376
|
+
Examples:
|
|
377
|
+
>>> repo.needs_migration()
|
|
378
|
+
True # Installed 0.18.0, repo at 0.17.2
|
|
379
|
+
"""
|
|
380
|
+
if not hasattr(self, '_Repo__config') or not self.__config:
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
installed_version = hop_version()
|
|
384
|
+
config_version = self.__config.hop_version
|
|
385
|
+
|
|
386
|
+
if not config_version:
|
|
387
|
+
return False
|
|
388
|
+
|
|
389
|
+
try:
|
|
390
|
+
return self.compare_versions(installed_version, config_version) > 0
|
|
391
|
+
except RepoError:
|
|
392
|
+
# If version comparison fails, assume no migration needed
|
|
393
|
+
return False
|
|
394
|
+
|
|
395
|
+
def run_migrations_if_needed(self, silent: bool = False) -> dict:
|
|
308
396
|
"""
|
|
309
397
|
Run pending migrations using MigrationManager.
|
|
310
398
|
|
|
311
|
-
|
|
312
|
-
|
|
399
|
+
Detects and runs migrations based on current half_orm_dev version
|
|
400
|
+
vs hop_version in .hop/config.
|
|
313
401
|
|
|
314
402
|
Behavior:
|
|
315
|
-
- On ho-prod: Runs migration with lock, creates commit,
|
|
316
|
-
- On other branches:
|
|
403
|
+
- On ho-prod: Runs migration with lock, creates commit, syncs to active branches
|
|
404
|
+
- On other branches: Raises RepoError directing user to checkout ho-prod
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
silent: If True, suppress informational messages (only show errors)
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
dict with keys:
|
|
411
|
+
- migration_needed: bool - True if migration was needed
|
|
412
|
+
- migration_run: bool - True if migration was executed
|
|
413
|
+
- target_version: str - Target version migrated to
|
|
414
|
+
- errors: list - Any errors encountered
|
|
415
|
+
|
|
416
|
+
Raises:
|
|
417
|
+
RepoError: If not on ho-prod branch and migration is needed
|
|
418
|
+
|
|
419
|
+
Examples:
|
|
420
|
+
# Run migration (raises if not on ho-prod)
|
|
421
|
+
result = repo.run_migrations_if_needed()
|
|
317
422
|
|
|
318
|
-
|
|
423
|
+
# Check result
|
|
424
|
+
if result['migration_run']:
|
|
425
|
+
print(f"Migrated to {result['target_version']}")
|
|
319
426
|
"""
|
|
427
|
+
result = {
|
|
428
|
+
'migration_needed': False,
|
|
429
|
+
'migration_run': False,
|
|
430
|
+
'target_version': None,
|
|
431
|
+
'errors': []
|
|
432
|
+
}
|
|
433
|
+
|
|
320
434
|
try:
|
|
321
435
|
# Create migration manager
|
|
322
436
|
migration_mgr = MigrationManager(self)
|
|
323
437
|
|
|
324
438
|
# Get current half_orm_dev version
|
|
325
439
|
current_version = hop_version()
|
|
440
|
+
result['target_version'] = current_version
|
|
326
441
|
|
|
327
442
|
# Check if migration is needed
|
|
328
443
|
if not migration_mgr.check_migration_needed(current_version):
|
|
329
|
-
return
|
|
444
|
+
return result
|
|
445
|
+
|
|
446
|
+
result['migration_needed'] = True
|
|
330
447
|
|
|
331
448
|
# Only run migrations on ho-prod branch
|
|
332
449
|
if not self.hgit or self.hgit.branch != 'ho-prod':
|
|
333
|
-
#
|
|
450
|
+
# Raise error directing user to checkout ho-prod
|
|
334
451
|
current_branch = self.hgit.branch if self.hgit else 'unknown'
|
|
335
452
|
config_version = self.__config.hop_version if hasattr(self, '_Repo__config') else '0.0.0'
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
453
|
+
raise RepoError(
|
|
454
|
+
f"Repository migration required\n\n"
|
|
455
|
+
f" Repository version: {config_version}\n"
|
|
456
|
+
f" Installed version: {current_version}\n"
|
|
457
|
+
f" Current branch: {current_branch}\n\n"
|
|
458
|
+
f" Please checkout to ho-prod branch and run:\n"
|
|
459
|
+
f" git checkout ho-prod\n"
|
|
460
|
+
f" half_orm dev migrate\n"
|
|
461
|
+
)
|
|
343
462
|
|
|
344
463
|
# Run migrations on ho-prod
|
|
345
464
|
# Branch sync is handled automatically by the decorator
|
|
346
|
-
|
|
465
|
+
migration_result = migration_mgr.run_migrations(
|
|
347
466
|
target_version=current_version,
|
|
348
467
|
create_commit=True
|
|
349
468
|
)
|
|
350
469
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
470
|
+
result['migration_run'] = True
|
|
471
|
+
result['errors'] = migration_result.get('errors', [])
|
|
472
|
+
|
|
473
|
+
# Log success if not silent
|
|
474
|
+
if not silent:
|
|
475
|
+
if migration_result.get('migrations_applied'):
|
|
476
|
+
print(f"✓ Applied {len(migration_result['migrations_applied'])} migration(s)")
|
|
477
|
+
else:
|
|
478
|
+
print(f"✓ Updated repository version to {current_version}")
|
|
355
479
|
|
|
480
|
+
except RepoError:
|
|
481
|
+
# Re-raise RepoError (for branch check)
|
|
482
|
+
raise
|
|
356
483
|
except MigrationManagerError as e:
|
|
357
|
-
# Log migration errors
|
|
358
|
-
|
|
484
|
+
# Log migration errors
|
|
485
|
+
error_msg = f"Migration failed: {e}"
|
|
486
|
+
result['errors'].append(error_msg)
|
|
487
|
+
raise RepoError(error_msg) from e
|
|
359
488
|
except Exception as e:
|
|
360
489
|
# Catch any unexpected errors
|
|
361
|
-
|
|
490
|
+
error_msg = f"Unexpected migration error: {e}"
|
|
491
|
+
result['errors'].append(error_msg)
|
|
492
|
+
raise RepoError(error_msg) from e
|
|
493
|
+
|
|
494
|
+
return result
|
|
362
495
|
|
|
363
496
|
def sync_hop_to_active_branches(self, reason: str = "update") -> dict:
|
|
364
497
|
"""
|
|
@@ -998,6 +1131,9 @@ class Repo:
|
|
|
998
1131
|
hooks_source_dir = os.path.join(TEMPLATE_DIRS, 'git-hooks')
|
|
999
1132
|
hooks_dest_dir = os.path.join(self.__base_dir, '.git', 'hooks')
|
|
1000
1133
|
|
|
1134
|
+
# Create .git/hooks directory if it doesn't exist
|
|
1135
|
+
os.makedirs(hooks_dest_dir, exist_ok=True)
|
|
1136
|
+
|
|
1001
1137
|
any_installed = False
|
|
1002
1138
|
overall_action = 'skipped'
|
|
1003
1139
|
|
|
@@ -2093,6 +2229,7 @@ See docs/half_orm_dev.md for complete documentation.
|
|
|
2093
2229
|
5. Create .hop/alt_config if custom database_name provided
|
|
2094
2230
|
6. Setup database (create + metadata if create_db=True)
|
|
2095
2231
|
7. Restore database from model/schema.sql to production version
|
|
2232
|
+
8. Install Git hooks (pre-commit, prepare-commit-msg)
|
|
2096
2233
|
|
|
2097
2234
|
Examples:
|
|
2098
2235
|
# Interactive with prompts for connection params
|
|
@@ -2216,3 +2353,6 @@ See docs/half_orm_dev.md for complete documentation.
|
|
|
2216
2353
|
raise RepoError(
|
|
2217
2354
|
f"Failed to restore database from schema: {e}"
|
|
2218
2355
|
) from e
|
|
2356
|
+
|
|
2357
|
+
# Step 9: Install Git hooks
|
|
2358
|
+
repo.install_git_hooks()
|
|
@@ -3,11 +3,48 @@
|
|
|
3
3
|
# Half-ORM pre-commit hook
|
|
4
4
|
# 1. Checks if current ho-* branch exists on remote origin
|
|
5
5
|
# 2. Protects ho-prod branch from direct commits
|
|
6
|
+
# 3. Optionally calls pre-commit-custom if it exists
|
|
6
7
|
# Generated by half_orm_dev
|
|
7
8
|
|
|
8
9
|
# Get current branch
|
|
9
10
|
CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
|
|
10
11
|
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# CUSTOM PRE-COMMIT HOOK SUPPORT
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# If a pre-commit-custom script exists in .git/hooks/, execute it first.
|
|
16
|
+
# This allows users to add their own custom checks (linting, formatting, etc.)
|
|
17
|
+
#
|
|
18
|
+
# ⚠️ WARNING: pre-commit-custom is NOT tracked by Git and NOT cloned.
|
|
19
|
+
# It must be added manually to each local repository clone.
|
|
20
|
+
#
|
|
21
|
+
# To create a custom hook:
|
|
22
|
+
# 1. Create .git/hooks/pre-commit-custom (executable)
|
|
23
|
+
# 2. Add your custom checks
|
|
24
|
+
# 3. Exit with 0 (success) or non-zero (failure) to block the commit
|
|
25
|
+
#
|
|
26
|
+
# Example .git/hooks/pre-commit-custom:
|
|
27
|
+
# #!/usr/bin/env bash
|
|
28
|
+
# # Run Python linter
|
|
29
|
+
# python -m pylint $(git diff --cached --name-only --diff-filter=ACM | grep '\.py$')
|
|
30
|
+
# exit $?
|
|
31
|
+
# =============================================================================
|
|
32
|
+
|
|
33
|
+
CUSTOM_HOOK=".git/hooks/pre-commit-custom"
|
|
34
|
+
if [ -x "$CUSTOM_HOOK" ]; then
|
|
35
|
+
"$CUSTOM_HOOK"
|
|
36
|
+
CUSTOM_EXIT_CODE=$?
|
|
37
|
+
if [ $CUSTOM_EXIT_CODE -ne 0 ]; then
|
|
38
|
+
echo ""
|
|
39
|
+
echo "❌ Custom pre-commit hook failed (exit code: $CUSTOM_EXIT_CODE)"
|
|
40
|
+
exit $CUSTOM_EXIT_CODE
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# =============================================================================
|
|
45
|
+
# HALF-ORM STANDARD CHECKS
|
|
46
|
+
# =============================================================================
|
|
47
|
+
|
|
11
48
|
# Check if current ho-* branch (except ho-prod) still exists on remote origin
|
|
12
49
|
# This prevents committing to ho-* branches that were deleted remotely
|
|
13
50
|
if [[ "$CURRENT_BRANCH" == ho-* ]] && [ "$CURRENT_BRANCH" != "ho-prod" ]; then
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.17.2-a7
|
|
@@ -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,103 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Main CLI module - Creates and configures the CLI group
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
from half_orm_dev.repo import Repo
|
|
7
|
-
from half_orm import utils
|
|
8
|
-
from .commands import ALL_COMMANDS
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Hop:
|
|
12
|
-
"""Sets the options available to the hop command"""
|
|
13
|
-
|
|
14
|
-
def __init__(self):
|
|
15
|
-
self.__repo: Repo = Repo() # Utilise le singleton
|
|
16
|
-
self.__available_cmds = self._determine_available_commands()
|
|
17
|
-
|
|
18
|
-
def _determine_available_commands(self):
|
|
19
|
-
"""
|
|
20
|
-
Determine which commands are available based on context.
|
|
21
|
-
|
|
22
|
-
Returns different command sets based on:
|
|
23
|
-
- Repository status (checked/unchecked)
|
|
24
|
-
- Development mode (devel flag - metadata presence)
|
|
25
|
-
- Environment (production flag)
|
|
26
|
-
"""
|
|
27
|
-
if not self.repo_checked:
|
|
28
|
-
# Outside hop repository - commands for project initialization
|
|
29
|
-
return ['init', 'clone']
|
|
30
|
-
|
|
31
|
-
# Inside hop repository
|
|
32
|
-
if not self.__repo.devel:
|
|
33
|
-
# Sync-only mode (no metadata)
|
|
34
|
-
return ['sync-package', 'check']
|
|
35
|
-
|
|
36
|
-
# Development mode (metadata present)
|
|
37
|
-
if self.__repo.database.production:
|
|
38
|
-
# PRODUCTION ENVIRONMENT - Release deployment only
|
|
39
|
-
return ['update', 'upgrade', 'check']
|
|
40
|
-
else:
|
|
41
|
-
# DEVELOPMENT ENVIRONMENT - Patch development
|
|
42
|
-
return ['patch', 'release', 'check']
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def repo_checked(self):
|
|
46
|
-
"""Returns whether we are in a repo or not."""
|
|
47
|
-
return self.__repo.checked
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def state(self):
|
|
51
|
-
"""Returns the state of the repo."""
|
|
52
|
-
return self.__repo.state
|
|
53
|
-
|
|
54
|
-
@property
|
|
55
|
-
def available_commands(self):
|
|
56
|
-
"""Returns the list of available commands."""
|
|
57
|
-
return self.__available_cmds
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def create_cli_group():
|
|
61
|
-
"""
|
|
62
|
-
Creates and returns the CLI group with appropriate commands.
|
|
63
|
-
|
|
64
|
-
Returns:
|
|
65
|
-
click.Group: Configured CLI group
|
|
66
|
-
"""
|
|
67
|
-
hop = Hop()
|
|
68
|
-
|
|
69
|
-
@click.group(invoke_without_command=True)
|
|
70
|
-
@click.pass_context
|
|
71
|
-
def dev(ctx):
|
|
72
|
-
"""halfORM development tools - Git-centric patch management and database synchronization"""
|
|
73
|
-
if ctx.invoked_subcommand is None:
|
|
74
|
-
# Show repo state when no subcommand is provided
|
|
75
|
-
if hop.repo_checked:
|
|
76
|
-
click.echo(hop.state)
|
|
77
|
-
click.echo(f"\n{utils.Color.bold('Available commands:')}")
|
|
78
|
-
|
|
79
|
-
# Adapt displayed commands based on environment
|
|
80
|
-
if hop.__repo.database.production:
|
|
81
|
-
# Production commands
|
|
82
|
-
click.echo(f" • {utils.Color.bold('update')} - Fetch and list available releases")
|
|
83
|
-
click.echo(f" • {utils.Color.bold('upgrade [--to-release=X.Y.Z]')} - Apply releases to production")
|
|
84
|
-
else:
|
|
85
|
-
# Development commands
|
|
86
|
-
click.echo(f" • {utils.Color.bold('patch')}")
|
|
87
|
-
click.echo(f" • {utils.Color.bold('prepare-release <level>')} - Prepare next release stage file (patch/minor/major)")
|
|
88
|
-
click.echo(f" • {utils.Color.bold('promote-to <target>')} - Promote stage to rc or prod")
|
|
89
|
-
|
|
90
|
-
click.echo(f"\nTry {utils.Color.bold('half_orm dev <command> --help')} for more information.\n")
|
|
91
|
-
else:
|
|
92
|
-
click.echo(hop.state)
|
|
93
|
-
click.echo("\nNot in a hop repository.")
|
|
94
|
-
click.echo(f"\n{utils.Color.bold('Available commands:')}")
|
|
95
|
-
click.echo(f"\n • {utils.Color.bold('init <package_name>')} - Create new halfORM project.")
|
|
96
|
-
click.echo(f"\n • {utils.Color.bold('clone <git origin>')} - Clone an existing halfORM project.\n")
|
|
97
|
-
|
|
98
|
-
# Add only available commands to the group
|
|
99
|
-
for cmd_name in hop.available_commands:
|
|
100
|
-
if cmd_name in ALL_COMMANDS:
|
|
101
|
-
dev.add_command(ALL_COMMANDS[cmd_name])
|
|
102
|
-
|
|
103
|
-
return dev
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.17.2-a5
|
|
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.2a5 → half_orm_dev-0.17.2a7}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/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
|
{half_orm_dev-0.17.2a5 → half_orm_dev-0.17.2a7}/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
|