diff --git a/odoo_repository/__init__.py b/odoo_repository/__init__.py
index f7209b1..5f713a8 100644
--- a/odoo_repository/__init__.py
+++ b/odoo_repository/__init__.py
@@ -1,2 +1,3 @@
from . import models
+from . import components
from . import controllers
diff --git a/odoo_repository/__manifest__.py b/odoo_repository/__manifest__.py
index 8cbc14c..6998f8e 100644
--- a/odoo_repository/__manifest__.py
+++ b/odoo_repository/__manifest__.py
@@ -1,9 +1,10 @@
# Copyright 2023 Camptocamp SA
+# Copyright 2026 Sébastien Alix
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
{
"name": "Odoo Repositories Data",
"summary": "Base module to host data collected from Odoo repositories.",
- "version": "18.0.1.0.0",
+ "version": "18.0.1.1.0",
"category": "Tools",
"author": "Camptocamp, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/module-composition-analysis",
@@ -15,9 +16,10 @@
"data/odoo_repository_org.xml",
"data/odoo_repository_addons_path.xml",
"data/odoo_repository.xml",
- "data/odoo.repository.csv",
"data/odoo_branch.xml",
"data/queue_job.xml",
+ "data/odoo_mca_backend.xml",
+ "data/ir_config_parameter.xml",
"views/menu.xml",
"views/authentication_token.xml",
"views/ssh_key.xml",
@@ -45,11 +47,14 @@
"base_time_window",
# OCA/queue
"queue_job",
+ # OCA/connector
+ "component",
],
"external_dependencies": {
"python": [
"gitpython",
"odoo-addons-parser",
+ "pyyaml",
# TODO to publish
# "odoo-repository-scanner"
],
diff --git a/odoo_repository/components/__init__.py b/odoo_repository/components/__init__.py
new file mode 100644
index 0000000..f8f3805
--- /dev/null
+++ b/odoo_repository/components/__init__.py
@@ -0,0 +1 @@
+from . import oca_repository_synchronizer
diff --git a/odoo_repository/components/oca_repository_synchronizer.py b/odoo_repository/components/oca_repository_synchronizer.py
new file mode 100644
index 0000000..b56e37e
--- /dev/null
+++ b/odoo_repository/components/oca_repository_synchronizer.py
@@ -0,0 +1,217 @@
+# Copyright 2026 Sébastien Alix
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+import logging
+
+import requests
+import yaml
+
+from odoo import Command
+
+from odoo.addons.component.core import Component
+from odoo.addons.queue_job.exception import RetryableJobError
+
+from ..utils import github
+
+_logger = logging.getLogger(__name__)
+
+
+class OCARepositorySynchronizer(Component):
+ """Component for synchronizing OCA repositories from GitHub configuration.
+
+ This component is syncing the list of OCA repositories in local database
+ from https://github.com/OCA/repo-maintainer-conf repository.
+ It creates new repositories, updates existing ones, and archive those
+ no longer declared in the OCA configuration.
+ """
+
+ _name = "oca.repository.synchronizer"
+ _collection = "odoo.mca.backend"
+ _usage = "oca.repository.synchronizer"
+
+ github_conf_api_url = "repos/OCA/repo-maintainer-conf/contents/conf/repo"
+
+ def _get_oca_repo_blacklist(self):
+ """Get OCA repository blacklist from configuration parameter.
+
+ Returns:
+ list: List of repository names to skip during synchronization
+ """
+ param_value = (
+ self.env["ir.config_parameter"]
+ .sudo()
+ .get_param("odoo_repository.oca_repo_blacklist", default="")
+ )
+ return [repo.strip() for repo in param_value.split(",") if repo.strip()]
+
+ def run(self):
+ """Execute OCA repository synchronization.
+
+ Fetches repository configurations from OCA/repo-maintainer-conf,
+ parses the YAML files, and synchronizes the local repository database by:
+ - Creating new repositories that exist in OCA config but not locally
+ - Updating existing repositories with any changes
+ - Archiving repositories that no longer exist in OCA config
+ """
+ try:
+ # Fetch YAML configurations from GitHub
+ repo_configs = self._fetch_oca_repo_configurations()
+ # Parse and extract repository data
+ current_repos = self._parse_oca_repo_configurations(repo_configs)
+ # Sync repositories (create/update/archive)
+ stats = self._sync_oca_repositories(current_repos)
+ _logger.info("OCA repository sync completed: %s", stats)
+ return stats
+ except Exception as e:
+ raise RetryableJobError("Failed to fetch OCA repositories") from e
+
+ def _fetch_oca_repo_configurations(self):
+ """Fetch OCA repository configurations from GitHub.
+
+ Returns:
+ dict: Repository configurations keyed by filename
+ """
+ try:
+ # Get list of files in conf/repo directory
+ files = github.request(self.work.env, self.github_conf_api_url)
+ repo_configs = {}
+ for file_info in files:
+ if file_info["name"].endswith(".yml"):
+ # Fetch the actual YAML content
+ download_url = file_info["download_url"]
+ response = requests.get(download_url, timeout=30)
+ response.raise_for_status()
+ repo_configs[file_info["name"]] = response.text
+ return repo_configs
+ except Exception as e:
+ _logger.error("Failed to fetch OCA repo configurations: %s", str(e))
+ raise
+
+ def _parse_oca_repo_configurations(self, repo_configs: dict):
+ """Parse YAML configurations and extract OCA repository data.
+
+ Args:
+ repo_configs: Dict of filename -> YAML content
+
+ Returns:
+ dict: Repository data keyed by repository name, only including
+ repositories with non-empty branches
+ """
+ current_repos = {}
+ for filename, yaml_content in repo_configs.items():
+ try:
+ # Parse YAML content
+ data = yaml.safe_load(yaml_content)
+ if not data:
+ continue
+ # Process each repository in the YAML file
+ for repo_name, repo_config in data.items():
+ # Get blacklist from backend configuration
+ blacklist = self._get_oca_repo_blacklist()
+ if repo_name in blacklist:
+ _logger.info("Skipping import of OCA repository %s", repo_name)
+ continue
+ # Skip repositories with default_branch = 'master' or 'main'
+ # These are not hosting Odoo modules
+ default_branch = repo_config.get("default_branch", "")
+ if default_branch in ["master", "main"]:
+ _logger.info(
+ "Skipping OCA repository %s "
+ "(default_branch=%s, not hosting Odoo modules)",
+ repo_name,
+ default_branch,
+ )
+ continue
+ # Only include repositories with branches.
+ # Others repositories are probably not hosting Odoo modules.
+ branches = repo_config.get("branches", [])
+ if not branches:
+ continue
+ # Construct repository URLs
+ repo_url = f"https://github.com/OCA/{repo_name}" # noqa: E231
+ current_repos[repo_name] = {
+ "name": repo_name,
+ "repo_url": repo_url,
+ "clone_url": repo_url,
+ "repo_type": "github",
+ }
+ except yaml.YAMLError as e:
+ _logger.warning("Failed to parse YAML file %s: %s", filename, str(e))
+ continue
+ except Exception as e:
+ _logger.warning("Error processing file %s: %s", filename, str(e))
+ continue
+ return current_repos
+
+ def _sync_oca_repositories(self, current_repos):
+ """Synchronize OCA repositories with current configuration.
+
+ Args:
+ current_repos: Dict of repository data from OCA configuration
+
+ Returns:
+ dict: Statistics about created/updated/archived repositories
+ """
+ stats = {"created": 0, "updated": 0, "archived": 0}
+ env = self.work.env
+ # Get OCA organization
+ oca_org = env.ref(
+ "odoo_repository.odoo_repository_org_oca", raise_if_not_found=False
+ )
+ if not oca_org:
+ _logger.warning("OCA organization not found in database")
+ return stats
+ # Get existing OCA repositories
+ odoo_repository = env["odoo.repository"]
+ existing_repos = odoo_repository.with_context(active_test=False).search(
+ [("org_id", "=", oca_org.id)]
+ )
+ # Process current repositories
+ for repo_name, repo_data in current_repos.items():
+ # Check if repository already exists
+ repo = existing_repos.filtered(
+ lambda r, repo_name=repo_name: r.name == repo_name
+ )
+ if repo:
+ # Update existing repository
+ update_vals = self._prepare_oca_repository_update_vals(repo_data)
+ # Only update if values have changed
+ if any(repo[field] != update_vals[field] for field in update_vals):
+ repo.write(update_vals)
+ stats["updated"] += 1
+ else:
+ # Create new repository
+ create_vals = self._prepare_oca_repository_create_vals(repo_data)
+ odoo_repository.create(create_vals)
+ stats["created"] += 1
+ # Archive repositories that no longer exist in configuration
+ for repo in existing_repos:
+ if repo.name not in current_repos and repo.active:
+ repo.active = repo.to_scan = False
+ stats["archived"] += 1
+ return stats
+
+ def _prepare_oca_repository_update_vals(self, repo_data):
+ return {
+ "repo_url": repo_data["repo_url"],
+ "clone_url": repo_data["clone_url"],
+ "repo_type": repo_data["repo_type"],
+ "active": True,
+ }
+
+ def _prepare_oca_repository_create_vals(self, repo_data):
+ oca_org = self.work.env.ref("odoo_repository.odoo_repository_org_oca")
+ addons_path_community = self.work.env.ref(
+ "odoo_repository.odoo_repository_addons_path_community"
+ )
+ return {
+ "org_id": oca_org.id,
+ "name": repo_data["name"],
+ "repo_url": repo_data["repo_url"],
+ "clone_url": repo_data["clone_url"],
+ "repo_type": repo_data["repo_type"],
+ "active": True,
+ "sequence": 200,
+ "to_scan": True, # Enable scanning by default
+ "addons_path_ids": [Command.link(addons_path_community.id)],
+ }
diff --git a/odoo_repository/data/ir_config_parameter.xml b/odoo_repository/data/ir_config_parameter.xml
new file mode 100644
index 0000000..6fcef25
--- /dev/null
+++ b/odoo_repository/data/ir_config_parameter.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ odoo_repository.oca_repo_blacklist
+ OCB,OpenUpgrade
+
+
diff --git a/odoo_repository/data/ir_cron.xml b/odoo_repository/data/ir_cron.xml
index 9a55c90..cc1f466 100644
--- a/odoo_repository/data/ir_cron.xml
+++ b/odoo_repository/data/ir_cron.xml
@@ -1,5 +1,6 @@
@@ -29,4 +30,18 @@
code
model.cron_fetch_data()
+
+
+ Odoo Repositories - Sync OCA repositories
+ 1
+ weeks
+
+
+
+ code
+ model.cron_sync_oca_repositories()
+
diff --git a/odoo_repository/data/odoo.repository.csv b/odoo_repository/data/odoo.repository.csv
deleted file mode 100644
index 652e0c9..0000000
--- a/odoo_repository/data/odoo.repository.csv
+++ /dev/null
@@ -1,216 +0,0 @@
-id,org_id:id,name,repo_url,clone_url,repo_type
-repo_oca_account_analytic,odoo_repository_org_oca,account-analytic,https://github.com/OCA/account-analytic,https://github.com/OCA/account-analytic,github
-repo_oca_account_budgeting,odoo_repository_org_oca,account-budgeting,https://github.com/OCA/account-budgeting,https://github.com/OCA/account-budgeting,github
-repo_oca_account_closing,odoo_repository_org_oca,account-closing,https://github.com/OCA/account-closing,https://github.com/OCA/account-closing,github
-repo_oca_account_consolidation,odoo_repository_org_oca,account-consolidation,https://github.com/OCA/account-consolidation,https://github.com/OCA/account-consolidation,github
-repo_oca_account_financial_reporting,odoo_repository_org_oca,account-financial-reporting,https://github.com/OCA/account-financial-reporting,https://github.com/OCA/account-financial-reporting,github
-repo_oca_account_financial_tools,odoo_repository_org_oca,account-financial-tools,https://github.com/OCA/account-financial-tools,https://github.com/OCA/account-financial-tools,github
-repo_oca_account_fiscal_rule,odoo_repository_org_oca,account-fiscal-rule,https://github.com/OCA/account-fiscal-rule,https://github.com/OCA/account-fiscal-rule,github
-repo_oca_account_invoice_reporting,odoo_repository_org_oca,account-invoice-reporting,https://github.com/OCA/account-invoice-reporting,https://github.com/OCA/account-invoice-reporting,github
-repo_oca_account_invoicing,odoo_repository_org_oca,account-invoicing,https://github.com/OCA/account-invoicing,https://github.com/OCA/account-invoicing,github
-repo_oca_account_payment,odoo_repository_org_oca,account-payment,https://github.com/OCA/account-payment,https://github.com/OCA/account-payment,github
-repo_oca_account_reconcile,odoo_repository_org_oca,account-reconcile,https://github.com/OCA/account-reconcile,https://github.com/OCA/account-reconcile,github
-repo_oca_agreement,odoo_repository_org_oca,agreement,https://github.com/OCA/agreement,https://github.com/OCA/agreement,github
-repo_oca_apps_store,odoo_repository_org_oca,apps-store,https://github.com/OCA/apps-store,https://github.com/OCA/apps-store,github
-repo_oca_bank_payment,odoo_repository_org_oca,bank-payment,https://github.com/OCA/bank-payment,https://github.com/OCA/bank-payment,github
-repo_oca_bank_payment_alternative,odoo_repository_org_oca,bank-payment-alternative,https://github.com/OCA/bank-payment-alternative,https://github.com/OCA/bank-payment-alternative,github
-repo_oca_bank_statement_import,odoo_repository_org_oca,bank-statement-import,https://github.com/OCA/bank-statement-import,https://github.com/OCA/bank-statement-import,github
-repo_oca_brand,odoo_repository_org_oca,brand,https://github.com/OCA/brand,https://github.com/OCA/brand,github
-repo_oca_business_requirement,odoo_repository_org_oca,business-requirement,https://github.com/OCA/business-requirement,https://github.com/OCA/business-requirement,github
-repo_oca_calendar,odoo_repository_org_oca,calendar,https://github.com/OCA/calendar,https://github.com/OCA/calendar,github
-repo_oca_commission,odoo_repository_org_oca,commission,https://github.com/OCA/commission,https://github.com/OCA/commission,github
-repo_oca_community_data_files,odoo_repository_org_oca,community-data-files,https://github.com/OCA/community-data-files,https://github.com/OCA/community-data-files,github
-repo_oca_connector,odoo_repository_org_oca,connector,https://github.com/OCA/connector,https://github.com/OCA/connector,github
-repo_oca_connector_accountedge,odoo_repository_org_oca,connector-accountedge,https://github.com/OCA/connector-accountedge,https://github.com/OCA/connector-accountedge,github
-repo_oca_connector_cmis,odoo_repository_org_oca,connector-cmis,https://github.com/OCA/connector-cmis,https://github.com/OCA/connector-cmis,github
-repo_oca_connector_ecommerce,odoo_repository_org_oca,connector-ecommerce,https://github.com/OCA/connector-ecommerce,https://github.com/OCA/connector-ecommerce,github
-repo_oca_connector_infor,odoo_repository_org_oca,connector-infor,https://github.com/OCA/connector-infor,https://github.com/OCA/connector-infor,github
-repo_oca_connector_interfaces,odoo_repository_org_oca,connector-interfaces,https://github.com/OCA/connector-interfaces,https://github.com/OCA/connector-interfaces,github
-repo_oca_connector_jira,odoo_repository_org_oca,connector-jira,https://github.com/OCA/connector-jira,https://github.com/OCA/connector-jira,github
-repo_oca_connector_lengow,odoo_repository_org_oca,connector-lengow,https://github.com/OCA/connector-lengow,https://github.com/OCA/connector-lengow,github
-repo_oca_connector_lims,odoo_repository_org_oca,connector-lims,https://github.com/OCA/connector-lims,https://github.com/OCA/connector-lims,github
-repo_oca_connector_magento,odoo_repository_org_oca,connector-magento,https://github.com/OCA/connector-magento,https://github.com/OCA/connector-magento,github
-repo_oca_connector_magento_php_extension,odoo_repository_org_oca,connector-magento-php-extension,https://github.com/OCA/connector-magento-php-extension,https://github.com/OCA/connector-magento-php-extension,github
-repo_oca_connector_odoo2odoo,odoo_repository_org_oca,connector-odoo2odoo,https://github.com/OCA/connector-odoo2odoo,https://github.com/OCA/connector-odoo2odoo,github
-repo_oca_connector_prestashop,odoo_repository_org_oca,connector-prestashop,https://github.com/OCA/connector-prestashop,https://github.com/OCA/connector-prestashop,github
-repo_oca_connector_redmine,odoo_repository_org_oca,connector-redmine,https://github.com/OCA/connector-redmine,https://github.com/OCA/connector-redmine,github
-repo_oca_connector_sage,odoo_repository_org_oca,connector-sage,https://github.com/OCA/connector-sage,https://github.com/OCA/connector-sage,github
-repo_oca_connector_salesforce,odoo_repository_org_oca,connector-salesforce,https://github.com/OCA/connector-salesforce,https://github.com/OCA/connector-salesforce,github
-repo_oca_connector_spscommerce,odoo_repository_org_oca,connector-spscommerce,https://github.com/OCA/connector-spscommerce,https://github.com/OCA/connector-spscommerce,github
-repo_oca_connector_telephony,odoo_repository_org_oca,connector-telephony,https://github.com/OCA/connector-telephony,https://github.com/OCA/connector-telephony,github
-repo_oca_connector_woocommerce,odoo_repository_org_oca,connector-woocommerce,https://github.com/OCA/connector-woocommerce,https://github.com/OCA/connector-woocommerce,github
-repo_oca_contract,odoo_repository_org_oca,contract,https://github.com/OCA/contract,https://github.com/OCA/contract,github
-repo_oca_cooperative,odoo_repository_org_oca,cooperative,https://github.com/OCA/cooperative,https://github.com/OCA/cooperative,github
-repo_oca_credit_control,odoo_repository_org_oca,credit-control,https://github.com/OCA/credit-control,https://github.com/OCA/credit-control,github
-repo_oca_crm,odoo_repository_org_oca,crm,https://github.com/OCA/crm,https://github.com/OCA/crm,github
-repo_oca_currency,odoo_repository_org_oca,currency,https://github.com/OCA/currency,https://github.com/OCA/currency,github
-repo_oca_data_protection,odoo_repository_org_oca,data-protection,https://github.com/OCA/data-protection,https://github.com/OCA/data-protection,github
-repo_oca_ddmrp,odoo_repository_org_oca,ddmrp,https://github.com/OCA/ddmrp,https://github.com/OCA/ddmrp,github
-repo_oca_delivery_carrier,odoo_repository_org_oca,delivery-carrier,https://github.com/OCA/delivery-carrier,https://github.com/OCA/delivery-carrier,github
-repo_oca_department,odoo_repository_org_oca,department,https://github.com/OCA/department,https://github.com/OCA/department,github
-repo_oca_dms,odoo_repository_org_oca,dms,https://github.com/OCA/dms,https://github.com/OCA/dms,github
-repo_oca_donation,odoo_repository_org_oca,donation,https://github.com/OCA/donation,https://github.com/OCA/donation,github
-repo_oca_e_commerce,odoo_repository_org_oca,e-commerce,https://github.com/OCA/e-commerce,https://github.com/OCA/e-commerce,github
-repo_oca_e_learning,odoo_repository_org_oca,e-learning,https://github.com/OCA/e-learning,https://github.com/OCA/e-learning,github
-repo_oca_edi,odoo_repository_org_oca,edi,https://github.com/OCA/edi,https://github.com/OCA/edi,github
-repo_oca_edi_framework,odoo_repository_org_oca,edi-framework,https://github.com/OCA/edi-framework,https://github.com/OCA/edi-framework,github
-repo_oca_event,odoo_repository_org_oca,event,https://github.com/OCA/event,https://github.com/OCA/event,github
-repo_oca_field_service,odoo_repository_org_oca,field-service,https://github.com/OCA/field-service,https://github.com/OCA/field-service,github
-repo_oca_fleet,odoo_repository_org_oca,fleet,https://github.com/OCA/fleet,https://github.com/OCA/fleet,github
-repo_oca_geospatial,odoo_repository_org_oca,geospatial,https://github.com/OCA/geospatial,https://github.com/OCA/geospatial,github
-repo_oca_helpdesk,odoo_repository_org_oca,helpdesk,https://github.com/OCA/helpdesk,https://github.com/OCA/helpdesk,github
-repo_oca_hr,odoo_repository_org_oca,hr,https://github.com/OCA/hr,https://github.com/OCA/hr,github
-repo_oca_hr_attendance,odoo_repository_org_oca,hr-attendance,https://github.com/OCA/hr-attendance,https://github.com/OCA/hr-attendance,github
-repo_oca_hr_expense,odoo_repository_org_oca,hr-expense,https://github.com/OCA/hr-expense,https://github.com/OCA/hr-expense,github
-repo_oca_hr_holidays,odoo_repository_org_oca,hr-holidays,https://github.com/OCA/hr-holidays,https://github.com/OCA/hr-holidays,github
-repo_oca_infrastructure,odoo_repository_org_oca,infrastructure,https://github.com/OCA/infrastructure,https://github.com/OCA/infrastructure,github
-repo_oca_interface_github,odoo_repository_org_oca,interface-github,https://github.com/OCA/interface-github,https://github.com/OCA/interface-github,github
-repo_oca_intrastat_extrastat,odoo_repository_org_oca,intrastat-extrastat,https://github.com/OCA/intrastat-extrastat,https://github.com/OCA/intrastat-extrastat,github
-repo_oca_iot,odoo_repository_org_oca,iot,https://github.com/OCA/iot,https://github.com/OCA/iot,github
-repo_oca_knowledge,odoo_repository_org_oca,knowledge,https://github.com/OCA/knowledge,https://github.com/OCA/knowledge,github
-repo_oca_l10n_argentina,odoo_repository_org_oca,l10n-argentina,https://github.com/OCA/l10n-argentina,https://github.com/OCA/l10n-argentina,github
-repo_oca_l10n_austria,odoo_repository_org_oca,l10n-austria,https://github.com/OCA/l10n-austria,https://github.com/OCA/l10n-austria,github
-repo_oca_l10n_belarus,odoo_repository_org_oca,l10n-belarus,https://github.com/OCA/l10n-belarus,https://github.com/OCA/l10n-belarus,github
-repo_oca_l10n_belgium,odoo_repository_org_oca,l10n-belgium,https://github.com/OCA/l10n-belgium,https://github.com/OCA/l10n-belgium,github
-repo_oca_l10n_brazil,odoo_repository_org_oca,l10n-brazil,https://github.com/OCA/l10n-brazil,https://github.com/OCA/l10n-brazil,github
-repo_oca_l10n_cambodia,odoo_repository_org_oca,l10n-cambodia,https://github.com/OCA/l10n-cambodia,https://github.com/OCA/l10n-cambodia,github
-repo_oca_l10n_canada,odoo_repository_org_oca,l10n-canada,https://github.com/OCA/l10n-canada,https://github.com/OCA/l10n-canada,github
-repo_oca_l10n_chile,odoo_repository_org_oca,l10n-chile,https://github.com/OCA/l10n-chile,https://github.com/OCA/l10n-chile,github
-repo_oca_l10n_china,odoo_repository_org_oca,l10n-china,https://github.com/OCA/l10n-china,https://github.com/OCA/l10n-china,github
-repo_oca_l10n_colombia,odoo_repository_org_oca,l10n-colombia,https://github.com/OCA/l10n-colombia,https://github.com/OCA/l10n-colombia,github
-repo_oca_l10n_costa_rica,odoo_repository_org_oca,l10n-costa-rica,https://github.com/OCA/l10n-costa-rica,https://github.com/OCA/l10n-costa-rica,github
-repo_oca_l10n_croatia,odoo_repository_org_oca,l10n-croatia,https://github.com/OCA/l10n-croatia,https://github.com/OCA/l10n-croatia,github
-repo_oca_l10n_ecuador,odoo_repository_org_oca,l10n-ecuador,https://github.com/OCA/l10n-ecuador,https://github.com/OCA/l10n-ecuador,github
-repo_oca_l10n_estonia,odoo_repository_org_oca,l10n-estonia,https://github.com/OCA/l10n-estonia,https://github.com/OCA/l10n-estonia,github
-repo_oca_l10n_ethiopia,odoo_repository_org_oca,l10n-ethiopia,https://github.com/OCA/l10n-ethiopia,https://github.com/OCA/l10n-ethiopia,github
-repo_oca_l10n_finland,odoo_repository_org_oca,l10n-finland,https://github.com/OCA/l10n-finland,https://github.com/OCA/l10n-finland,github
-repo_oca_l10n_france,odoo_repository_org_oca,l10n-france,https://github.com/OCA/l10n-france,https://github.com/OCA/l10n-france,github
-repo_oca_l10n_germany,odoo_repository_org_oca,l10n-germany,https://github.com/OCA/l10n-germany,https://github.com/OCA/l10n-germany,github
-repo_oca_l10n_greece,odoo_repository_org_oca,l10n-greece,https://github.com/OCA/l10n-greece,https://github.com/OCA/l10n-greece,github
-repo_oca_l10n_india,odoo_repository_org_oca,l10n-india,https://github.com/OCA/l10n-india,https://github.com/OCA/l10n-india,github
-repo_oca_l10n_indonesia,odoo_repository_org_oca,l10n-indonesia,https://github.com/OCA/l10n-indonesia,https://github.com/OCA/l10n-indonesia,github
-repo_oca_l10n_iran,odoo_repository_org_oca,l10n-iran,https://github.com/OCA/l10n-iran,https://github.com/OCA/l10n-iran,github
-repo_oca_l10n_ireland,odoo_repository_org_oca,l10n-ireland,https://github.com/OCA/l10n-ireland,https://github.com/OCA/l10n-ireland,github
-repo_oca_l10n_italy,odoo_repository_org_oca,l10n-italy,https://github.com/OCA/l10n-italy,https://github.com/OCA/l10n-italy,github
-repo_oca_l10n_japan,odoo_repository_org_oca,l10n-japan,https://github.com/OCA/l10n-japan,https://github.com/OCA/l10n-japan,github
-repo_oca_l10n_luxemburg,odoo_repository_org_oca,l10n-luxemburg,https://github.com/OCA/l10n-luxemburg,https://github.com/OCA/l10n-luxemburg,github
-repo_oca_l10n_macedonia,odoo_repository_org_oca,l10n-macedonia,https://github.com/OCA/l10n-macedonia,https://github.com/OCA/l10n-macedonia,github
-repo_oca_l10n_mexico,odoo_repository_org_oca,l10n-mexico,https://github.com/OCA/l10n-mexico,https://github.com/OCA/l10n-mexico,github
-repo_oca_l10n_morocco,odoo_repository_org_oca,l10n-morocco,https://github.com/OCA/l10n-morocco,https://github.com/OCA/l10n-morocco,github
-repo_oca_l10n_netherlands,odoo_repository_org_oca,l10n-netherlands,https://github.com/OCA/l10n-netherlands,https://github.com/OCA/l10n-netherlands,github
-repo_oca_l10n_norway,odoo_repository_org_oca,l10n-norway,https://github.com/OCA/l10n-norway,https://github.com/OCA/l10n-norway,github
-repo_oca_l10n_peru,odoo_repository_org_oca,l10n-peru,https://github.com/OCA/l10n-peru,https://github.com/OCA/l10n-peru,github
-repo_oca_l10n_poland,odoo_repository_org_oca,l10n-poland,https://github.com/OCA/l10n-poland,https://github.com/OCA/l10n-poland,github
-repo_oca_l10n_portugal,odoo_repository_org_oca,l10n-portugal,https://github.com/OCA/l10n-portugal,https://github.com/OCA/l10n-portugal,github
-repo_oca_l10n_romania,odoo_repository_org_oca,l10n-romania,https://github.com/OCA/l10n-romania,https://github.com/OCA/l10n-romania,github
-repo_oca_l10n_russia,odoo_repository_org_oca,l10n-russia,https://github.com/OCA/l10n-russia,https://github.com/OCA/l10n-russia,github
-repo_oca_l10n_slovenia,odoo_repository_org_oca,l10n-slovenia,https://github.com/OCA/l10n-slovenia,https://github.com/OCA/l10n-slovenia,github
-repo_oca_l10n_spain,odoo_repository_org_oca,l10n-spain,https://github.com/OCA/l10n-spain,https://github.com/OCA/l10n-spain,github
-repo_oca_l10n_switzerland,odoo_repository_org_oca,l10n-switzerland,https://github.com/OCA/l10n-switzerland,https://github.com/OCA/l10n-switzerland,github
-repo_oca_l10n_taiwan,odoo_repository_org_oca,l10n-taiwan,https://github.com/OCA/l10n-taiwan,https://github.com/OCA/l10n-taiwan,github
-repo_oca_l10n_thailand,odoo_repository_org_oca,l10n-thailand,https://github.com/OCA/l10n-thailand,https://github.com/OCA/l10n-thailand,github
-repo_oca_l10n_turkey,odoo_repository_org_oca,l10n-turkey,https://github.com/OCA/l10n-turkey,https://github.com/OCA/l10n-turkey,github
-repo_oca_l10n_ukraine,odoo_repository_org_oca,l10n-ukraine,https://github.com/OCA/l10n-ukraine,https://github.com/OCA/l10n-ukraine,github
-repo_oca_l10n_united_kingdom,odoo_repository_org_oca,l10n-united-kingdom,https://github.com/OCA/l10n-united-kingdom,https://github.com/OCA/l10n-united-kingdom,github
-repo_oca_l10n_uruguay,odoo_repository_org_oca,l10n-uruguay,https://github.com/OCA/l10n-uruguay,https://github.com/OCA/l10n-uruguay,github
-repo_oca_l10n_usa,odoo_repository_org_oca,l10n-usa,https://github.com/OCA/l10n-usa,https://github.com/OCA/l10n-usa,github
-repo_oca_l10n_venezuela,odoo_repository_org_oca,l10n-venezuela,https://github.com/OCA/l10n-venezuela,https://github.com/OCA/l10n-venezuela,github
-repo_oca_l10n_vietnam,odoo_repository_org_oca,l10n-vietnam,https://github.com/OCA/l10n-vietnam,https://github.com/OCA/l10n-vietnam,github
-repo_oca_mail,odoo_repository_org_oca,mail,https://github.com/OCA/mail,https://github.com/OCA/mail,github
-repo_oca_maintenance,odoo_repository_org_oca,maintenance,https://github.com/OCA/maintenance,https://github.com/OCA/maintenance,github
-repo_oca_management_system,odoo_repository_org_oca,management-system,https://github.com/OCA/management-system,https://github.com/OCA/management-system,github
-repo_oca_manufacture,odoo_repository_org_oca,manufacture,https://github.com/OCA/manufacture,https://github.com/OCA/manufacture,github
-repo_oca_manufacture_reporting,odoo_repository_org_oca,manufacture-reporting,https://github.com/OCA/manufacture-reporting,https://github.com/OCA/manufacture-reporting,github
-repo_oca_margin_analysis,odoo_repository_org_oca,margin-analysis,https://github.com/OCA/margin-analysis,https://github.com/OCA/margin-analysis,github
-repo_oca_mis_builder,odoo_repository_org_oca,mis-builder,https://github.com/OCA/mis-builder,https://github.com/OCA/mis-builder,github
-repo_oca_mis_builder_contrib,odoo_repository_org_oca,mis-builder-contrib,https://github.com/OCA/mis-builder-contrib,https://github.com/OCA/mis-builder-contrib,github
-repo_oca_multi_company,odoo_repository_org_oca,multi-company,https://github.com/OCA/multi-company,https://github.com/OCA/multi-company,github
-repo_oca_oca_custom,odoo_repository_org_oca,oca-custom,https://github.com/OCA/oca-custom,https://github.com/OCA/oca-custom,github
-repo_oca_odoo_pim,odoo_repository_org_oca,odoo-pim,https://github.com/OCA/odoo-pim,https://github.com/OCA/odoo-pim,github
-repo_oca_operating_unit,odoo_repository_org_oca,operating-unit,https://github.com/OCA/operating-unit,https://github.com/OCA/operating-unit,github
-repo_oca_partner_contact,odoo_repository_org_oca,partner-contact,https://github.com/OCA/partner-contact,https://github.com/OCA/partner-contact,github
-repo_oca_payroll,odoo_repository_org_oca,payroll,https://github.com/OCA/payroll,https://github.com/OCA/payroll,github
-repo_oca_pms,odoo_repository_org_oca,pms,https://github.com/OCA/pms,https://github.com/OCA/pms,github
-repo_oca_pos,odoo_repository_org_oca,pos,https://github.com/OCA/pos,https://github.com/OCA/pos,github
-repo_oca_product_attribute,odoo_repository_org_oca,product-attribute,https://github.com/OCA/product-attribute,https://github.com/OCA/product-attribute,github
-repo_oca_product_configurator,odoo_repository_org_oca,product-configurator,https://github.com/OCA/product-configurator,https://github.com/OCA/product-configurator,github
-repo_oca_product_kitting,odoo_repository_org_oca,product-kitting,https://github.com/OCA/product-kitting,https://github.com/OCA/product-kitting,github
-repo_oca_product_pack,odoo_repository_org_oca,product-pack,https://github.com/OCA/product-pack,https://github.com/OCA/product-pack,github
-repo_oca_product_variant,odoo_repository_org_oca,product-variant,https://github.com/OCA/product-variant,https://github.com/OCA/product-variant,github
-repo_oca_program,odoo_repository_org_oca,program,https://github.com/OCA/program,https://github.com/OCA/program,github
-repo_oca_project,odoo_repository_org_oca,project,https://github.com/OCA/project,https://github.com/OCA/project,github
-repo_oca_project_agile,odoo_repository_org_oca,project-agile,https://github.com/OCA/project-agile,https://github.com/OCA/project-agile,github
-repo_oca_project_reporting,odoo_repository_org_oca,project-reporting,https://github.com/OCA/project-reporting,https://github.com/OCA/project-reporting,github
-repo_oca_purchase_reporting,odoo_repository_org_oca,purchase-reporting,https://github.com/OCA/purchase-reporting,https://github.com/OCA/purchase-reporting,github
-repo_oca_purchase_workflow,odoo_repository_org_oca,purchase-workflow,https://github.com/OCA/purchase-workflow,https://github.com/OCA/purchase-workflow,github
-repo_oca_pwa_builder,odoo_repository_org_oca,pwa-builder,https://github.com/OCA/pwa-builder,https://github.com/OCA/pwa-builder,github
-repo_oca_queue,odoo_repository_org_oca,queue,https://github.com/OCA/queue,https://github.com/OCA/queue,github
-repo_oca_repair,odoo_repository_org_oca,repair,https://github.com/OCA/repair,https://github.com/OCA/repair,github
-repo_oca_report_print_send,odoo_repository_org_oca,report-print-send,https://github.com/OCA/report-print-send,https://github.com/OCA/report-print-send,github
-repo_oca_reporting_engine,odoo_repository_org_oca,reporting-engine,https://github.com/OCA/reporting-engine,https://github.com/OCA/reporting-engine,github
-repo_oca_resource,odoo_repository_org_oca,resource,https://github.com/OCA/resource,https://github.com/OCA/resource,github
-repo_oca_rest_api,odoo_repository_org_oca,rest-api,https://github.com/OCA/rest-api,https://github.com/OCA/rest-api,github
-repo_oca_rest_framework,odoo_repository_org_oca,rest-framework,https://github.com/OCA/rest-framework,https://github.com/OCA/rest-framework,github
-repo_oca_rma,odoo_repository_org_oca,rma,https://github.com/OCA/rma,https://github.com/OCA/rma,github
-repo_oca_role_policy,odoo_repository_org_oca,role-policy,https://github.com/OCA/role-policy,https://github.com/OCA/role-policy,github
-repo_oca_sale_blanket,odoo_repository_org_oca,sale-blanket,https://github.com/OCA/sale-blanket,https://github.com/OCA/sale-blanket,github
-repo_oca_sale_channel,odoo_repository_org_oca,sale-channel,https://github.com/OCA/sale-channel,https://github.com/OCA/sale-channel,github
-repo_oca_sale_financial,odoo_repository_org_oca,sale-financial,https://github.com/OCA/sale-financial,https://github.com/OCA/sale-financial,github
-repo_oca_sale_promotion,odoo_repository_org_oca,sale-promotion,https://github.com/OCA/sale-promotion,https://github.com/OCA/sale-promotion,github
-repo_oca_sale_reporting,odoo_repository_org_oca,sale-reporting,https://github.com/OCA/sale-reporting,https://github.com/OCA/sale-reporting,github
-repo_oca_sale_workflow,odoo_repository_org_oca,sale-workflow,https://github.com/OCA/sale-workflow,https://github.com/OCA/sale-workflow,github
-repo_oca_search_engine,odoo_repository_org_oca,search-engine,https://github.com/OCA/search-engine,https://github.com/OCA/search-engine,github
-repo_oca_server_auth,odoo_repository_org_oca,server-auth,https://github.com/OCA/server-auth,https://github.com/OCA/server-auth,github
-repo_oca_server_backend,odoo_repository_org_oca,server-backend,https://github.com/OCA/server-backend,https://github.com/OCA/server-backend,github
-repo_oca_server_brand,odoo_repository_org_oca,server-brand,https://github.com/OCA/server-brand,https://github.com/OCA/server-brand,github
-repo_oca_server_env,odoo_repository_org_oca,server-env,https://github.com/OCA/server-env,https://github.com/OCA/server-env,github
-repo_oca_server_tools,odoo_repository_org_oca,server-tools,https://github.com/OCA/server-tools,https://github.com/OCA/server-tools,github
-repo_oca_server_ux,odoo_repository_org_oca,server-ux,https://github.com/OCA/server-ux,https://github.com/OCA/server-ux,github
-repo_oca_shift_planning,odoo_repository_org_oca,shift-planning,https://github.com/OCA/shift-planning,https://github.com/OCA/shift-planning,github
-repo_oca_shopfloor_app,odoo_repository_org_oca,shopfloor-app,https://github.com/OCA/shopfloor-app,https://github.com/OCA/shopfloor-app
-repo_oca_sign,odoo_repository_org_oca,sign,https://github.com/OCA/sign,https://github.com/OCA/sign,github
-repo_oca_social,odoo_repository_org_oca,social,https://github.com/OCA/social,https://github.com/OCA/social,github
-repo_oca_spreadsheet,odoo_repository_org_oca,spreadsheet,https://github.com/OCA/spreadsheet,https://github.com/OCA/spreadsheet,github
-repo_oca_stock_logistics_availability,odoo_repository_org_oca,stock-logistics-availability,https://github.com/OCA/stock-logistics-availability,https://github.com/OCA/stock-logistics-availability,github
-repo_oca_stock_logistics_barcode,odoo_repository_org_oca,stock-logistics-barcode,https://github.com/OCA/stock-logistics-barcode,https://github.com/OCA/stock-logistics-barcode,github
-repo_oca_stock_logistics_interfaces,odoo_repository_org_oca,stock-logistics-interfaces,https://github.com/OCA/stock-logistics-interfaces,https://github.com/OCA/stock-logistics-interfaces,github
-repo_oca_stock_logistics_putaway,odoo_repository_org_oca,stock-logistics-putaway,https://github.com/OCA/stock-logistics-putaway,https://github.com/OCA/stock-logistics-putaway,github
-repo_oca_stock_logistics_orderpoint,odoo_repository_org_oca,stock-logistics-orderpoint,https://github.com/OCA/stock-logistics-orderpoint,https://github.com/OCA/stock-logistics-orderpoint,github
-repo_oca_stock_logistics_release_channel,odoo_repository_org_oca,stock-logistics-release-channel,https://github.com/OCA/stock-logistics-release-channel,https://github.com/OCA/stock-logistics-release-channel,github
-repo_oca_stock_logistics_reporting,odoo_repository_org_oca,stock-logistics-reporting,https://github.com/OCA/stock-logistics-reporting,https://github.com/OCA/stock-logistics-reporting,github
-repo_oca_stock_logistics_request,odoo_repository_org_oca,stock-logistics-request,https://github.com/OCA/stock-logistics-request,https://github.com/OCA/stock-logistics-request,github
-repo_oca_stock_logistics_reservation,odoo_repository_org_oca,stock-logistics-reservation,https://github.com/OCA/stock-logistics-reservation,https://github.com/OCA/stock-logistics-reservation,github
-repo_oca_stock_logistics_shopfloor,odoo_repository_org_oca,stock-logistics-shopfloor,https://github.com/OCA/stock-logistics-shopfloor,https://github.com/OCA/stock-logistics-shopfloor,github
-repo_oca_stock_logistics_tracking,odoo_repository_org_oca,stock-logistics-tracking,https://github.com/OCA/stock-logistics-tracking,https://github.com/OCA/stock-logistics-tracking,github
-repo_oca_stock_logistics_transport,odoo_repository_org_oca,stock-logistics-transport,https://github.com/OCA/stock-logistics-transport,https://github.com/OCA/stock-logistics-transport,github
-repo_oca_stock_logistics_warehouse,odoo_repository_org_oca,stock-logistics-warehouse,https://github.com/OCA/stock-logistics-warehouse,https://github.com/OCA/stock-logistics-warehouse,github
-repo_oca_stock_logistics_workflow,odoo_repository_org_oca,stock-logistics-workflow,https://github.com/OCA/stock-logistics-workflow,https://github.com/OCA/stock-logistics-workflow,github
-repo_oca_stock_weighing,odoo_repository_org_oca,stock-weighing,https://github.com/OCA/stock-weighing,https://github.com/OCA/stock-weighing,github
-repo_oca_storage,odoo_repository_org_oca,storage,https://github.com/OCA/storage,https://github.com/OCA/storage,github
-repo_oca_survey,odoo_repository_org_oca,survey,https://github.com/OCA/survey,https://github.com/OCA/survey,github
-repo_oca_timesheet,odoo_repository_org_oca,timesheet,https://github.com/OCA/timesheet,https://github.com/OCA/timesheet,github
-repo_oca_vertical_abbey,odoo_repository_org_oca,vertical-abbey,https://github.com/OCA/vertical-abbey,https://github.com/OCA/vertical-abbey,github
-repo_oca_vertical_agriculture,odoo_repository_org_oca,vertical-agriculture,https://github.com/OCA/vertical-agriculture,https://github.com/OCA/vertical-agriculture,github
-repo_oca_vertical_association,odoo_repository_org_oca,vertical-association,https://github.com/OCA/vertical-association,https://github.com/OCA/vertical-association,github
-repo_oca_vertical_community,odoo_repository_org_oca,vertical-community,https://github.com/OCA/vertical-community,https://github.com/OCA/vertical-community,github
-repo_oca_vertical_construction,odoo_repository_org_oca,vertical-construction,https://github.com/OCA/vertical-construction,https://github.com/OCA/vertical-construction,github
-repo_oca_vertical_cooperative_supermarket,odoo_repository_org_oca,vertical-cooperative-supermarket,https://github.com/OCA/vertical-cooperative-supermarket,https://github.com/OCA/vertical-cooperative-supermarket,github
-repo_oca_vertical_edition,odoo_repository_org_oca,vertical-edition,https://github.com/OCA/vertical-edition,https://github.com/OCA/vertical-edition,github
-repo_oca_vertical_education,odoo_repository_org_oca,vertical-education,https://github.com/OCA/vertical-education,https://github.com/OCA/vertical-education,github
-repo_oca_vertical_hotel,odoo_repository_org_oca,vertical-hotel,https://github.com/OCA/vertical-hotel,https://github.com/OCA/vertical-hotel,github
-repo_oca_vertical_isp,odoo_repository_org_oca,vertical-isp,https://github.com/OCA/vertical-isp,https://github.com/OCA/vertical-isp,github
-repo_oca_vertical_medical,odoo_repository_org_oca,vertical-medical,https://github.com/OCA/vertical-medical,https://github.com/OCA/vertical-medical,github
-repo_oca_vertical_ngo,odoo_repository_org_oca,vertical-ngo,https://github.com/OCA/vertical-ngo,https://github.com/OCA/vertical-ngo,github
-repo_oca_vertical_realestate,odoo_repository_org_oca,vertical-realestate,https://github.com/OCA/vertical-realestate,https://github.com/OCA/vertical-realestate,github
-repo_oca_vertical_rental,odoo_repository_org_oca,vertical-rental,https://github.com/OCA/vertical-rental,https://github.com/OCA/vertical-rental,github
-repo_oca_vertical_travel,odoo_repository_org_oca,vertical-travel,https://github.com/OCA/vertical-travel,https://github.com/OCA/vertical-travel,github
-repo_oca_wallet,odoo_repository_org_oca,wallet,https://github.com/OCA/wallet,https://github.com/OCA/wallet,github
-repo_oca_web,odoo_repository_org_oca,web,https://github.com/OCA/web,https://github.com/OCA/web,github
-repo_oca_web_api,odoo_repository_org_oca,web-api,https://github.com/OCA/web-api,https://github.com/OCA/web-api,github
-repo_oca_web_api_contrib,odoo_repository_org_oca,web-api-contrib,https://github.com/OCA/web-api-contrib,https://github.com/OCA/web-api-contrib,github
-repo_oca_webhook,odoo_repository_org_oca,webhook,https://github.com/OCA/webhook,https://github.com/OCA/webhook,github
-repo_oca_webkit_tools,odoo_repository_org_oca,webkit-tools,https://github.com/OCA/webkit-tools,https://github.com/OCA/webkit-tools,github
-repo_oca_website,odoo_repository_org_oca,website,https://github.com/OCA/website,https://github.com/OCA/website,github
-repo_oca_website_cms,odoo_repository_org_oca,website-cms,https://github.com/OCA/website-cms,https://github.com/OCA/website-cms,github
-repo_oca_website_themes,odoo_repository_org_oca,website-themes,https://github.com/OCA/website-themes,https://github.com/OCA/website-themes,github
-repo_oca_wms,odoo_repository_org_oca,wms,https://github.com/OCA/wms,https://github.com/OCA/wms,github
diff --git a/odoo_repository/data/odoo_mca_backend.xml b/odoo_repository/data/odoo_mca_backend.xml
new file mode 100644
index 0000000..ee36ea7
--- /dev/null
+++ b/odoo_repository/data/odoo_mca_backend.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ MCA
+
+
diff --git a/odoo_repository/data/queue_job.xml b/odoo_repository/data/queue_job.xml
index f38e67a..99e5c7b 100644
--- a/odoo_repository/data/queue_job.xml
+++ b/odoo_repository/data/queue_job.xml
@@ -1,5 +1,6 @@
@@ -51,4 +52,14 @@
+
+
+
+ _sync_oca_repositories_job
+
+
+
diff --git a/odoo_repository/migrations/18.0.1.1.0/pre-migration.py b/odoo_repository/migrations/18.0.1.1.0/pre-migration.py
new file mode 100644
index 0000000..ac69c5b
--- /dev/null
+++ b/odoo_repository/migrations/18.0.1.1.0/pre-migration.py
@@ -0,0 +1,48 @@
+# Copyright 2026 Sébastien Alix
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+def migrate(cr, version):
+ if not version:
+ return
+ remove_xmlids_from_oca_repositories(cr)
+
+
+def remove_xmlids_from_oca_repositories(cr):
+ """Remove XML-IDs from OCA repositories to prevent deletion during upgrade."""
+ _logger.info("Removing XML-IDs from OCA repositories...")
+ # First, get the OCA organization ID
+ cr.execute("SELECT id FROM odoo_repository_org WHERE name = 'OCA'")
+ oca_org_result = cr.fetchone()
+ if not oca_org_result:
+ _logger.warning("OCA organization not found in database")
+ return
+ oca_org_id = oca_org_result[0]
+ # Get all OCA repositories that have XML-IDs
+ cr.execute(
+ """
+ SELECT imd.id, imd.res_id
+ FROM ir_model_data imd
+ JOIN odoo_repository repo ON imd.res_id = repo.id
+ WHERE imd.model = 'odoo.repository'
+ AND repo.org_id = %s
+ """,
+ (oca_org_id,),
+ )
+ xmlid_records = cr.fetchall()
+ if not xmlid_records:
+ _logger.info("No OCA repositories with XML-IDs found")
+ return
+ # Delete the XML-IDs from ir.model.data
+ xmlid_ids = [record[0] for record in xmlid_records]
+ repository_ids = [record[1] for record in xmlid_records]
+ cr.execute("DELETE FROM ir_model_data WHERE id = ANY(%s)", (xmlid_ids,))
+ _logger.info(
+ "Removed %s XML-IDs from OCA repositories (repository IDs: %s)",
+ len(xmlid_ids),
+ repository_ids,
+ )
diff --git a/odoo_repository/models/__init__.py b/odoo_repository/models/__init__.py
index e32af68..6c8e744 100644
--- a/odoo_repository/models/__init__.py
+++ b/odoo_repository/models/__init__.py
@@ -10,6 +10,7 @@
from . import odoo_python_dependency
from . import odoo_repository_org
from . import odoo_repository_addons_path
+from . import odoo_mca_backend
from . import odoo_repository
from . import odoo_repository_branch
from . import odoo_module_branch
diff --git a/odoo_repository/models/odoo_mca_backend.py b/odoo_repository/models/odoo_mca_backend.py
new file mode 100644
index 0000000..166a3e4
--- /dev/null
+++ b/odoo_repository/models/odoo_mca_backend.py
@@ -0,0 +1,17 @@
+# Copyright 2026 Sébastien Alix
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from odoo import fields, models
+
+
+class OdooMCABackend(models.Model):
+ """Backend collection for Odoo MCA system.
+
+ This backend is used as the collection for all MCA-related components.
+ """
+
+ _name = "odoo.mca.backend"
+ _inherit = "collection.base"
+ _description = "Odoo MCA Backend"
+
+ name = fields.Char(required=True)
diff --git a/odoo_repository/models/odoo_repository.py b/odoo_repository/models/odoo_repository.py
index f0d0ec3..71bbeba 100644
--- a/odoo_repository/models/odoo_repository.py
+++ b/odoo_repository/models/odoo_repository.py
@@ -1,4 +1,5 @@
# Copyright 2023 Camptocamp SA
+# Copyright 2026 Sébastien Alix
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import json
@@ -669,6 +670,36 @@ def unlink(self):
rec.branch_ids.module_ids.sudo().unlink()
return super().unlink()
+ @api.model
+ def cron_sync_oca_repositories(self):
+ """Create and update OCA repositories from GitHub configuration.
+
+ This method spawns a job to fetch the repository configurations from
+ OCA/repo-maintainer-conf, parse the YAML files, and synchronize the local
+ repository database by:
+ - Creating new repositories that exist in OCA config but not locally
+ - Updating existing repositories with any changes
+ - Archiving repositories that no longer exist in OCA config
+ """
+ delayable = self.delayable(
+ identity_key=identity_exact,
+ priority=1,
+ )
+ delayable._sync_oca_repositories_job()
+ delayable.delay()
+ return True
+
+ @api.model
+ def _sync_oca_repositories_job(self):
+ """Queue job method to synchronize OCA repositories.
+
+ Performs the actual synchronization of OCA repositories from GitHub.
+ """
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ return synchronizer.run()
+
def open_modules(self):
self.ensure_one()
xml_id = "odoo_repository.odoo_module_branch_action"
diff --git a/odoo_repository/models/res_config_settings.py b/odoo_repository/models/res_config_settings.py
index ef16923..c6d6d50 100644
--- a/odoo_repository/models/res_config_settings.py
+++ b/odoo_repository/models/res_config_settings.py
@@ -1,4 +1,5 @@
# Copyright 2023 Camptocamp SA
+# Copyright 2026 Sébastien Alix
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import fields, models
@@ -21,3 +22,11 @@ class ResConfigSettings(models.TransientModel):
config_odoo_repository_main_node_url = fields.Char(
string="Main Node", config_parameter="odoo_repository_main_node_url"
)
+ config_odoo_repository_oca_blacklist = fields.Char(
+ string="OCA Repository Blacklist",
+ config_parameter="odoo_repository.oca_repo_blacklist",
+ help=(
+ "Comma-separated list of OCA repositories to skip during synchronization. "
+ "Example: OCB,OpenUpgrade"
+ ),
+ )
diff --git a/odoo_repository/readme/DESCRIPTION.md b/odoo_repository/readme/DESCRIPTION.md
index 2fea33b..fc891ae 100644
--- a/odoo_repository/readme/DESCRIPTION.md
+++ b/odoo_repository/readme/DESCRIPTION.md
@@ -2,5 +2,5 @@ Base module to host data collected from Odoo repositories.
It allows you to:
- declare the Odoo versions (last 3 versions by default)
-- declare repositories containing modules (Odoo and OCA repositories included by default)
+- declare repositories containing modules (Odoo repositories included by default, and OCA ones are synced automatically)
- scan these repositories to collect modules informations per Odoo version
diff --git a/odoo_repository/security/ir.model.access.csv b/odoo_repository/security/ir.model.access.csv
index db75b49..b3b9972 100644
--- a/odoo_repository/security/ir.model.access.csv
+++ b/odoo_repository/security/ir.model.access.csv
@@ -25,3 +25,4 @@ access_odoo_module_branch_user,odoo_module_branch_user,model_odoo_module_branch,
access_odoo_module_branch_manager,odoo_module_branch_manager,model_odoo_module_branch,group_odoo_repository_manager,1,1,0,0
access_odoo_module_branch_version_user,odoo_module_branch_version_user,model_odoo_module_branch_version,group_odoo_repository_user,1,0,0,0
access_odoo_module_branch_version_manager,odoo_module_branch_version_manager,model_odoo_module_branch_version,group_odoo_repository_manager,1,1,1,1
+access_odoo_mca_backend_user,odoo_mca_backend_user,model_odoo_mca_backend,group_odoo_repository_user,1,0,0,0
diff --git a/odoo_repository/tests/__init__.py b/odoo_repository/tests/__init__.py
index a806756..8f3f0b6 100644
--- a/odoo_repository/tests/__init__.py
+++ b/odoo_repository/tests/__init__.py
@@ -4,3 +4,4 @@
from . import test_odoo_repository_scan
from . import test_sync_node
from . import test_odoo_module_branch
+from . import test_oca_repository_synchronizer
diff --git a/odoo_repository/tests/test_oca_repository_synchronizer.py b/odoo_repository/tests/test_oca_repository_synchronizer.py
new file mode 100644
index 0000000..f668bd6
--- /dev/null
+++ b/odoo_repository/tests/test_oca_repository_synchronizer.py
@@ -0,0 +1,280 @@
+# Copyright 2026 Sébastien Alix
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from unittest.mock import MagicMock, patch
+
+from odoo import tools
+
+from odoo.addons.component.tests.common import TransactionComponentCase
+from odoo.addons.queue_job.exception import RetryableJobError
+
+
+class TestOCARepositorySynchronizer(TransactionComponentCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.env = cls.env(context=dict(cls.env.context, queue_job__no_delay=True))
+ cls.repository_model = cls.env["odoo.repository"]
+ cls.oca_org = cls.env.ref("odoo_repository.odoo_repository_org_oca")
+ # Create a test repository that should be archived
+ cls.test_repo = cls.repository_model.create(
+ {
+ "org_id": cls.oca_org.id,
+ "name": "test-archive-repo",
+ "repo_url": "https://github.com/OCA/test-archive-repo",
+ "clone_url": "https://github.com/OCA/test-archive-repo",
+ "repo_type": "github",
+ "active": True,
+ }
+ )
+
+ def _mock_requests_get(self, mock_get):
+ # Mock GitHub API response for directory listing
+ mock_dir_response = MagicMock()
+ mock_dir_response.json.return_value = [
+ {
+ "name": "test.yml",
+ "download_url": (
+ "https://raw.githubusercontent.com/"
+ "OCA/repo-maintainer-conf/master/conf/repo/test.yml"
+ ),
+ }
+ ]
+ mock_dir_response.raise_for_status.return_value = None
+ # Mock YAML file content
+ mock_yaml_response = MagicMock()
+ mock_yaml_response.text = """
+ test-repo:
+ branches:
+ - "16.0"
+ name: Test
+ """
+ mock_yaml_response.raise_for_status.return_value = None
+
+ # Setup mock to return different responses based on URL
+ def get_side_effect(url, **kwargs):
+ if "contents/conf/repo" in url:
+ return mock_dir_response
+ else:
+ return mock_yaml_response
+
+ mock_get.side_effect = get_side_effect
+
+ def test_parse_oca_repo_configurations(self):
+ """Test OCA repository configurations parsing.
+
+ Only those with branches are returned.
+ """
+ yaml_content = """
+ test-repo-no-branches:
+ branches: []
+ name: Test Repo
+ test-repo-with-branches:
+ branches:
+ - "16.0"
+ - "17.0"
+ name: Test Repo With Branches
+ """
+ repo_configs = {"test.yml": yaml_content}
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ result = synchronizer._parse_oca_repo_configurations(repo_configs)
+ # Should only include repo with branches
+ self.assertEqual(len(result), 1)
+ self.assertNotIn("test-repo-no-branches", result)
+ self.assertIn("test-repo-with-branches", result)
+ repo_data = result["test-repo-with-branches"]
+ self.assertEqual(repo_data["name"], "test-repo-with-branches")
+ self.assertEqual(
+ repo_data["repo_url"], "https://github.com/OCA/test-repo-with-branches"
+ )
+
+ def test_parse_oca_repo_configurations_default_branch(self):
+ """Test OCA repository configurations parsing with default_branch filtering.
+
+ Repositories with default_branch='master' or 'main' should be skipped.
+ """
+ yaml_content = """
+ repo-master:
+ default_branch: master
+ branches:
+ - "16.0"
+ name: Master Branch Repo
+ repo-main:
+ default_branch: main
+ branches:
+ - "16.0"
+ name: Main Branch Repo
+ repo-odoo-branch:
+ default_branch: 16.0
+ branches:
+ - "16.0"
+ - "17.0"
+ name: Odoo Branch Repo
+ repo-no-default:
+ branches:
+ - "16.0"
+ name: No Default Branch Repo
+ """
+ repo_configs = {"test.yml": yaml_content}
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ result = synchronizer._parse_oca_repo_configurations(repo_configs)
+ # Should only include repos with non-master/main default_branch
+ # or no default_branch
+ self.assertEqual(len(result), 2)
+ self.assertNotIn("repo-master", result)
+ self.assertNotIn("repo-main", result)
+ self.assertIn("repo-odoo-branch", result)
+ self.assertIn("repo-no-default", result)
+
+ def test_sync_oca_repositories_archive(self):
+ """Test that repositories not in current config are archived."""
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ # Mock current repositories (empty = no repos in config)
+ current_repos = {}
+ stats = synchronizer._sync_oca_repositories(current_repos)
+ # Check that our test repo was archived
+ self.assertEqual(stats["archived"], 1)
+ self.test_repo.invalidate_model()
+ self.assertFalse(self.test_repo.active)
+ self.assertFalse(self.test_repo.to_scan)
+
+ def test_sync_oca_repositories_create(self):
+ """Test creation of new repositories from config."""
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ current_repos = {
+ "new-test-repo": {
+ "name": "new-test-repo",
+ "repo_url": "https://github.com/OCA/new-test-repo",
+ "clone_url": "https://github.com/OCA/new-test-repo",
+ "repo_type": "github",
+ }
+ }
+ stats = synchronizer._sync_oca_repositories(current_repos)
+ self.assertEqual(stats["created"], 1)
+ # Check that repository was created
+ new_repo = self.repository_model.search([("name", "=", "new-test-repo")])
+ self.assertTrue(new_repo.exists())
+ self.assertEqual(new_repo.org_id, self.oca_org)
+ self.assertEqual(new_repo.repo_url, "https://github.com/OCA/new-test-repo")
+ self.assertTrue(new_repo.active)
+ self.assertTrue(new_repo.to_scan)
+
+ def test_sync_oca_repositories_update(self):
+ """Test updating existing repositories."""
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ current_repos = {
+ "test-archive-repo": {
+ "name": "test-archive-repo",
+ "repo_url": "https://github.com/OCA/test-archive-repo-updated",
+ "clone_url": "https://github.com/OCA/test-archive-repo-updated",
+ "repo_type": "github",
+ }
+ }
+ stats = synchronizer._sync_oca_repositories(current_repos)
+ self.assertEqual(stats["updated"], 1)
+ # Check that repository was updated
+ self.test_repo.invalidate_model()
+ self.assertEqual(
+ self.test_repo.repo_url, "https://github.com/OCA/test-archive-repo-updated"
+ )
+ self.assertTrue(self.test_repo.active)
+
+ @patch("requests.get")
+ def test_fetch_oca_repo_configurations(self, mock_get):
+ """Test fetching repository configuration from GitHub."""
+ self._mock_requests_get(mock_get)
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ result = synchronizer._fetch_oca_repo_configurations()
+ self.assertIn("test.yml", result)
+ self.assertIn("test-repo:", result["test.yml"])
+
+ @patch("requests.get")
+ def test_run_oca_repository_synchronizer(self, mock_get):
+ """Test running the repository synchronizer component (main entrypoint)."""
+ self._mock_requests_get(mock_get)
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ stats = synchronizer.run()
+ self.assertEqual(stats["created"], 1)
+ self.assertEqual(stats["updated"], 0)
+
+ def test_run_oca_repository_synchronizer_error(self):
+ """Test running the repository synchronizer component raising exception."""
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ synchronizer.github_conf_api_url = "wrong_api_url"
+ with self.assertRaises(RetryableJobError):
+ with tools.mute_logger(
+ "odoo.addons.odoo_repository.components.oca_repository_synchronizer"
+ ):
+ synchronizer.run()
+
+ @patch("requests.get")
+ def test_cron_sync_oca_repositories(self, mock_get):
+ """Test cron method syncing OCA repositories from GitHub."""
+ self._mock_requests_get(mock_get)
+ result = self.repository_model.cron_sync_oca_repositories()
+ self.assertTrue(result)
+ test_repo = self.repository_model.search([("name", "=", "test-repo")])
+ self.assertEqual(test_repo.name, "test-repo")
+ self.assertEqual(
+ test_repo.repo_url, test_repo.clone_url, "https://github.com/OCA/test-repo"
+ )
+
+ def test_oca_repo_blacklist_configurable(self):
+ """Test that OCA repository blacklist is configurable through settings."""
+ yaml_content = """
+ blacklisted-repo:
+ branches:
+ - "16.0"
+ name: Blacklisted Repo
+ normal-repo:
+ branches:
+ - "16.0"
+ name: Normal Repo
+ """
+ repo_configs = {"test.yml": yaml_content}
+ # Test with default blacklist (should skip OCB and OpenUpgrade)
+ mca_backend = self.env.ref("odoo_repository.mca_backend")
+ with mca_backend.work_on("odoo.repository") as work:
+ synchronizer = work.component(usage="oca.repository.synchronizer")
+ # Test default blacklist
+ default_blacklist = synchronizer._get_oca_repo_blacklist()
+ self.assertIn("OCB", default_blacklist)
+ self.assertIn("OpenUpgrade", default_blacklist)
+ # Test parsing with custom blacklist (comma-separated)
+ self.env["ir.config_parameter"].sudo().set_param(
+ "odoo_repository.oca_repo_blacklist", "OCB,OpenUpgrade,blacklisted-repo"
+ )
+ custom_blacklist = synchronizer._get_oca_repo_blacklist()
+ self.assertIn("OCB", custom_blacklist)
+ self.assertIn("OpenUpgrade", custom_blacklist)
+ self.assertIn("blacklisted-repo", custom_blacklist)
+ # Test that blacklisted repo is skipped
+ result = synchronizer._parse_oca_repo_configurations(repo_configs)
+ self.assertNotIn("blacklisted-repo", result)
+ self.assertIn("normal-repo", result)
+ # Test with empty blacklist
+ self.env["ir.config_parameter"].sudo().set_param(
+ "odoo_repository.oca_repo_blacklist", ""
+ )
+ empty_blacklist = synchronizer._get_oca_repo_blacklist()
+ self.assertEqual(empty_blacklist, [])
+ # Test that both repos are included when blacklist is empty
+ result = synchronizer._parse_oca_repo_configurations(repo_configs)
+ self.assertIn("blacklisted-repo", result)
+ self.assertIn("normal-repo", result)
diff --git a/odoo_repository/tests/test_odoo_module_branch.py b/odoo_repository/tests/test_odoo_module_branch.py
index c0424d7..096b027 100644
--- a/odoo_repository/tests/test_odoo_module_branch.py
+++ b/odoo_repository/tests/test_odoo_module_branch.py
@@ -1,4 +1,5 @@
# Copyright 2024 Camptocamp SA
+# Copyright 2026 Sébastien Alix
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo.exceptions import ValidationError
diff --git a/odoo_repository/views/res_config_settings.xml b/odoo_repository/views/res_config_settings.xml
index 79d43ad..3367ccc 100644
--- a/odoo_repository/views/res_config_settings.xml
+++ b/odoo_repository/views/res_config_settings.xml
@@ -1,5 +1,6 @@
@@ -52,6 +53,15 @@
placeholder="https://example.net/odoo-repository/data"
/>
+
+
+
diff --git a/requirements.txt b/requirements.txt
index bc654e6..1130c18 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
# generated from manifests external_dependencies
gitpython
odoo-addons-parser
+pyyaml