diff --git a/odev/commands/git/clone.py b/odev/commands/git/clone.py index 89c49f5de..58f6c4a67 100644 --- a/odev/commands/git/clone.py +++ b/odev/commands/git/clone.py @@ -34,14 +34,17 @@ def _clone_repository(self): """Find and clone the correct repository.""" git = GitConnector(self.args.repository or self._database.repository.full_name) - if git.path.exists(): - logger.info(f"Repository {git.name!r} already cloned under {git.path.as_posix()}") - git.checkout(revision=self.args.branch or None) - else: - git.clone(revision=self.args.branch or None) - - if not git.path.exists(): - raise self.error(f"Failed to clone repository {git.name!r}") + try: + if git.path.exists(): + logger.info(f"Repository {git.name!r} already cloned under {git.path.as_posix()}") + git.checkout(revision=self.args.branch or None) + else: + git.clone(revision=self.args.branch or None) + + if not git.path.exists(): + raise self.error(f"Failed to clone repository {git.name!r}") + except Exception as e: + raise self.error(f"An error occurred while cloning the repository: {e}") def __check_repository(self): """Check if a repository is available to clone.""" diff --git a/odev/common/connectors/git.py b/odev/common/connectors/git.py index 4401021d0..3087a93bc 100644 --- a/odev/common/connectors/git.py +++ b/odev/common/connectors/git.py @@ -505,11 +505,9 @@ def clone(self, revision: str | None = None): multi_options=self._get_clone_options(revision), ) except GitCommandError as error: - message: str = f"Failed to clone repository {self.name!r} to {self.path}" - + message: str = f"Failed to clone repository {self.name!r} to {self.path}\n{str(error)}" if error.stderr: message += f": {error.stderr}" - raise ConnectorError(message, self) from error else: logger.info( diff --git a/odev/common/databases/local.py b/odev/common/databases/local.py index b574839b4..c7d5cd231 100644 --- a/odev/common/databases/local.py +++ b/odev/common/databases/local.py @@ -187,7 +187,7 @@ def version(self) -> OdooVersion | None: # type: ignore [override] if version is not None: return OdooVersion(version) - return OdooVersion("master") + return None @cached_property def edition(self) -> Literal["community", "enterprise"] | None: # type: ignore [override] @@ -724,6 +724,7 @@ def _restore_buffered_sql( if mode == "sql": self._buffered_sql_check_restrict(dump) self._buffered_sql_enable_extensions(dump) + self.ensure_roles() elif mode == "dump": self.unaccent() @@ -927,14 +928,40 @@ def _buffered_sql_enable_extensions(self, dump: gzip.GzipFile | bz2.BZ2File | IO :param dump: The dump file to restore SQL data from. """ + immutable_defined = False for index, line in enumerate(dump): - if index >= SQL_DUMP_IGNORE_LINES_NUMBER or "LANGUAGE sql IMMUTABLE" in line.decode(): + if index >= SQL_DUMP_IGNORE_LINES_NUMBER: break - else: + if "LANGUAGE sql IMMUTABLE" in line.decode(errors="ignore"): + immutable_defined = True + break + + if not immutable_defined: self.unaccent() dump.seek(0) + @ensure_connected + def ensure_roles(self) -> bool: + """Create database roles commonly referenced by Odoo SQL dumps. + + Plain SQL dumps often contain ``GRANT ... TO odoo`` statements. When restoring in fast mode + (``--single-transaction`` with ``ON_ERROR_STOP=1``), a missing ``odoo`` role raises + ``role "odoo" does not exist`` and rolls back the whole restore. Creating the role beforehand + keeps those statements valid without falling back to the slower degraded mode. + """ + return self.query( + """ + DO $$ + BEGIN + CREATE ROLE odoo; + EXCEPTION + WHEN duplicate_object + THEN null; + END; $$ + """ + ) + @ensure_connected def unaccent(self) -> bool: """Install the unaccent extension on the database.""" @@ -982,15 +1009,12 @@ def pg_vector(self) -> bool: try: self.query(pg_vector_query) - except RuntimeError: + except RuntimeError as re: link = string.link( "pgextwlist", "https://github.com/dimitri/pgextwlist?tab=readme-ov-file#postgresql-extension-whitelist", ) - logger.error( - "Failed to install 'pgvector' extension, please ensure it is installed on your system " - f"and whitelisted with {link}" - ) + logger.error(re.args[0]) return False return True diff --git a/odev/common/version.py b/odev/common/version.py index a5151d6f7..e6f94d387 100644 --- a/odev/common/version.py +++ b/odev/common/version.py @@ -131,6 +131,6 @@ def _cmpkey(master: bool, major: int, minor: int, module: tuple, enterprise: boo _saas = int(saas) # Master versions should sort before non-master versions - _master = int(master) + _master = -int(master) return _master, major, minor, _module, enterprise, _saas