half-orm-dev 0.17.3a7__tar.gz → 0.17.3a9__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.3a7/half_orm_dev.egg-info → half_orm_dev-0.17.3a9}/PKG-INFO +2 -2
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/database.py +21 -157
- half_orm_dev-0.17.3a9/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +204 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/patch_manager.py +264 -61
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/release_file.py +32 -18
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/release_manager.py +168 -36
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/repo.py +125 -54
- half_orm_dev-0.17.3a9/half_orm_dev/version.txt +1 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9/half_orm_dev.egg-info}/PKG-INFO +2 -2
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev.egg-info/SOURCES.txt +1 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev.egg-info/requires.txt +1 -1
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/setup.py +1 -1
- half_orm_dev-0.17.3a7/half_orm_dev/version.txt +0 -1
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/AUTHORS +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/LICENSE +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/README.md +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/migration_manager.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/modules.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/scripts/repair-metadata.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/Pipfile +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/module_template_1 +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/pyproject.toml +0 -0
- {half_orm_dev-0.17.3a7 → half_orm_dev-0.17.3a9}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: half_orm_dev
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.3a9
|
|
4
4
|
Summary: half_orm development Framework.
|
|
5
5
|
Author-email: Joël Maïzi <joel.maizi@collorg.org>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -23,7 +23,7 @@ Requires-Dist: GitPython
|
|
|
23
23
|
Requires-Dist: click
|
|
24
24
|
Requires-Dist: pydash
|
|
25
25
|
Requires-Dist: pytest
|
|
26
|
-
Requires-Dist: half_orm<0.18.0,>=0.17.
|
|
26
|
+
Requires-Dist: half_orm<0.18.0,>=0.17.3
|
|
27
27
|
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
28
28
|
Requires-Dist: tomli_w>=1.0.0
|
|
29
29
|
Dynamic: license-file
|
|
@@ -1224,126 +1224,12 @@ class Database:
|
|
|
1224
1224
|
|
|
1225
1225
|
return complete_params
|
|
1226
1226
|
|
|
1227
|
-
@classmethod
|
|
1228
|
-
def _load_configuration(cls, database_name):
|
|
1229
|
-
"""
|
|
1230
|
-
Load existing database configuration file, replacing DbConn functionality.
|
|
1231
|
-
|
|
1232
|
-
Reads halfORM configuration file and returns connection parameters as a dictionary.
|
|
1233
|
-
This method completely replaces DbConn.__init() logic, supporting both minimal
|
|
1234
|
-
configurations (PostgreSQL trust mode) and complete parameter sets.
|
|
1235
|
-
|
|
1236
|
-
Args:
|
|
1237
|
-
database_name (str): Name of the database to load configuration for
|
|
1238
|
-
|
|
1239
|
-
Returns:
|
|
1240
|
-
dict | None: Connection parameters dictionary with standardized keys:
|
|
1241
|
-
- name (str): Database name (always present)
|
|
1242
|
-
- user (str): Database user (defaults to $USER environment variable)
|
|
1243
|
-
- password (str): Database password (empty string if not set)
|
|
1244
|
-
- host (str): Database host (empty string for Unix socket, 'localhost' otherwise)
|
|
1245
|
-
- port (int): Database port (5432 if not specified)
|
|
1246
|
-
- production (bool): Production environment flag (defaults to False)
|
|
1247
|
-
Returns None if configuration file doesn't exist.
|
|
1248
|
-
|
|
1249
|
-
Raises:
|
|
1250
|
-
FileNotFoundError: If CONF_DIR doesn't exist or isn't accessible
|
|
1251
|
-
PermissionError: If configuration file exists but isn't readable
|
|
1252
|
-
ValueError: If configuration file format is invalid or corrupted
|
|
1253
|
-
|
|
1254
|
-
Examples:
|
|
1255
|
-
# Complete configuration file
|
|
1256
|
-
config = Database._load_configuration("production_db")
|
|
1257
|
-
# Returns: {'name': 'production_db', 'user': 'app_user', 'password': 'secret',
|
|
1258
|
-
# 'host': 'db.company.com', 'port': 5432, 'production': True}
|
|
1259
|
-
|
|
1260
|
-
# Minimal trust mode configuration (only name=database_name)
|
|
1261
|
-
config = Database._load_configuration("local_dev")
|
|
1262
|
-
# Returns: {'name': 'local_dev', 'user': 'joel', 'password': '',
|
|
1263
|
-
# 'host': '', 'port': 5432, 'production': False}
|
|
1264
|
-
|
|
1265
|
-
# Non-existent configuration
|
|
1266
|
-
config = Database._load_configuration("unknown_db")
|
|
1267
|
-
# Returns: None
|
|
1268
|
-
|
|
1269
|
-
Migration Notes:
|
|
1270
|
-
- Completely replaces DbConn.__init() and DbConn.__init logic
|
|
1271
|
-
- Maintains backward compatibility with existing config files
|
|
1272
|
-
- Standardizes return format (int for port, bool for production)
|
|
1273
|
-
- Integrates PostgreSQL trust mode defaults directly into Database class
|
|
1274
|
-
- Eliminates external DbConn dependency while preserving all functionality
|
|
1275
|
-
"""
|
|
1276
|
-
from half_orm.model import CONF_DIR
|
|
1277
|
-
|
|
1278
|
-
# Check if configuration directory exists
|
|
1279
|
-
if not os.path.exists(CONF_DIR):
|
|
1280
|
-
raise FileNotFoundError(f"Configuration directory {CONF_DIR} doesn't exist")
|
|
1281
|
-
|
|
1282
|
-
# Build configuration file path
|
|
1283
|
-
config_file = os.path.join(CONF_DIR, database_name)
|
|
1284
|
-
|
|
1285
|
-
# Return None if configuration file doesn't exist
|
|
1286
|
-
if not os.path.exists(config_file):
|
|
1287
|
-
return None
|
|
1288
|
-
|
|
1289
|
-
# Check if file is readable before attempting to parse
|
|
1290
|
-
if not os.access(config_file, os.R_OK):
|
|
1291
|
-
raise PermissionError(f"Configuration file {config_file} is not readable")
|
|
1292
|
-
|
|
1293
|
-
# Read configuration file
|
|
1294
|
-
config = ConfigParser()
|
|
1295
|
-
try:
|
|
1296
|
-
config.read(config_file)
|
|
1297
|
-
except Exception as e:
|
|
1298
|
-
raise ValueError(f"Configuration file format is invalid: {e}")
|
|
1299
|
-
|
|
1300
|
-
# Check if [database] section exists
|
|
1301
|
-
if not config.has_section('database'):
|
|
1302
|
-
raise ValueError("Configuration file format is invalid: missing [database] section")
|
|
1303
|
-
|
|
1304
|
-
# Extract configuration values with PostgreSQL defaults
|
|
1305
|
-
try:
|
|
1306
|
-
name = config.get('database', 'name')
|
|
1307
|
-
user = config.get('database', 'user', fallback=os.environ.get('USER', ''))
|
|
1308
|
-
password = config.get('database', 'password', fallback='')
|
|
1309
|
-
host = config.get('database', 'host', fallback='')
|
|
1310
|
-
port_str = config.get('database', 'port', fallback='')
|
|
1311
|
-
production_str = config.get('database', 'production', fallback='False')
|
|
1312
|
-
docker_container = config.get('database', 'docker_container', fallback='')
|
|
1313
|
-
|
|
1314
|
-
# Convert port to int (default 5432 if empty)
|
|
1315
|
-
if port_str == '':
|
|
1316
|
-
port = 5432
|
|
1317
|
-
else:
|
|
1318
|
-
port = int(port_str)
|
|
1319
|
-
|
|
1320
|
-
# Convert production to bool
|
|
1321
|
-
production = config.getboolean('database', 'production', fallback=False)
|
|
1322
|
-
|
|
1323
|
-
return {
|
|
1324
|
-
'name': name,
|
|
1325
|
-
'user': user,
|
|
1326
|
-
'password': password,
|
|
1327
|
-
'host': host,
|
|
1328
|
-
'port': port,
|
|
1329
|
-
'production': production,
|
|
1330
|
-
'docker_container': docker_container
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
except (ValueError, TypeError) as e:
|
|
1334
|
-
raise ValueError(f"Configuration file format is invalid: {e}")
|
|
1335
|
-
|
|
1336
1227
|
def _get_connection_params(self):
|
|
1337
1228
|
"""
|
|
1338
1229
|
Get current connection parameters for this database instance.
|
|
1339
1230
|
|
|
1340
|
-
Returns the connection parameters dictionary
|
|
1341
|
-
|
|
1342
|
-
unified interface for accessing connection parameters during the migration
|
|
1343
|
-
from DbConn to integrated Database functionality.
|
|
1344
|
-
|
|
1345
|
-
Uses instance-level caching to avoid repeated file reads within the same
|
|
1346
|
-
Database instance lifecycle.
|
|
1231
|
+
Returns the connection parameters dictionary using Model._dbinfo,
|
|
1232
|
+
which is already loaded by half_orm from the configuration file.
|
|
1347
1233
|
|
|
1348
1234
|
Returns:
|
|
1349
1235
|
dict: Connection parameters dictionary with standardized keys:
|
|
@@ -1353,59 +1239,37 @@ class Database:
|
|
|
1353
1239
|
- host (str): Database host (empty string for Unix socket)
|
|
1354
1240
|
- port (int): Database port (5432 default)
|
|
1355
1241
|
- production (bool): Production environment flag
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
Examples:
|
|
1359
|
-
# Get connection parameters for existing database instance
|
|
1360
|
-
db = Database(repo)
|
|
1361
|
-
params = db._get_connection_params()
|
|
1362
|
-
# Returns: {'name': 'my_db', 'user': 'dev', 'password': '',
|
|
1363
|
-
# 'host': 'localhost', 'port': 5432, 'production': False}
|
|
1364
|
-
|
|
1365
|
-
# Access specific parameters (replaces DbConn.property access)
|
|
1366
|
-
user = db._get_connection_params()['user'] # replaces self.__connection_params.user
|
|
1367
|
-
host = db._get_connection_params()['host'] # replaces self.__connection_params.host
|
|
1368
|
-
prod = db._get_connection_params()['production'] # replaces self.__connection_params.production
|
|
1369
|
-
|
|
1370
|
-
Implementation Notes:
|
|
1371
|
-
- Uses _load_configuration() internally but handles all exceptions
|
|
1372
|
-
- Provides stable interface - never raises exceptions
|
|
1373
|
-
- Returns sensible defaults if configuration is missing/invalid
|
|
1374
|
-
- Serves as protective wrapper around _load_configuration()
|
|
1375
|
-
- Exceptions from _load_configuration() are caught and handled gracefully
|
|
1376
|
-
- Uses instance-level cache to avoid repeated file reads
|
|
1377
|
-
|
|
1378
|
-
Migration Notes:
|
|
1379
|
-
- Replaces self.__connection_params.user, .host, .port, .production access
|
|
1380
|
-
- Serves as transition method during DbConn elimination
|
|
1381
|
-
- Maintains compatibility with existing Database instance usage patterns
|
|
1382
|
-
- Will be used by state, production, and execute_pg_command properties
|
|
1242
|
+
- docker_container (str): Docker container name (if configured)
|
|
1383
1243
|
"""
|
|
1384
1244
|
# Return cached parameters if already loaded
|
|
1385
1245
|
if hasattr(self, '_Database__connection_params_cache') and self.__connection_params_cache is not None:
|
|
1386
1246
|
return self.__connection_params_cache
|
|
1387
1247
|
|
|
1388
|
-
#
|
|
1248
|
+
# Use connection info from Model._dbinfo (already loaded by half_orm)
|
|
1249
|
+
if self.__model is not None and hasattr(self.__model, '_dbinfo'):
|
|
1250
|
+
dbinfo = self.__model._dbinfo
|
|
1251
|
+
config = {
|
|
1252
|
+
'name': self.__repo.name,
|
|
1253
|
+
'user': dbinfo.get('user', os.environ.get('USER', '')),
|
|
1254
|
+
'password': dbinfo.get('password', ''),
|
|
1255
|
+
'host': dbinfo.get('host', ''),
|
|
1256
|
+
'port': int(dbinfo.get('port', 5432) or 5432),
|
|
1257
|
+
'production': not self.__model._production_mode, # devel=False means production
|
|
1258
|
+
'docker_container': dbinfo.get('docker_container', ''),
|
|
1259
|
+
}
|
|
1260
|
+
self.__connection_params_cache = config
|
|
1261
|
+
return config
|
|
1262
|
+
|
|
1263
|
+
# Fallback: defaults (should not happen in normal usage)
|
|
1389
1264
|
config = {
|
|
1390
1265
|
'name': self.__repo.name,
|
|
1391
1266
|
'user': os.environ.get('USER', ''),
|
|
1392
1267
|
'password': '',
|
|
1393
1268
|
'host': '',
|
|
1394
1269
|
'port': 5432,
|
|
1395
|
-
'production': False
|
|
1270
|
+
'production': False,
|
|
1271
|
+
'docker_container': '',
|
|
1396
1272
|
}
|
|
1397
|
-
|
|
1398
|
-
try:
|
|
1399
|
-
# Try to load configuration for this database
|
|
1400
|
-
loaded_config = self._load_configuration(self.__repo.name)
|
|
1401
|
-
if loaded_config is not None:
|
|
1402
|
-
config = loaded_config
|
|
1403
|
-
except (FileNotFoundError, PermissionError, ValueError):
|
|
1404
|
-
# Handle all possible exceptions from _load_configuration gracefully
|
|
1405
|
-
# Return sensible defaults to maintain stable interface
|
|
1406
|
-
pass
|
|
1407
|
-
|
|
1408
|
-
# Cache the result for subsequent calls
|
|
1409
1273
|
self.__connection_params_cache = config
|
|
1410
1274
|
return config
|
|
1411
1275
|
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration: Convert TOML patches to dict format with merge_commit.
|
|
3
|
+
|
|
4
|
+
This migration converts the old TOML format:
|
|
5
|
+
[patches]
|
|
6
|
+
"1-auth" = "staged"
|
|
7
|
+
"2-api" = "candidate"
|
|
8
|
+
|
|
9
|
+
To the new dict format:
|
|
10
|
+
[patches]
|
|
11
|
+
"1-auth" = { status = "staged", merge_commit = "abc123de" }
|
|
12
|
+
"2-api" = { status = "candidate" }
|
|
13
|
+
|
|
14
|
+
For staged patches, the merge_commit hash is retrieved from git history.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import tomli
|
|
23
|
+
except ImportError:
|
|
24
|
+
import tomllib as tomli
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import tomli_w
|
|
28
|
+
except ImportError:
|
|
29
|
+
raise ImportError(
|
|
30
|
+
"tomli_w is required for this migration. "
|
|
31
|
+
"Install it with: pip install tomli_w"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_description():
|
|
36
|
+
"""Return migration description."""
|
|
37
|
+
return "Convert TOML patches to dict format with merge_commit"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_merge_commit(repo, patch_id: str, version: str) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Find the merge commit hash for a staged patch.
|
|
43
|
+
|
|
44
|
+
Searches git history for the commit that merged the patch branch
|
|
45
|
+
into the release branch.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
repo: Repo instance
|
|
49
|
+
patch_id: Patch identifier (e.g., "456-user-auth")
|
|
50
|
+
version: Release version (e.g., "0.17.0")
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Commit hash (8 characters) or empty string if not found
|
|
54
|
+
"""
|
|
55
|
+
release_branch = f"ho-release/{version}"
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Search for merge commit message pattern
|
|
59
|
+
# Pattern: [HOP] Merge #PATCH_ID into %"VERSION"
|
|
60
|
+
result = subprocess.run(
|
|
61
|
+
['git', 'log', '--all', '--grep', f'Merge #{patch_id}',
|
|
62
|
+
'--format=%H', '-n', '1'],
|
|
63
|
+
cwd=repo.base_dir,
|
|
64
|
+
capture_output=True,
|
|
65
|
+
text=True,
|
|
66
|
+
check=True
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
commit_hash = result.stdout.strip()
|
|
70
|
+
if commit_hash:
|
|
71
|
+
return commit_hash[:8]
|
|
72
|
+
|
|
73
|
+
# Fallback: search for the move to stage commit
|
|
74
|
+
# Pattern: [HOP] move patch #PATCH_ID from candidate to stage
|
|
75
|
+
result = subprocess.run(
|
|
76
|
+
['git', 'log', '--all', '--grep', f'move patch #{patch_id}',
|
|
77
|
+
'--format=%H', '-n', '1'],
|
|
78
|
+
cwd=repo.base_dir,
|
|
79
|
+
capture_output=True,
|
|
80
|
+
text=True,
|
|
81
|
+
check=True
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
commit_hash = result.stdout.strip()
|
|
85
|
+
if commit_hash:
|
|
86
|
+
# Get the parent commit (the merge commit is the one before the move)
|
|
87
|
+
result = subprocess.run(
|
|
88
|
+
['git', 'rev-parse', f'{commit_hash}^'],
|
|
89
|
+
cwd=repo.base_dir,
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
check=True
|
|
93
|
+
)
|
|
94
|
+
parent_hash = result.stdout.strip()
|
|
95
|
+
if parent_hash:
|
|
96
|
+
return parent_hash[:8]
|
|
97
|
+
|
|
98
|
+
except subprocess.CalledProcessError:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
return ""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def migrate(repo):
|
|
105
|
+
"""
|
|
106
|
+
Execute migration: Convert TOML patches to dict format.
|
|
107
|
+
|
|
108
|
+
For each X.Y.Z-patches.toml file:
|
|
109
|
+
1. Read current content
|
|
110
|
+
2. Check if already in dict format
|
|
111
|
+
3. Convert to dict format:
|
|
112
|
+
- candidates: { status = "candidate" }
|
|
113
|
+
- staged: { status = "staged", merge_commit = "..." }
|
|
114
|
+
4. Find merge_commit from git history for staged patches
|
|
115
|
+
5. Write updated TOML file
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
repo: Repo instance
|
|
119
|
+
"""
|
|
120
|
+
print("Migrating TOML patches to dict format with merge_commit...")
|
|
121
|
+
|
|
122
|
+
releases_dir = Path(repo.releases_dir)
|
|
123
|
+
if not releases_dir.exists():
|
|
124
|
+
print(" No releases directory found, skipping migration.")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
# Find all TOML patches files
|
|
128
|
+
toml_files = list(releases_dir.glob("*-patches.toml"))
|
|
129
|
+
|
|
130
|
+
if not toml_files:
|
|
131
|
+
print(" No TOML patches files found, skipping migration.")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
migrated_count = 0
|
|
135
|
+
|
|
136
|
+
for toml_file in toml_files:
|
|
137
|
+
# Extract version from filename
|
|
138
|
+
version = toml_file.stem.replace('-patches', '')
|
|
139
|
+
|
|
140
|
+
print(f" Processing {version}...")
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
# Read current TOML content
|
|
144
|
+
with toml_file.open('rb') as f:
|
|
145
|
+
data = tomli.load(f)
|
|
146
|
+
|
|
147
|
+
patches = data.get("patches", {})
|
|
148
|
+
|
|
149
|
+
if not patches:
|
|
150
|
+
print(f" No patches in {version}, skipping")
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# Check if already in dict format
|
|
154
|
+
first_value = next(iter(patches.values()))
|
|
155
|
+
if isinstance(first_value, dict):
|
|
156
|
+
print(f" Already in dict format, skipping")
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
# Convert to dict format
|
|
160
|
+
new_patches = {}
|
|
161
|
+
staged_without_commit = []
|
|
162
|
+
|
|
163
|
+
for patch_id, status in patches.items():
|
|
164
|
+
if status == "candidate":
|
|
165
|
+
new_patches[patch_id] = {"status": "candidate"}
|
|
166
|
+
elif status == "staged":
|
|
167
|
+
# Find merge commit from git history
|
|
168
|
+
merge_commit = find_merge_commit(repo, patch_id, version)
|
|
169
|
+
if merge_commit:
|
|
170
|
+
new_patches[patch_id] = {
|
|
171
|
+
"status": "staged",
|
|
172
|
+
"merge_commit": merge_commit
|
|
173
|
+
}
|
|
174
|
+
else:
|
|
175
|
+
# No merge_commit found, store without it
|
|
176
|
+
new_patches[patch_id] = {"status": "staged"}
|
|
177
|
+
staged_without_commit.append(patch_id)
|
|
178
|
+
else:
|
|
179
|
+
# Unknown status, preserve as-is in dict format
|
|
180
|
+
new_patches[patch_id] = {"status": status}
|
|
181
|
+
|
|
182
|
+
# Update data and write
|
|
183
|
+
data["patches"] = new_patches
|
|
184
|
+
|
|
185
|
+
with toml_file.open('wb') as f:
|
|
186
|
+
tomli_w.dump(data, f)
|
|
187
|
+
|
|
188
|
+
print(f" Converted {len(patches)} patch(es)")
|
|
189
|
+
if staged_without_commit:
|
|
190
|
+
print(f" Warning: No merge_commit found for: {', '.join(staged_without_commit)}",
|
|
191
|
+
file=sys.stderr)
|
|
192
|
+
|
|
193
|
+
migrated_count += 1
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(f" Error processing {version}: {e}", file=sys.stderr)
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
repo.hgit.add('.hop')
|
|
200
|
+
|
|
201
|
+
if migrated_count > 0:
|
|
202
|
+
print(f"\nMigration complete: {migrated_count} file(s) converted to dict format")
|
|
203
|
+
else:
|
|
204
|
+
print("\nNo files needed migration")
|