Integrate claude, improve selenium and support qemu #1
2 changed files with 123 additions and 21 deletions
|
|
@ -2523,6 +2523,15 @@ TRANSLATIONS = {
|
||||||
"fr": "Reprise à partir de l'étape",
|
"fr": "Reprise à partir de l'étape",
|
||||||
"en": "Replaying from step",
|
"en": "Replaying from step",
|
||||||
},
|
},
|
||||||
|
"Replay the upgrade from version N": {
|
||||||
|
"fr": "Reprendre la montée à partir de la version N",
|
||||||
|
"en": "Replay the upgrade from version N",
|
||||||
|
},
|
||||||
|
"rebuilds the intermediate database": {
|
||||||
|
"fr": "recrée la base intermédiaire",
|
||||||
|
"en": "rebuilds the intermediate database",
|
||||||
|
},
|
||||||
|
"Unknown version": {"fr": "Version inconnue", "en": "Unknown version"},
|
||||||
"The progression file is invalid, ignoring it": {
|
"The progression file is invalid, ignoring it": {
|
||||||
"fr": "Le fichier de progression est invalide, on l'ignore",
|
"fr": "Le fichier de progression est invalide, on l'ignore",
|
||||||
"en": "The progression file is invalid, ignoring it",
|
"en": "The progression file is invalid, ignoring it",
|
||||||
|
|
|
||||||
|
|
@ -186,11 +186,18 @@ class TodoUpgrade:
|
||||||
icon, detail = self.step_status(old_dct_progression, step)
|
icon, detail = self.step_status(old_dct_progression, step)
|
||||||
print(f" [{step}] {icon} {t(label):<44} {detail}")
|
print(f" [{step}] {icon} {t(label):<44} {detail}")
|
||||||
print()
|
print()
|
||||||
|
lst_version = self.version_bumps(old_dct_progression)
|
||||||
print(f" [c] {t('Continue where it stopped')}")
|
print(f" [c] {t('Continue where it stopped')}")
|
||||||
print(
|
print(
|
||||||
f" [0-4] {t('Replay from that step')}"
|
f" [0-4] {t('Replay from that step')}"
|
||||||
f" ({t('erases the progression of that step and the next ones')})"
|
f" ({t('erases the progression of that step and the next ones')})"
|
||||||
)
|
)
|
||||||
|
if lst_version:
|
||||||
|
print(
|
||||||
|
f" [4.N] {t('Replay the upgrade from version N')}"
|
||||||
|
f" ({'/'.join(str(v) for v in lst_version)}) —"
|
||||||
|
f" {t('rebuilds the intermediate database')}"
|
||||||
|
)
|
||||||
print(f" [n] {t('New migration, erase everything')}")
|
print(f" [n] {t('New migration, erase everything')}")
|
||||||
print(f" [r] {t('Keep the zip only, ask every question again')}")
|
print(f" [r] {t('Keep the zip only, ask every question again')}")
|
||||||
answer = input(f"💬 {t('Your choice')} : ").strip().lower()
|
answer = input(f"💬 {t('Your choice')} : ").strip().lower()
|
||||||
|
|
@ -204,6 +211,17 @@ class TodoUpgrade:
|
||||||
"migration_file": old_dct_progression.get("migration_file"),
|
"migration_file": old_dct_progression.get("migration_file"),
|
||||||
"date_create": old_dct_progression.get("date_create"),
|
"date_create": old_dct_progression.get("date_create"),
|
||||||
}, True
|
}, True
|
||||||
|
if answer.startswith("4.") and lst_version:
|
||||||
|
target = answer.split(".", 1)[1].strip()
|
||||||
|
if target.isdigit() and int(target) in lst_version:
|
||||||
|
return (
|
||||||
|
self.rewind_version_bump(
|
||||||
|
old_dct_progression, lst_version.index(int(target))
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
print(f"⚠️ {t('Unknown version')} : {target}")
|
||||||
|
return old_dct_progression, False
|
||||||
if answer.isdigit() and 0 <= int(answer) <= MIGRATION_STEP[-1][0]:
|
if answer.isdigit() and 0 <= int(answer) <= MIGRATION_STEP[-1][0]:
|
||||||
return (
|
return (
|
||||||
self.rewind_progression(old_dct_progression, int(answer)),
|
self.rewind_progression(old_dct_progression, int(answer)),
|
||||||
|
|
@ -213,6 +231,54 @@ class TodoUpgrade:
|
||||||
print(f"⚠️ {t('Unknown choice, continuing where it stopped')}.")
|
print(f"⚠️ {t('Unknown choice, continuing where it stopped')}.")
|
||||||
return old_dct_progression, False
|
return old_dct_progression, False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def version_bumps(dct_progression):
|
||||||
|
"""Odoo versions the step 4 loop walks through, e.g. [13, 14, ..., 18].
|
||||||
|
|
||||||
|
The per-version lists all end on the target, so the first bump is
|
||||||
|
« target - len + 1 ». Returns [] when step 4 has not started.
|
||||||
|
"""
|
||||||
|
total = max(
|
||||||
|
[
|
||||||
|
len(value)
|
||||||
|
for key, value in dct_progression.items()
|
||||||
|
if key.startswith("state_4_")
|
||||||
|
and key.endswith("_odoo_lst")
|
||||||
|
and isinstance(value, list)
|
||||||
|
]
|
||||||
|
or [0]
|
||||||
|
)
|
||||||
|
if not total:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
last = int(float(dct_progression["target_odoo_version"]))
|
||||||
|
except (KeyError, TypeError, ValueError):
|
||||||
|
return []
|
||||||
|
return list(range(last - total + 1, last + 1))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def rewind_version_bump(old_dct_progression, index):
|
||||||
|
"""Replay the step 4 loop from one version bump onwards.
|
||||||
|
|
||||||
|
Only the per-version lists are trimmed from `index`: earlier bumps stay
|
||||||
|
migrated and steps 0 to 3 are untouched. Resetting the clone entry is
|
||||||
|
the point — the intermediate database of a failed bump is half
|
||||||
|
migrated, so it must be dropped and rebuilt from the previous version
|
||||||
|
rather than upgraded again.
|
||||||
|
"""
|
||||||
|
dct_kept = dict(old_dct_progression)
|
||||||
|
for key, value in old_dct_progression.items():
|
||||||
|
if (
|
||||||
|
key.startswith("state_4_")
|
||||||
|
and isinstance(value, list)
|
||||||
|
and key.endswith(("_odoo_lst", "_module"))
|
||||||
|
):
|
||||||
|
dct_kept[key] = [
|
||||||
|
item if i < index else False
|
||||||
|
for i, item in enumerate(value)
|
||||||
|
]
|
||||||
|
return dct_kept
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def rewind_progression(old_dct_progression, step):
|
def rewind_progression(old_dct_progression, step):
|
||||||
"""Drop the progression of `step` and of every later step.
|
"""Drop the progression of `step` and of every later step.
|
||||||
|
|
@ -916,20 +982,8 @@ class TodoUpgrade:
|
||||||
self.read_uninstall_module_list(start_version, database_name)
|
self.read_uninstall_module_list(start_version, database_name)
|
||||||
)
|
)
|
||||||
if lst_uninstall_reason:
|
if lst_uninstall_reason:
|
||||||
# Show WHY each module goes away: which modules must be dropped
|
|
||||||
# depends on the database, and a removal without a stated reason
|
|
||||||
# is a decision nobody can review later.
|
|
||||||
print("✨ Modules to uninstall before migration :")
|
print("✨ Modules to uninstall before migration :")
|
||||||
for name, reason, origin in lst_uninstall_reason:
|
self.print_uninstall_reason(lst_uninstall_reason)
|
||||||
print(
|
|
||||||
f" - {name}"
|
|
||||||
+ (
|
|
||||||
f" — {reason}"
|
|
||||||
if reason
|
|
||||||
else " — ⚠️ no reason given"
|
|
||||||
)
|
|
||||||
+ f" [{origin}]"
|
|
||||||
)
|
|
||||||
|
|
||||||
if config_state_1_uninstall_module:
|
if config_state_1_uninstall_module:
|
||||||
lst_module_to_uninstall = (
|
lst_module_to_uninstall = (
|
||||||
|
|
@ -1169,9 +1223,24 @@ class TodoUpgrade:
|
||||||
)
|
)
|
||||||
|
|
||||||
if not lst_module_uninstall_module[index]:
|
if not lst_module_uninstall_module[index]:
|
||||||
lst_module_to_uninstall = config_state_4_uninstall_module[
|
lst_module_to_uninstall = (
|
||||||
index
|
config_state_4_uninstall_module[index] or []
|
||||||
]
|
)
|
||||||
|
# Same file convention as step 1, one file per version bump:
|
||||||
|
# uninstall_module_list_odoo130_to_odoo140.txt is read HERE,
|
||||||
|
# right before the 13 -> 14 data migration. Without this the
|
||||||
|
# per-bump files existed in name only and were never read.
|
||||||
|
lst_file, lst_detail = self.read_uninstall_module_list(
|
||||||
|
next_version - 1, database_name
|
||||||
|
)
|
||||||
|
if lst_detail:
|
||||||
|
print(
|
||||||
|
f"✨ Modules to uninstall before Odoo{next_version} :"
|
||||||
|
)
|
||||||
|
self.print_uninstall_reason(lst_detail)
|
||||||
|
lst_module_to_uninstall = list(
|
||||||
|
dict.fromkeys(list(lst_module_to_uninstall) + lst_file)
|
||||||
|
)
|
||||||
|
|
||||||
if lst_module_to_uninstall:
|
if lst_module_to_uninstall:
|
||||||
self.uninstall_from_database(
|
self.uninstall_from_database(
|
||||||
|
|
@ -1735,11 +1804,17 @@ class TodoUpgrade:
|
||||||
# forced: their arch is a real customization.
|
# forced: their arch is a real customization.
|
||||||
self.neutralize_cow_views(database_name_upgrade, next_version)
|
self.neutralize_cow_views(database_name_upgrade, next_version)
|
||||||
|
|
||||||
|
# wait_at_error=False on purpose: the generic « [1] to redo the
|
||||||
|
# command » would replay OpenUpgrade on a database it has just
|
||||||
|
# half migrated, which never recovers. The failure is handled
|
||||||
|
# below by dropping the clone flag so the replay REBUILDS the
|
||||||
|
# intermediate database from the previous version.
|
||||||
status, cmd_executed = self.todo_upgrade_execute(
|
status, cmd_executed = self.todo_upgrade_execute(
|
||||||
cmd_upgrade,
|
cmd_upgrade,
|
||||||
new_env={
|
new_env={
|
||||||
"OPENUPGRADE_TARGET_VERSION": f"{next_version}.0"
|
"OPENUPGRADE_TARGET_VERSION": f"{next_version}.0"
|
||||||
},
|
},
|
||||||
|
wait_at_error=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# This is THE data migration. Recording it as done when it
|
# This is THE data migration. Recording it as done when it
|
||||||
|
|
@ -1757,11 +1832,15 @@ class TodoUpgrade:
|
||||||
)
|
)
|
||||||
self.write_config()
|
self.write_config()
|
||||||
print(
|
print(
|
||||||
f"❌ -> Database migration to Odoo{next_version} FAILED"
|
f"\n❌ -> Database migration to Odoo{next_version}"
|
||||||
f" (status {status}). Stopping before version"
|
f" FAILED (status {status}).\n"
|
||||||
f" {next_version + 1} to avoid migrating a broken"
|
f" '{database_name_upgrade}' is now half migrated:"
|
||||||
" database. Fix the cause, then relaunch: this version"
|
" replaying the command on it would never recover, so"
|
||||||
f" replays from a fresh clone of '{database_name}'."
|
" it is NOT offered.\n"
|
||||||
|
" The clone step has been reset. Fix the cause, then"
|
||||||
|
" relaunch the migration and answer [c] (continue):"
|
||||||
|
f" '{database_name_upgrade}' will be dropped and"
|
||||||
|
" rebuilt from the previous version before retrying."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -2046,6 +2125,20 @@ class TodoUpgrade:
|
||||||
lst_module.append((module_name, reason.strip()))
|
lst_module.append((module_name, reason.strip()))
|
||||||
return lst_module
|
return lst_module
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def print_uninstall_reason(lst_detail):
|
||||||
|
"""Show WHY each module goes away.
|
||||||
|
|
||||||
|
Which modules must be dropped depends on the database, and a removal
|
||||||
|
without a stated reason is a decision nobody can review later.
|
||||||
|
"""
|
||||||
|
for name, reason, origin in lst_detail:
|
||||||
|
print(
|
||||||
|
f" - {name}"
|
||||||
|
+ (f" — {reason}" if reason else " — ⚠️ no reason given")
|
||||||
|
+ f" [{origin}]"
|
||||||
|
)
|
||||||
|
|
||||||
def read_uninstall_module_list(self, start_version, database_name):
|
def read_uninstall_module_list(self, start_version, database_name):
|
||||||
"""Modules to uninstall before migrating start_version -> next.
|
"""Modules to uninstall before migrating start_version -> next.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue