diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py
index 55479e5c..b56d4b66 100644
--- a/odoo_project/models/odoo_project.py
+++ b/odoo_project/models/odoo_project.py
@@ -16,28 +16,34 @@ class OdooProject(models.Model):
active = fields.Boolean(default=True)
repository_id = fields.Many2one(
comodel_name="odoo.repository",
- ondelete="restrict",
string="Repository",
- domain=[
- ("clone_branch_id", "!=", False),
- ("specific", "=", True),
- ("odoo_version_id", "!=", False),
- ],
+ domain=[("specific", "=", True)],
+ store=True,
+ index=True,
help=(
- "Repository is optional. "
+ "Repository this project is based on (optional). "
"You can start to build/simulate a project without repository "
"to get some figures."
),
)
+ available_odoo_version_ids = fields.One2many(
+ comodel_name="odoo.branch",
+ compute="_compute_available_odoo_version_ids",
+ string="Available Odoo Versions",
+ )
odoo_version_id = fields.Many2one(
comodel_name="odoo.branch",
ondelete="restrict",
string="Odoo Version",
- domain=[("odoo_version", "=", True)],
- required=True,
- compute="_compute_odoo_version_id",
store=True,
- readonly=False,
+ index=True,
+ )
+ repository_branch_id = fields.Many2one(
+ comodel_name="odoo.repository.branch",
+ string="Repository / Branch",
+ compute="_compute_repository_branch_id",
+ store=True,
+ index=True,
)
project_module_ids = fields.One2many(
comodel_name="odoo.project.module",
@@ -74,10 +80,22 @@ class OdooProject(models.Model):
)
@api.depends("repository_id")
- def _compute_odoo_version_id(self):
+ def _compute_available_odoo_version_ids(self):
+ all_versions = self.env["odoo.branch"]._get_all_odoo_versions()
for rec in self:
+ rec.available_odoo_version_ids = all_versions
if rec.repository_id:
- rec.odoo_version_id = rec.repository_id.odoo_version_id
+ rec.available_odoo_version_ids = rec.repository_id.branch_ids.branch_id
+
+ @api.depends("repository_id", "odoo_version_id")
+ def _compute_repository_branch_id(self):
+ for rec in self:
+ rec.repository_branch_id = False
+ if not rec.repository_id or not rec.odoo_version_id:
+ continue
+ rec.repository_branch_id = rec.repository_id.branch_ids.filtered(
+ lambda rb: rb.branch_id == rec.odoo_version_id
+ )
@api.depends("project_module_ids.module_id")
def _compute_module_ids(self):
@@ -90,11 +108,11 @@ def _compute_modules_count(self):
rec.modules_count = len(rec.project_module_ids)
@api.depends(
- "repository_id.branch_ids.module_ids", "project_module_ids.module_branch_id"
+ "repository_branch_id.module_ids", "project_module_ids.module_branch_id"
)
def _compute_module_not_installed_ids(self):
for rec in self:
- all_module_ids = set(rec.repository_id.branch_ids.module_ids.ids)
+ all_module_ids = set(rec.repository_branch_id.module_ids.ids)
installed_module_ids = set(rec.project_module_ids.module_branch_id.ids)
rec.module_not_installed_ids = list(all_module_ids - installed_module_ids)
@@ -145,12 +163,12 @@ def action_find_unknown_modules(self):
module.action_find_pr_url()
def _get_repositories_to_scan(self):
- """Returnt the repositories to scan."""
+ """Return the repositories to scan."""
domain = self.env["odoo.repository"]._cron_scanner_domain()
return self.project_module_ids.repository_id.filtered_domain(domain)
def _get_branches_to_scan(self):
- """Return the branches to scan."""
+ """Return the branches/versions to scan."""
return self.project_module_ids.repository_branch_id.branch_id
def action_scan(self, force=False):
@@ -163,7 +181,7 @@ def action_scan(self, force=False):
branches = self._get_branches_to_scan()
if branches:
repositories.action_scan(
- branches=branches.mapped("name"), force=force, raise_exc=False
+ branch_ids=branches.ids, force=force, raise_exc=False
)
# Scan the underlying project repository itself
self.repository_id.action_scan(force=force, raise_exc=True)
diff --git a/odoo_project/tests/test_import_modules.py b/odoo_project/tests/test_import_modules.py
index 390fdcd4..361de268 100644
--- a/odoo_project/tests/test_import_modules.py
+++ b/odoo_project/tests/test_import_modules.py
@@ -134,8 +134,8 @@ def test_match_generic_module(self):
def test_match_project_repo_module(self):
# Assign a repository to the project
- self.odoo_repository.odoo_version_id = self.branch
self.project.repository_id = self.odoo_repository
+ self.project.odoo_version_id = self.branch
mod1 = "test1"
mod2 = "test2"
mod1_in_repo = self.wiz_model._get_module(mod1)
diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml
index 59502512..4cc06d91 100644
--- a/odoo_project/views/odoo_project.xml
+++ b/odoo_project/views/odoo_project.xml
@@ -44,13 +44,17 @@
+
diff --git a/odoo_project_migration/models/odoo_project_module_migration.py b/odoo_project_migration/models/odoo_project_module_migration.py
index 47885d25..13d8bbfb 100644
--- a/odoo_project_migration/models/odoo_project_module_migration.py
+++ b/odoo_project_migration/models/odoo_project_module_migration.py
@@ -6,7 +6,6 @@
class OdooProjectModuleMigration(models.Model):
_name = "odoo.project.module.migration"
- # TODO test to inherit from 'module_migration_id' field too
_inherits = {"odoo.module.branch": "source_module_branch_id"}
_description = "Module migration line of an Odoo Project"
_order = (
@@ -144,7 +143,12 @@ def _compute_target_module_branch_id(self):
domain=[("installable", "=", True)],
)
- @api.depends("migration_path_id", "source_module_branch_id")
+ # NOTE: 'migration_scan' is here to re-trigger the computation
+ # each time the source module has its state updated regarding migration.
+ # FIXME: this could trigger too much computations on irrelevant records
+ # (one not related to the updated migration path), we should switch to
+ # component events to handle such cases.
+ @api.depends("migration_path_id", "source_module_branch_id.migration_scan")
def _compute_module_migration_id(self):
migration_model = self.env["odoo.module.branch.migration"]
for rec in self:
diff --git a/odoo_repository/__manifest__.py b/odoo_repository/__manifest__.py
index d1d990ca..74cfb866 100644
--- a/odoo_repository/__manifest__.py
+++ b/odoo_repository/__manifest__.py
@@ -3,7 +3,7 @@
{
"name": "Odoo Repositories Data",
"summary": "Base module to host data collected from Odoo repositories.",
- "version": "16.0.1.1.0",
+ "version": "16.0.1.3.0",
"category": "Tools",
"author": "Camptocamp, Odoo Community Association (OCA)",
"website": "https://github.com/camptocamp/odoo-repository",
diff --git a/odoo_repository/data/odoo_branch.xml b/odoo_repository/data/odoo_branch.xml
index 382a76a6..46196f60 100644
--- a/odoo_repository/data/odoo_branch.xml
+++ b/odoo_repository/data/odoo_branch.xml
@@ -14,61 +14,56 @@
8.0
-
9.0
-
10.0
-
11.0
-
12.0
-
13.0
-
14.0
-
15.0
-
16.0
-
17.0
-
+
+
+
+
+ 18.0
diff --git a/odoo_repository/lib/scanner.py b/odoo_repository/lib/scanner.py
index b75d4737..b59c4ef2 100644
--- a/odoo_repository/lib/scanner.py
+++ b/odoo_repository/lib/scanner.py
@@ -421,6 +421,8 @@ def __init__(
org: str,
name: str,
clone_url: str,
+ # FIXME: put specific branch names to clone in 'migration_path':
+ # E.g. [('14.0', 'master'), ('18.0', '18.0-mig')]
migration_path: tuple[str],
new_repo_name: str = None,
new_repo_url: str = None,
@@ -431,7 +433,7 @@ def __init__(
workaround_fs_errors: bool = False,
clone_name: str = None,
):
- branches = sorted(migration_path)
+ branches = [mp[1] for mp in sorted(migration_path)]
super().__init__(
org,
name,
@@ -460,7 +462,7 @@ def sync(self, fetch=True):
self._set_git_remote_url(repo, self.new_repo_name, self.new_repo_url)
return res
- def scan(self, addons_path=".", module_names=None):
+ def scan(self, addons_path=".", target_addons_path=".", module_names=None):
# Clone/fetch has been done during the repository scan, the migration
# scan will be processed on the current history of commits
res = self.sync(fetch=False)
@@ -468,7 +470,10 @@ def scan(self, addons_path=".", module_names=None):
# there is nothing to scan then.
if not res:
return False
- source_branch, target_branch = self.migration_path
+ source_version = self.migration_path[0][0]
+ source_branch = self.migration_path[0][1]
+ target_version = self.migration_path[1][0]
+ target_branch = self.migration_path[1][1]
target_remote = "origin"
with self.repo() as repo:
if self.new_repo_name and self.new_repo_url:
@@ -482,10 +487,13 @@ def scan(self, addons_path=".", module_names=None):
):
return self._scan_migration_path(
repo,
+ source_version,
source_branch,
target_remote,
+ target_version,
target_branch,
addons_path=addons_path,
+ target_addons_path=target_addons_path,
module_names=module_names,
)
return res
@@ -493,10 +501,13 @@ def scan(self, addons_path=".", module_names=None):
def _scan_migration_path(
self,
repo,
+ source_version,
source_branch,
target_remote,
+ target_version,
target_branch,
addons_path=".",
+ target_addons_path=".",
module_names=None,
):
repo_source_commit = self._get_last_fetched_commit(repo, source_branch)
@@ -520,23 +531,27 @@ def _scan_migration_path(
continue
repo_id = self._get_odoo_repository_id()
module_branch_id = self._get_odoo_module_branch_id(
- repo_id, module, source_branch
+ repo_id, module, source_version
)
if not module_branch_id:
_logger.warning(
- "Module '%s' for branch %s does not exist on Odoo, "
+ "Module '%s' for version %s does not exist on Odoo, "
"a new scan of the repository is required. Aborted"
- % (module, source_branch)
+ % (module, source_version)
)
continue
# For each module and source/target branch:
# - get commit of 'module' relative to the last fetched commit
# - get commit of 'module' relative to the last scanned commit
+ module_path = str(pathlib.Path(addons_path).joinpath(module))
+ target_module_path = str(
+ pathlib.Path(target_addons_path).joinpath(target_module)
+ )
module_source_tree = self._get_subtree(
- repo.commit(repo_source_commit).tree, module
+ repo.commit(repo_source_commit).tree, module_path
)
module_target_tree = self._get_subtree(
- repo.commit(repo_target_commit).tree, target_module
+ repo.commit(repo_target_commit).tree, target_module_path
)
module_source_commit = self._get_last_commit_of_git_tree(
repo_source_commit, module_source_tree
@@ -550,7 +565,7 @@ def _scan_migration_path(
)
# Retrieve existing migration data if any and check if it is outdated
data = self._get_odoo_module_branch_migration_data(
- repo_id, module, source_branch, target_branch
+ repo_id, module, source_version, target_version
)
if (
data.get("last_source_mig_scanned_commit") != module_source_commit
@@ -559,11 +574,14 @@ def _scan_migration_path(
scanned_data = self._scan_module(
repo,
addons_path,
+ target_addons_path,
module,
target_module,
module_branch_id,
+ source_version,
source_branch,
target_remote,
+ target_version,
target_branch,
module_source_commit,
module_target_commit,
@@ -579,11 +597,14 @@ def _scan_module(
self,
repo: git.Repo,
addons_path: str,
+ target_addons_path: str,
module: str,
target_module: str,
module_branch_id: int,
+ source_version: str,
source_branch: str,
target_remote: str,
+ target_version: str,
target_branch: str,
source_commit: str,
target_commit: str,
@@ -595,14 +616,19 @@ def _scan_module(
"""Collect the migration data of a module."""
data = {
"addons_path": addons_path,
+ "target_addons_path": addons_path,
"module": module,
+ "source_version": source_version,
"source_branch": source_branch,
+ "target_version": target_version,
"target_branch": target_branch,
"source_commit": source_last_scanned_commit,
"target_commit": target_last_scanned_commit,
}
module_path = str(pathlib.Path(addons_path).joinpath(module))
- target_module_path = str(pathlib.Path(addons_path).joinpath(target_module))
+ target_module_path = str(
+ pathlib.Path(target_addons_path).joinpath(target_module)
+ )
# If files updated in the module since the last scan are not relevant
# (e.g. all new commits are updating PO files), we skip the scan.
source_scan_relevant = self._is_scan_module_relevant(
@@ -631,14 +657,16 @@ def _scan_module(
"%s: relevant changes detected in '%s' (%s -> %s)",
self.full_name,
module if source_scan_relevant else target_module,
- source_branch,
- target_branch,
+ source_version,
+ target_version,
)
oca_port_data = self._run_oca_port(
module_path,
target_module_path,
+ source_version,
source_branch,
target_remote,
+ target_version,
target_branch,
)
data["report"] = oca_port_data
@@ -696,8 +724,10 @@ def _run_oca_port(
self,
module_path,
target_module_path,
+ source_version,
source_branch,
target_remote,
+ target_version,
target_branch,
):
_logger.info(
@@ -710,7 +740,9 @@ def _run_oca_port(
# Initialize the oca-port app
params = {
"source": f"origin/{source_branch}",
+ "source_version": source_version,
"target": f"{target_remote}/{target_branch}",
+ "target_version": target_version,
"addon_path": module_path,
"target_addon_path": target_module_path,
"upstream_org": self.org,
@@ -756,7 +788,7 @@ def _get_odoo_module_branch_migration_id(
raise NotImplementedError
def _get_odoo_module_branch_migration_data(
- self, repo_id, module, source_branch, target_branch
+ self, repo_id, module, source_version, target_version
) -> dict:
"""Return last scanned commits regarding `module`."""
raise NotImplementedError
@@ -776,6 +808,7 @@ def __init__(
org: str,
name: str,
clone_url: str,
+ version: str,
branch: str,
addons_paths_data: list,
repositories_path: str = None,
@@ -797,6 +830,7 @@ def __init__(
workaround_fs_errors,
clone_name,
)
+ self.version = version
self.branch = branch
self.addons_paths_data = addons_paths_data
@@ -813,8 +847,13 @@ def detect_modules_to_scan(self):
def _detect_modules_to_scan(self, repo, repo_id):
if not self._branch_exists(repo, self.branch):
return
- branch_id = self._get_odoo_branch_id(repo_id, self.branch)
- repo_branch_id = self._create_odoo_repository_branch(repo_id, branch_id)
+ branch_id = self._get_odoo_branch_id(self.version)
+ cloned_branch = None
+ if self.version != self.branch:
+ cloned_branch = self.branch
+ repo_branch_id = self._create_odoo_repository_branch(
+ repo_id, branch_id, cloned_branch=cloned_branch
+ )
last_fetched_commit = self._get_last_fetched_commit(repo, self.branch)
last_scanned_commit = self._get_repo_last_scanned_commit(repo_branch_id)
data = {
@@ -877,8 +916,13 @@ def _detect_modules_to_scan_in_addons_path(
def scan_module(self, module_path, specs):
self._apply_git_global_config()
repo_id = self._get_odoo_repository_id()
- branch_id = self._get_odoo_branch_id(repo_id, self.branch)
- repo_branch_id = self._create_odoo_repository_branch(repo_id, branch_id)
+ branch_id = self._get_odoo_branch_id(self.version)
+ cloned_branch = None
+ if self.version != self.branch:
+ cloned_branch = self.branch
+ repo_branch_id = self._create_odoo_repository_branch(
+ repo_id, branch_id, cloned_branch=cloned_branch
+ )
with self.repo() as repo:
# Checkout the source branch to perform module code analysis
branch_commit = self._get_last_fetched_commit(repo, self.branch)
@@ -1049,13 +1093,8 @@ def _get_odoo_repository_id(self):
"""Return the ID of the 'odoo.repository' record."""
raise NotImplementedError
- def _get_odoo_branch_id(self, repo_id, branch):
- """Return the ID of the relevant 'odoo.branch' record.
-
- If the repository is cloned from a specific branch name
- (like 'master' or 'main'), return the ID of the configured
- Odoo version (`odoo.branch.odoo_version_id`).
- """
+ def _get_odoo_branch_id(self, version):
+ """Return the ID of the relevant 'odoo.branch' record."""
raise NotImplementedError
def _get_odoo_repository_branch_id(self, repo_id, branch_id):
diff --git a/odoo_repository/migrations/16.0.1.2.0/pre-migration.py b/odoo_repository/migrations/16.0.1.2.0/pre-migration.py
new file mode 100644
index 00000000..158310ab
--- /dev/null
+++ b/odoo_repository/migrations/16.0.1.2.0/pre-migration.py
@@ -0,0 +1,43 @@
+import logging
+
+from openupgradelib import openupgrade as ou
+
+from odoo.tools import sql
+
+_logger = logging.getLogger(__name__)
+
+
+def migrate(cr, version):
+ if not version:
+ return
+ set_xml_ids_on_odoo_branch(cr)
+ migrate_repository_clone_branch_id_to_repository_branch(cr)
+
+
+def set_xml_ids_on_odoo_branch(cr):
+ _logger.info("Add XML-ID on 'odoo.branch' 18.0...")
+ query = "SELECT id FROM odoo_branch WHERE name='18.0';"
+ cr.execute(query)
+ row = cr.fetchone()
+ if row:
+ branch_id = row[0]
+ ou.add_xmlid(cr, "odoo_repository", "odoo_branch_18", "odoo.branch", branch_id)
+
+
+def migrate_repository_clone_branch_id_to_repository_branch(cr):
+ _logger.info(
+ "Migrate 'clone_branch_id' from 'odoo.repository' to 'odoo.repository.branch'..."
+ )
+ # Create 'odoo_repository_branch.cloned_branch'
+ if not sql.column_exists(cr, "odoo_repository_branch", "cloned_branch"):
+ sql.create_column(cr, "odoo_repository_branch", "cloned_branch", "varchar")
+ # Migrate values from 'odoo_repository.clone_branch_id' to this new column
+ query = """
+ UPDATE odoo_repository_branch
+ SET cloned_branch=br.name
+ FROM odoo_repository repo
+ JOIN odoo_branch br
+ ON repo.clone_branch_id=br.id
+ WHERE repo.id = odoo_repository_branch.repository_id;
+ """
+ cr.execute(query)
diff --git a/odoo_repository/migrations/16.0.1.3.0/post-migration.py b/odoo_repository/migrations/16.0.1.3.0/post-migration.py
new file mode 100644
index 00000000..88da69e6
--- /dev/null
+++ b/odoo_repository/migrations/16.0.1.3.0/post-migration.py
@@ -0,0 +1,19 @@
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+def migrate(cr, version):
+ if not version:
+ return
+ set_manual_branches_on_odoo_repository(cr)
+
+
+def set_manual_branches_on_odoo_repository(cr):
+ _logger.info("Set 'manual_branches = True' on specific repositories...")
+ query = """
+ UPDATE odoo_repository
+ SET manual_branches = true
+ WHERE specific = true;
+ """
+ cr.execute(query)
diff --git a/odoo_repository/models/odoo_branch.py b/odoo_repository/models/odoo_branch.py
index 66b0f64d..8d26dae2 100644
--- a/odoo_repository/models/odoo_branch.py
+++ b/odoo_repository/models/odoo_branch.py
@@ -1,16 +1,26 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
-from odoo import api, fields, models
+import re
+
+from odoo import _, api, fields, models
+from odoo.exceptions import ValidationError
class OdooBranch(models.Model):
_name = "odoo.branch"
- _description = "Odoo Branch"
+ _description = "Odoo Branch/Version"
_order = "sequence, name"
- name = fields.Char(required=True, index=True)
- odoo_version = fields.Boolean(default=True)
+ name = fields.Char(
+ string="Version",
+ required=True,
+ index=True,
+ help=(
+ "An Odoo version is also used as an Odoo branch name in generic "
+ "repositories (Odoo, OCA...)."
+ ),
+ )
active = fields.Boolean(default=True)
repository_branch_ids = fields.One2many(
comodel_name="odoo.repository.branch",
@@ -24,6 +34,14 @@ class OdooBranch(models.Model):
("name_uniq", "UNIQUE (name)", "This branch already exists."),
]
+ @api.constrains("name")
+ def _constrains_name(self):
+ odoo_version_pattern = r"^[0-9]+\.[0-9]$"
+ for rec in self:
+ version = re.search(odoo_version_pattern, rec.name)
+ if not version:
+ raise ValidationError(_("Version must match the pattern 'x.y'."))
+
@api.model
def _recompute_sequence(self):
"""Recompute the 'sequence' field to get release branches sorted."""
@@ -41,7 +59,6 @@ def _recompute_sequence(self):
ORDER BY string_to_array(name, '.')::int[]
) AS position
FROM odoo_branch
- WHERE odoo_version = true
) as pos
WHERE pos.id = %(id)s
)
@@ -76,8 +93,6 @@ def action_force_scan(self):
"""
return self.action_scan(force=True)
- def _get_all_odoo_versions(self):
+ def _get_all_odoo_versions(self, active_test=False):
"""Return all Odoo versions, even archived ones."""
- return self.with_context(active_test=False).search(
- [("odoo_version", "=", True)]
- )
+ return self.with_context(active_test=active_test).search([])
diff --git a/odoo_repository/models/odoo_module_branch.py b/odoo_repository/models/odoo_module_branch.py
index 4250cde9..569e6c89 100644
--- a/odoo_repository/models/odoo_module_branch.py
+++ b/odoo_repository/models/odoo_module_branch.py
@@ -58,8 +58,7 @@ class OdooModuleBranch(models.Model):
# modules without knowing in advance what is their repo (orphaned modules).
comodel_name="odoo.branch",
ondelete="cascade",
- string="Branch",
- domain=[("odoo_version", "=", True)],
+ string="Odoo Version",
required=True,
index=True,
)
@@ -243,16 +242,23 @@ def _compute_full_path(self):
rec.module_name
)
- @api.depends("repository_id.repo_url", "branch_name", "addons_path", "module_name")
+ @api.depends(
+ "repository_id.repo_url",
+ "branch_name",
+ "repository_branch_id.cloned_branch",
+ "addons_path",
+ "module_name",
+ )
def _compute_url(self):
for rec in self:
rec.url = False
if not rec.repository_id:
continue
- module_path = "/".join([self.addons_path or ".", self.module_name])
- rec.url = self.repository_id._get_resource_url(
- self.branch_name, module_path
- )
+ branch = rec.branch_name
+ if rec.repository_branch_id.cloned_branch:
+ branch = rec.repository_branch_id.cloned_branch
+ module_path = "/".join([rec.addons_path or ".", rec.module_name])
+ rec.url = rec.repository_id._get_resource_url(branch, module_path)
@api.depends("repository_branch_id.name", "module_id.name")
def _compute_name(self):
diff --git a/odoo_repository/models/odoo_module_branch_version.py b/odoo_repository/models/odoo_module_branch_version.py
index 534ee988..e248a24e 100644
--- a/odoo_repository/models/odoo_module_branch_version.py
+++ b/odoo_repository/models/odoo_module_branch_version.py
@@ -105,8 +105,10 @@ def _compute_migration_script_url(self):
rec.manifest_value,
]
)
+ rb = rec.module_branch_id.repository_branch_id
+ branch_name = rb.cloned_branch or rb.branch_id.name
rec.migration_script_url = repo._get_resource_url(
- rec.module_branch_id.branch_name, migration_path
+ branch_name, migration_path
)
def _to_dict(self):
diff --git a/odoo_repository/models/odoo_repository.py b/odoo_repository/models/odoo_repository.py
index 632d372d..521c8a58 100644
--- a/odoo_repository/models/odoo_repository.py
+++ b/odoo_repository/models/odoo_repository.py
@@ -77,19 +77,6 @@ class OdooRepository(models.Model):
string="Token",
help="Token used to clone/fetch this repository.",
)
- clone_branch_id = fields.Many2one(
- comodel_name="odoo.branch",
- ondelete="restrict",
- string="Branch to clone",
- help="Branch to clone if different than configured ones",
- domain=[("odoo_version", "=", False)],
- )
- odoo_version_id = fields.Many2one(
- comodel_name="odoo.branch",
- ondelete="restrict",
- string="Odoo Version",
- domain=[("odoo_version", "=", True)],
- )
active = fields.Boolean(default=True)
addons_path_ids = fields.Many2many(
comodel_name="odoo.repository.addons_path",
@@ -100,7 +87,6 @@ class OdooRepository(models.Model):
comodel_name="odoo.repository.branch",
inverse_name="repository_id",
string="Branches",
- readonly=True,
)
scan_weekday_ids = fields.Many2many(
comodel_name="time.weekday",
@@ -110,14 +96,17 @@ class OdooRepository(models.Model):
"certain days only. If not defined, the scan will happen every day."
),
)
+ manual_branches = fields.Boolean(
+ string="Configure branches manually",
+ help=(
+ "By default repository branches follows the configured Odoo versions "
+ "(e.g: 17.0, 18.0...). Enable this option to configure your own branches."
+ ),
+ )
specific = fields.Boolean(
- compute="_compute_specific",
- store=True,
- readonly=False,
help=(
- "Host specific modules. "
- "By default if the repository clones a specific branch, "
- "that means it hosts specific modules."
+ "Host specific modules (that are not generic). "
+ "Used for project repositories."
),
)
@@ -143,8 +132,8 @@ def _compute_github_url(self):
_sql_constraints = [
(
- "org_id_name_repository_id_uniq",
- "UNIQUE (org_id, name, odoo_version_id)",
+ "org_id_name_uniq",
+ "UNIQUE (org_id, name)",
"This repository already exists.",
),
]
@@ -154,11 +143,6 @@ def _compute_display_name(self):
for rec in self:
rec.display_name = f"{rec.org_id.name}/{rec.name}"
- @api.depends("clone_branch_id")
- def _compute_specific(self):
- for rec in self:
- rec.specific = bool(rec.clone_branch_id)
-
@api.onchange("repo_url", "to_scan", "clone_url")
def _onchange_repo_url(self):
if not self.repo_url:
@@ -171,9 +155,11 @@ def _onchange_repo_url(self):
self.clone_url = self.repo_url
break
- @api.model
- def _get_odoo_branches_to_clone(self):
- return self.env["odoo.branch"].search([("odoo_version", "=", True)])
+ def _get_odoo_branches_to_scan(self):
+ self.ensure_one()
+ if self.manual_branches:
+ return self.branch_ids.branch_id
+ return self.env["odoo.branch"]._get_all_odoo_versions(active_test=True)
def _cron_scanner_domain(self):
today = fields.Date.today()
@@ -197,12 +183,16 @@ def cron_scanner(self, branches=None, force=False):
As the scanner is run on the same server than Odoo, a special class
`RepositoryScannerOdooEnv` is used so the scanner can request Odoo
through an environment (api.Environment).
+
+ `branches` parameter allows to filter the `odoo.branch` to take into
+ account for the scan, e.g. `branches=["16.0", "18.0"]`.
"""
repositories = self.search(self._cron_scanner_domain())
- if not branches:
- branches = self._get_odoo_branches_to_clone().mapped("name")
+ branches_ = self.env["odoo.branch"]._get_all_odoo_versions(active_test=True)
+ if branches:
+ branches_ = branches_.filtered(lambda br: br.name in branches)
for repo in repositories:
- repo.action_scan(branches=branches, force=force, raise_exc=False)
+ repo.action_scan(branch_ids=branches_.ids, force=force, raise_exc=False)
def _check_config(self):
# Check the configuration of repositories folder
@@ -250,26 +240,32 @@ def _check_existing_jobs(self, raise_exc=True):
return True
return False
- def action_scan(self, branches=None, force=False, raise_exc=True):
+ def action_scan(self, branch_ids=None, force=False, raise_exc=True):
"""Scan the whole repository."""
self._check_config()
for rec in self:
+ if not rec.to_scan:
+ continue
if rec._check_existing_jobs(raise_exc=raise_exc):
continue
- # Copy `branches` list to not override initial values
- branches_ = branches and branches[:] or []
- if not rec.to_scan:
- return False
- if rec.clone_branch_id:
- # Repository qualified with e.g. '17.0' branch but cloning a
- # different branch like 'main'
- branches_ = [rec.clone_branch_id.name]
- if not branches_:
- branches_ = rec._get_odoo_branches_to_clone().mapped("name")
- if not branches_:
- raise UserError(_("No branches to scan."))
+ # Get branch records to scan
+ branches = rec._get_odoo_branches_to_scan()
+ if branch_ids:
+ branches = branches & self.env["odoo.branch"].search(
+ [("id", "in", branch_ids)]
+ )
+ if not branches:
+ continue
+ # Create a list of tuples ({odoo_version}, {branch_name})
+ versions_branches = [(branch.name, branch.name) for branch in branches]
+ if rec.manual_branches:
+ versions_branches = [
+ (rb.branch_id.name, rb.cloned_branch or rb.branch_id.name)
+ for rb in rec.branch_ids
+ if rb.branch_id in branches
+ ]
if force:
- rec._reset_scanned_commits(branches_)
+ rec._reset_scanned_commits(branch_ids=branch_ids)
# Scan repository branches sequentially as they need to be checked out
# to perform the analysis
# Here the launched job is responsible to:
@@ -278,27 +274,33 @@ def action_scan(self, branches=None, force=False, raise_exc=True):
# 3) spawn a job to update the last scanned commit of the repo/branch
# 4) spawn the next job responsible to detect modules updated
# on the next branch
- branch = branches_[0]
- next_branches = branches_[1:]
+ version_branch = versions_branches[0]
+ next_versions_branches = versions_branches[1:]
job = rec._create_job_detect_modules_to_scan_on_branch(
- branch, next_branches, branches_
+ version_branch, next_versions_branches, versions_branches
)
job.delay()
return True
def _create_job_detect_modules_to_scan_on_branch(
- self, branch, next_branches, all_branches
+ self, version_branch, next_versions_branches, all_versions_branches
):
self.ensure_one()
+ version, branch = version_branch
+ branch_str = branch
+ if version != branch:
+ branch_str = f"{branch} ({version})"
delayable = self.delayable(
- description=f"Detect modules to scan in {self.display_name}#{branch}",
+ description=f"Detect modules to scan in {self.display_name}#{branch_str}",
identity_key=identity_exact,
)
return delayable._detect_modules_to_scan_on_branch(
- branch, next_branches, all_branches
+ version_branch, next_versions_branches, all_versions_branches
)
- def _detect_modules_to_scan_on_branch(self, branch, next_branches, all_branches):
+ def _detect_modules_to_scan_on_branch(
+ self, version_branch, next_versions_branches, all_versions_branches
+ ):
"""Detect the modules to scan on `branch`.
It will spawn a job for each module to scan, and two other jobs to:
@@ -307,14 +309,15 @@ def _detect_modules_to_scan_on_branch(self, branch, next_branches, all_branches)
This ensure to scan different branches sequentially for a given repository.
"""
+ version, branch = version_branch
try:
# Get the list of modules updated since last scan
- params = self._prepare_scanner_parameters(branch)
+ params = self._prepare_scanner_parameters(version, branch)
scanner = RepositoryScannerOdooEnv(**params)
data = scanner.detect_modules_to_scan()
# Prepare all subsequent jobs based on modules to scan
jobs = self._create_subsequent_jobs(
- branch, next_branches, all_branches, data
+ version_branch, next_versions_branches, all_versions_branches, data
)
# Chain them altogether
if jobs:
@@ -322,13 +325,16 @@ def _detect_modules_to_scan_on_branch(self, branch, next_branches, all_branches)
except Exception as exc:
raise RetryableJobError("Scanner error") from exc
- def _create_subsequent_jobs(self, branch, next_branches, all_branches, data):
+ def _create_subsequent_jobs(
+ self, version_branch, next_versions_branches, all_versions_branches, data
+ ):
jobs = []
+ version, branch = version_branch
# Spawn one job per module to scan
for data_ in data.get("addons_paths", {}).values():
for module_path in data_["modules_to_scan"]:
job = self._create_job_scan_module_on_branch(
- branch, module_path, data_["specs"]
+ version, branch, module_path, data_["specs"]
)
jobs.append(job)
# + another one to update the last scanned commit of the repository
@@ -339,28 +345,31 @@ def _create_subsequent_jobs(self, branch, next_branches, all_branches, data):
)
jobs.append(job)
# + another one to detect modules to scan on the next branch
- branch = next_branches and next_branches[0]
- next_branches = next_branches[1:]
- if branch:
+ version_branch = next_versions_branches and next_versions_branches[0]
+ next_versions_branches = next_versions_branches[1:]
+ if version_branch:
jobs.append(
self._create_job_detect_modules_to_scan_on_branch(
- branch, next_branches, all_branches
+ version_branch, next_versions_branches, all_versions_branches
)
)
return jobs
- def _create_job_scan_module_on_branch(self, branch, module_path, specs):
+ def _create_job_scan_module_on_branch(self, version, branch, module_path, specs):
self.ensure_one()
+ branch_str = branch
+ if version != branch:
+ branch_str = f"{branch} ({version})"
delayable = self.delayable(
- description=f"Scan {self.display_name}#{branch} - {module_path}",
+ description=f"Scan {self.display_name}#{branch_str} - {module_path}",
identity_key=identity_exact,
)
- return delayable._scan_module_on_branch(branch, module_path, specs)
+ return delayable._scan_module_on_branch(version, branch, module_path, specs)
- def _scan_module_on_branch(self, branch, module_path, specs):
+ def _scan_module_on_branch(self, version, branch, module_path, specs):
"""Scan `module_path` from `branch`."""
try:
- params = self._prepare_scanner_parameters(branch)
+ params = self._prepare_scanner_parameters(version, branch)
scanner = RepositoryScannerOdooEnv(**params)
return scanner.scan_module(module_path, specs)
except Exception as exc:
@@ -378,22 +387,20 @@ def _create_job_update_last_scanned_commit(
)
return delayable._update_last_scanned_commit(last_scanned_commit)
- def _reset_scanned_commits(self, branches=None):
+ def _reset_scanned_commits(self, branch_ids=None):
"""Reset the scanned commits.
This will make the next repository scan restarting from the beginning,
and thus making it slower.
"""
self.ensure_one()
- if branches is None:
- branches = []
- branches_ = (
- self.branch_ids.filtered(lambda br: br.branch_id.name in branches)
- if branches and not self.clone_branch_id
- else self.branch_ids
+ if branch_ids is None:
+ branch_ids = self.branch_ids.branch_id.ids
+ repo_branches = self.branch_ids.filtered(
+ lambda rb: rb.branch_id.id in branch_ids
)
- branches_.write({"last_scanned_commit": False})
- branches_.module_ids.sudo().write({"last_scanned_commit": False})
+ repo_branches.write({"last_scanned_commit": False})
+ repo_branches.module_ids.sudo().write({"last_scanned_commit": False})
def _get_token(self):
"""Return the first available token found for this repository.
@@ -410,13 +417,14 @@ def _get_token(self):
or os.environ.get("GITHUB_TOKEN")
)
- def _prepare_scanner_parameters(self, branch):
+ def _prepare_scanner_parameters(self, version, branch):
ir_config = self.env["ir.config_parameter"]
repositories_path = ir_config.sudo().get_param(self._repositories_path_key)
return {
"org": self.org_id.name,
"name": self.name,
"clone_url": self.clone_url,
+ "version": version,
"branch": branch,
"addons_paths_data": self.addons_path_ids.read(
[
@@ -437,14 +445,14 @@ def _prepare_scanner_parameters(self, branch):
"env": self.env,
}
- def action_force_scan(self, branches=None, raise_exc=True):
+ def action_force_scan(self, branch_ids=None, raise_exc=True):
"""Force the scan of the repositories.
It will restart the scan without considering the last scanned commit,
overriding already collected module data if any.
"""
self.ensure_one()
- return self.action_scan(branches=branches, force=True, raise_exc=raise_exc)
+ return self.action_scan(branch_ids=branch_ids, force=True, raise_exc=raise_exc)
@api.model
def cron_fetch_data(self, branches=None, force=False):
@@ -456,7 +464,7 @@ def cron_fetch_data(self, branches=None, force=False):
)
if not main_node_url:
return False
- branch_domain = [("odoo_version", "=", True)]
+ branch_domain = []
if branches:
branch_domain.append(("name", "in", branches))
branches = self.env["odoo.branch"].search(branch_domain)
@@ -485,9 +493,7 @@ def _import_data(self, data):
def _prepare_module_branch_values(self, data):
# Get branch, repository and technical module
- branch = self.env["odoo.branch"].search(
- [("odoo_version", "=", True), ("name", "=", data["branch"])]
- )
+ branch = self.env["odoo.branch"].search([("name", "=", data["branch"])])
org = self._get_repository_org(data["repository"]["org"])
repository = self._get_repository(
org.id, data["repository"]["name"], data["repository"]
diff --git a/odoo_repository/models/odoo_repository_branch.py b/odoo_repository/models/odoo_repository_branch.py
index f6871899..d748c2a8 100644
--- a/odoo_repository/models/odoo_repository_branch.py
+++ b/odoo_repository/models/odoo_repository_branch.py
@@ -17,13 +17,26 @@ class OdooRepositoryBranch(models.Model):
index=True,
readonly=True,
)
+ manual_branches = fields.Boolean(
+ related="repository_id.manual_branches",
+ store=True,
+ )
+ specific = fields.Boolean(
+ related="repository_id.specific",
+ store=True,
+ )
branch_id = fields.Many2one(
comodel_name="odoo.branch",
ondelete="cascade",
- string="Branch",
+ string="Odoo Version",
required=True,
index=True,
- readonly=True,
+ )
+ cloned_branch = fields.Char(
+ help=(
+ "Force the branch to clone (optional). Used on repositories with "
+ "'Configure branches manually' option enabled."
+ ),
)
module_ids = fields.One2many(
comodel_name="odoo.module.branch",
@@ -55,7 +68,7 @@ def _compute_active(self):
def action_scan(self, force=False, raise_exc=True):
"""Scan the repository/branch."""
return self.repository_id.action_scan(
- branches=self.branch_id.mapped("name"), force=force, raise_exc=raise_exc
+ branch_ids=self.branch_id.ids, force=force, raise_exc=raise_exc
)
def action_force_scan(self, raise_exc=True):
diff --git a/odoo_repository/tests/common.py b/odoo_repository/tests/common.py
index 9c91edb7..2242a1df 100644
--- a/odoo_repository/tests/common.py
+++ b/odoo_repository/tests/common.py
@@ -52,9 +52,9 @@ def setUp(self):
self.branch = self.env["odoo.branch"].create(
{
"name": self.branch1_name,
- "odoo_version": True,
}
)
+ self.branch.active = True
# branch2
self.branch2_name = self.source2.split("/")[1]
self.branch2 = (
@@ -66,9 +66,9 @@ def setUp(self):
self.branch2 = self.env["odoo.branch"].create(
{
"name": self.branch2_name,
- "odoo_version": True,
}
)
+ self.branch2.active = True
# branch3
self.branch3_name = self.target2.split("/")[1]
# technical module
@@ -136,10 +136,10 @@ def _update_module_installable_on_branch(self, branch, installable=True):
)
return commit.hexsha
- def _run_odoo_repository_action_scan(self, branch, force=False):
- """Run `action_scan` for given `branch` on the Odoo repository."""
+ def _run_odoo_repository_action_scan(self, branch_id, force=False):
+ """Run `action_scan` for given `branch_id` on the Odoo repository."""
self.odoo_repository.with_context(queue_job__no_delay=True).action_scan(
- [branch], force=force
+ branch_ids=[branch_id], force=force
)
def _create_odoo_module(self, name):
diff --git a/odoo_repository/tests/test_odoo_repository_scan.py b/odoo_repository/tests/test_odoo_repository_scan.py
index 3d6c0ebb..558963ba 100644
--- a/odoo_repository/tests/test_odoo_repository_scan.py
+++ b/odoo_repository/tests/test_odoo_repository_scan.py
@@ -1,6 +1,8 @@
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+from odoo import fields
+
from .common import Common
@@ -13,7 +15,7 @@ def test_action_scan_basic(self):
self.assertFalse(self.odoo_repository.specific)
module = self.env["odoo.module"].search([("name", "=", self.module_name)])
self.assertFalse(module)
- self._run_odoo_repository_action_scan(self.branch.name)
+ self._run_odoo_repository_action_scan(self.branch.id)
# Check module technical name
module = self.env["odoo.module"].search([("name", "=", self.module_name)])
self.assertTrue(module)
@@ -54,8 +56,16 @@ def test_action_scan_basic(self):
def test_action_scan_repo_specific(self):
"""Test the creation of a module when scanning a specific repository."""
self.odoo_repository.specific = True
+ self.odoo_repository.write(
+ {
+ "specific": True,
+ "branch_ids": [
+ fields.Command.create({"branch_id": self.branch.id}),
+ ],
+ }
+ )
self.assertTrue(self.odoo_repository.specific)
- self._run_odoo_repository_action_scan(self.branch.name)
+ self._run_odoo_repository_action_scan(self.branch.id)
# Check module data
module = self.env["odoo.module"].search([("name", "=", self.module_name)])
module_branch = self.env["odoo.module.branch"].search(
@@ -71,7 +81,7 @@ def test_action_scan_repo_module_exists(self):
scan will trigger an update of its data.
"""
# First scan, like in `test_action_scan_first_time`
- self._run_odoo_repository_action_scan(self.branch.name)
+ self._run_odoo_repository_action_scan(self.branch.id)
module = self.env["odoo.module"].search([("name", "=", self.module_name)])
module_branch = self.env["odoo.module.branch"].search(
[("module_id", "=", module.id), ("branch_id", "=", self.branch.id)]
@@ -79,7 +89,7 @@ def test_action_scan_repo_module_exists(self):
# Change some data in the module before triggering the second scan
module_branch.write({"title": False})
# Launch a second scan (force it to make it happen)
- self._run_odoo_repository_action_scan(self.branch.name, force=True)
+ self._run_odoo_repository_action_scan(self.branch.id, force=True)
self.assertEqual(module_branch.title, "Test")
def test_action_scan_orphaned_module_exists(self):
@@ -96,7 +106,7 @@ def test_action_scan_orphaned_module_exists(self):
# Create an orphaned module.
# To ease its creation, we run a scan to get the record created, and
# we update it to make it orphaned.
- self._run_odoo_repository_action_scan(self.branch.name)
+ self._run_odoo_repository_action_scan(self.branch.id)
module = self.env["odoo.module"].search([("name", "=", self.module_name)])
module_branch = self.env["odoo.module.branch"].search(
[("module_id", "=", module.id), ("branch_id", "=", self.branch.id)]
@@ -109,7 +119,7 @@ def test_action_scan_orphaned_module_exists(self):
}
)
# Launch a scan
- self._run_odoo_repository_action_scan(self.branch.name, force=True)
+ self._run_odoo_repository_action_scan(self.branch.id, force=True)
self.assertEqual(module_branch.repository_id, self.odoo_repository)
def _create_wrong_repo_branch(self, repo_sequence=100):
@@ -134,7 +144,7 @@ def _create_wrong_repo_branch(self, repo_sequence=100):
def _create_unmerged_module_branch(self):
# To ease the creation of such module, we run a scan to get the record
# created, and we update it to make it unmerged/pending.
- self._run_odoo_repository_action_scan(self.branch.name)
+ self._run_odoo_repository_action_scan(self.branch.id)
module = self.env["odoo.module"].search([("name", "=", self.module_name)])
module_branch = self.env["odoo.module.branch"].search(
[("module_id", "=", module.id), ("branch_id", "=", self.branch.id)]
@@ -175,7 +185,7 @@ def test_action_scan_repo_generic_unmerged_module_exists(self):
self.assertFalse(module_branch.specific)
self.assertNotEqual(module_branch.repository_id, self.odoo_repository)
# Launch a scan
- self._run_odoo_repository_action_scan(self.branch.name, force=True)
+ self._run_odoo_repository_action_scan(self.branch.id, force=True)
self.assertFalse(module_branch.specific)
self.assertEqual(module_branch.repository_id, self.odoo_repository)
@@ -191,7 +201,7 @@ def test_action_scan_repo_specific_unmerged_module_exists(self):
self.assertFalse(module_branch.specific)
self.assertNotEqual(module_branch.repository_id, self.odoo_repository)
# Launch a scan
- self._run_odoo_repository_action_scan(self.branch.name, force=True)
+ self._run_odoo_repository_action_scan(self.branch.id, force=True)
# Unmerged module hasn't been attached to the scanned repository
self.assertNotEqual(module_branch.repository_id, self.odoo_repository)
@@ -206,7 +216,7 @@ def test_action_scan_uninstallable_module(self):
self._update_module_installable_on_branch(self.branch.name, installable=False)
module = self.env["odoo.module"].search([("name", "=", self.module_name)])
self.assertFalse(module)
- self._run_odoo_repository_action_scan(self.branch.name)
+ self._run_odoo_repository_action_scan(self.branch.id)
module = self.env["odoo.module"].search([("name", "=", self.module_name)])
self.assertTrue(module)
# Check module branch
diff --git a/odoo_repository/tests/test_repository_scanner.py b/odoo_repository/tests/test_repository_scanner.py
index 6740883d..7102b547 100644
--- a/odoo_repository/tests/test_repository_scanner.py
+++ b/odoo_repository/tests/test_repository_scanner.py
@@ -12,6 +12,7 @@ def _init_scanner(self, **params):
"org": self.org.name,
"name": self.repo_name,
"clone_url": self.repo_upstream_path,
+ "version": self.branch.name,
"branch": self.branch.name,
"addons_paths_data": [
{
@@ -46,14 +47,13 @@ def test_get_odoo_repository_id(self):
def test_get_odoo_branch_id(self):
scanner = self._init_scanner()
- repo_id = scanner._get_odoo_repository_id()
- branch_id = scanner._get_odoo_branch_id(repo_id, self.branch.name)
+ branch_id = scanner._get_odoo_branch_id(self.branch.name)
self.assertEqual(branch_id, self.branch.id)
def test_create_odoo_repository_branch(self):
scanner = self._init_scanner()
repo_id = scanner._get_odoo_repository_id()
- branch_id = scanner._get_odoo_branch_id(repo_id, self.branch.name)
+ branch_id = scanner._get_odoo_branch_id(self.branch.name)
# The repository branch doesn't exist yet
expected_repo_branch_id = scanner._get_odoo_repository_branch_id(
repo_id, branch_id
@@ -69,7 +69,7 @@ def test_create_odoo_repository_branch(self):
def test_get_repo_last_scanned_commit(self):
scanner = self._init_scanner()
repo_id = scanner._get_odoo_repository_id()
- branch_id = scanner._get_odoo_branch_id(repo_id, self.branch.name)
+ branch_id = scanner._get_odoo_branch_id(self.branch.name)
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
repo_branch = self.env["odoo.repository.branch"].browse(repo_branch_id)
# Nothing has been scanned until now
@@ -101,7 +101,7 @@ def test_detect_modules_to_scan_in_addons_path(self):
with scanner.repo() as repo:
scanner._checkout_branch(repo, self.branch.name)
repo_id = scanner._get_odoo_repository_id()
- branch_id = scanner._get_odoo_branch_id(repo_id, self.branch.name)
+ branch_id = scanner._get_odoo_branch_id(self.branch.name)
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
last_fetched_commit = scanner._get_last_fetched_commit(
repo, self.branch.name
@@ -124,7 +124,7 @@ def test_scan_module(self):
with scanner.repo() as repo:
scanner._checkout_branch(repo, self.branch.name)
repo_id = scanner._get_odoo_repository_id()
- branch_id = scanner._get_odoo_branch_id(repo_id, self.branch.name)
+ branch_id = scanner._get_odoo_branch_id(self.branch.name)
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
module_path = self.addon
remote_branch = f"origin/{self.branch.name}"
@@ -158,7 +158,7 @@ def test_push_scanned_data(self):
with scanner.repo() as repo:
scanner._checkout_branch(repo, self.branch.name)
repo_id = scanner._get_odoo_repository_id()
- branch_id = scanner._get_odoo_branch_id(repo_id, self.branch.name)
+ branch_id = scanner._get_odoo_branch_id(self.branch.name)
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
module = self.addon
remote_branch = f"origin/{self.branch.name}"
@@ -201,7 +201,7 @@ def test_update_last_scanned_commit(self):
scanner = self._init_scanner()
scanner._clone()
repo_id = scanner._get_odoo_repository_id()
- branch_id = scanner._get_odoo_branch_id(repo_id, self.branch.name)
+ branch_id = scanner._get_odoo_branch_id(self.branch.name)
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
repo_branch = self.env["odoo.repository.branch"].browse(repo_branch_id)
with scanner.repo() as repo:
diff --git a/odoo_repository/tests/test_sync_node.py b/odoo_repository/tests/test_sync_node.py
index 078b0a02..7b2db777 100644
--- a/odoo_repository/tests/test_sync_node.py
+++ b/odoo_repository/tests/test_sync_node.py
@@ -8,7 +8,7 @@ class TestSyncNode(Common):
def test_sync_node(self):
# Scan a repository
self.odoo_repository.with_context(queue_job__no_delay=True).action_scan(
- [self.branch.name]
+ self.branch.ids
)
# Check data to sync
data = self.env["odoo.module.branch"]._get_modules_data()
diff --git a/odoo_repository/utils/scanner.py b/odoo_repository/utils/scanner.py
index 5c66ef27..954b80e0 100644
--- a/odoo_repository/utils/scanner.py
+++ b/odoo_repository/utils/scanner.py
@@ -25,14 +25,8 @@ def _get_odoo_repository_id(self):
.id
)
- def _get_odoo_branch_id(self, repo_id, branch):
- repo = self.env["odoo.repository"].browse(repo_id)
- if repo.clone_branch_id and repo.odoo_version_id:
- return repo.odoo_version_id.id
- branch = self.env["odoo.branch"].search(
- [("name", "=", branch), ("odoo_version", "=", True)]
- )
- return branch.id
+ def _get_odoo_branch_id(self, version):
+ return self.env["odoo.branch"].search([("name", "=", version)]).id
def _get_odoo_repository_branch_id(self, repo_id, branch_id):
args = [
@@ -43,13 +37,15 @@ def _get_odoo_repository_branch_id(self, repo_id, branch_id):
if repo_branch:
return repo_branch.id
- def _create_odoo_repository_branch(self, repo_id, branch_id):
+ def _create_odoo_repository_branch(self, repo_id, branch_id, cloned_branch=None):
repo_branch_id = self._get_odoo_repository_branch_id(repo_id, branch_id)
if not repo_branch_id:
values = {
"repository_id": repo_id,
"branch_id": branch_id,
}
+ if cloned_branch:
+ values["cloned_branch"] = cloned_branch
repo_branch_model = self.env["odoo.repository.branch"]
repo_branch_id = repo_branch_model.create(values).id
return repo_branch_id
diff --git a/odoo_repository/views/odoo_branch.xml b/odoo_repository/views/odoo_branch.xml
index 3085adca..806fdbfd 100644
--- a/odoo_repository/views/odoo_branch.xml
+++ b/odoo_repository/views/odoo_branch.xml
@@ -31,10 +31,6 @@
name="name"
attrs="{'readonly': [('repository_branch_ids', '!=', [])]}"
/>
-
@@ -56,7 +52,6 @@
-
@@ -69,17 +64,12 @@
-
- Branches
+ Versions
ir.actions.act_window
odoo.branch
diff --git a/odoo_repository/views/odoo_repository.xml b/odoo_repository/views/odoo_repository.xml
index 85ffca76..c2f9862a 100644
--- a/odoo_repository/views/odoo_repository.xml
+++ b/odoo_repository/views/odoo_repository.xml
@@ -65,21 +65,10 @@
>
-
@@ -98,14 +87,22 @@
-
-
-
-
+
+
+
+
+
diff --git a/odoo_repository/views/odoo_repository_branch.xml b/odoo_repository/views/odoo_repository_branch.xml
index 26711933..25640b82 100644
--- a/odoo_repository/views/odoo_repository_branch.xml
+++ b/odoo_repository/views/odoo_repository_branch.xml
@@ -22,6 +22,7 @@
+
@@ -39,6 +40,7 @@
+
diff --git a/odoo_repository_migration/models/odoo_migration_path.py b/odoo_repository_migration/models/odoo_migration_path.py
index 7dd314be..b3db3de6 100644
--- a/odoo_repository_migration/models/odoo_migration_path.py
+++ b/odoo_repository_migration/models/odoo_migration_path.py
@@ -14,13 +14,11 @@ class OdooMigrationPath(models.Model):
source_branch_id = fields.Many2one(
comodel_name="odoo.branch",
ondelete="cascade",
- domain=[("odoo_version", "=", True)],
required=True,
)
target_branch_id = fields.Many2one(
comodel_name="odoo.branch",
ondelete="cascade",
- domain=[("odoo_version", "=", True)],
required=True,
)
diff --git a/odoo_repository_migration/models/odoo_module_branch.py b/odoo_repository_migration/models/odoo_module_branch.py
index a33a66c3..bf13371b 100644
--- a/odoo_repository_migration/models/odoo_module_branch.py
+++ b/odoo_repository_migration/models/odoo_module_branch.py
@@ -51,10 +51,7 @@ def _compute_next_odoo_version_id(self):
next_odoo_version = False
if rec.branch_id:
next_odoo_version = self.env["odoo.branch"].search(
- [
- ("odoo_version", "=", True),
- ("sequence", ">", rec.branch_id.sequence),
- ],
+ [("sequence", ">", rec.branch_id.sequence)],
limit=1,
)
rec.next_odoo_version_id = next_odoo_version
diff --git a/odoo_repository_migration/models/odoo_module_branch_migration.py b/odoo_repository_migration/models/odoo_module_branch_migration.py
index 720f4706..515b0b05 100644
--- a/odoo_repository_migration/models/odoo_module_branch_migration.py
+++ b/odoo_repository_migration/models/odoo_module_branch_migration.py
@@ -318,8 +318,8 @@ def _compute_migration_scan(self):
def push_scanned_data(self, module_branch_id, data):
migration_path = self.env["odoo.migration.path"].search(
[
- ("source_branch_id", "=", data["source_branch"]),
- ("target_branch_id", "=", data["target_branch"]),
+ ("source_branch_id", "=", data["source_version"]),
+ ("target_branch_id", "=", data["target_version"]),
]
)
values = {
diff --git a/odoo_repository_migration/models/odoo_repository.py b/odoo_repository_migration/models/odoo_repository.py
index 96d2ed75..b3a23203 100644
--- a/odoo_repository_migration/models/odoo_repository.py
+++ b/odoo_repository_migration/models/odoo_repository.py
@@ -19,16 +19,25 @@ class OdooRepository(models.Model):
default=False,
)
- def _reset_scanned_commits(self, branches=None):
- res = super()._reset_scanned_commits(branches)
- if branches is None:
- branches = []
- branches_ = (
- self.branch_ids.filtered(lambda br: br.branch_id.name in branches)
- if branches
- else self.branch_ids
+ def action_scan(self, branch_ids=None, force=False, raise_exc=True):
+ for rec in self:
+ # Scan only relevant branches regarding migration paths
+ rec_ctx = rec
+ if rec.specific:
+ rec_ctx = rec.with_context(strict_branches_scan=True)
+ super(OdooRepository, rec_ctx).action_scan(
+ branch_ids=branch_ids, force=force, raise_exc=raise_exc
+ )
+ return True
+
+ def _reset_scanned_commits(self, branch_ids=None):
+ res = super()._reset_scanned_commits(branch_ids=branch_ids)
+ if branch_ids is None:
+ branch_ids = self.branch_ids.branch_id.ids
+ repo_branches = self.branch_ids.filtered(
+ lambda rb: rb.branch_id.id in branch_ids
)
- branches_.module_ids.migration_ids.sudo().write(
+ repo_branches.module_ids.migration_ids.sudo().write(
{
"last_source_scanned_commit": False,
"last_target_scanned_commit": False,
@@ -36,12 +45,14 @@ def _reset_scanned_commits(self, branches=None):
)
return res
- def _create_subsequent_jobs(self, branch, next_branches, all_branches, data):
+ def _create_subsequent_jobs(
+ self, version_branch, next_versions_branches, all_versions_branches, data
+ ):
jobs = super()._create_subsequent_jobs(
- branch, next_branches, all_branches, data
+ version_branch, next_versions_branches, all_versions_branches, data
)
# Prepare migration scan jobs when its the last repository scan
- last_scan = not next_branches
+ last_scan = not next_versions_branches
if not last_scan:
return jobs
# Check if the addons_paths are compatible with 'oca_port'
@@ -50,50 +61,91 @@ def _create_subsequent_jobs(self, branch, next_branches, all_branches, data):
return jobs
# Override to run the MigrationScanner once branches are scanned
args = []
- if all_branches:
+ if all_versions_branches:
+ all_versions = [vb[0] for vb in all_versions_branches]
# A strict scan of branches avoids unwanted migration scans
# For instance if we are interested only by 14.0 and 17.0 branches,
# this avoids to scan other migration paths like 15.0 -> 17.0
- strict_scan = self.env.context.get("strict_branches_scan")
+ # NOTE: a strict scan always occurs on specific repositories
+ strict_scan = self.env.context.get("strict_branches_scan") or self.specific
args = [
"&" if strict_scan else "|",
- ("source_branch_id", "in", all_branches),
- ("target_branch_id", "in", all_branches),
+ ("source_branch_id", "in", all_versions),
+ ("target_branch_id", "in", all_versions),
]
migration_paths = self.env["odoo.migration.path"].search(args)
# Launch one job for all migration_paths
if migration_paths:
+ # Migration paths parameter containing the migration path ID +
+ # the Odoo versions and branches to scan.
+ # E.g. {MIG_PATH_ID: [('14.0', 'master'), ('18.0', '18.0-mig')], ...}
+ migration_paths_param = {}
+ for migration_path in migration_paths:
+ source_rb = self.branch_ids.filtered(
+ lambda rb: rb.branch_id == migration_path.source_branch_id
+ )
+ target_rb = self.branch_ids.filtered(
+ lambda rb: rb.branch_id == migration_path.target_branch_id
+ )
+ # Need the two Odoo versions of the migration path available
+ # in the scanned repository
+ if not source_rb or not target_rb:
+ continue
+ # Build list of tuples (Odoo version, branch name) corresponding
+ # to the migration path
+ versions_branches = [
+ (
+ source_rb.branch_id.name,
+ source_rb.cloned_branch or source_rb.branch_id.name,
+ ),
+ (
+ target_rb.branch_id.name,
+ target_rb.cloned_branch or target_rb.branch_id.name,
+ ),
+ ]
+ migration_paths_param[migration_path.id] = versions_branches
+
delayable = self.delayable(
description=f"Collect {self.display_name} migration data",
identity_key=identity_exact,
)
- job = delayable._scan_migration_paths(migration_paths.ids)
+ job = delayable._scan_migration_paths(migration_paths_param)
jobs.append(job)
return jobs
- def _scan_migration_paths(self, migration_path_ids):
+ def _scan_migration_paths(self, migration_paths_param):
"""Scan repository branches to collect modules migration data.
Spawn one job per module to scan.
"""
self.ensure_one()
jobs = []
- migration_paths = (
- self.env["odoo.migration.path"].browse(migration_path_ids).exists()
- )
- for migration_path in migration_paths:
+ for migration_path_id in migration_paths_param:
+ versions_branches = migration_paths_param[migration_path_id]
+ migration_path = (
+ self.env["odoo.migration.path"]
+ .browse(
+ # Job encodes dict key as string => convert it to integer
+ int(migration_path_id)
+ )
+ .exists()
+ )
+ if not migration_path:
+ continue
modules_to_scan = self._migration_get_modules_to_scan(migration_path)
if modules_to_scan:
jobs.extend(
self._migration_create_jobs_scan_module(
- migration_path, modules_to_scan
+ migration_path, versions_branches, modules_to_scan
)
)
if jobs:
chain(*jobs).delay()
return True
- def _migration_create_jobs_scan_module(self, migration_path, modules_to_scan):
+ def _migration_create_jobs_scan_module(
+ self, migration_path, versions_branches, modules_to_scan
+ ):
jobs = []
mig_path = (
migration_path.source_branch_id.name,
@@ -106,11 +158,15 @@ def _migration_create_jobs_scan_module(self, migration_path, modules_to_scan):
),
identity_key=identity_exact,
)
- job = delayable._scan_migration_module(migration_path.id, module.id)
+ job = delayable._scan_migration_module(
+ migration_path.id, versions_branches, module.id
+ )
jobs.append(job)
return jobs
- def _scan_migration_module(self, migration_path_id, module_branch_id):
+ def _scan_migration_module(
+ self, migration_path_id, versions_branches, module_branch_id
+ ):
"""Scan migration path for `module_branch_id`.
The migration scan can only occur if:
@@ -154,7 +210,7 @@ def _scan_migration_module(self, migration_path_id, module_branch_id):
f"{target_repository.display_name}."
)
params = self._prepare_migration_scanner_parameters(
- migration_path, target_repository
+ versions_branches, target_repository
)
module_names = [module.module_id.name]
if target_module:
@@ -164,7 +220,9 @@ def _scan_migration_module(self, migration_path_id, module_branch_id):
try:
scanner = MigrationScannerOdooEnv(**params)
return scanner.scan(
- addons_path=module.addons_path, module_names=module_names
+ addons_path=module.addons_path,
+ target_addons_path=target_module.addons_path or module.addons_path,
+ module_names=module_names,
)
except Exception as exc:
raise RetryableJobError("Scanner error") from exc
@@ -172,7 +230,8 @@ def _scan_migration_module(self, migration_path_id, module_branch_id):
def _migration_get_modules_to_scan(self, migration_path):
"""Return `odoo.module.branch` records that need a migration scan."""
self.ensure_one()
- return self.env["odoo.module.branch"].search(
+ mb_model = self.env["odoo.module.branch"]
+ modules = mb_model.search(
[
(
"repository_id",
@@ -183,21 +242,27 @@ def _migration_get_modules_to_scan(self, migration_path):
("migration_scan", "=", True),
]
)
+ module_ids = []
+ for module in modules:
+ migration = module.migration_ids.filtered(
+ lambda mig: mig.migration_path_id == migration_path
+ )
+ if migration and not migration.migration_scan:
+ # Skip module that do not need a scan for the given migration path
+ continue
+ module_ids.append(module.id)
+ return mb_model.browse(module_ids)
def _prepare_migration_scanner_parameters(
self, migration_path, target_repository=None
):
ir_config = self.env["ir.config_parameter"]
repositories_path = ir_config.sudo().get_param(self._repositories_path_key)
- mig_path = (
- migration_path.source_branch_id.name,
- migration_path.target_branch_id.name,
- )
params = {
"org": self.org_id.name,
"name": self.name,
"clone_url": self.clone_url,
- "migration_path": mig_path,
+ "migration_path": migration_path,
"repositories_path": repositories_path,
"repo_type": self.repo_type,
"ssh_key": self.ssh_key_id.private_key,
@@ -221,10 +286,10 @@ def _pre_create_or_update_module_branch(self, rec, values, raw_data):
values["migration_ids"] = []
for mig in migrations:
source_branch = self.env["odoo.branch"].search(
- [("odoo_version", "=", True), ("name", "=", mig["source_branch"])]
+ [("name", "=", mig["source_branch"])]
)
target_branch = self.env["odoo.branch"].search(
- [("odoo_version", "=", True), ("name", "=", mig["target_branch"])]
+ [("name", "=", mig["target_branch"])]
)
if not source_branch or not target_branch:
# Such branches are not configured on this instance, skip
diff --git a/odoo_repository_migration/tests/test_odoo_module_branch.py b/odoo_repository_migration/tests/test_odoo_module_branch.py
index 09bf8eb8..0797d32f 100644
--- a/odoo_repository_migration/tests/test_odoo_module_branch.py
+++ b/odoo_repository_migration/tests/test_odoo_module_branch.py
@@ -38,7 +38,9 @@ def _simulate_migration_scan(self, target_commit, report=None):
"""Helper method that pushes scanned migration data."""
data = {
"module": self.module_branch.module_name,
+ "source_version": self.branch.name,
"source_branch": self.branch.name,
+ "target_version": self.branch2.name,
"target_branch": self.branch2.name,
"source_commit": self.module_branch.last_scanned_commit,
"target_commit": target_commit,
diff --git a/odoo_repository_migration/utils/scanner.py b/odoo_repository_migration/utils/scanner.py
index c4f3eb81..a5734fcc 100644
--- a/odoo_repository_migration/utils/scanner.py
+++ b/odoo_repository_migration/utils/scanner.py
@@ -70,13 +70,13 @@ def _get_odoo_module_branch_migration_id(
return migration.id
def _get_odoo_module_branch_migration_data(
- self, repo_id: int, module: str, source_branch: str, target_branch: str
+ self, repo_id: int, module: str, source_version: str, target_version: str
) -> dict:
args = [
("module_branch_id.repository_id", "=", repo_id),
("module_id", "=", module),
- ("source_branch_id", "=", source_branch),
- ("target_branch_id", "=", target_branch),
+ ("source_branch_id", "=", source_version),
+ ("target_branch_id", "=", target_version),
]
migration = self.env["odoo.module.branch.migration"].search(args)
if migration: