From ea03d56c8e2e8023146dfaa1bc92b4e34035e1c8 Mon Sep 17 00:00:00 2001 From: git_admin Date: Sat, 2 May 2026 11:14:23 +0000 Subject: [PATCH] =?UTF-8?q?Tower:=20unpublish=20cetmix=5Ftower=5Fgit=20?= =?UTF-8?q?=E2=80=94=20remove=20source=20from=2016.0=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/cetmix_tower_git/README.rst | 125 -- addons/cetmix_tower_git/__init__.py | 1 - addons/cetmix_tower_git/__manifest__.py | 40 - addons/cetmix_tower_git/demo/demo_data.xml | 166 --- .../i18n/cetmix_tower_git.pot | 1029 ---------------- addons/cetmix_tower_git/i18n/fi.po | 595 --------- addons/cetmix_tower_git/i18n/hr.po | 635 ---------- addons/cetmix_tower_git/i18n/it.po | 1085 ----------------- .../migrations/16.0.2.0.0/post-migration.py | 82 -- addons/cetmix_tower_git/models/__init__.py | 15 - .../cetmix_tower_git/models/cetmix_tower.py | 35 - .../models/cx_tower_command.py | 37 - .../cetmix_tower_git/models/cx_tower_file.py | 47 - .../models/cx_tower_file_template.py | 32 - .../models/cx_tower_git_project.py | 334 ----- .../cx_tower_git_project_file_template_rel.py | 113 -- .../models/cx_tower_git_project_rel.py | 177 --- .../models/cx_tower_git_remote.py | 415 ------- .../models/cx_tower_git_repo.py | 409 ------- .../models/cx_tower_git_repo_owner.py | 107 -- .../models/cx_tower_git_source.py | 189 --- .../models/cx_tower_plan_line.py | 32 - .../models/cx_tower_server.py | 156 --- addons/cetmix_tower_git/pyproject.toml | 3 - addons/cetmix_tower_git/readme/CONFIGURE.md | 1 - addons/cetmix_tower_git/readme/DESCRIPTION.md | 3 - addons/cetmix_tower_git/readme/HISTORY.md | 43 - addons/cetmix_tower_git/readme/USAGE.md | 1 - .../readme/newsfragments/.gitkeep | 0 ...git_project_file_template_rel_security.xml | 49 - .../cx_tower_git_project_rel_security.xml | 45 - .../cx_tower_git_project_security.xml | 67 - .../security/cx_tower_git_remote_security.xml | 73 -- .../cx_tower_git_repo_owner_security.xml | 26 - .../security/cx_tower_git_repo_security.xml | 26 - .../security/cx_tower_git_source_security.xml | 75 -- .../security/ir.model.access.csv | 15 - .../static/description/banner.png | Bin 86695 -> 0 bytes .../static/description/icon.png | Bin 22128 -> 0 bytes .../static/description/index.html | 497 -------- addons/cetmix_tower_git/tests/__init__.py | 7 - addons/cetmix_tower_git/tests/common.py | 136 --- .../cetmix_tower_git/tests/test_file_rel.py | 390 ------ .../tests/test_file_template_rel.py | 308 ----- addons/cetmix_tower_git/tests/test_project.py | 315 ----- addons/cetmix_tower_git/tests/test_remote.py | 462 ------- addons/cetmix_tower_git/tests/test_repo.py | 84 -- addons/cetmix_tower_git/tests/test_server.py | 198 --- addons/cetmix_tower_git/tests/test_source.py | 226 ---- .../cetmix_tower_git/tools/git_aggregator.py | 0 .../views/cx_tower_file_template_views.xml | 21 - .../views/cx_tower_file_views.xml | 18 - .../views/cx_tower_git_project_views.xml | 236 ---- .../views/cx_tower_git_remote_views.xml | 84 -- .../views/cx_tower_git_repo_owner_views.xml | 65 - .../views/cx_tower_git_repo_views.xml | 163 --- .../views/cx_tower_git_source_views.xml | 86 -- .../views/cx_tower_plan_line_view.xml | 19 - .../views/cx_tower_server_view.xml | 45 - addons/cetmix_tower_git/views/menuitems.xml | 32 - 60 files changed, 9675 deletions(-) delete mode 100644 addons/cetmix_tower_git/README.rst delete mode 100644 addons/cetmix_tower_git/__init__.py delete mode 100644 addons/cetmix_tower_git/__manifest__.py delete mode 100644 addons/cetmix_tower_git/demo/demo_data.xml delete mode 100644 addons/cetmix_tower_git/i18n/cetmix_tower_git.pot delete mode 100644 addons/cetmix_tower_git/i18n/fi.po delete mode 100644 addons/cetmix_tower_git/i18n/hr.po delete mode 100644 addons/cetmix_tower_git/i18n/it.po delete mode 100644 addons/cetmix_tower_git/migrations/16.0.2.0.0/post-migration.py delete mode 100644 addons/cetmix_tower_git/models/__init__.py delete mode 100644 addons/cetmix_tower_git/models/cetmix_tower.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_command.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_file.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_file_template.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_git_project.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_git_project_file_template_rel.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_git_project_rel.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_git_remote.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_git_repo.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_git_repo_owner.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_git_source.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_plan_line.py delete mode 100644 addons/cetmix_tower_git/models/cx_tower_server.py delete mode 100644 addons/cetmix_tower_git/pyproject.toml delete mode 100644 addons/cetmix_tower_git/readme/CONFIGURE.md delete mode 100644 addons/cetmix_tower_git/readme/DESCRIPTION.md delete mode 100644 addons/cetmix_tower_git/readme/HISTORY.md delete mode 100644 addons/cetmix_tower_git/readme/USAGE.md delete mode 100644 addons/cetmix_tower_git/readme/newsfragments/.gitkeep delete mode 100644 addons/cetmix_tower_git/security/cx_tower_git_project_file_template_rel_security.xml delete mode 100644 addons/cetmix_tower_git/security/cx_tower_git_project_rel_security.xml delete mode 100644 addons/cetmix_tower_git/security/cx_tower_git_project_security.xml delete mode 100644 addons/cetmix_tower_git/security/cx_tower_git_remote_security.xml delete mode 100644 addons/cetmix_tower_git/security/cx_tower_git_repo_owner_security.xml delete mode 100644 addons/cetmix_tower_git/security/cx_tower_git_repo_security.xml delete mode 100644 addons/cetmix_tower_git/security/cx_tower_git_source_security.xml delete mode 100644 addons/cetmix_tower_git/security/ir.model.access.csv delete mode 100644 addons/cetmix_tower_git/static/description/banner.png delete mode 100644 addons/cetmix_tower_git/static/description/icon.png delete mode 100644 addons/cetmix_tower_git/static/description/index.html delete mode 100644 addons/cetmix_tower_git/tests/__init__.py delete mode 100644 addons/cetmix_tower_git/tests/common.py delete mode 100644 addons/cetmix_tower_git/tests/test_file_rel.py delete mode 100644 addons/cetmix_tower_git/tests/test_file_template_rel.py delete mode 100644 addons/cetmix_tower_git/tests/test_project.py delete mode 100644 addons/cetmix_tower_git/tests/test_remote.py delete mode 100644 addons/cetmix_tower_git/tests/test_repo.py delete mode 100644 addons/cetmix_tower_git/tests/test_server.py delete mode 100644 addons/cetmix_tower_git/tests/test_source.py delete mode 100644 addons/cetmix_tower_git/tools/git_aggregator.py delete mode 100644 addons/cetmix_tower_git/views/cx_tower_file_template_views.xml delete mode 100644 addons/cetmix_tower_git/views/cx_tower_file_views.xml delete mode 100644 addons/cetmix_tower_git/views/cx_tower_git_project_views.xml delete mode 100644 addons/cetmix_tower_git/views/cx_tower_git_remote_views.xml delete mode 100644 addons/cetmix_tower_git/views/cx_tower_git_repo_owner_views.xml delete mode 100644 addons/cetmix_tower_git/views/cx_tower_git_repo_views.xml delete mode 100644 addons/cetmix_tower_git/views/cx_tower_git_source_views.xml delete mode 100644 addons/cetmix_tower_git/views/cx_tower_plan_line_view.xml delete mode 100644 addons/cetmix_tower_git/views/cx_tower_server_view.xml delete mode 100644 addons/cetmix_tower_git/views/menuitems.xml diff --git a/addons/cetmix_tower_git/README.rst b/addons/cetmix_tower_git/README.rst deleted file mode 100644 index 29cd298..0000000 --- a/addons/cetmix_tower_git/README.rst +++ /dev/null @@ -1,125 +0,0 @@ -================ -Cetmix Tower Git -================ - -.. - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! This file is generated by oca-gen-addon-readme !! - !! changes will be overwritten. !! - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:583744e8956f294682a551fc082f086b174b8d2b72652c21b1dd68f3933e7211 - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png - :target: https://odoo-community.org/page/development-status - :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 -.. |badge3| image:: https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github - :target: https://github.com/cetmix/cetmix-tower/tree/16.0/cetmix_tower_git - :alt: cetmix/cetmix-tower - -|badge1| |badge2| |badge3| - -This module implements Git Management functionality for `Cetmix -Tower `__. - -Please refer to the `official -documentation `__ for detailed information. - -**Table of contents** - -.. contents:: - :local: - -Configuration -============= - -Please refer to the `official -documentation `__ for detailed configuration -instructions. - -Usage -===== - -Please refer to the `official -documentation `__ for detailed usage -instructions. - -Changelog -========= - -16.0.2.0.1 (2025-12-11) ------------------------ - -- Features: Improve search views, implement the search panel for - selected views. (5139) - -16.0.2.0.0 (2025-10-27) ------------------------ - -- Features: Major refactoring: implement Git repository entity. (4914) - -16.0.1.0.6 (2025-08-18) ------------------------ - -- Features: Link or copy a git project when uploading the linked file - using command (4759) - -16.0.1.0.5 (2025-08-17) ------------------------ - -- Features: Search servers by git reference (4838) - -16.0.1.0.4 (2025-07-29) ------------------------ - -- Features: Export related commands and flight plans together with - server (4849) - -16.0.1.0.3 (2025-05-23) ------------------------ - -- Bugfixes: Duplicated file is created when importing a YAML file with a - git project. (4715) - -16.0.1.0.2 (2025-05-16) ------------------------ - -- Features: Record references for git relations. (4670) - -16.0.1.0.1 (2025-05-09) ------------------------ - -- Bugfixes: Non-critical issues and performance improvements. (4663) - -16.0.1.0.0 ----------- - -Release for Odoo 16.0 - -Bug Tracker -=========== - -Bugs are tracked on `GitHub Issues `_. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. - -Do not contact contributors directly about support or help with technical issues. - -Credits -======= - -Authors -------- - -* Cetmix - -Maintainers ------------ - -This module is part of the `cetmix/cetmix-tower `_ project on GitHub. - -You are welcome to contribute. diff --git a/addons/cetmix_tower_git/__init__.py b/addons/cetmix_tower_git/__init__.py deleted file mode 100644 index 0650744..0000000 --- a/addons/cetmix_tower_git/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models diff --git a/addons/cetmix_tower_git/__manifest__.py b/addons/cetmix_tower_git/__manifest__.py deleted file mode 100644 index e69fb94..0000000 --- a/addons/cetmix_tower_git/__manifest__.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright Cetmix OU -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -{ - "name": "Cetmix Tower Git", - "summary": "Cetmix Tower Git Management Tools", - "version": "16.0.2.0.2", - "development_status": "Beta", - "category": "Productivity", - "website": "https://tower.cetmix.com", - "author": "Cetmix", - "license": "AGPL-3", - "application": False, - "depends": ["cetmix_tower_yaml"], - "external_dependencies": { - "python": ["giturlparse==0.12.0"], - }, - "data": [ - "security/ir.model.access.csv", - "security/cx_tower_git_project_security.xml", - "security/cx_tower_git_source_security.xml", - "security/cx_tower_git_remote_security.xml", - "security/cx_tower_git_repo_security.xml", - "security/cx_tower_git_repo_owner_security.xml", - "security/cx_tower_git_project_rel_security.xml", - "security/cx_tower_git_project_file_template_rel_security.xml", - "views/cx_tower_git_project_views.xml", - "views/cx_tower_git_source_views.xml", - "views/cx_tower_git_remote_views.xml", - "views/cx_tower_git_repo_views.xml", - "views/cx_tower_git_repo_owner_views.xml", - "views/cx_tower_file_views.xml", - "views/cx_tower_file_template_views.xml", - "views/cx_tower_server_view.xml", - "views/cx_tower_plan_line_view.xml", - "views/menuitems.xml", - ], - "demo": [ - "demo/demo_data.xml", - ], -} diff --git a/addons/cetmix_tower_git/demo/demo_data.xml b/addons/cetmix_tower_git/demo/demo_data.xml deleted file mode 100644 index 9f15679..0000000 --- a/addons/cetmix_tower_git/demo/demo_data.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - Demo Git Project - demo_git_project - This is a demo git project. - - - - https://github.com/cetmix-demo/cetmix-tower-demo.git - - - https://github.com/oca-demo/web-demo.git - - - https://github.com/odoo-demo/enterprise-demo.git - - - - https://gitlab.com/cetmix-demo/cetmix-tower-demo.git - - - - https://bitbucket.com/cetmix-demo/cetmix-tower-demo-enterprise.git - - - - - - Cetmix Tower - cetmix_tower - - - - - - - branch - 14.0 - - - - - pr - 176 - - - - OCA Web - oca_web - - - - - - - branch - 14.0 - - - - Odoo Enterprise (Private) - odoo_enterprise - - - - - - - branch - 19.0 - - - - - Sample Semi Private Gitlab - gitlab_private - - - - - - - branch - main - - - - - pr - 1234 - - - - Sample Private Bitbucket - bitbucket_private - - - - - - - branch - dev - - - - - commit - 1234567890 - - - - - repos.yaml - - tower - text - {{ instance_name }}/config - - - - - - - - git_aggregator - - - - - Demo Git URL - demo_git_url - - - - - Parse Git URL - python_code - -if {{ demo_git_url }}: - parsed_url = giturlparse.parse({{ demo_git_url }}) - repo = parsed_url.repo - owner = parsed_url.owner - host = parsed_url.host - platform = parsed_url.platform - message = "Repo: " + repo + ", Owner: " + owner + ", Host: " + host + ", Platform: " + platform - result={"exit_code": 0, "message": message} -else: - result={"exit_code": -100, "message": "Git URL is not defined!"} - - 1 - - Run Python Code: Check Branch - - diff --git a/addons/cetmix_tower_git/i18n/cetmix_tower_git.pot b/addons/cetmix_tower_git/i18n/cetmix_tower_git.pot deleted file mode 100644 index 96271fc..0000000 --- a/addons/cetmix_tower_git/i18n/cetmix_tower_git.pot +++ /dev/null @@ -1,1029 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * cetmix_tower_git -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" -"Report-Msgid-Bugs-To: \n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0 -#, python-format -msgid "" -"\n" -"# You need to set the following variables in your environment:\n" -"# %(vars)s\n" -"# and run git-aggregator with '--expand-env' parameter.\n" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0 -#, python-format -msgid "" -"# This file is generated with Cetmix Tower https://cetmix.com/tower\n" -"# It's designed to be used with git-aggregator tool developed by Acsone.\n" -"# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "* Sources where all remotes are private" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "* Sources where some remotes are private" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "..to be autogenerated" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Managers. All users who have \"Manager\" group and are set as \"Managers\" in all related servers.\n" -" This is done to avoid unpredictable consequences when some of the servers are not updated due to access restrictions when a project is updated." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Users. All users who have \"Manager\" group and are either set in " -"\"Users\" or in \"Managers\" in all related servers." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_repo_unique_repo_host_owner -msgid "A repository with the same name, host, and owner already exists." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Access" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__active -msgid "Active" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Archived" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__auto_sync -msgid "Auto Sync" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__branch -msgid "Branch" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -msgid "Branch/PR/commit number or link" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project_rel__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo_owner__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__reference -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_owner_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "" -"Can contain English letters, digits and '_'. Leave blank to autogenerate" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_command -msgid "Cetmix Tower Command" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_file -msgid "Cetmix Tower File" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_file_template -msgid "Cetmix Tower File Template" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_plan_line -msgid "Cetmix Tower Flight Plan Line" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project -msgid "Cetmix Tower Git Project" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project_file_template_rel -msgid "Cetmix Tower Git Project relation to File Templates" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project_rel -msgid "Cetmix Tower Git Project relation to Files and Servers" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_remote -msgid "Cetmix Tower Git Remote" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_repo -msgid "Cetmix Tower Git Repository" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_repo_owner -msgid "Cetmix Tower Git Repository Owner" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_source -msgid "Cetmix Tower Git Source" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cetmix_tower -msgid "Cetmix Tower Odoo Automation" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_server -msgid "Cetmix Tower Server" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_file_template_rel.py:0 -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0 -#, python-format -msgid "Code generator function for '%(project_format)s' format not found." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__commit -msgid "Commit" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form -msgid "Configure" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_plan_line__is_make_copy -msgid "" -"Create a copy of the Git Project instead of linking the file to the existing" -" one." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_remote -msgid "Create your first git remote!" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_repo_owner -msgid "Create your first repository owner!" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_repo -msgid "Create your first repository!" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_uid -msgid "Created by" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_date -msgid "Created on" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__secret_id -msgid "Custom secret used for this repository" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo_owner__secret_id -msgid "Custom secret used for this repository owner" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Disabled" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__display_name -msgid "Display Name" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__url -msgid "Displayed in 'https' format, but can be entered in any format" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_source.py:0 -#, python-format -msgid "Empty Source" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__enabled -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__enabled -msgid "Enable in configuration and exported to files" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__enabled -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__enabled -msgid "Enabled" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Export YAML" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__file_id -msgid "File" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0 -#, python-format -msgid "File '%(file)s' doesn't belong to server '%(server)s'" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__file_template_id -msgid "File Template" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__file_template_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "File Templates" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_project_rel_project_server_file_format_uniq -msgid "File is already related to the same project and format" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_project_file_template_rel_project_server_file_format_uniq -msgid "File template is already related to the same project and format" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__file_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Files" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__project_format -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__project_format -msgid "Format" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__git -msgid "GIT" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__url_git -msgid "GIT URL" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__url_git -msgid "GIT URL of the repository" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "General" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__url -msgid "Generic URL" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Git Aggregator" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir -msgid "Git Aggregator Root Dir" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "" -"Git Aggregator: Bitbucket does not support fetching PRs. Please use branch instead.\n" -"\n" -"Source: %(src)s\n" -"URL: %(url)s\n" -"Head: %(head)s" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Git Aggregator: Head number is empty in %(head)s" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__git_project_id -msgid "Git Configuration" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file_template__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__git_project_ids -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_plan_line__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_ids -msgid "Git Project" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__git_project_count -msgid "Git Project Count" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_project_file_template_rel_ids -msgid "Git Project File Template Relations" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_rel_ids -msgid "Git Project Rel" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_rel_ids -msgid "Git Project Relations" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_project_rel_ids -msgid "Git Project Server File Relations" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.cx_tower_git_project_action -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file_template__git_project_ids -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_project -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_project_settings -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form -msgid "Git Projects" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.action_cx_tower_git_remote -msgid "Git Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir -msgid "" -"Git aggregator root directory where sources will be cloned. Eg '/tmp/git-" -"aggregator' Will use '.' if not set" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Git aggregator root directory where sources will be cloned. Leave blank to " -"use '.'" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__head -msgid "Git remote head. Link to branch, PR, commit or commit hash." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_remote -msgid "" -"Git remotes represent branches, pull requests, or commits from git " -"repositories." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "GitProjects" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Group By" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__https -msgid "HTTPS" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes -msgid "Has Partially Private Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes -msgid "Has Private Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head -msgid "Head" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head_type -msgid "Head Type" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__host -msgid "Host" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__id -msgid "ID" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project_rel__auto_sync -msgid "If enabled file will be synced automatically using cron" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__repo_id -msgid "" -"If selected, the remote URL will be filled from the repo settings based on " -"the remote protocol" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes -msgid "Indicates if the project has any partially private remotes." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes -msgid "Indicates if the project has any private remotes." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__active -msgid "Indicates if the repository is active" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__is_private -msgid "Indicates if the repository is private" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source____last_update -msgid "Last Modified on" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_date -msgid "Last Updated on" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_plan_line__is_make_copy -msgid "Make a Copy" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__manager_ids -msgid "Managers" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__manager_ids -msgid "Managers who can modify this record" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__name -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Name" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo_owner__name -msgid "Name of the repository owner (e.g., 'cetmix', 'OCA')" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Name. Leave blank to autogenerate" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Name/Reference" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_repo.py:0 -#, python-format -msgid "Not a valid repository URL!" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__note -msgid "Note" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__git_project_count -msgid "Number of projects this repository is used in" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__remote_count -msgid "Number of remotes this repository is used in" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Open File Template" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form -msgid "Open Git Project" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Open Server" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Open file template" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Org" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__owner_id -msgid "Owner" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__is_private -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__is_private -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Private" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count_private -msgid "Private Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "Projects" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__git_project_ids -msgid "Projects this repository is used in" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url_protocol -msgid "Protocol" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__repo_provider -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__provider -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Provider" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Provider: Other" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Public" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__pr -msgid "Pull/Merge Request" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Put your notes here..." -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_command.py:0 -#, python-format -msgid "" -"Python library for Git URL parsing. Available methods: 'parse', 'validate'." -" Documentation on GitHub." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__reference -msgid "Reference" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "" -"Reference. Can contain English letters, digits and '_'. Leave blank to " -"autogenerate" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__remote_ids -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_ids -msgid "Remote" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__remote_count -msgid "Remote Count" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__remote_ids -msgid "Remotes that use this repository" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Repos" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.action_cx_tower_git_repo -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__repo_ids -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__repo_ids -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_repositories -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_owner_view_form -msgid "Repositories" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo_owner__repo_ids -msgid "Repositories owned by this organization/user" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_repo -msgid "" -"Repositories represent git repositories with their metadata and configuration.\n" -" They can be linked to remotes to automatically populate URL information." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__repo_ids -msgid "Repositories used in this project through its sources and remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__repo_id -msgid "Repository" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__repo -msgid "Repository Name" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.action_cx_tower_git_repo_owner -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_repository_owners -msgid "Repository Owners" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Repository URL is not set" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__host -msgid "Repository host (e.g., 'github.com', 'gitlab.com')" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__is_private -msgid "Repository is private" -msgstr "" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Repository is required" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__repo -msgid "Repository name (e.g., 'cetmix-tower', 'odoo')" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__owner_id -msgid "Repository owner (e.g., 'cetmix' or 'OCA')" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_repo_owner -msgid "" -"Repository owners represent organizations or users that own git repositories.\n" -" Examples include \"cetmix\", \"OCA\", etc." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__repo_provider -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__provider -msgid "Repository provider to determine provider-based behaviour" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Root Directory" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__ssh -msgid "SSH" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__url_ssh -msgid "SSH URL" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__url_ssh -msgid "SSH URL of the repository" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__secret_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__secret_id -msgid "Secret" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_plan_line__git_project_id -msgid "Select a git project to be linked to the file and server." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__sequence -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__sequence -msgid "Sequence" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__server_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__server_id -msgid "Server" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__server_ids -msgid "Servers" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__server_ids -msgid "" -"Servers are added automatically based on the files linked to the project.\n" -"IMPORTANT: This field may contain duplicates because of the relation nature!" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__source_id -msgid "Source" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__source_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Sources" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "" -"The top one remote will be used as a merge target.\n" -" You can re-arrange remotes by dragging them or changing their sequence value." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__user_ids -msgid "Users" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__user_ids -msgid "Users who can view this record" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "YAML" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__yaml_code -msgid "Yaml Code" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"You can edit these fields at your own risk. However keep in mind that they " -"will be automatically updated each time related servers are added, removed " -"or updated." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "You must be a member of the \"YAML/Export\" group to export data as YAML." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_owner_view_form -msgid "e.g., Cetmix, OCA" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_owner_view_form -msgid "e.g., cetmix, oca" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "e.g., cetmix-tower, odoo" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "https, ssh or git formats are accepted" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "managers who can modify this record" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_tree -msgid "select or enter a link" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "users who can view this record" -msgstr "" diff --git a/addons/cetmix_tower_git/i18n/fi.po b/addons/cetmix_tower_git/i18n/fi.po deleted file mode 100644 index 0cd54fa..0000000 --- a/addons/cetmix_tower_git/i18n/fi.po +++ /dev/null @@ -1,595 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * cetmix_tower_git -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" -"Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: fi\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0 -#, python-format -msgid "" -"\n" -"# You need to set the following variables in your environment:\n" -"# %(vars)s \n" -"# and run git-aggregator with '--expand-env' parameter.\n" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0 -#, python-format -msgid "" -"# This file is generated with Cetmix Tower https://cetmix.com/tower\n" -"# It's designed to be used with git-aggregator tool developed by Acsone.\n" -"# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "* Sources where all remotes are private" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "* Sources where some remotes are private" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Managers. All users who have \"Manager\" group and are set as \"Managers\" in all related servers.\n" -" This is done to avoid unpredictable consequences when some of the servers are not updated due to access restrictions when a project is updated." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Users. All users who have \"Manager\" group and are either set in " -"\"Users\" or in \"Managers\" in all related servers." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Access" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__active -msgid "Active" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Archived" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__bitbucket -msgid "Bitbucket" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__branch -msgid "Branch" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -msgid "Branch/PR/commit number or link" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__reference -msgid "" -"Can contain English letters, digits and '_'. Leave blank to autogenerate" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project -msgid "Cetmix Tower Git Configuration" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_remote -msgid "Cetmix Tower Git Remote" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_source -msgid "Cetmix Tower Git Source" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_server -msgid "Cetmix Tower Server" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0 -#, python-format -msgid "Code generator function for '%(project_format)s' format not found." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__commit -msgid "Commit" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_uid -msgid "Created by" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_date -msgid "Created on" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_file -msgid "Cx Tower File" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Disabled" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__display_name -msgid "Display Name" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__enabled -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__enabled -msgid "Enable in configuration and exported to files" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__enabled -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__enabled -msgid "Enabled" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Export YAML" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__file_id -msgid "File" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0 -#, python-format -msgid "File '%(file)s' doesn't belong to server '%(server)s'" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_file.py:0 -#, python-format -msgid "" -"File '%(file)s' is related to multiple projects: %(projects)s \n" -"Please select only one project." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_project_rel_project_server_file_format_uniq -msgid "File is already related to the same project and format" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__file_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Files" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__project_format -msgid "Format" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir -msgid "Git Aggregator Root Dir" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "" -"Git Aggregator: Bitbucket does not support fetching PRs. Please use branch instead.\n" -"\n" -"Source: %(src)s\n" -"URL: %(url)s\n" -"Head: %(head)s" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Git Aggregator: Head number is empty in %(head)s" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__git_project_id -msgid "Git Configuration" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_ids -msgid "Git Project" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_rel_ids -msgid "Git Project Rel" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_project_rel_ids -msgid "Git Project Server File Relations" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project_rel -msgid "Git Project relation to other model records" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.cx_tower_git_project_action -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_ids -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_project -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form -msgid "Git Projects" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir -msgid "" -"Git aggregator root directory where sources will be cloned. Eg '/tmp/git-" -"aggregator' Will use '.' if not set" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Git aggregator root directory where sources will be cloned. Leave blank to " -"use '.'" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__url -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -msgid "" -"Git remote URL. Eg 'https://github.com/cetmix/cetmix-tower.git' or " -"'git@github.com:cetmix/cetmix-tower.git'" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__head -msgid "" -"Git remote head. Link to branch, PR, commit or commit hash. Leave blank to " -"auto-detect" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__github -msgid "GitHub" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__gitlab -msgid "GitLab" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__https -msgid "HTTPS" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes -msgid "Has Partially Private Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes -msgid "Has Private Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head -msgid "Head" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head_type -msgid "Head Type" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__id -msgid "ID" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes -msgid "Indicates if the project has any partially private remotes." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes -msgid "Indicates if the project has any private remotes." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__is_private -msgid "Is Private" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server____last_update -msgid "Last Modified on" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_date -msgid "Last Updated on" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__manager_ids -msgid "Managers" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__manager_ids -msgid "Managers who can modify this record" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__name -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Name" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Not a valid URL. URL must end with '.git'" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Not a valid URL. URL must start with 'https://' or 'git@'" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "" -"Not a valid URL: %(url_msg)s\n" -"URL must contain at least two parts separated by dot." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__other -msgid "Other" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Private" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count_private -msgid "Private Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__pr -msgid "Pull/Merge Request" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__reference -msgid "Reference" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "" -"Reference. Can contain English letters, digits and '_'. Leave blank to " -"autogenerate" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__repo_provider -msgid "Repository Provider" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__is_private -msgid "Repository is private" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__ssh -msgid "SSH" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__sequence -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__sequence -msgid "Sequence" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__server_id -msgid "Server" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__server_ids -msgid "Servers" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__server_ids -msgid "" -"Servers are added automatically based on the files linked to the project.\n" -"IMPORTANT: This field may contain duplicates because of the relation nature!" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__source_id -msgid "Source" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__source_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Sources" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "" -"The top one remote will be used as a merge target.\n" -" You can re-arrange remotes by dragging them or changing their sequence value." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url -msgid "URL" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url_protocol -msgid "URL Protocol" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "URL is required" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__user_ids -msgid "Users" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__user_ids -msgid "Users who can view this record" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__repo_provider -msgid "" -"Will be tried to be determined from the URL. Please select manually if auto-" -"detection fails." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "YAML" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__yaml_code -msgid "Yaml Code" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"You can edit these fields at your own risk. However keep in mind that they " -"will be automatically updated each time related servers are added, removed " -"or updated." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "You must be a member of the \"YAML/Export\" group to export data as YAML." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "managers who can modify this record" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "users who can view this record" -msgstr "" diff --git a/addons/cetmix_tower_git/i18n/hr.po b/addons/cetmix_tower_git/i18n/hr.po deleted file mode 100644 index 1b2e1c7..0000000 --- a/addons/cetmix_tower_git/i18n/hr.po +++ /dev/null @@ -1,635 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * cetmix_tower_git -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" -"Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2025-03-06 09:11+0000\n" -"Last-Translator: Bole \n" -"Language-Team: Croatian \n" -"Language: hr\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Weblate 5.10.3-dev\n" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0 -#, python-format -msgid "" -"\n" -"# You need to set the following variables in your environment:\n" -"# %(vars)s \n" -"# and run git-aggregator with '--expand-env' parameter.\n" -msgstr "" -"\n" -"# Potrebno je postaviti sljedeće varijable u vaše okruženje:\n" -"# %(vars)s \n" -"# i pokrenuti git-aggregator sa ' --expand-env' parametrom\n" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0 -#, python-format -msgid "" -"# This file is generated with Cetmix Tower https://cetmix.com/tower\n" -"# It's designed to be used with git-aggregator tool developed by Acsone.\n" -"# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n" -msgstr "" -"# Ova datotek aje generiran pomoću Cetmix Tower sustava: https://cetmix.com/" -"tower\n" -"# Dizajniran je za korištenje sa git-aggregator alatom razvijenim od Ascone." -"\n" -"# Dokumentacija za git-aggregator : https://github.com/acsone/git-" -"aggregator\n" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "* Sources where all remotes are private" -msgstr "* izvori sa svim udaljenim lokacijama koje su privatne" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "* Sources where some remotes are private" -msgstr "* Izvori u kojima su neke udaljene lokacije privatne" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Managers. All users who have \"Manager\" group and are set as \"Managers\" in all related servers.\n" -" This is done to avoid unpredictable consequences when some of the servers are not updated due to access restrictions when a project is updated." -msgstr "" -"Manageri. Svi korisnici koji imaju \"Manager\" grupu i postavljeni su " -"kao \"Manageri\" u svim povezanim serverima.\n" -" " -"Ovo je napravljeno kako bi izbjegli nepredviđene posljedice kad neki od " -"servera nisu ažurirani zbog ograničenog pristupa prilikom ažuriranja " -"projekta." - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Users. All users who have \"Manager\" group and are either set in " -"\"Users\" or in \"Managers\" in all related servers." -msgstr "" -"Korisnici. Svi korisnici koji imaju \"Manager\" grupu i postavljeni " -"su ili kao \"Korisnik\" ili kao \"Manager\" u svim povezanim " -"serverima." - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Access" -msgstr "Pristup" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__active -msgid "Active" -msgstr "Aktivno" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Archived" -msgstr "Arhivirano" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__bitbucket -msgid "Bitbucket" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__branch -msgid "Branch" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -msgid "Branch/PR/commit number or link" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__reference -msgid "" -"Can contain English letters, digits and '_'. Leave blank to autogenerate" -msgstr "" -"Može sadržavati slova engleske abecede, brojke i ':'. Ostavite prazno za " -"automatsko generiranje" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project -msgid "Cetmix Tower Git Configuration" -msgstr "Cetmix Tower Git postavke" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_remote -msgid "Cetmix Tower Git Remote" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_source -msgid "Cetmix Tower Git Source" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_server -msgid "Cetmix Tower Server" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0 -#, python-format -msgid "Code generator function for '%(project_format)s' format not found." -msgstr "" -"Funkcija generiranja koda za '%(project_format)s' format nije pronađena." - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__commit -msgid "Commit" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_uid -msgid "Created by" -msgstr "Kreirao" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_date -msgid "Created on" -msgstr "Kreirano" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_file -msgid "Cx Tower File" -msgstr "Cx Tower datoteka" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Disabled" -msgstr "Onemogućen" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__display_name -msgid "Display Name" -msgstr "Naziv" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__enabled -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__enabled -msgid "Enable in configuration and exported to files" -msgstr "Omogućen u postavkama i izvezen u datoteku" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__enabled -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__enabled -msgid "Enabled" -msgstr "Omogućen" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Export YAML" -msgstr "Izvoz YAML" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__file_id -msgid "File" -msgstr "Datoteka" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0 -#, python-format -msgid "File '%(file)s' doesn't belong to server '%(server)s'" -msgstr "Datoteka '%(file)s' ne pripada serveru '%(server)s'" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_file.py:0 -#, python-format -msgid "" -"File '%(file)s' is related to multiple projects: %(projects)s \n" -"Please select only one project." -msgstr "" -"Datoteka '%(file)s' je povezana sa višestrukim projektima: %(projects)s \n" -"Molim odaberite samo jedan projekt." - -#. module: cetmix_tower_git -#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_project_rel_project_server_file_format_uniq -msgid "File is already related to the same project and format" -msgstr "Datoteka je već povezana sa ovim projektom i ovim formatom" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__file_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Files" -msgstr "Datoteke" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__project_format -msgid "Format" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir -msgid "Git Aggregator Root Dir" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "" -"Git Aggregator: Bitbucket does not support fetching PRs. Please use branch instead.\n" -"\n" -"Source: %(src)s\n" -"URL: %(url)s\n" -"Head: %(head)s" -msgstr "" -"Git Aggregator: BitBucket ne podržava dohvaćanje PRova. Molim koristite " -"branch.\n" -"\n" -"Source: %(src)s\n" -"URL: %(url)s\n" -"Head: %(head)s" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Git Aggregator: Head number is empty in %(head)s" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__git_project_id -msgid "Git Configuration" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_ids -msgid "Git Project" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_rel_ids -msgid "Git Project Rel" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_project_rel_ids -msgid "Git Project Server File Relations" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project_rel -msgid "Git Project relation to other model records" -msgstr "Git projekt povezan sa ostalim zapisima modela" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.cx_tower_git_project_action -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_ids -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_project -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form -msgid "Git Projects" -msgstr "Git projekti" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir -msgid "" -"Git aggregator root directory where sources will be cloned. Eg '/tmp/git-" -"aggregator' Will use '.' if not set" -msgstr "" -"GitAgregator izvorni direktorij u koji će izvori biti klonirani. Npr. '/tmp/" -"git-aggregator' Ako ništa nije postavljeno koristi se '.'" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Git aggregator root directory where sources will be cloned. Leave blank to " -"use '.'" -msgstr "" -"GitAgregtor izvorni dirketorij u koji će izvori biti klonirani. Ostavite " -"prazno za korištenje '.'" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__url -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -msgid "" -"Git remote URL. Eg 'https://github.com/cetmix/cetmix-tower.git' or " -"'git@github.com:cetmix/cetmix-tower.git'" -msgstr "" -"Git remote URL. Eg 'https://github.com/cetmix/cetmix-tower.git' ili " -"'git@github.com:cetmix/cetmix-tower.git'" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__head -msgid "" -"Git remote head. Link to branch, PR, commit or commit hash. Leave blank to " -"auto-detect" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__github -msgid "GitHub" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__gitlab -msgid "GitLab" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__https -msgid "HTTPS" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes -msgid "Has Partially Private Remotes" -msgstr "Ima djelomično privatne udaljene izvore" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes -msgid "Has Private Remotes" -msgstr "Ima privatne udaljene izvore" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head -msgid "Head" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head_type -msgid "Head Type" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__id -msgid "ID" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes -msgid "Indicates if the project has any partially private remotes." -msgstr "Indicira ima li projekt djelomično privatnih udaljenih izvora." - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes -msgid "Indicates if the project has any private remotes." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__is_private -msgid "Is Private" -msgstr "Je privatno" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server____last_update -msgid "Last Modified on" -msgstr "Zadnje modificirano" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_uid -msgid "Last Updated by" -msgstr "Zadnji ažurirao" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_date -msgid "Last Updated on" -msgstr "Zadnje ažurirano" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__manager_ids -msgid "Managers" -msgstr "Manageri" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__manager_ids -msgid "Managers who can modify this record" -msgstr "Manageri koji mogu urediti ovaj zapis" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__name -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Name" -msgstr "Naziv" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Not a valid URL. URL must end with '.git'" -msgstr "Nije valjani URL. URL mora završavati sa '.git'" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Not a valid URL. URL must start with 'https://' or 'git@'" -msgstr "Nije valjani URL. URL mora počinjati sa 'https://' ili 'git@'" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "" -"Not a valid URL: %(url_msg)s\n" -"URL must contain at least two parts separated by dot." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__other -msgid "Other" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Private" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count_private -msgid "Private Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__pr -msgid "Pull/Merge Request" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__reference -msgid "Reference" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "" -"Reference. Can contain English letters, digits and '_'. Leave blank to " -"autogenerate" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Remotes" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__repo_provider -msgid "Repository Provider" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__is_private -msgid "Repository is private" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__ssh -msgid "SSH" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__sequence -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__sequence -msgid "Sequence" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__server_id -msgid "Server" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__server_ids -msgid "Servers" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__server_ids -msgid "" -"Servers are added automatically based on the files linked to the project.\n" -"IMPORTANT: This field may contain duplicates because of the relation nature!" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__source_id -msgid "Source" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__source_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Sources" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "" -"The top one remote will be used as a merge target.\n" -" You can re-arrange remotes by dragging them or changing their sequence value." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url -msgid "URL" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url_protocol -msgid "URL Protocol" -msgstr "" - -#. module: cetmix_tower_git -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "URL is required" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__user_ids -msgid "Users" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__user_ids -msgid "Users who can view this record" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__repo_provider -msgid "" -"Will be tried to be determined from the URL. Please select manually if auto-" -"detection fails." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "YAML" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__yaml_code -msgid "Yaml Code" -msgstr "YAML kod" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"You can edit these fields at your own risk. However keep in mind that they " -"will be automatically updated each time related servers are added, removed " -"or updated." -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "You must be a member of the \"YAML/Export\" group to export data as YAML." -msgstr "Morate bit član \"YAML/Izvoz\" grupe za izvoz podataka u YAML." - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "managers who can modify this record" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "users who can view this record" -msgstr "" diff --git a/addons/cetmix_tower_git/i18n/it.po b/addons/cetmix_tower_git/i18n/it.po deleted file mode 100644 index efa696b..0000000 --- a/addons/cetmix_tower_git/i18n/it.po +++ /dev/null @@ -1,1085 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * cetmix_tower_git -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: 2025-11-11 15:54+0100\n" -"Last-Translator: \n" -"Language-Team: Italian \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: it\n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Poedit 2.3\n" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0 -#, python-format -msgid "" -"\n" -"# You need to set the following variables in your environment:\n" -"# %(vars)s\n" -"# and run git-aggregator with '--expand-env' parameter.\n" -msgstr "" -"\n" -"# È necessario impostare le seguenti variabili d'ambiente:\n" -"# %(vars)s\n" -"# ed eseguire git-aggregator con il parametro '--expand-env'.\n" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0 -#, python-format -msgid "" -"# This file is generated with Cetmix Tower https://cetmix.com/tower\n" -"# It's designed to be used with git-aggregator tool developed by Acsone.\n" -"# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n" -msgstr "" -"# Questo file è generato con Cetmix Tower https://cetmix.com/tower\n" -"# È progettato per essere usato con il tool git-aggregator sviluppato da Acsone.\n" -"# Documentazione per git-aggregator: https://github.com/acsone/git-aggregator\n" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "* Sources where all remotes are private" -msgstr "* Origini dove tutti i remote sono privati" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "* Sources where some remotes are private" -msgstr "* Origini dove alcuni remote sono privati" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "..to be autogenerated" -msgstr "... da autogenerare" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "" -"Managers. All users who have \"Manager\" group and are set as \"Managers\" in all related servers.\n" -" This is done to avoid unpredictable consequences when some of the servers are not updated due to access restrictions when a project is updated." -msgstr "" -"
Responsabili. Tutti gli utenti che hanno il gruppo \"Responsabili\" e seno impostati a \"Responsabile\" in tutti i server relativi.\n" -" Questo serve ad evitare conseguenze impreviste quando alcuni dei server non sono aggiornati per limitazioni di accesso nell'aggiornamento di un progetto." - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Users. All users who have \"Manager\" group and are either set in \"Users\" or in \"Managers\" in all related servers." -msgstr "Utenti. Tutti gli utenti che hanno il gruppo \"Responsabili\" e sono impostati in \"Utenti\" o \"Responsabili\" in tutti i server correlati." - -#. module: cetmix_tower_git -#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_repo_unique_repo_host_owner -msgid "A repository with the same name, host, and owner already exists." -msgstr "Esiste già un repository con lo stesso nome server, host e proprietario." - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Access" -msgstr "Accesso" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__active -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__active -msgid "Active" -msgstr "Attivo" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Archived" -msgstr "In archivio" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__auto_sync -msgid "Auto Sync" -msgstr "Auto sincronizza" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__branch -msgid "Branch" -msgstr "Branch" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -msgid "Branch/PR/commit number or link" -msgstr "Numero o link branch/PR/commit" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project_rel__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo_owner__reference -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__reference -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_owner_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "Can contain English letters, digits and '_'. Leave blank to autogenerate" -msgstr "Può contenere lettere inglesi, cifre e '_'. Lasciare vuoto per la generazione automatica" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_command -msgid "Cetmix Tower Command" -msgstr "Comando Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_file -msgid "Cetmix Tower File" -msgstr "File Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_file_template -msgid "Cetmix Tower File Template" -msgstr "Modello file Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_plan_line -msgid "Cetmix Tower Flight Plan Line" -msgstr "Riga piano di volo Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project -msgid "Cetmix Tower Git Project" -msgstr "Progetto Git Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project_file_template_rel -msgid "Cetmix Tower Git Project relation to File Templates" -msgstr "Relazione progetto Git a modelli file" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project_rel -msgid "Cetmix Tower Git Project relation to Files and Servers" -msgstr "Relazione progetto Git ad file e server" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_remote -msgid "Cetmix Tower Git Remote" -msgstr "Remote Git Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_repo -msgid "Cetmix Tower Git Repository" -msgstr "Repository Git Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_repo_owner -msgid "Cetmix Tower Git Repository Owner" -msgstr "Proprietario repository Git Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_source -msgid "Cetmix Tower Git Source" -msgstr "Origine Git Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cetmix_tower -msgid "Cetmix Tower Odoo Automation" -msgstr "Automazione Odoo Cetmix Tower" - -#. module: cetmix_tower_git -#: model:ir.model,name:cetmix_tower_git.model_cx_tower_server -msgid "Cetmix Tower Server" -msgstr "Server Cetmix Tower" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_file_template_rel.py:0 -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0 -#, python-format -msgid "Code generator function for '%(project_format)s' format not found." -msgstr "Funzione generazione codice per il formato '%(project_format)s' non trovata." - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__commit -msgid "Commit" -msgstr "Commit" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form -msgid "Configure" -msgstr "Configura" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_plan_line__is_make_copy -msgid "Create a copy of the Git Project instead of linking the file to the existing one." -msgstr "Creare una copia del progetto Git invece di collegare il file ad uno esistente." - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_remote -msgid "Create your first git remote!" -msgstr "Crea il tuo primo remote Git!" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_repo_owner -msgid "Create your first repository owner!" -msgstr "Crea il tuo primo proprietario di repository!" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_repo -msgid "Create your first repository!" -msgstr "Crea il tuo primo repository!" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__create_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_uid -msgid "Created by" -msgstr "Creato da" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__create_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_date -msgid "Created on" -msgstr "Creato il" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__secret_id -msgid "Custom secret used for this repository" -msgstr "Secreto personalizzato utilizzato per questo repository" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo_owner__secret_id -msgid "Custom secret used for this repository owner" -msgstr "Segreto personalizzato utilizzato per questo proprietario del repository" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Disabled" -msgstr "Disabilitato" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__display_name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__display_name -msgid "Display Name" -msgstr "Nome visualizzato" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__url -msgid "Displayed in 'https' format, but can be entered in any format" -msgstr "Visualizzato in formato 'https', ma può essere inserito in qualsiasi formato" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_source.py:0 -#, python-format -msgid "Empty Source" -msgstr "Origine vuota" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__enabled -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__enabled -msgid "Enable in configuration and exported to files" -msgstr "Abilitato in configurazione ed esportato nei file" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__enabled -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__enabled -msgid "Enabled" -msgstr "Abilitato" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Export YAML" -msgstr "Esporta YAML" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__file_id -msgid "File" -msgstr "File" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0 -#, python-format -msgid "File '%(file)s' doesn't belong to server '%(server)s'" -msgstr "Il file '%(file)s' non appartiene al server '%(server)s'" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__file_template_id -msgid "File Template" -msgstr "Modello file" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__file_template_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "File Templates" -msgstr "Modelli file" - -#. module: cetmix_tower_git -#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_project_rel_project_server_file_format_uniq -msgid "File is already related to the same project and format" -msgstr "Il file è già relativo allo stesso progetto e formato" - -#. module: cetmix_tower_git -#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_project_file_template_rel_project_server_file_format_uniq -msgid "File template is already related to the same project and format" -msgstr "Il file modello è già relativo allo stesso progetto e formato" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__file_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Files" -msgstr "File" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__project_format -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__project_format -msgid "Format" -msgstr "Formato" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__git -msgid "GIT" -msgstr "GIT" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__url_git -msgid "GIT URL" -msgstr "URL GIT" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__url_git -msgid "GIT URL of the repository" -msgstr "URL git del repository" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "General" -msgstr "Generale" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__url -msgid "Generic URL" -msgstr "URL generico" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Git Aggregator" -msgstr "Git Aggregator" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir -msgid "Git Aggregator Root Dir" -msgstr "Directory radice Git Aggregator" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "" -"Git Aggregator: Bitbucket does not support fetching PRs. Please use branch instead.\n" -"\n" -"Source: %(src)s\n" -"URL: %(url)s\n" -"Head: %(head)s" -msgstr "" -"Git Aggregator: Bitbucket non supporta il fetch delle PR. In alternativa usare branch.\n" -"\n" -"Origine: %(src)s\n" -"URL: %(url)s\n" -"Head: %(head)s" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Git Aggregator: Head number is empty in %(head)s" -msgstr "Git Aggregator: il numero Head è vuoto in %(head)s" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__git_project_id -msgid "Git Configuration" -msgstr "Configurazione Git" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file_template__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__git_project_ids -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_plan_line__git_project_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_ids -msgid "Git Project" -msgstr "Progetto Git" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__git_project_count -msgid "Git Project Count" -msgstr "Conteggio progetti Git" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_project_file_template_rel_ids -msgid "Git Project File Template Relations" -msgstr "Relazioni modello file progetto Git" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_rel_ids -msgid "Git Project Rel" -msgstr "Rel progetto Git" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_rel_ids -msgid "Git Project Relations" -msgstr "Relazioni progetto Git" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_project_rel_ids -msgid "Git Project Server File Relations" -msgstr "Relazioni file server progetto Git" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.cx_tower_git_project_action -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file_template__git_project_ids -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_project -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_project_settings -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form -msgid "Git Projects" -msgstr "Progetti Git" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.action_cx_tower_git_remote -msgid "Git Remotes" -msgstr "Remote Git" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir -msgid "Git aggregator root directory where sources will be cloned. Eg '/tmp/git-aggregator' Will use '.' if not set" -msgstr "La directory radice di Git Aggregator dove l'origine verrà clonata. Es. '/tmp/git-aggregator'. Verrà usato '.' se non impostata" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Git aggregator root directory where sources will be cloned. Leave blank to use '.'" -msgstr "La directory radice di Git Aggregator dove l'origine verrà clonata. Lasciare vuoto per usare '.'" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__head -msgid "Git remote head. Link to branch, PR, commit or commit hash." -msgstr "Head remoto Git. Link al branch, PR, commit o hash del commit. " - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_remote -msgid "Git remotes represent branches, pull requests, or commits from git repositories." -msgstr "I remote git rappresentano branch, pull reguest o commit dai repository Git." - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "GitProjects" -msgstr "" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Group By" -msgstr "Raggruppa per" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__https -msgid "HTTPS" -msgstr "HTTPS" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes -msgid "Has Partially Private Remotes" -msgstr "Ha remote parzialmente privati" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes -msgid "Has Private Remotes" -msgstr "Ha remote privati" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head -msgid "Head" -msgstr "Intestazione" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head_type -msgid "Head Type" -msgstr "Tipo head" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__host -msgid "Host" -msgstr "Host" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__id -msgid "ID" -msgstr "ID" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project_rel__auto_sync -msgid "If enabled file will be synced automatically using cron" -msgstr "Se abilitata il file verrà sincronizzato automaticamente usando il cron" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__repo_id -msgid "If selected, the remote URL will be filled from the repo settings based on the remote protocol" -msgstr "Se selezionata, l'URL del remote verrà compilato dalle impostazione del repository in base al protocollo del remote" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes -msgid "Indicates if the project has any partially private remotes." -msgstr "Indica se il progetto ha qualche remote parzialmente privato." - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes -msgid "Indicates if the project has any private remotes." -msgstr "Indica se il progetto ha qualche remote privato." - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__active -msgid "Indicates if the repository is active" -msgstr "Indica se il repository è attivo." - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__is_private -msgid "Indicates if the repository is private" -msgstr "Indica se il repositoty è privato." - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner____last_update -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source____last_update -msgid "Last Modified on" -msgstr "Ultima modifica il" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__write_uid -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_uid -msgid "Last Updated by" -msgstr "Ultimo aggiornamento di" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__write_date -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_date -msgid "Last Updated on" -msgstr "Ultimo aggiornamento il" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_plan_line__is_make_copy -msgid "Make a Copy" -msgstr "Crea una copia" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__manager_ids -msgid "Managers" -msgstr "Responsabili" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__manager_ids -msgid "Managers who can modify this record" -msgstr "Responsabili che possono modificare questo record" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_file_template_rel__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__name -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__name -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Name" -msgstr "Nome" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo_owner__name -msgid "Name of the repository owner (e.g., 'cetmix', 'OCA')" -msgstr "Nome del proprietario del repository (es. 'cetmix', 'OCA')" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Name. Leave blank to autogenerate" -msgstr "Nome. Lasciare vuoto per autogenerarlo" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_repo.py:0 -#, python-format -msgid "Not a valid repository URL!" -msgstr "URL repository non valido!" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__note -msgid "Note" -msgstr "Nota" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__git_project_count -msgid "Number of projects this repository is used in" -msgstr "Numero dei progetti in cui è usato questo repository" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__remote_count -msgid "Number of remotes this repository is used in" -msgstr "Numero dei remote in cui è usato questo repository" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Open File Template" -msgstr "Apri modello file" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form -msgid "Open Git Project" -msgstr "Apri progetto Git" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Open Server" -msgstr "Apri server" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Open file template" -msgstr "Apri modello file" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Org" -msgstr "Org" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__owner_id -msgid "Owner" -msgstr "Proprietario" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__is_private -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__is_private -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Private" -msgstr "Privato" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count_private -msgid "Private Remotes" -msgstr "Remote privati" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "Projects" -msgstr "Progetti" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__git_project_ids -msgid "Projects this repository is used in" -msgstr "Progetto in cui è usato questo repository" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url_protocol -msgid "Protocol" -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__repo_provider -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__provider -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Provider" -msgstr "Provider" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Provider: Other" -msgstr "Provider: altro" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_search -msgid "Public" -msgstr "Pubblico" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__pr -msgid "Pull/Merge Request" -msgstr "Pull/Merge Request" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Put your notes here..." -msgstr "Inserisci qui le note..." - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_command.py:0 -#, python-format -msgid "Python library for Git URL parsing. Available methods: 'parse', 'validate'. Documentation on GitHub." -msgstr "Libreria Python per il parsing dell'URL Git. Metofi disponibili: 'parse', 'validate'. Documentazione su GitHub." - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__reference -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__reference -msgid "Reference" -msgstr "Riferimento" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Reference. Can contain English letters, digits and '_'. Leave blank to autogenerate" -msgstr "Riferimento. Può contenere lettere inglesi, cifre e '_'. Lasciare vuoto per la generazione automatica" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__remote_ids -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_ids -msgid "Remote" -msgstr "Remote" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__remote_count -msgid "Remote Count" -msgstr "Conteggio remote" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "Remotes" -msgstr "Remote" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__remote_ids -msgid "Remotes that use this repository" -msgstr "Remote che usano questo repository" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Repos" -msgstr "Repository" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.action_cx_tower_git_repo -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__repo_ids -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__repo_ids -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_repositories -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_owner_view_form -msgid "Repositories" -msgstr "Repository" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo_owner__repo_ids -msgid "Repositories owned by this organization/user" -msgstr "Repository di proprietà du eusta organizzazione/utente" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_repo -msgid "" -"Repositories represent git repositories with their metadata and configuration.\n" -" They can be linked to remotes to automatically populate URL information." -msgstr "" -"I repository rappresentano i reposoitory Git con i loro metadati e configurazione.\n" -" Possono essere collegati a remore per compilare automaticamente le informazioni sull'URL." - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__repo_ids -msgid "Repositories used in this project through its sources and remotes" -msgstr "Repository utilizzati in questo progetto attraverso i suo sorgente e i remote" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__repo_id -msgid "Repository" -msgstr "Repository" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__repo -msgid "Repository Name" -msgstr "Nome repository" - -#. module: cetmix_tower_git -#: model:ir.actions.act_window,name:cetmix_tower_git.action_cx_tower_git_repo_owner -#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_repository_owners -msgid "Repository Owners" -msgstr "Proprietari repository" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Repository URL is not set" -msgstr "L'URL del repository non è impostato" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__host -msgid "Repository host (e.g., 'github.com', 'gitlab.com')" -msgstr "Host repository (es. 'github.com', 'gitlab.com')" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__is_private -msgid "Repository is private" -msgstr "Il repository è privato" - -#. module: cetmix_tower_git -#. odoo-python -#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0 -#, python-format -msgid "Repository is required" -msgstr "Il repository è richiesto" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__repo -msgid "Repository name (e.g., 'cetmix-tower', 'odoo')" -msgstr "Nome repository (es. 'cetmix-tower', 'odoo')" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__owner_id -msgid "Repository owner (e.g., 'cetmix' or 'OCA')" -msgstr "Proprietari orepository (es. 'cetmix' or 'OCA')" - -#. module: cetmix_tower_git -#: model_terms:ir.actions.act_window,help:cetmix_tower_git.action_cx_tower_git_repo_owner -msgid "" -"Repository owners represent organizations or users that own git repositories.\n" -" Examples include \"cetmix\", \"OCA\", etc." -msgstr "" -"I proprietari del repository rappresentano organizzazioni o utenti che possiedono i repository Git.\n" -" Esempi includono \"cetmix\", \"OCA\", etc." - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__repo_provider -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__provider -msgid "Repository provider to determine provider-based behaviour" -msgstr "Provider repository per determinare l'aspetto in base al provider" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Root Directory" -msgstr "Cartella radice" - -#. module: cetmix_tower_git -#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__ssh -msgid "SSH" -msgstr "SSH" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__url_ssh -msgid "SSH URL" -msgstr "URL SSH" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_repo__url_ssh -msgid "SSH URL of the repository" -msgstr "URL SSH del repository" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__secret_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__secret_id -msgid "Secret" -msgstr "Segreto" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_plan_line__git_project_id -msgid "Select a git project to be linked to the file and server." -msgstr "Selezionare un progetto Git da collegare al file e al server." - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__sequence -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__sequence -msgid "Sequence" -msgstr "Sequenza" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__server_id -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__server_id -msgid "Server" -msgstr "Server" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__server_ids -msgid "Servers" -msgstr "Server" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__server_ids -msgid "" -"Servers are added automatically based on the files linked to the project.\n" -"IMPORTANT: This field may contain duplicates because of the relation nature!" -msgstr "" -"I server vengono aggiunti automaticamente in base ai file collegati al progetto.\n" -"IMPORTANTE: questo campo può contenere duplicati per la natura della relazione!" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__source_id -msgid "Source" -msgstr "Origine" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__source_ids -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "Sources" -msgstr "Origini" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form -msgid "" -"The top one remote will be used as a merge target.\n" -" You can re-arrange remotes by dragging them or changing their sequence value." -msgstr "" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__user_ids -msgid "Users" -msgstr "Utenti" - -#. module: cetmix_tower_git -#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__user_ids -msgid "Users who can view this record" -msgstr "Utenti che possono vedere questo record" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "YAML" -msgstr "YAML" - -#. module: cetmix_tower_git -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_repo_owner__yaml_code -#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__yaml_code -msgid "Yaml Code" -msgstr "Codice YAML" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "You can edit these fields at your own risk. However keep in mind that they will be automatically updated each time related servers are added, removed or updated." -msgstr "Si possono modificare questi campi a proprio rischio e pericolo. Tuttavia, tenere presente che verranno aggiornati automaticamente ogni volta che vengono aggiunti, rimossi o aggiornati server correlati." - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "You must be a member of the \"YAML/Export\" group to export data as YAML." -msgstr "Bisogna appartenere al gruppo \"YAML/Export\" per esportare dati in YAML." - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_owner_view_form -msgid "e.g., Cetmix, OCA" -msgstr "es. Cetmix, OCA" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_owner_view_form -msgid "e.g., cetmix, oca" -msgstr "es. cetmix, oca" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "e.g., cetmix-tower, odoo" -msgstr "es. cetmix-tower, odoo" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_repo_view_form -msgid "https, ssh or git formats are accepted" -msgstr "Sono accettati formati Git https o ssh" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "managers who can modify this record" -msgstr "responsabili che possono modificare questi record" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_tree -msgid "select or enter a link" -msgstr "selezionare o inserire un link" - -#. module: cetmix_tower_git -#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form -msgid "users who can view this record" -msgstr "utenti che possono vedere questo record" - -#~ msgid "Bitbucket" -#~ msgstr "Bitbucket" - -#~ msgid "" -#~ "Git remote URL. Eg 'https://github.com/cetmix/cetmix-tower.git' or " -#~ "'git@github.com:cetmix/cetmix-tower.git'" -#~ msgstr "" -#~ "URL remote Git. Es. 'https://github.com/cetmix/cetmix-tower.git' o " -#~ "'git@github.com:cetmix/cetmix-tower.git'" - -#~ msgid "GitHub" -#~ msgstr "GitHub" - -#~ msgid "GitLab" -#~ msgstr "GitLab" - -#~ msgid "Is Private" -#~ msgstr "È privato" - -#~ msgid "Not a valid URL. URL must end with '.git'" -#~ msgstr "URL non valido. L'URL deve finire con '.git'" - -#~ msgid "Not a valid URL. URL must start with 'https://', 'git@', or 'git://'" -#~ msgstr "" -#~ "URL non valido. L'URL deve iniziare con 'https://', 'git@', or 'git://'" - -#~ msgid "Open" -#~ msgstr "Aperto" - -#~ msgid "Open server" -#~ msgstr "Apri server" - -#~ msgid "URL" -#~ msgstr "URL" - -#~ msgid "URL is required" -#~ msgstr "È richiesto l'URL" - -#~ msgid "" -#~ "Will be tried to be determined from the URL. Please select manually if " -#~ "auto-detection fails." -#~ msgstr "" -#~ "Si cercherà di determinarlo dall'URL. Selezionarlo manualmente se " -#~ "fallisce l'auto determinazione." - -#~ msgid "" -#~ "File '%(file)s' is related to multiple projects: %(projects)s \n" -#~ "Please select only one project." -#~ msgstr "" -#~ "Il file '%(file)s' è relativo a progetti multipli: %(projects)s \n" -#~ "Selezionare solo un progetto." - -#~ msgid "" -#~ "Not a valid URL: %(url_msg)s\n" -#~ "URL must contain at least two parts separated by dot." -#~ msgstr "" -#~ "URL non valdio: %(url_msg)s\n" -#~ "URL deve contenere almeno due parti separate da un punto." diff --git a/addons/cetmix_tower_git/migrations/16.0.2.0.0/post-migration.py b/addons/cetmix_tower_git/migrations/16.0.2.0.0/post-migration.py deleted file mode 100644 index d5ead46..0000000 --- a/addons/cetmix_tower_git/migrations/16.0.2.0.0/post-migration.py +++ /dev/null @@ -1,82 +0,0 @@ -import logging - -from odoo import SUPERUSER_ID, api - -_logger = logging.getLogger(__name__) - - -def migrate(cr, version): - """ - Convert URLs in remotes to repositories. - Add repo_id to remotes. - """ - - _logger.info( - "Converting URLs in remotes to repositories and adding repo_id to remotes." - ) - env = api.Environment(cr, SUPERUSER_ID, {}) - - # Fetch all remotes using SQL query Group them {"url": [remote_id, remote_id, ...]} - cr.execute( - """ - SELECT url, array_agg(id) as remote_ids - FROM cx_tower_git_remote - GROUP BY url - """ - ) - remote_urls = cr.fetchall() - remote_urls_dict = {url: remote_ids for url, remote_ids in remote_urls} - - # Create repo for each url and add this repo to all remotes - url_count = 0 - remote_obj = env["cx.tower.git.remote"] - repo_obj = env["cx.tower.git.repo"] - for url, remote_ids in remote_urls_dict.items(): - repo_id = repo_obj.name_create(url)[0] - # Check if any of the remotes is private - remotes = remote_obj.browse(remote_ids) - is_private = bool(remotes.filtered(lambda r: r.is_private)) - - # Add repo to remotes - # We are using SQL to avoid post-write triggers - cr.execute( - """ - UPDATE cx_tower_git_remote - SET repo_id = %s - WHERE id = ANY(%s) - """, - (repo_id, remote_ids), - ) - - # Update repo.is_private - # We are using SQL to avoid post-write triggers - if is_private: - cr.execute( - """ - UPDATE cx_tower_git_repo - SET is_private = true - WHERE id = %s - """, - (repo_id,), - ) - - url_count += 1 - - # Compute project_ids for repositories - _logger.info("Computing project_ids for repositories.") - remote_obj.invalidate_model() - repo_obj.invalidate_model() - repo_obj.search([])._compute_git_project_ids() - - # Sanitize all remote heads that contain a slash - # Use the SQL query to avoid post-write triggers - _logger.info("Sanitizing remote heads that contain a slash.") - cr.execute( - """ - UPDATE cx_tower_git_remote - SET head = (regexp_match(head, '[^/]+$'))[1] - WHERE head LIKE '%/%' - """ - ) - - _logger.info("Migration completed. %s unique urls processed", url_count) diff --git a/addons/cetmix_tower_git/models/__init__.py b/addons/cetmix_tower_git/models/__init__.py deleted file mode 100644 index 06e0f96..0000000 --- a/addons/cetmix_tower_git/models/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# cx_tower_git_project_rel must be the first one in the list -# in order to create the relation table properly -from . import cx_tower_git_project_rel -from . import cx_tower_git_project_file_template_rel -from . import cx_tower_file -from . import cx_tower_file_template -from . import cx_tower_git_project -from . import cx_tower_git_remote -from . import cx_tower_git_repo -from . import cx_tower_git_repo_owner -from . import cx_tower_git_source -from . import cx_tower_server -from . import cetmix_tower -from . import cx_tower_plan_line -from . import cx_tower_command diff --git a/addons/cetmix_tower_git/models/cetmix_tower.py b/addons/cetmix_tower_git/models/cetmix_tower.py deleted file mode 100644 index 4a7448f..0000000 --- a/addons/cetmix_tower_git/models/cetmix_tower.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, models - - -class CetmixTower(models.AbstractModel): - _inherit = "cetmix.tower" - - @api.model - def servers_by_git_ref(self, repository_url, head=None, head_type=None): - """ - Return servers linked to a given Git repository reference. - - This is a thin shortcut that delegates to - :meth:`cx.tower.server.get_servers_by_git_ref`. - - Parameters - ---------- - repository_url : str - Pre-normalized canonical Git URL - (e.g. ``https://host/owner/repo.git``). - head : str, optional - Branch name, commit SHA, or PR identifier. - head_type : {'branch', 'commit', 'pr'}, optional - Type of the ``head`` argument. - - Returns - ------- - recordset of cx.tower.server - Matching servers. Empty recordset if no matches. - """ - return self.env["cx.tower.server"].get_servers_by_git_ref( - repository_url, head, head_type - ) diff --git a/addons/cetmix_tower_git/models/cx_tower_command.py b/addons/cetmix_tower_git/models/cx_tower_command.py deleted file mode 100644 index 7726e36..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_command.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2024 Cetmix OÜ -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from odoo import _, models -from odoo.tools.safe_eval import wrap_module - -# Wrap giturlparse safely -giturlparse = wrap_module(__import__("giturlparse"), ["parse", "validate"]) - - -class CxTowerCommand(models.Model): - """Extends cx.tower.command to add giturlparse functionality.""" - - _inherit = "cx.tower.command" - - def _custom_python_libraries(self): - """ - Add the giturlparse library to the available libraries. - """ - custom_python_libraries = super()._custom_python_libraries() - custom_python_libraries.update( - { - "cetmix_tower_git": { - "giturlparse": { - "import": giturlparse, - "help": _( - "Python library for Git URL parsing. " - "Available methods: 'parse', 'validate'. " - " Documentation on GitHub." - ), - }, - } - } - ) - return custom_python_libraries diff --git a/addons/cetmix_tower_git/models/cx_tower_file.py b/addons/cetmix_tower_git/models/cx_tower_file.py deleted file mode 100644 index 583f935..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_file.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models - - -class CxTowerFile(models.Model): - _inherit = "cx.tower.file" - - git_project_id = fields.Many2one( - comodel_name="cx.tower.git.project", - compute="_compute_git_project_id", - store=True, - ) - git_project_rel_ids = fields.One2many( - comodel_name="cx.tower.git.project.rel", - inverse_name="file_id", - string="Git Project Relations", - copy=False, - ) - - # Get server from the first related git project relation - # This is needed for YAML import - server_id = fields.Many2one( - comodel_name="cx.tower.server", - compute="_compute_git_project_id", - store=True, - readonly=False, - ) - - @api.depends("git_project_rel_ids.server_id", "git_project_rel_ids.git_project_id") - def _compute_git_project_id(self): - """ - Link to project using the proxy model. - """ - for record in self: - # File is related to project via proxy model. - # So there can be only one record in o2m field. - git_project_relation = ( - record.git_project_rel_ids and record.git_project_rel_ids[0] - ) - if git_project_relation: - record.update( - { - "git_project_id": git_project_relation.git_project_id, - "server_id": git_project_relation.server_id, - } - ) diff --git a/addons/cetmix_tower_git/models/cx_tower_file_template.py b/addons/cetmix_tower_git/models/cx_tower_file_template.py deleted file mode 100644 index 792c453..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_file_template.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models - - -class CxTowerFileTemplate(models.Model): - _inherit = "cx.tower.file.template" - - git_project_ids = fields.Many2many( - comodel_name="cx.tower.git.project", - relation="cx_tower_git_project_file_template_rel", - column1="file_template_id", - column2="git_project_id", - string="Git Projects", - copy=False, - ) - git_project_id = fields.Many2one( - comodel_name="cx.tower.git.project", - compute="_compute_git_project_id", - ) - - @api.depends("git_project_ids") - def _compute_git_project_id(self): - """ - Link to project using the proxy model. - """ - for record in self: - # File is related to project via proxy model. - # So there can be only one record in o2m field. - record.git_project_id = ( - record.git_project_ids and record.git_project_ids[0].id - ) diff --git a/addons/cetmix_tower_git/models/cx_tower_git_project.py b/addons/cetmix_tower_git/models/cx_tower_git_project.py deleted file mode 100644 index 47a54f1..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_git_project.py +++ /dev/null @@ -1,334 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import re - -from odoo import _, api, fields, models - - -class CxTowerGitProject(models.Model): - """ - Git Project. - Implements pre-defined git configuration. - """ - - _name = "cx.tower.git.project" - _description = "Cetmix Tower Git Project" - _order = "name" - - _inherit = [ - "cx.tower.reference.mixin", - "cx.tower.yaml.mixin", - "cx.tower.access.role.mixin", - ] - - def _get_post_create_fields(self): - res = super()._get_post_create_fields() - return res + [ - "source_ids", - "git_project_rel_ids", - "git_project_file_template_rel_ids", - ] - - active = fields.Boolean(default=True) - # IMPORTANT: This field may contain duplicates because of the relation nature! - server_ids = fields.Many2many( - comodel_name="cx.tower.server", - relation="cx_tower_git_project_rel", - column1="git_project_id", - column2="server_id", - string="Servers", - readonly=True, - copy=False, - help="Servers are added automatically based on the files linked to the project." - "\nIMPORTANT: This field may contain duplicates" - " because of the relation nature!", - ) - source_ids = fields.One2many( - comodel_name="cx.tower.git.source", - inverse_name="git_project_id", - string="Sources", - auto_join=True, - copy=True, - ) - git_project_rel_ids = fields.One2many( - comodel_name="cx.tower.git.project.rel", - inverse_name="git_project_id", - string="Git Project Server File Relations", - copy=False, - ) - # Helper field to get all files related to git project - file_ids = fields.Many2many( - comodel_name="cx.tower.file", - relation="cx_tower_git_project_rel", - column1="git_project_id", - column2="file_id", - string="Files", - readonly=True, - depends=["git_project_rel_ids"], - copy=False, - ) - git_project_file_template_rel_ids = fields.One2many( - comodel_name="cx.tower.git.project.file.template.rel", - inverse_name="git_project_id", - string="Git Project File Template Relations", - copy=False, - ) - # Helper field to get all file templates related to git project - file_template_ids = fields.Many2many( - comodel_name="cx.tower.file.template", - relation="cx_tower_git_project_file_template_rel", - column1="git_project_id", - column2="file_template_id", - string="File Templates", - readonly=True, - depends=["git_project_file_template_rel_ids"], - copy=False, - ) - # Helper field to get all repositories used in this project - repo_ids = fields.Many2many( - comodel_name="cx.tower.git.repo", - relation="cx_tower_git_repo_project_rel", - column1="project_id", - column2="repo_id", - string="Repositories", - readonly=True, - copy=False, - help="Repositories used in this project through its sources and remotes", - ) - note = fields.Text() - - # ---- Access. Add relation for mixin fields - user_ids = fields.Many2many( - relation="cx_tower_git_project_user_rel", - compute="_compute_user_ids", - readonly=False, - store=True, - precompute=True, - domain=lambda self: [ - ("groups_id", "in", self.env.ref("cetmix_tower_server.group_manager").ids) - ], - ) - manager_ids = fields.Many2many( - relation="cx_tower_git_project_manager_rel", - compute="_compute_user_ids", - readonly=False, - store=True, - precompute=True, - ) - - # -- UI/UX fields - has_private_remotes = fields.Boolean( - compute="_compute_has_private_remotes", - help="Indicates if the project has any private remotes.", - ) - has_partially_private_remotes = fields.Boolean( - compute="_compute_has_private_remotes", - help="Indicates if the project has any partially private remotes.", - ) - - # -- Git Aggregator related fields - git_aggregator_root_dir = fields.Char( - help="Git aggregator root directory where sources will be cloned." - " Eg '/tmp/git-aggregator'" - " Will use '.' if not set", - ) - - def _selection_project_format(self): - """ - Possible project formats. - Inherit and extend when adding new project formats. - - Returns: - List of tuples: (code, name) - """ - return [ - ("git_aggregator", "Git Aggregator"), - ] - - def _default_project_format(self): - """ - Default project format. - """ - return "git_aggregator" - - @api.depends( - "git_project_rel_ids.server_id", - "git_project_rel_ids.server_id.user_ids", - "git_project_rel_ids.server_id.manager_ids", - ) - def _compute_user_ids(self): - """ - Users. All users who have "Manager" group and are either set in "Users" - or in "Managers" in all related servers. - Managers. All users who have "Manager" group and are set as "Managers" - in all related servers. - - This is done to avoid unpredictable consequences when some of the servers - are not updated due to access restrictions when a project is updated. - """ - for project in self: - # Do not compute if no servers are related - server_ids = project.git_project_rel_ids.server_id - if not server_ids: - continue - - # Get all user and manager ids from related servers - all_user_ids = server_ids.user_ids.filtered( - lambda u: u.has_group("cetmix_tower_server.group_manager") - ).ids - all_manager_ids = server_ids.manager_ids.ids - - # Create a final list of user and manager ids - user_ids = [] - manager_ids = [] - # Check if user is present in all servers - for user_id in all_user_ids: - if all( - user_id in server.user_ids.ids or user_id in server.manager_ids.ids - for server in server_ids - ): - user_ids.append(user_id) - # Check if manager is present in all servers - for manager_id in all_manager_ids: - if all(manager_id in server.manager_ids.ids for server in server_ids): - manager_ids.append(manager_id) - - # Set the final lists - project.update( - { - "user_ids": [(6, 0, user_ids)], - "manager_ids": [(6, 0, manager_ids)], - } - ) - - @api.depends( - "source_ids", "source_ids.remote_ids", "source_ids.remote_ids.is_private" - ) - def _compute_has_private_remotes(self): - for project in self: - project.has_private_remotes = any( - source.remote_count > 0 - and source.remote_count_private == source.remote_count - for source in project.source_ids - ) - project.has_partially_private_remotes = any( - source.remote_count_private > 0 - and source.remote_count_private != source.remote_count - for source in project.source_ids - ) - - @api.model_create_multi - def create(self, vals_list): - res = super().create(vals_list) - # Update related files and templates on create - res._update_related_files_and_templates() - return res - - def write(self, vals): - res = super().write(vals) - # Update related files and templates on update - self._update_related_files_and_templates() - return res - - def _update_related_files_and_templates(self): - # Update related files and templates - if self.git_project_rel_ids: - self.git_project_rel_ids._save_to_file() - if self.git_project_file_template_rel_ids: - self.git_project_file_template_rel_ids._save_to_file_template() - - def _extract_variables_from_text(self, text): - """Extract environment variables from text. - Helper method for file content generation. - - Returns: - List: List of variables - """ - variables = re.findall(r"\$([A-Z0-9_]+)", text) - return sorted(list(set(variables))) - - # ------------------------------ - # YAML mixin methods - # ------------------------------ - def _get_fields_for_yaml(self): - res = super()._get_fields_for_yaml() - res += [ - "name", - "note", - "source_ids", - "git_aggregator_root_dir", - ] - return res - - # ------------------------------- - # Git Aggregator related methods - # ------------------------------- - def _git_aggregator_prepare_record(self): - """Prepare json structure for git aggregator. - - Returns: - Dict: Json structure for git aggregator - """ - self.ensure_one() - values = {} - for source in self.source_ids: - if source.enabled and source.remote_count: - root_dir = self.git_aggregator_root_dir or "." - values.update( - { - f"/{source.reference}" - if root_dir == "/" - else f"{root_dir}/{source.reference}": source._git_aggregator_prepare_record() # noqa: E501 - } - ) - return values - - def _git_aggregator_prepare_yaml_comment(self, yaml_code): - """Generate commentary for yaml file. - It includes brief instructions for git aggregator - and lists environment variables that are required. - - Args: - yaml_code (str): Yaml code - - Returns: - Char: comment text or None - """ - - comment_text = _( - "# This file is generated with Cetmix Tower https://cetmix.com/tower\n" - "# It's designed to be used with git-aggregator tool developed by Acsone.\n" - "# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n" - ) - variable_list = self._extract_variables_from_text(yaml_code) - if variable_list: - comment_text += _( - "\n# You need to set the following variables in your environment:\n# %(vars)s\n" # noqa: E501 - "# and run git-aggregator with '--expand-env' parameter.\n", # noqa: E501 - vars=(", ".join(variable_list)), - ) - return comment_text - - def _generate_code_git_aggregator(self, record): - """Generate code in git-aggregator format. - - Args: - record (recordset()): Model record to generate code for. - must be a single record and have git_project_id field. - - Returns: - Text: Yaml code - """ - yaml_mixin = self.env["cx.tower.yaml.mixin"] - - # Do not generate code if record values are empty - record_values = record.git_project_id._git_aggregator_prepare_record() - if record_values: - yaml_code = yaml_mixin._convert_dict_to_yaml(record_values) - # Prepend comment to yaml code - comment = record.git_project_id._git_aggregator_prepare_yaml_comment( - yaml_code - ) - return f"{comment}\n{yaml_code}" - return "" diff --git a/addons/cetmix_tower_git/models/cx_tower_git_project_file_template_rel.py b/addons/cetmix_tower_git/models/cx_tower_git_project_file_template_rel.py deleted file mode 100644 index def2f61..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_git_project_file_template_rel.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import _, api, fields, models -from odoo.exceptions import ValidationError - - -class CxTowerGitProjectFileTemplateRel(models.Model): - """ - Relation between git projects and file templates. - """ - - _name = "cx.tower.git.project.file.template.rel" - _table = "cx_tower_git_project_file_template_rel" - _description = "Cetmix Tower Git Project relation to File Templates" - _log_access = False - - name = fields.Char(related="git_project_id.name", readonly=True) - git_project_id = fields.Many2one( - comodel_name="cx.tower.git.project", - index=True, - required=True, - ondelete="cascade", - ) - file_template_id = fields.Many2one( - comodel_name="cx.tower.file.template", - required=True, - ondelete="cascade", - ) - project_format = fields.Selection( - selection=lambda self: self.env[ - "cx.tower.git.project" - ]._selection_project_format(), - default=lambda self: self.env["cx.tower.git.project"]._default_project_format(), - required=True, - string="Format", - ) - - _sql_constraints = [ - ( - "project_server_file_format_uniq", - "unique(git_project_id, file_template_id, project_format)", - "File template is already related to the same project and format", - ), - ] - - @api.model_create_multi - def create(self, vals_list): - res = super().create(vals_list) - - # Export project to file - res._save_to_file_template() - return res - - def write(self, vals): - res = super().write(vals) - # Export project to file - self._save_to_file_template() - return res - - def action_open_file_template(self): - """ - Open file template record in current window - """ - self.ensure_one() - return { - "type": "ir.actions.act_window", - "name": self.file_template_id.name, - "res_model": "cx.tower.file.template", - "res_id": self.file_template_id.id, # pylint: disable=no-member - "view_mode": "form", - "view_type": "form", - "target": "current", - } - - # ---------------------------------------------------- - # Save project to linked file based on selected format - # ---------------------------------------------------- - def _save_to_file_template(self): - """Save project to linked file using format-specific function.""" - - # Get required function based on project format - # Following the pattern: _generate_code__ where format - # is one of the values in _selection_project_format - # Function gets a single record as an argument. - - # Save resolved functions to dict for faster access - code_generator_functions = {} - - for record in self: - code_generator_function = code_generator_functions.get( - record.project_format - ) - if not code_generator_function: - code_generator_function = getattr( - self.git_project_id, f"_generate_code_{record.project_format}", None - ) - if not code_generator_function: - raise ValidationError( - _( - "Code generator function for '%(project_format)s'" - " format not found.", - project_format=record.project_format, - ) - ) - code_generator_functions[ - record.project_format - ] = code_generator_function - - # Generate code for current record - code = code_generator_function(record) - if record.file_template_id.code != code: - record.file_template_id.write({"code": code}) diff --git a/addons/cetmix_tower_git/models/cx_tower_git_project_rel.py b/addons/cetmix_tower_git/models/cx_tower_git_project_rel.py deleted file mode 100644 index d9fba6b..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_git_project_rel.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import _, api, fields, models -from odoo.exceptions import ValidationError - - -class CxTowerGitProjectRel(models.Model): - """ - Relation between git projects and other model records. - """ - - _name = "cx.tower.git.project.rel" - _inherit = [ - "cx.tower.reference.mixin", - "cx.tower.yaml.mixin", - ] - _table = "cx_tower_git_project_rel" - _description = "Cetmix Tower Git Project relation to Files and Servers" - _log_access = False - - name = fields.Char(related="git_project_id.name", readonly=True) - git_project_id = fields.Many2one( - comodel_name="cx.tower.git.project", - index=True, - required=True, - ondelete="cascade", - ) - server_id = fields.Many2one( - comodel_name="cx.tower.server", - index=True, - required=True, - ondelete="cascade", - ) - file_id = fields.Many2one( - comodel_name="cx.tower.file", - domain="[('server_id', '=', server_id)," - "('source', '=', 'tower')," - "('file_type', '=', 'text')]", - required=True, - ondelete="cascade", - ) - project_format = fields.Selection( - selection=lambda self: self.env[ - "cx.tower.git.project" - ]._selection_project_format(), - default=lambda self: self.env["cx.tower.git.project"]._default_project_format(), - required=True, - string="Format", - ) - auto_sync = fields.Boolean(related="file_id.auto_sync", readonly=False) - - _sql_constraints = [ - ( - "project_server_file_format_uniq", - "unique(git_project_id, file_id, project_format)", - "File is already related to the same project and format", - ), - ] - - @api.constrains("server_id", "file_id") - def _check_server_file_relation(self): - """ - Check if server and file are related. - """ - for record in self: - if ( - record.file_id.server_id - and record.server_id != record.file_id.server_id - ): - raise ValidationError( - _( - "File '%(file)s' doesn't belong to server '%(server)s'", - file=record.file_id.name, - server=record.server_id.name, - ) - ) - - @api.model_create_multi - def create(self, vals_list): - res = super().create(vals_list) - - # Export project to file - res._save_to_file() - return res - - def write(self, vals): - res = super().write(vals) - # Export project to file - self._save_to_file() - return res - - def action_open_project(self): - """ - Open project record in current window - """ - self.ensure_one() - return { - "type": "ir.actions.act_window", - "name": self.name, - "res_model": "cx.tower.git.project", - "res_id": self.git_project_id.id, # pylint: disable=no-member - "view_mode": "form", - "view_type": "form", - "target": "current", - } - - def action_open_server(self): - """ - Open server record in current window - """ - self.ensure_one() - return { - "type": "ir.actions.act_window", - "name": self.server_id.name, - "res_model": "cx.tower.server", - "res_id": self.server_id.id, # pylint: disable=no-member - "view_mode": "form", - "view_type": "form", - "target": "current", - } - - # ---------------------------------------------------- - # Save project to linked file based on selected format - # ---------------------------------------------------- - def _save_to_file(self): - """Save project to linked file using format-specific function.""" - - # Get required function based on project format - # Following the pattern: _generate_code_ where format - # is one of the values in _selection_project_format - # Function gets a single record as an argument. - - # Save resolved functions to dict for faster access - code_generator_functions = {} - - for record in self: - # Disconnect file from file template if it is connected - if record.file_id.template_id: - record.file_id.action_unlink_from_template() - - code_generator_function = code_generator_functions.get( - record.project_format - ) - if not code_generator_function: - code_generator_function = getattr( - self.git_project_id, f"_generate_code_{record.project_format}", None - ) - if not code_generator_function: - raise ValidationError( - _( - "Code generator function for '%(project_format)s'" - " format not found.", - project_format=record.project_format, - ) - ) - code_generator_functions[ - record.project_format - ] = code_generator_function - - # Generate code for current record - code = code_generator_function(record) - if record.file_id.code != code: - record.file_id.write({"code": code}) - - # ------------------------------ - # YAML mixin methods - # ------------------------------ - def _get_fields_for_yaml(self): - res = super()._get_fields_for_yaml() - res += [ - "file_id", - "git_project_id", - "project_format", - "auto_sync", - ] - return res diff --git a/addons/cetmix_tower_git/models/cx_tower_git_remote.py b/addons/cetmix_tower_git/models/cx_tower_git_remote.py deleted file mode 100644 index 7c556a4..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_git_remote.py +++ /dev/null @@ -1,415 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import giturlparse - -from odoo import _, api, fields, models -from odoo.exceptions import ValidationError - - -class CxTowerGitRemote(models.Model): - """ - Git Remote. - Implements single git remote. - Eg a branch or a pull request. - """ - - _name = "cx.tower.git.remote" - _inherit = [ - "cx.tower.reference.mixin", - "cx.tower.yaml.mixin", - ] - _description = "Cetmix Tower Git Remote" - _order = "sequence, name" - - # Used to detect git ssh urls - GIT_SSH_URL_PATTERN = r"^[\w\.-]+@[\w\.-]+:.*\.git$" - GIT_HTTPS_URL_PATTERN = r"^https://.*\.git$" - GIT_GIT_URL_PATTERN = r"^git://.*\.git$" - - active = fields.Boolean(related="source_id.active", store=True, readonly=True) - enabled = fields.Boolean( - default=True, help="Enable in configuration and exported to files" - ) - sequence = fields.Integer(default=10) - name = fields.Char(compute="_compute_name", store=True, default="remote") - source_id = fields.Many2one( - comodel_name="cx.tower.git.source", - required=True, - ondelete="cascade", - auto_join=True, - ) - git_project_id = fields.Many2one( - comodel_name="cx.tower.git.project", - related="source_id.git_project_id", - store=True, - readonly=True, - ) - repo_id = fields.Many2one( - comodel_name="cx.tower.git.repo", - string="Repository", - required=True, - ondelete="restrict", - help="If selected, the remote URL will be filled from the" - " repo settings based on the remote protocol", - ) - repo_provider = fields.Selection( - related="repo_id.provider", - readonly=True, - ) - # -- Repo related fields - url_protocol = fields.Selection( - string="Protocol", - selection=[ - ("ssh", "SSH"), - ("https", "HTTPS"), - ("git", "GIT"), - ], - required=True, - default=lambda self: self._get_default_url_protocol(), - ) - is_private = fields.Boolean( - string="Private", - help="Repository is private", - related="repo_id.is_private", - store=True, - readonly=True, - ) - head_type = fields.Selection( - selection=[ - ("branch", "Branch"), - ("pr", "Pull/Merge Request"), - ("commit", "Commit"), - ], - required=True, - ) - head = fields.Char( - help="Git remote head. Link to branch, PR, commit or commit hash.", - required=True, - index=True, - ) - - def _get_default_url_protocol(self): - """Default URL protocol for new remote. - - Returns: - Char: Default URL protocol. - """ - return "https" - - @api.depends("source_id", "sequence") - def _compute_name(self): - """ - Compute remote name. - By default all remotes are named `remote_` - where position is the position of the remote in the source. - Eg first remote is `remote_1`, second is `remote_2`, etc. - """ - for remote in self: - if remote.source_id: - for index, source_remote in enumerate(remote.source_id.remote_ids): - source_remote.name = f"remote_{index + 1}" - - @api.onchange("head") - def onchange_head(self): - """ - Extract head number from head url - and set it as head. - """ - for remote in self: - if remote.head and "/" in remote.head: - remote.head = self._sanitize_head(remote.head) - - @api.model_create_multi - def create(self, vals_list): - # Sanitize head - for vals in vals_list: - head = vals.get("head") - if head and "/" in head: - vals["head"] = self._sanitize_head(head) - res = super().create(vals_list) - # Export project to related files and templates - res._update_related_files_and_templates() - return res - - def write(self, vals): - # Sanitize head - if "head" in vals: - head = vals["head"] - if head and "/" in head: - vals["head"] = self._sanitize_head(head) - res = super().write(vals) - # Update related files and templates on update - self._update_related_files_and_templates() - return res - - def unlink(self): - """ - Override to update related files and templates on unlink - """ - related_files = self.mapped("git_project_id").mapped("git_project_rel_ids") - related_templates = self.mapped("git_project_id").mapped( - "git_project_file_template_rel_ids" - ) - res = super().unlink() - - # Update related files and templates on unlink - if related_files: - related_files._save_to_file() - if related_templates: - related_templates._save_to_file_template() - return res - - def _sanitize_head(self, head): - """Sanitize head. - Extract head number from head url - and set it as head. - - Args: - head (Char): Head to sanitize - - Returns: - Char: Sanitized head - """ - if head and "/" in head: - return head.split("/")[-1].strip() - return head - - @api.model - def get_head_data(self): - """ - This method is used to get values for the dropdown dynamic widget. - It is designed for integrations with repo providers using APIs. - - Returns: - List: List of tuples(selection, name) - eg [('18.0', '18.0'), ('main', 'main'), ('develop', 'develop')] - """ - values = [ - ("18.0", "18.0"), - ("main", "Main"), - ("develop", "Develop"), - ("17.0", "17.0"), - ] - return values - - def _update_related_files_and_templates(self): - # Update related files on update - related_files = self.mapped("git_project_id").mapped("git_project_rel_ids") - if related_files: - related_files._save_to_file() - related_templates = self.mapped("git_project_id").mapped( - "git_project_file_template_rel_ids" - ) - if related_templates: - related_templates._save_to_file_template() - - # ------------------------------ - # Reference mixin methods - # ------------------------------ - def _get_pre_populated_model_data(self): - res = super()._get_pre_populated_model_data() - res.update({"cx.tower.git.remote": ["cx.tower.git.source", "source_id"]}) - return res - - # ------------------------------ - # YAML mixin methods - # ------------------------------ - def _get_fields_for_yaml(self): - res = super()._get_fields_for_yaml() - res += [ - "name", - "enabled", - "sequence", - "repo_id", - "head", - "head_type", - ] - return res - - # ------------------------------ - # Git Aggregator related methods - # ------------------------------ - def _git_aggregator_prepare_url(self): - """Prepare url for git aggregator - - Returns: - Char: Prepared url for git aggregator - """ - self.ensure_one() - - if not self.repo_id: - raise ValidationError(_("Repository is required")) - if not self.repo_id.url: - raise ValidationError(_("Repository URL is not set")) - - url = self.repo_id.url - prepared_url = giturlparse.parse(url).urls.get(self.url_protocol, url) - - # If repo is public or is not using HTTPS protocol return URL as is - if not self.is_private or self.url_protocol != "https": - return prepared_url - - if self.repo_provider == "github": - prepared_url = self._git_aggregator_prepare_url_github(prepared_url) - elif self.repo_provider == "gitlab": - prepared_url = self._git_aggregator_prepare_url_gitlab(prepared_url) - elif self.repo_provider == "bitbucket": - prepared_url = self._git_aggregator_prepare_url_bitbucket(prepared_url) - - return prepared_url - - def _git_aggregator_prepare_url_github(self, url): - """Prepare url for git aggregator - for private Github repo using https protocol. - - Args: - url (Char): URL to prepare - - Returns: - Char: Prepared url for git aggregator - """ - self.ensure_one() - - # This is how final url will look like - # https://$GITHUB_TOKEN:x-oauth-basic@github.com/soem_org/some_private_repo.git - url_without_protocol = url.replace("https://", "") - url = f"https://$GITHUB_TOKEN:x-oauth-basic@{url_without_protocol}" - return url - - def _git_aggregator_prepare_url_gitlab(self, url): - """Prepare url for git aggregator - for private GitLab repo using https protocol. - - Args: - url (Char): URL to prepare - - Returns: - Char: Prepared url for git aggregator - """ - self.ensure_one() - - # This is how final url will look like - # https://:@.git - url_without_protocol = url.replace("https://", "") - url = f"https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@{url_without_protocol}" - return url - - def _git_aggregator_prepare_url_bitbucket(self, url): - """Prepare url for git aggregator - for private Github repo using https protocol. - - Args: - url (Char): URL to prepare - - Returns: - Char: Prepared url for git aggregator - """ - self.ensure_one() - - # This is how final url will look like - # https://x-token-auth:{access_token}@bitbucket.org/user/repo.git - # From https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/ - url_without_protocol = url.replace("https://", "") - url = f"https://x-token-auth:$BITBUCKET_TOKEN@{url_without_protocol}" - return url - - def _git_aggregator_prepare_head(self): - """Prepare head for git aggregator - - Returns: - Char: Prepared head for git aggregator - """ - self.ensure_one() - if self.repo_provider == "github": - return self._git_aggregator_prepare_head_github() - if self.repo_provider == "gitlab": - return self._git_aggregator_prepare_head_gitlab() - if self.repo_provider == "bitbucket": - return self._git_aggregator_prepare_head_bitbucket() - return self.head - - def _git_aggregator_prepare_head_github(self): - """Prepare head for git aggregator for Github. - - Returns: - Char: Prepared head for git aggregator - """ - - # Extract branch name, PR/MR or commit number from head - head_number = self.head.split("/")[-1] - if not head_number: - raise ValidationError( - _("Git Aggregator: " "Head number is empty in %(head)s", head=self.head) - ) - - # PR/MR - if self.head_type == "pr": - return f"refs/pull/{head_number}/head" - - # Commit - if self.head_type in ["commit", "branch"]: - return f"{head_number}" - - # Fallback to original head - return self.head - - def _git_aggregator_prepare_head_gitlab(self): - """Prepare head for git aggregator for GitLab. - - Returns: - Char: Prepared head for git aggregator - """ - # Extract branch name, PR/MR or commit number from head - head_number = self.head.split("/")[-1] - if not head_number: - raise ValidationError( - _("Git Aggregator: " "Head number is empty in %(head)s", head=self.head) - ) - - # PR/MR - if self.head_type == "pr": - return f"merge-requests/{head_number}/head" - - # Commit - # https://gitlab.com/cetmix/test/-/tree/17.0-test-branch?ref_type=heads - if self.head_type in ["commit", "branch"]: - head_parts = head_number.split("?") - return f"{head_parts[0]}" - - # Fallback to original head - return self.head - - def _git_aggregator_prepare_head_bitbucket(self): - """Prepare head for git aggregator for Bitbucket. - - Returns: - Char: Prepared head for git aggregator - """ - # Extract branch name, PR/MR or commit number from head - head_number = self.head.split("/")[-1] - if not head_number: - raise ValidationError( - _("Git Aggregator: " "Head number is empty in %(head)s", head=self.head) - ) - # PR/MR - if self.head_type == "pr": - raise ValidationError( - _( - "Git Aggregator: " - "Bitbucket does not support" - " fetching PRs. Please use branch instead.\n\n" - "Source: %(src)s\n" - "URL: %(url)s\n" - "Head: %(head)s", - src=self.source_id.name, - url=self.repo_id.url, - head=self.head, - ) - ) - - # Commit - if self.head_type in ["commit", "branch"]: - return f"{head_number}" - - # Fallback to original head - return self.head diff --git a/addons/cetmix_tower_git/models/cx_tower_git_repo.py b/addons/cetmix_tower_git/models/cx_tower_git_repo.py deleted file mode 100644 index e8dc3a7..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_git_repo.py +++ /dev/null @@ -1,409 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import giturlparse - -from odoo import _, api, fields, models -from odoo.exceptions import ValidationError -from odoo.tools import ormcache - - -class CxTowerGitRepo(models.Model): - """ - Git Repository. - Represents a git repository with its metadata and configuration. - """ - - _name = "cx.tower.git.repo" - _inherit = [ - "cx.tower.reference.mixin", - "cx.tower.yaml.mixin", - ] - _description = "Cetmix Tower Git Repository" - _order = "name" - _rec_names_search = ["repo", "host", "owner_id"] - - active = fields.Boolean(default=True, help="Indicates if the repository is active") - name = fields.Char( - compute="_compute_name", store=True, required=False, index="trigram" - ) - reference = fields.Char( - index=True, - compute="_compute_name", - required=False, - store=True, - ) - repo = fields.Char( - string="Repository Name", - readonly=True, - help="Repository name (e.g., 'cetmix-tower', 'odoo')", - ) - url = fields.Char( - string="Generic URL", - help="Displayed in 'https' format, but can be entered in any format", - compute="_compute_url", - inverse="_inverse_url", - required=True, - compute_sudo=True, - ) - url_ssh = fields.Char( - string="SSH URL", - help="SSH URL of the repository", - compute="_compute_url", - compute_sudo=True, - ) - url_git = fields.Char( - string="GIT URL", - help="GIT URL of the repository", - compute="_compute_url", - compute_sudo=True, - ) - is_private = fields.Boolean( - string="Private", default=False, help="Indicates if the repository is private" - ) - provider = fields.Selection( - selection="_selection_provider", - required=True, - default="other", - help="Repository provider to determine provider-based behaviour", - ) - host = fields.Char( - readonly=True, - index=True, - help="Repository host (e.g., 'github.com', 'gitlab.com')", - ) - owner_id = fields.Many2one( - comodel_name="cx.tower.git.repo.owner", - readonly=True, - help="Repository owner (e.g., 'cetmix' or 'OCA')", - ) - secret_id = fields.Many2one( - comodel_name="cx.tower.key", - string="Secret", - domain="[('key_type', '=', 's')]", - help="Custom secret used for this repository", - ) - remote_ids = fields.One2many( - comodel_name="cx.tower.git.remote", - inverse_name="repo_id", - help="Remotes that use this repository", - ) - git_project_ids = fields.Many2many( - comodel_name="cx.tower.git.project", - relation="cx_tower_git_repo_project_rel", - column1="repo_id", - column2="project_id", - compute="_compute_git_project_ids", - store=True, - help="Projects this repository is used in", - ) - remote_count = fields.Integer( - compute="_compute_remote_count", - help="Number of remotes this repository is used in", - ) - git_project_count = fields.Integer( - compute="_compute_git_project_count", - help="Number of projects this repository is used in", - ) - - _sql_constraints = [ - ( - "unique_repo_host_owner", - "unique(repo, host, owner_id)", - "A repository with the same name, host, and owner already exists.", - ), - ] - - # -- Selection - def _selection_provider(self): - """Available repository providers. - - Returns: - List of tuples: available options. - """ - return [ - ("github", "GitHub"), - ("gitlab", "GitLab"), - ("bitbucket", "Bitbucket"), - ("assembla", "Assembla"), - ("other", "Other"), - ] - - # -- Computes - @api.depends("host", "owner_id", "repo") - def _compute_name(self): - """ - Compute name in format: host/owner/name. - Compute reference based on name. - """ - for repo in self: - if repo.host and repo.owner_id and repo.repo: - name = f"{repo.host}/{repo.owner_id.name}/{repo.repo}" - reference = repo._generate_or_fix_reference(name) - repo.update( - { - "name": name, - "reference": reference, - } - ) - else: - repo.update( - { - "name": False, - "reference": False, - } - ) - - @api.depends("remote_ids", "remote_ids.git_project_id") - def _compute_git_project_ids(self): - """Compute projects this repository is used in.""" - for repo in self: - projects = repo.remote_ids.mapped("git_project_id") - repo.git_project_ids = [(6, 0, projects.ids)] - - @api.depends("remote_ids") - def _compute_remote_count(self): - """Compute remote count field.""" - for repo in self: - repo.remote_count = len(repo.remote_ids) - - @api.depends("git_project_ids") - def _compute_git_project_count(self): - """Compute project count field.""" - for repo in self: - repo.git_project_count = len(repo.git_project_ids) - - @api.depends("repo", "host", "owner_id") - def _compute_url(self): - """Compute URL from repository properties.""" - for repo in self: - if repo.repo and repo.host and repo.owner_id: - https_url = f"https://{repo.host}/{repo.owner_id.name}/{repo.repo}.git" - elif repo.repo and repo.host: - https_url = f"https://{repo.host}/{repo.repo}.git" - else: - https_url = "" - if https_url: - try: - parsed_urls = giturlparse.parse(https_url).urls - urls = { - "url": https_url, - "url_ssh": parsed_urls["ssh"], - "url_git": parsed_urls["git"], - } - except Exception as e: # noqa: F841 catch all errors - urls = { - "url": "", - "url_ssh": "", - "url_git": "", - } - else: - urls = { - "url": "", - "url_ssh": "", - "url_git": "", - } - repo.update(urls) - - def _inverse_url(self): - """Parse URL to update repository properties.""" - for repo in self: - if not repo.url: - continue - # Parse URL - parsed_url_dict = self._parse_url(repo.url) - # Update repository properties - repo.update(parsed_url_dict) - - def action_view_remotes(self): - """Open remotes list view.""" - self.ensure_one() - action = self.env["ir.actions.actions"]._for_xml_id( - "cetmix_tower_git.action_cx_tower_git_remote" - ) - action.update( - { - "domain": [("repo_id", "=", self.id)], - "context": {"default_repo_id": self.id}, - } - ) - return action - - def action_view_projects(self): - """Open projects list view.""" - self.ensure_one() - action = self.env["ir.actions.actions"]._for_xml_id( - "cetmix_tower_git.cx_tower_git_project_action" - ) - action.update( - { - "domain": [("repo_ids", "in", self.id)], - "context": {"default_repo_ids": [(4, self.id)]}, - } - ) - return action - - @api.model_create_multi - def create(self, vals_list): - """Create multiple repositories.""" - # Check if any of the repositories already exist - # This is needed to allow creating repositories using just an URL. - # Eg when importing repositories from a YAML file. - res = self.browse() - existing_repo_ids = [] - vals_list_to_create = [] - for vals in vals_list: - url = vals.get("url") - if url: - # Try to get repository by URL - repo_id = self._get_repo_id_by_url( - url=url, create=False, raise_if_invalid=False - ) - if repo_id: - existing_repo_ids.append(repo_id) - continue - # Parse URL and update vals - parsed_url_dict = self._parse_url(url=url, raise_if_invalid=True) - vals.update(parsed_url_dict) - # Otherwise, add to create list - vals_list_to_create.append(vals) - # Compose the result - if vals_list_to_create: - res |= super().create(vals_list_to_create) - if existing_repo_ids: - res |= self.browse(existing_repo_ids) - self.clear_caches() - return res - - def write(self, vals): - """Write repositories.""" - res = super().write(vals) - self.clear_caches() - return res - - def unlink(self): - """Unlink repositories.""" - res = super().unlink() - self.clear_caches() - return res - - @api.model - def name_create(self, name): - """ - Create a new repository from a URL. - """ - repo_id = self._get_repo_id_by_url(url=name, create=True, raise_if_invalid=True) - repo = self.browse(repo_id) - - return repo_id, repo.display_name - - @ormcache("self.env.uid", "self.env.su", "url") - def _get_repo_id_by_url(self, url, create=False, raise_if_invalid=False): - """Get repository id by URL. - - Args: - url (Char): URL to get repository id - create (Bool, optional): Create repository if not found. - Default is False. - raise_if_invalid (Bool, optional): Raise ValidationError - if the URL is not valid. Default is False. - - Returns: - int: Repository ID - or False if the URL is not valid and raise_if_invalid is False - - Raises: - ValidationError: If the URL is not valid and raise_if_invalid is True - """ - # Parse URL - parsed_url_dict = self._parse_url(url, raise_if_invalid=raise_if_invalid) - if not parsed_url_dict: - return False - - # Check if repository already exists and use it - repo = self.search( - [ - ("repo", "=", parsed_url_dict["repo"]), - ("host", "=", parsed_url_dict["host"]), - ("owner_id", "=", parsed_url_dict["owner_id"]), - ], - limit=1, - ) - - # Otherwise, create a new one - if not repo and create: - repo = self.create(parsed_url_dict) - - return repo.id if repo else False - - def _parse_url(self, url, raise_if_invalid=True): - """Parse URL to get name, host and owner. - - Args: - url (Char): URL to parse - - Raises: - ValidationError: If the URL is not valid - - Returns: - Dict: Dictionary with name, host and owner - or empty dict if the URL is not valid and raise_if_invalid is False - """ - - # Validate URL - if not giturlparse.validate(url): - if raise_if_invalid: - raise ValidationError(_("Not a valid repository URL!")) - return {} - - # Parse URL - parsed_url = giturlparse.parse(url) - - # Get or create owner - owner_id = self.env["cx.tower.git.repo.owner"]._get_owner_id_by_name( - name=parsed_url.owner, - create=True, - ) - - # Get provider based on host - provider = self._get_provider(parsed_url) - - return { - "repo": parsed_url.repo, - "host": parsed_url.host, - "owner_id": owner_id, - "provider": provider, - } - - def _get_provider(self, parsed_url): - """Get provider. - - Args: - parsed_url (GitUrlParsed): Parsed URL object - - Returns: - str: Provider name - """ - provider = "other" - if parsed_url.assembla: - provider = "assembla" - elif parsed_url.bitbucket or "bitbucket" in parsed_url.host: - provider = "bitbucket" - elif parsed_url.gitlab: - provider = "gitlab" - elif parsed_url.github: - provider = "github" - - return provider - - # ------------------------------ - # YAML mixin methods - # ------------------------------ - def _get_fields_for_yaml(self): - res = super()._get_fields_for_yaml() - res += [ - "url", - "is_private", - "secret_id", - ] - return res diff --git a/addons/cetmix_tower_git/models/cx_tower_git_repo_owner.py b/addons/cetmix_tower_git/models/cx_tower_git_repo_owner.py deleted file mode 100644 index 204f3d4..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_git_repo_owner.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, fields, models -from odoo.tools import ormcache - - -class CxTowerGitRepoOwner(models.Model): - """ - Git Repository Owner. - Represents an organization or user that owns repositories. - Examples: "cetmix", "OCA", etc. - """ - - _name = "cx.tower.git.repo.owner" - _inherit = ["cx.tower.reference.mixin", "cx.tower.yaml.mixin"] - _description = "Cetmix Tower Git Repository Owner" - _order = "name" - - display_name = fields.Char( - readonly=False, compute="_compute_display_name", store=True - ) - - name = fields.Char( - help="Name of the repository owner (e.g., 'cetmix', 'OCA')", - ) - reference = fields.Char( - index=True, - compute="_compute_display_name", - required=False, - store=True, - ) - repo_ids = fields.One2many( - comodel_name="cx.tower.git.repo", - inverse_name="owner_id", - string="Repositories", - copy=False, - help="Repositories owned by this organization/user", - ) - secret_id = fields.Many2one( - comodel_name="cx.tower.key", - string="Secret", - domain="[('key_type', '=', 's')]", - help="Custom secret used for this repository owner", - ) - - @api.depends("name") - def _compute_display_name(self): - """Compute display name.""" - for owner in self: - # By default, display name is the same as name - name = owner.name - owner.update( - { - "display_name": name or False, - "reference": owner._generate_or_fix_reference(name) - if name - else False, - } - ) - - @ormcache("self.env.uid", "self.env.su", "name") - def _get_owner_id_by_name(self, name, create=False): - """Get owner id by name. - - Args: - name (str): Owner name - create (bool): Create owner if not found - Returns: - int: Owner ID or None if not found - """ - owner = self.search([("name", "=ilike", name)], limit=1) if name else None - if not owner and create and name: - owner = self.create({"name": name}) - return owner.id if owner else None - - @api.model_create_multi - def create(self, vals_list): - """Clear cache on create.""" - res = super().create(vals_list) - self.clear_caches() - return res - - def write(self, vals): - """Clear cache on write.""" - res = super().write(vals) - if "name" in vals: - self.clear_caches() - return res - - def unlink(self): - """Clear cache on unlink.""" - res = super().unlink() - self.clear_caches() - return res - - # ------------------------------ - # YAML mixin methods - # ------------------------------ - def _get_fields_for_yaml(self): - res = super()._get_fields_for_yaml() - res += [ - "display_name", - "name", - "secret_id", - ] - return res diff --git a/addons/cetmix_tower_git/models/cx_tower_git_source.py b/addons/cetmix_tower_git/models/cx_tower_git_source.py deleted file mode 100644 index a850431..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_git_source.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, api, fields, models - - -class CxTowerGitSource(models.Model): - """ - Git Source. - Implements single git source. - Each source can include multiple remotes which can be - branches or pull requests of different repositories. - """ - - _name = "cx.tower.git.source" - _description = "Cetmix Tower Git Source" - - _inherit = [ - "cx.tower.reference.mixin", - "cx.tower.yaml.mixin", - ] - _order = "sequence, name" - - active = fields.Boolean(related="git_project_id.active", store=True, readonly=True) - enabled = fields.Boolean( - default=True, help="Enable in configuration and exported to files" - ) - name = fields.Char(required=False) - sequence = fields.Integer(default=10) - git_project_id = fields.Many2one( - comodel_name="cx.tower.git.project", - string="Git Configuration", - required=True, - ondelete="cascade", - auto_join=True, - ) - - remote_ids = fields.One2many( - comodel_name="cx.tower.git.remote", - inverse_name="source_id", - auto_join=True, - copy=True, - ) - remote_count = fields.Integer( - compute="_compute_remote_count", - string="Remotes", - ) - remote_count_private = fields.Integer( - compute="_compute_remote_count", - string="Private Remotes", - ) - - @api.depends("remote_ids", "remote_ids.enabled", "remote_ids.is_private") - def _compute_remote_count(self): - for record in self: - remote_count = private_remote_count = 0 - for remote in record.remote_ids: - if not remote.enabled: - continue - if remote.is_private: - private_remote_count += 1 - remote_count += 1 - record.update( - { - "remote_count": remote_count, - "remote_count_private": private_remote_count, - } - ) - - @api.model_create_multi - def create(self, vals_list): - res = super().create(vals_list) - # Update name - res._compose_name() - # Update related files and templates on create - res._update_related_files_and_templates() - return res - - def write(self, vals): - res = super().write(vals) - # Compose name - if "name" in vals and not vals.get("name"): - self._compose_name() - # Update related files and templates on update - self._update_related_files_and_templates() - return res - - def unlink(self): - """ - Override to update related files and templates on unlink - """ - related_files = self.mapped("git_project_id").mapped("git_project_rel_ids") - related_templates = self.mapped("git_project_id").mapped( - "git_project_file_template_rel_ids" - ) - res = super().unlink() - # Update related files and templates on unlink - if related_files: - related_files._save_to_file() - if related_templates: - related_templates._save_to_file_template() - return res - - def _compose_name(self): - """Compose name if not provided explicitly""" - for source in self: - if source.name: - continue - remote = fields.first(source.remote_ids) - if not remote: - source.name = _("Empty Source") - continue - - remote_repo = remote.repo_id - source.name = f"{remote_repo.owner_id.name}/{remote_repo.repo}" - - def _update_related_files_and_templates(self): - # Update related files and templates on update - related_files = self.mapped("git_project_id").mapped("git_project_rel_ids") - if related_files: - related_files._save_to_file() - related_templates = self.mapped("git_project_id").mapped( - "git_project_file_template_rel_ids" - ) - if related_templates: - related_templates._save_to_file_template() - - # ------------------------------ - # Reference mixin methods - # ------------------------------ - def _get_pre_populated_model_data(self): - res = super()._get_pre_populated_model_data() - res.update({"cx.tower.git.source": ["cx.tower.git.project", "git_project_id"]}) - return res - - # ------------------------------ - # YAML mixin methods - # ------------------------------ - def _get_fields_for_yaml(self): - res = super()._get_fields_for_yaml() - res += [ - "name", - "enabled", - "sequence", - "remote_ids", - ] - return res - - # ------------------------------ - # Git Aggregator related methods - # ------------------------------ - def _git_aggregator_prepare_record(self): - """Prepare json structure for git aggregator. - - Returns: - Dict: Json structure for git aggregator - """ - self.ensure_one() - - # Prepare remotes, merges and target - remotes = {} - merges = [] - target = None - for remote in self.remote_ids: - if remote.enabled: - remotes.update({remote.name: remote._git_aggregator_prepare_url()}) - merges.append( - { - "remote": remote.name, - "ref": remote._git_aggregator_prepare_head(), - } - ) - # Set target to first remote name - if not target: - target = remote.name - - # If no remotes, return empty dict - if not remotes: - return {} - - vals = { - "remotes": remotes, - "merges": merges, - "target": target, - } - - # Fetch only first commit if there is only one remote - if len(remotes) == 1: - vals.update({"defaults": {"depth": 1}}) - return vals diff --git a/addons/cetmix_tower_git/models/cx_tower_plan_line.py b/addons/cetmix_tower_git/models/cx_tower_plan_line.py deleted file mode 100644 index 0c676c0..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_plan_line.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2025 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import fields, models - - -class CxTowerPlanLine(models.Model): - """Flight Plan Line""" - - _inherit = "cx.tower.plan.line" - - git_project_id = fields.Many2one( - comodel_name="cx.tower.git.project", - string="Git Project", - help="Select a git project to be linked to the file and server.", - ) - is_make_copy = fields.Boolean( - string="Make a Copy", - help="Create a copy of the Git Project instead of linking " - "the file to the existing one.", - ) - - # ------------------------------ - # YAML mixin methods - # ------------------------------ - def _get_fields_for_yaml(self): - res = super()._get_fields_for_yaml() - res += [ - "git_project_id", - "is_make_copy", - ] - return res diff --git a/addons/cetmix_tower_git/models/cx_tower_server.py b/addons/cetmix_tower_git/models/cx_tower_server.py deleted file mode 100644 index 5f6bbba..0000000 --- a/addons/cetmix_tower_git/models/cx_tower_server.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright (C) 2024 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, fields, models - - -class CxTowerServer(models.Model): - _inherit = "cx.tower.server" - - git_project_rel_ids = fields.One2many( - comodel_name="cx.tower.git.project.rel", - inverse_name="server_id", - copy=False, - depends=["git_project_ids"], - groups="cetmix_tower_server.group_manager,cetmix_tower_server.group_root", - ) - - # Helper field to get all git projects related to server - # IMPORTANT: This field may contain duplicates because of the relation nature! - git_project_ids = fields.Many2many( - comodel_name="cx.tower.git.project", - relation="cx_tower_git_project_rel", - column1="server_id", - column2="git_project_id", - readonly=True, - copy=False, - depends=["git_project_rel_ids"], - groups="cetmix_tower_server.group_manager,cetmix_tower_server.group_root", - ) - - # ------------------------------ - # YAML mixin methods - # ------------------------------ - def _get_fields_for_yaml(self): - res = super()._get_fields_for_yaml() - res += [ - "git_project_rel_ids", - ] - return res - - def _get_force_x2m_resolve_models(self): - res = super()._get_force_x2m_resolve_models() - - # Add File in order to always try to use existing one - res += ["cx.tower.file"] - return res - - def _update_or_create_related_record( - self, model, reference, values, create_immediately=False - ): - # Files must be created immediately because they are related - # to both server and git project. - # So if a file is not created immediately when it is created - # for the server, the same file will be created for the git project. - # This will lead to creation of two files with the same content - # for the same server. - - if model._name == "cx.tower.file": - create_immediately = True - return super()._update_or_create_related_record( - model, reference, values, create_immediately=create_immediately - ) - - @api.model - def get_servers_by_git_ref(self, repository_url, head=None, head_type=None): - """ - Return servers linked to a given Git repository reference. - - Parameters - ---------- - repository_url : str - Pre-normalized canonical Git URL - (e.g. ``https://host/owner/repo.git``). - head : str, optional - Branch name, commit SHA, or PR identifier. - head_type : {'branch', 'commit', 'pr'}, optional - Type of the ``head`` argument. - If only ``head`` is provided, it will match across all head types. - If only ``head_type`` is provided, it will filter by type regardless of head - - Returns - ------- - recordset of cx.tower.server - Matching servers. Empty recordset if no matches. - """ - - server_obj = self.env["cx.tower.server"] - # URL MUST be already canonical. - if not repository_url: - return server_obj - - # Get repository id by URL - repo_id = self.env["cx.tower.git.repo"]._get_repo_id_by_url( - repository_url, raise_if_invalid=False - ) - if not repo_id: - return server_obj - repo = self.env["cx.tower.git.repo"].browse(repo_id) - - # Compose domain for remotes - remote_domain = [ - ("source_id.enabled", "=", True), - ("enabled", "=", True), - ] - if head: - head = self.env["cx.tower.git.remote"]._sanitize_head(head) - remote_domain.append(("head", "=", head)) - if head_type: - remote_domain.append(("head_type", "=", head_type)) - - # Get remotes - remotes = repo.remote_ids.filtered_domain(remote_domain) - if not remotes: - return server_obj - - # Get servers from remotes - servers = remotes.mapped("git_project_id.git_project_rel_ids.server_id") - return servers - - def _command_runner_file_using_template_create_file( - self, - file_template_id, - server_dir, - plan_line, - if_file_exists, - **kwargs, - ): - """Override to create git project relation - when creating a file using a template. - """ - file = super()._command_runner_file_using_template_create_file( - file_template_id, server_dir, plan_line, if_file_exists, **kwargs - ) - if file and plan_line: - git_project = plan_line.git_project_id - if not git_project: - return file - - if plan_line.is_make_copy: - # Remove default_server_ids from context, because this relation - # will be created through git_project_rel_ids. - # default_server_ids will interfere at the moment when - # pairs of values are created through SQL query - # in the method write_real and it does not take into account - # that in this case we are creating a copy of the git project - git_project = git_project.with_context(default_server_ids=False).copy() - - self.env["cx.tower.git.project.rel"].create( - { - "git_project_id": git_project.id, - "server_id": self.id, - "file_id": file.id, - "project_format": git_project._default_project_format(), - } - ) - return file diff --git a/addons/cetmix_tower_git/pyproject.toml b/addons/cetmix_tower_git/pyproject.toml deleted file mode 100644 index 4231d0c..0000000 --- a/addons/cetmix_tower_git/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["whool"] -build-backend = "whool.buildapi" diff --git a/addons/cetmix_tower_git/readme/CONFIGURE.md b/addons/cetmix_tower_git/readme/CONFIGURE.md deleted file mode 100644 index 8c717e5..0000000 --- a/addons/cetmix_tower_git/readme/CONFIGURE.md +++ /dev/null @@ -1 +0,0 @@ -Please refer to the [official documentation](https://cetmix.com/tower) for detailed configuration instructions. diff --git a/addons/cetmix_tower_git/readme/DESCRIPTION.md b/addons/cetmix_tower_git/readme/DESCRIPTION.md deleted file mode 100644 index 19da7e3..0000000 --- a/addons/cetmix_tower_git/readme/DESCRIPTION.md +++ /dev/null @@ -1,3 +0,0 @@ -This module implements Git Management functionality for [Cetmix Tower](https://cetmix.com/tower). - -Please refer to the [official documentation](https://cetmix.com/tower) for detailed information. diff --git a/addons/cetmix_tower_git/readme/HISTORY.md b/addons/cetmix_tower_git/readme/HISTORY.md deleted file mode 100644 index 61fd2cf..0000000 --- a/addons/cetmix_tower_git/readme/HISTORY.md +++ /dev/null @@ -1,43 +0,0 @@ -## 16.0.2.0.1 (2025-12-11) - -- Features: Improve search views, implement the search panel for selected views. (5139) - - -## 16.0.2.0.0 (2025-10-27) - -- Features: Major refactoring: implement Git repository entity. (4914) - - -## 16.0.1.0.6 (2025-08-18) - -- Features: Link or copy a git project when uploading the linked file using command (4759) - - -## 16.0.1.0.5 (2025-08-17) - -- Features: Search servers by git reference (4838) - - -## 16.0.1.0.4 (2025-07-29) - -- Features: Export related commands and flight plans together with server (4849) - - -## 16.0.1.0.3 (2025-05-23) - -- Bugfixes: Duplicated file is created when importing a YAML file with a git project. (4715) - - -## 16.0.1.0.2 (2025-05-16) - -- Features: Record references for git relations. (4670) - - -## 16.0.1.0.1 (2025-05-09) - -- Bugfixes: Non-critical issues and performance improvements. (4663) - - -## 16.0.1.0.0 - -Release for Odoo 16.0 diff --git a/addons/cetmix_tower_git/readme/USAGE.md b/addons/cetmix_tower_git/readme/USAGE.md deleted file mode 100644 index 901f5a6..0000000 --- a/addons/cetmix_tower_git/readme/USAGE.md +++ /dev/null @@ -1 +0,0 @@ -Please refer to the [official documentation](https://cetmix.com/tower) for detailed usage instructions. diff --git a/addons/cetmix_tower_git/readme/newsfragments/.gitkeep b/addons/cetmix_tower_git/readme/newsfragments/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/addons/cetmix_tower_git/security/cx_tower_git_project_file_template_rel_security.xml b/addons/cetmix_tower_git/security/cx_tower_git_project_file_template_rel_security.xml deleted file mode 100644 index 6d4f088..0000000 --- a/addons/cetmix_tower_git/security/cx_tower_git_project_file_template_rel_security.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - Git Project File Template Relation: Manager Read Access - - ['&', - '|', - ('git_project_id.user_ids', 'in', [user.id]), - ('git_project_id.manager_ids', 'in', [user.id]), - '|', - ('file_template_id.user_ids', 'in', [user.id]), - ('file_template_id.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Project File Template Relation: Manager Write Access - - [ - ('git_project_id.manager_ids', 'in', [user.id]), - ('file_template_id.manager_ids', 'in', [user.id])] - - - - - - - - - - - Git Project File Template Relation: Root Full Access - - [(1, '=', 1)] - - - - diff --git a/addons/cetmix_tower_git/security/cx_tower_git_project_rel_security.xml b/addons/cetmix_tower_git/security/cx_tower_git_project_rel_security.xml deleted file mode 100644 index 3365257..0000000 --- a/addons/cetmix_tower_git/security/cx_tower_git_project_rel_security.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - Git Project Relation: Manager Read Access - - ['&', - '|', - ('git_project_id.user_ids', 'in', [user.id]), - ('git_project_id.manager_ids', 'in', [user.id]), - '|', - ('server_id.user_ids', 'in', [user.id]), - ('server_id.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Project Relation: Manager Create/Write/Delete Access - - [('git_project_id.manager_ids', 'in', [user.id]), - ('server_id.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Project Relation: Root Full Access - - [(1, '=', 1)] - - - - diff --git a/addons/cetmix_tower_git/security/cx_tower_git_project_security.xml b/addons/cetmix_tower_git/security/cx_tower_git_project_security.xml deleted file mode 100644 index 2149081..0000000 --- a/addons/cetmix_tower_git/security/cx_tower_git_project_security.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - Git Project: Manager Read Access - - ['|', ('user_ids', 'in', [user.id]), ('manager_ids', 'in', [user.id])] - - - - - - - - - - Git Project: Manager Read Access via Server - - ['|', - ('server_ids.user_ids', 'in', [user.id]), - ('server_ids.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Project: Manager Write Access - - [('manager_ids', 'in', [user.id])] - - - - - - - - - - Git Project: Manager Delete Access - - [('manager_ids', 'in', [user.id]), ('create_uid', '=', user.id)] - - - - - - - - - - Git Project: Root Full Access - - [(1, '=', 1)] - - - - - diff --git a/addons/cetmix_tower_git/security/cx_tower_git_remote_security.xml b/addons/cetmix_tower_git/security/cx_tower_git_remote_security.xml deleted file mode 100644 index bd4a48d..0000000 --- a/addons/cetmix_tower_git/security/cx_tower_git_remote_security.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - Git Remote: Manager Read Access - - ['|', ('git_project_id.user_ids', 'in', [user.id]), ('git_project_id.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Remote: Manager Read Access via Server - - ['|', - ('git_project_id.server_ids.user_ids', 'in', [user.id]), - ('git_project_id.server_ids.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Remote: Manager Write/Create Access - - [('git_project_id.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Remote: Manager Delete Access - - [('git_project_id.manager_ids', 'in', [user.id]), ('create_uid', '=', user.id)] - - - - - - - - - - Git Remote: Root Full Access - - [(1, '=', 1)] - - - - - - - - - diff --git a/addons/cetmix_tower_git/security/cx_tower_git_repo_owner_security.xml b/addons/cetmix_tower_git/security/cx_tower_git_repo_owner_security.xml deleted file mode 100644 index 7ef0ee9..0000000 --- a/addons/cetmix_tower_git/security/cx_tower_git_repo_owner_security.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - Git Repository Owner: Manager Write/Create Access - - [('create_uid', '=', user.id)] - - - - - - - - - - Git Repository Owner: Root Full Access - - [(1, '=', 1)] - - - - diff --git a/addons/cetmix_tower_git/security/cx_tower_git_repo_security.xml b/addons/cetmix_tower_git/security/cx_tower_git_repo_security.xml deleted file mode 100644 index 9968a42..0000000 --- a/addons/cetmix_tower_git/security/cx_tower_git_repo_security.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - Git Repository: Manager Write/Create Access - - [('create_uid', '=', user.id)] - - - - - - - - - - Git Repository: Root Full Access - - [(1, '=', 1)] - - - - diff --git a/addons/cetmix_tower_git/security/cx_tower_git_source_security.xml b/addons/cetmix_tower_git/security/cx_tower_git_source_security.xml deleted file mode 100644 index bad536b..0000000 --- a/addons/cetmix_tower_git/security/cx_tower_git_source_security.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - Git Source: Manager Read Access - - ['|', ('git_project_id.user_ids', 'in', [user.id]), ('git_project_id.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Source: Manager Read Access via Server - - ['|', - ('git_project_id.server_ids.user_ids', 'in', [user.id]), - ('git_project_id.server_ids.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Source: Manager Write/Create Access - - [('git_project_id.manager_ids', 'in', [user.id])] - - - - - - - - - - Git Source: Manager Delete Access - - [('git_project_id.manager_ids', 'in', [user.id]), ('create_uid', '=', user.id)] - - - - - - - - - - Git Source: Root Full Access - - [(1, '=', 1)] - - - - - - - - - diff --git a/addons/cetmix_tower_git/security/ir.model.access.csv b/addons/cetmix_tower_git/security/ir.model.access.csv deleted file mode 100644 index 750134f..0000000 --- a/addons/cetmix_tower_git/security/ir.model.access.csv +++ /dev/null @@ -1,15 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_git_config_manager,Git Config Manager,model_cx_tower_git_project,cetmix_tower_server.group_manager,1,1,1,1 -access_git_config_root,Git Config Root,model_cx_tower_git_project,cetmix_tower_server.group_root,1,1,1,1 -access_git_source_manager,Git Source Manager,model_cx_tower_git_source,cetmix_tower_server.group_manager,1,1,1,1 -access_git_source_root,Git Source Root,model_cx_tower_git_source,cetmix_tower_server.group_root,1,1,1,1 -access_git_remote_manager,Git Remote Manager,model_cx_tower_git_remote,cetmix_tower_server.group_manager,1,1,1,1 -access_git_remote_root,Git Remote Root,model_cx_tower_git_remote,cetmix_tower_server.group_root,1,1,1,1 -access_git_repo_manager,Git Repository Manager,model_cx_tower_git_repo,cetmix_tower_server.group_manager,1,1,1,1 -access_git_repo_root,Git Repository Root,model_cx_tower_git_repo,cetmix_tower_server.group_root,1,1,1,1 -access_git_repo_owner_manager,Git Repository Owner Manager,model_cx_tower_git_repo_owner,cetmix_tower_server.group_manager,1,1,1,0 -access_git_repo_owner_root,Git Repository Owner Root,model_cx_tower_git_repo_owner,cetmix_tower_server.group_root,1,1,1,1 -access_git_project_server_file_rel,Git Project Server File Rel Manager,model_cx_tower_git_project_rel,cetmix_tower_server.group_manager,1,1,1,1 -access_git_project_server_file_rel_root,Git Project Server File Rel Root,model_cx_tower_git_project_rel,cetmix_tower_server.group_root,1,1,1,1 -access_git_project_file_template_rel,Git Project File Template Rel Manager,model_cx_tower_git_project_file_template_rel,cetmix_tower_server.group_manager,1,1,1,1 -access_git_project_file_template_rel_root,Git Project File Template Rel Root,model_cx_tower_git_project_file_template_rel,cetmix_tower_server.group_root,1,1,1,1 diff --git a/addons/cetmix_tower_git/static/description/banner.png b/addons/cetmix_tower_git/static/description/banner.png deleted file mode 100644 index 48d0b508a8c55bc767c0422908f4644ca1284072..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86695 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 z 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 }spw8BM!` zo%cDTD!4Sd%bXMdXRC*KL@Y-JewtX z07@S^h?K$Ygr z?A#8fzNHDQ_yEAc`DGNqpkVCww~U|9H&Fu#0El28YYf}mjTZ2 zy?qT5K%cl=@sI%m+Va)4w9s$s^r#6?0!DRrF6+El@Qe#D1cvOmv*7ZUm=Wy4>zk6k zE$&bN`5ZN{>Xi|xeBJJ9f2u%{_aFkGc=pS|_S3pgn{cPWDGgp*j?cdX^*&zAHO<`U zGRr6}cHV?VfUMu;kvAZI5`Ne;yz-Qd&%oW4Si@)QG zsIdd#z=32Ko;B=hJoPbVV;<<#)SnB+^*a4LxbX;==g;A!Lw)F>GZ$`2p5Datugxv@ z0zrOmD3S1Ki|7mK*5K}PZWX;N&&#`bNcvpXdKC`k7uujrVU z6MVwCT;*7PzV-%=2La8m>{g#(EiaUj|Et6LQ^f_mE+lM2m(&qou*$!jadK+Rz;H!t z{pr`ha=@{k7jx1q209^JM5Knb0$aM?tQm84IGM0+%FNH(Y-u41WcK;jixVf#FR(7DA(wY@ue6D58%b_X?m7m)Z#lZfrc+Kyn5r=3fIMsJOj_ z_c(_OFoni47taghuIQA7UcMzjL157!0yFD=mT|eLoWVU{vf0?i^5R#A2t{ruCTId%wTWI%ugUgMQ0I(4HI~k%XuXuE_+oG&%T>Re#l8M z?qmw4~-L`TEkoLDuw;smhXUE+%`<~$~08%T5|2s&q#YRAn-+dPcwjJNa z4=8T{f{ip&Gfxf(B7Fdx#!gzGQrkS|{tbyMrhe-;6&6m!=tr`BZ!4h%;C8s2#DkJb zYNG9)E)txY-Y3BM6)cV@4tY^e+PD}rphUzik>KH6?L(OXg-UDObf0z$9t zy@K$lUNbzf@EdD2H3kYfIG3&t=hJ?^l*k+~qQc_K3rEb1M0L z`HugV|Fzwnk-%}U&9f!j&d@`M43JTd>NWbGd}quwLuWq=Pv=|^f3y->6^<$gTqk*` zVEf~}Yd9ZJ+d>~U74J7zd7uw#t_dl0Bg=QjCc^>19ri4r26c{GRQmC0t~XF?RAg_| z2k?F8=e<|S0}gOBi?=;%<6Xfg+%7SxC;q!ua<6bJIfb%;?C*FyBhS#YZ~ciS_viVO z|7p;Ao&`?|L+^zQDkQ-?u=+n6K-oY{&XLo?eBB(aNSlYaZ3rfd?$^CxXXp3L|Ba|X zoF5aPb&)JNDoy(w?xqw_E`pjRBUq^WnuqN0KUX9U9a;Gw^z#=Dsc z2OFUMpTpW>UPB#+W@bB3dY)K6UhpjzXUc-0oN?W2={72_yqtol{He5HDKF}|K#@qB zicSiK;)v(m)#}$wS#NNB>jTNKJ2_19YrAWB9Ehi>=$)50z8v^Y@Ke0k+_gdY_U&-z zC{=LUynJQ%@nIp;_Befi2NO@b(W``}P^{L{7BNNV*8k0m`v!+H6mV@nTkIJ&8`~8y zwC(p1E{}>&wXJHkz{`ErynXms=e)e8k~Rx_v|5S()wK8R10I?$0{Fv_&u1(+hr>Uu zWJG@wak!YYNGmEh5aAg+$Jc^}l8n9MwZ~d*5x@8~+oe-EE_okHrVO%Ks3#~;z%S+tbDBE`*JW-Ev2CTN|>{AI{MD1 zR0YB`etZ0RZPhyWPPJF(=~v1d7ks$4dRWxI9WUGp#%!6C2-jCnED*ne87}Ml%GQ!j zTP;)F?^=+CO_^EwoUZQ$y|4Pt*p9m6dsmHr7d6aoKd9(^2;Jv%_gjX9BW;tVj)0~@ zY~E6aw=#948qBr(4sl=c1D@c7OEb%1_GodlEkN(m6^Kz;?cTle*fb3lPg!A@rc+NS z=rI3{!jk@-&fe6-2W8bw{#xARJK9A`f~0R_kkkFm68?3MwMg%y`>yr%Srh&E^Pl-t zjIussqQA+TV5c7a4?#0!8UB04s&!*tu>BS<%`(rd*3@d}Jdf2W zON0?#u26_BZC=dN^Nw6Sd=~vDkGZ9E@*Hxk2|Zrzz0sF~S-05>Sqhx;Eeq{TAG?i~ z8fDhL_Km)ZT6^%$NR3uCq*d9!7k%jsyVc*(-IK@oOrlj|$18F+kY}}Wx826J?j-0q zHGT>C)}6I6yChOl&J^hIWL?_&PzZoEo5VHHg>1Lzysr9a^fQBL=ANnY~*78^M~{1{kX(f-8bAPPJ}6U7=Kj%%*Ag)U);5yDaSq z6qYjB-5vaUe2|%g`q(>`@w@$L(Xd8cJg)JATmyHAT~2**31tsS+fukal}4GBYdU7&F^Oq{peyY zETjr-@`3z;!Sb_yY(W!&Ng^sj@r_0i&E8n>PgnM?MP-AHt(=rSdJ#d6%FaGsX=tnX z7`wv+bMp%_LJbQ>U6q%`Yft7FuM)2$NoQgAv-l<3g0@@vv&h4lqvYCxVl&eA~)gK78VWz>;<4rOmpwo(p_{0(5nT|4GUGwEpQge*dZ5l!>v;7Y35W*JfVRAtJ*3bX0xqQyJyqER$~C56(V z!vXe6(T2yPEY-EPHm(^VJny`N5hT5+CL$MFBLTbfrFsRsnUKnCquF>R%^-Hu$%U~! z0ItA1ki++o~r zVqUoFh0NFqUcc{IGj2S_NI;-9;2{eX-5u{;l%pTIZ?7nel{kuS&0PATzeWQ{k3I`3 z=JA`7BG$88PqUES->t`vnWb8!I=`-0CwkGLS4+5~^$+BScmAy5;UqR-hCOc?6Ebt! zbTM%((~F-@UO1PTJHFyy#1nOqiUP*?ZPDKDHxc6>w0&V3txs>&Fd5s~)n zWA@vJB(FG?k_&#Y|Cb+UUGI+{VgIEQzQk@+?s(o)%YyX}FKe!pYWtrQM{t@zc zsWgPi%@Kv1Va%!B9U-fu6CAwJhr@22`^S@CCHO{neR9?$BArKZ)?i`79^J76+)qYVGFF5OpQF}_RX|*=OWprP8gX*Z;s?+OON}-&C2FfvJ=~Y>wkfZiK0cb@gzFpJm5agOV(29S;zgxJqH6QM@h7EC);ac-&SZmgo;^6ueur;+S}(^M9i5$OFxoF<+C zLIokBZ_=>b2SrB`j8pp7YTLWfZvFdmml^11Gr8lyv2usIVbhwXY1Z#O)D`)C1!FvY z+|RjdsL$W!(fiVgm`rs9KEJq2lo{NTI(xyI=Q|PNm7Wh7F5xVUbk6R;v7w7 zL-vrFQSIb8jYNK3W_N#<6Q-r5x218n#f>_9(g_Ug=LJ+ce*7! z0WD<%Hha2Ue+5wi!GGhvG{13JKCZ(GRf~o2z)6tuamrKQ2&Gm1HIm8)?ahA-L4(cn zuk^nt#JsUqMHkK9Ma>$2HT#xf6j~O4St(PXmUWWe`BIee{z3$c2OD?T~j29YenDxv0l|2|^57_sZ5L-Sm|BBbvS45|KnpK9a;rpvl_ zlT9ZQgm8F%W!h*)`8421p4P(M&Tj*&UlQ?o2(DPIl1bnf9U6^$OHCLUXIlN`;fbXV zM8N%$+&pBlR~&xgoX?x`p<1mqbB6R#*X@iJc7_adBL?+h>Bs)Q4Z5t1=jSUe>I~xf z5X-e3nEx?mAYG%FJVMF%;Rn-?yq_0Jt5c)aURM-CC&@2%jwm|TjAxA~JwLKrV-UVh z3OZ0gmkX_qj-!3MyYv_=r=0mz>W0yFt^LIy=kfRhCY?t!VHh@}Y?i27mq1iGEq&8$ zL5jjmbRYW%XqOB1@(Nj!VdX(7d=(4fIo3 zKnHlWiODB&v=W~956HRYm^H@4zOu`(=S<(3=lWyY;%Zks`%f*GVOY7N1AmyS3o@Tf zt@X_*oH4x0d6nQRKI25VP0UqPR>|x~*=jp!s?p%NPj6J=uAh0vPwGXJLJRXg8r;|5 zE3vUC*HLWcJ-~tJyGi$@qOhx$d^h&)s@OZ#oXRuzdI^H7T>r|K4NK+S3pDA zew?`Bg8ygpW`WS&RD8`*I#p&8Bh(2~hByInmQNijiH_g=lok(2cB4aY_}%DQGwiDZ zdY*jU-u|>QVTBOVf+Qo!S$Ncr*gj$k{oq5Za(>ntEUix5dbcuAgA=I02|qhXNd*_l zYviIRH6(J}fQ!}RcXPxPPg;i9bT!l0t>g#uwGC(dMNFxZRh~uhc)w#LnWJJ-%90;i zR5w@U`XD6qu=%EL=fK1^cU{`j9xpGy5sR&*l4mcH%-8H!F*uDdw-nJ!3g-M8)S6XW zJ<*pXkz5~%3FP#qjwIf^jdyN0`BKl}7d(^rvq>2VQx=b|n5&$7eTPHTRT%Pa0 z7+p12q$K-p(z2#lDVDyLe+ixtxZ(H3iVEti7+8@zvbNU`wq>qZ772sVOluN&TWOzF z9C4!{tqZ@!$5g;YW|q)Cz6kxagq(R`2EkX z6uK<)&0dtSJHgBV{vKgrWOO+nZn;UO|H15<8`7i6aS9S@`Yxp@VsdXYG14LT&^>?K9H-}IbW?@ zwl>+>t5xFWcKX>^Jaseg+A7ATedsm5agT$=Sak+1_^ENXQo#()6vWdUXnAz} z#mS_>q1Z=v$dA~1V$A@){GxN5BrGRE8`$Z~1eX4eB20Wu4JC&uQZ5mZ8UKh93|sJp zAfsm=@X#ByTDT#PB6BEVJg1`*0;lCqsUTC?d5U;EP0xMfOn?Ges@~&4pT@D*7ZZQ1@wxY6;}U9~5i#FKnzrf} z@;H#DpfT%@LCuTq)!VpMX;4D!Bo&pt_^lUKhh!c7NhzwaqMGnvkCtCeo$*3_pbNiL%}}Y7SO|f*>S%ys+a*h}YeLA_d9h+a!d zA++zqF|y5+010$vL}da!ixeC#RvC~rerq90GBOQq2S#P}cW- zzwppvZT=)>Q&o^!0I!7Kel20aETc&KpPKxl7r-}l7ukMJ)~$zrz3^n_LYyKVzZWQ{9Or4^89Ft?XS>3S$l1nB)V6P)o>X#O;Af3HT~d=w4o znHI36PlZ^NY6xCJpkiYBoW*7`b#z)~9BaqBbF)3amsPUm%MJfNb9qT%^PWZiWUq!O zDvWY~npNZVz}20*Ee39?CJ{)=i1oT+`jV)rO}w6cTT)Z+IV!pOWL5s*n+eM=x}r!O z0YZLqjYU~xceqFSJKI1tF7Vq&!VUi=qL+kW>=vd)3s=gS5#OM7TH?K3aZGspW6Szr zN{hPyNo-H;Ym|AH!FuSjX4ECxE!@bcQ8%?Yco^&N9ZEGJ~#Rx&NYp9xMT^yzhj0Ui^=| zZ7xQYi~*e%l$?#Hnb~x%0 znkyQ2Z*#?9R>*h$!1B^~I=axGCN^b{lVFf@QN$wUXR+Z+ZI7Fugw>0==->GM#JPne zU|})R6MQ*$Zho5cq7woC4%T6Ne*L~vQHk)Ap(6>{3E^d6?Xwims_f2NPFJa20c*8`4vq%+9i2c;kuq|M~*RB z@NdJp?V59@EamXNv{v}ni?f+aM9@e=CG@yH8lA@1ekYQ~RYF|dd_ImHiIkl@$N+`( z)#h8(jp+ua$1&l58N*?+yJm<=4yEvn;ti(wJ( z%x63{1~;9g!>MY1xwMF_r-3~BNJ9J;SzKwQAU(_yCG9NgC_^;s&?S-dgTq=oP4 zgZySSTPu6ZB1iFSd&A>Q^s!!tvcgTXhRpW&ivDmdmd>F^Xsp#Y(q)(ZXRsA{@2)!7 zo!-G;PehQYkyMkfIBUZ^E@a^j#0ync7il)Q ziGPs8T$G-=J|=PH#s93%MzGKrwu31YFr>BhP}cMW$wzc)HNLca{q8wM*=V?MaMOG3 zj3Cz+`2L2rFk7e zIuS4c&un~->i?S(UlX&QJt)9l*baQ<&c8=w)|BURE4K$Td0Onwp_y$jiSh;|2mO;U z7-=&$1Q(x9rp}Ia_14<^dySb?i@o(tp&GLl_$H^MHL(VEuE*^}9%9=DGpGH^50WIV zlDV6+RtX>iNkyIbUn-@er0KZUx;3Iqxq0gzYO&%$295zDkvUI8zeB;uzG)C;XYWeh zxS@JHE=0XaPnL}AbY<=KT!*1}wJ}4+pRf>~+Q;U6)9CzE``7lolw3OJ1(^n+UmvFT z9&g?|Kd^hn+N18iKqS)M>(KTN_T1Ead?SAGxyS_gprtxLqBhKs%*MMP(hrzh*uZj$ zK3-b57~UmR*42GFBJhANL^YzLBG{DM-Q@t65pvjeaH(zen}DtDT7X5Le|pT|X1guJ z9RZ|Tg_iH9HEDh{5~3@mIaPc+3un=7Sr*9}*eG8jTJ%czm$v~xoCILCFx@{I+%7VC zT+dpsj~IBqm@K%_6idM>j|tNfyWG>$lMsCR^o3RCNTy&<1$8!klrSJxdZ)29Zhj?* zv59OsI1VXfNzx3u`H<(;dN`W$qQ>Jg3Nw%;>Q1g8Oz9)an9gQ8G~Mp5g@uiR4K~|_ z--s*vFp#IxhD$HbonE-L;Hi&YSHtz{`VR6q+3=IN7>SWEjNu07II!89oX@JUAgWxK zmur=JZ~hi>YIx*H6xo$pbdufKB|j?H~p4fsvFv(!E$oA7Zq=+)QM0!n%F3*qlYm)>iq#`u<)vF7aREgHjI9Dt+6v^Ls%jXxWwOKj!Gq>$9Ra&-J|w zE#9%M#SfXTv$Z{tww^b906E+CKT`i(wuZ?3V0tWb-oNk;(_t^V-TvWQOx=PvttD;a zv|1P7{6)S4|A6!lvQ0-b5R#x%*4NOe;ZYC$v9JwzmM$A+%Ip7#es@}JBRjt2vo7qls1Q;baQ0yXt`+EV z?Ao_qPZI8lLTERw*-%(Q(0Ce6YIZ!bQ;+D5<7}wm`i4;bGs+_pf;{2o+ngG9MoTOe z`VUO){`;Y}O=^Ld*b<{-WY&>JCd1EG*+>vx(kOzVcT_r127j+a?k_a!uSmc5i-TA3 z#}DM_cR!l{G*)xHa=De?KR!>K$*OTD)=8hx9!g+Rl=jjmt>%1%xUcR7yIqdHZ39`K z<4eIlL(Y#U$6M}zH{yP>-WXlJOaLIO)@ECoHuMiR_Jt`y3S0Z z2RzV|GkQ?E$FRCK|e zBa)6Xr>mYy7Z=p)oEGC}f{$JpQQmtZ+h*l`SoHUq#JTFGRg=(de z&pu;}|NQE?P<=Tu{pi)ri#PA$F)GdVd11}_31cf@0xEyknZ);h_6&9z+t zy^sq-M?QJNN1`Zl2oh6-2~2vxorHVZLH7^l_V*64AC>w2Zil|L(*^`ieHCtf+_*C2 z{pgWz8R)*OV|67!uNo6nYV;L2L8}&=6C2>SfoISQB9Na8+u*TV>Wi|O7cT+xcMOUz zLA0t|`Rvq+OzriYZ+88&eyZ-DoEboSl=WfZ3)|}ihVT0BKsNpQ`uIa`UXbR#eM1!z zwAq_9)7!G)jB9Lwy^45xUKlHV6s>H=2gH@EGb_^e#AP@kI}nb18czss?YYrPM^JQI zCK|Fk>ToI-%Gp)`rAppk!FD3>Sjgq^#95T=>~-}{JfN`i;LNVU5jNw=V)1kA-F4Mm zKg+*@A9f&9hSNVh94-qdrrsP~P~;7Uv>jS?9W&rRd^_sS1r;H!f=!kXRsu;Trx^PNP( znp0lhhsZi7N*PTk1La@tm68lrax&QUsBLB$rmjlS1xHB?Je9FW_IHWS_G<(pgu>j1 zdp`VJ9kG`HKh>dR+RbDC&RYuXmfx28JMn|0Oz@fxs0oNIknA}0qRXJ(^L&b=Gm!yg z{!EA-GJ?=UPc2oH07z6UFF6u=dtC*Lt=tu#eBDO&eIs>DWR*}BOESuX!25pRHch}A69k+UL91_C(3v9cOa`Z8R7)=AZk8F*P=qYC z)c&0bq3VSH*$v;>q3j(=&_58c5%_fJV|I>f(}oN2EbKMw7ACl)X!KJvvwj*uEPZ;L zqTp-s6fJvcZsCHW()FRa62%)$+!Zz2)w>?VO*rwr2ocgz%8P9;cg}o0>%^u`>^YRT1Rbw&?uO+%E$kM{3(aE23^Vm!6@|Z?-dlyRF%R51; zMV*D>QKbs#$bRk5cR@cIzhimN7l{?0tI!ldmJqc72BzTvp!x}R-#<>g%Eou4?jMtv zhzPUy*t6t<-NL>gymbmflgNp+w=CC(0$lKGVq(=!%iq*AdRT~}$2t<=shhDbcBXm@ z1TDGm+dh~cKXb5BVgeQ!?zI*kAYB2z!8 z8OvqA%*h07$OQ^91Lr{4WUu}W!`3LHl#1BX|7I=#9NMWOo`iTt z3Mj%JuzVTc4q_IfB$`ituC76_3hFJDcCN3mp4O~b>_Y`SF5KuZDJ|RU#mQzoT@I$O z?@ub|E8VR<-w9Kx<;f-iSxGal_s!h{m7q}uafIn`x|-i6tuNQ@w!rVSJM2?f!_sn8 zs2vH25s67v*4UsKXeF+~CpbHMl+yd@h~|#@3@u3Hhng-zkt=30{BxGP!=(&9@4EYd zUp;PD!nb4Z`^iK-qL)mserg=&rk&s64NXvRyp`O9PuH#oG}FxX#Ey(bLlC5Ew?3Hu zEa5wGHIiq@sHuqj4Hb|#&?%$*QTYOA*SkL0-0y&U?*gyE%3J2 zaIvlUnJz1tMi@5cw&_N^fkDSb0teWsec@letlEMC$Y?M8J0~5Hl7wlHYMB4n=6uHb z2x0N>Fz?A+ZVUV)&apKz*d0L_j9_5p^!&FlJ4)NUD}KTL)0awHVzjILS5yRcDuSY@ z!VfyRawz6s)L!xlqGOg+S3bJnf;)z%k{>)#z&=F^o2KM;RA*W<28JN5`B_FJvW4`> zr4m9l=|rhFDRBHWxbE0deFO#Y5vBO>9h;?$m@~$2WZmH1zTqCDTE(UUwYe zFs&!&)nFayikZwRun80g4K4PjginR6ySfG|-Nz9lZ>d2>Ac6hQC_;T+x)cWZ2g&3q zl7650K^@3RpTbViqneVrFqV!=vYKW${*F+#ZO8M^yq&ciQ6(9j!fiLN4my9ZLHnnB zC!UZP=~%LTEwns(wC1yb$vdN)84S|c9q_bkMgIus#IS8@fslM*T(u!5Rp(;H_{5!} zLLo_Y25*7^NyQcsA`E^I#*5JmW-SNfyW%LGi=0ap&hn;hBwR%)-pcMU;{7S(l!!4q&?zSm6ZU|yO?QI!}a_V2rJm>F~6vaD9vKOC6Z*EAW}t#D0?tedQ_dZ+J{W1|U5QT`&Ziko06NaYWy@Wli!El6{ ziwG+2Z!tguP^MVj2luKeOkgA_B~-B_ta1YvwqRk)&mvU+oA~Zw3i~N_mMX);TH3i( z)wSreuAHY@-+0e2Yh^5-?8*N6y^mR|69w#A|Gs_L^ix*))6V?YWv3wu2Ogl)0g3Kq z21$e)cC8NN{Ec)DjuoeILAjxk@tZi~mdTt>kPZfC4wa|qAHW@K_8zuoGH386GcU#a z5;sJsxsD1?P%x&>S~;wx(FEz-Y(vd?fk9!M|}l9>)x&v<`DA zCA4e~^=4d8yuDiN?RpwS!^DzrF}mI1=YlU)7cejGr}=O;VAE27{2i@xmx9KiE^Ypc za5y~~J$63y3|pd7OFSx?o&8!oAvGN3gLeI2ZokG7iH^sY3iZ0YTVn&_4f134T?ORY zxDE5N@6(zz|Kz?WjY&OB*WOOSD6(;leD*lnbjQ~{*8Mn1>j4%X{5=p|h99nA@Afgb zaDq*XB=Ls3j`5hCTKy~NLLn7@dMD44U8h?NkGC3LwIq=Ii@F|9e+fDFF>+uVeQ{-h~m!F@cN_j=Ub7{w~=Rm@g2jPs(3v z^5T2!lG4kBepHA4G1>gUP#tg*fLig<<$T6NB7NUbXo_7FSmPdD@lUS(PrTO22F>z>0T({mXYI;#IPD|w7MZ<7ugTf z2dkI^RUU|GZ+=L~6%gf!;OYD>Cgu7${cE-4%y)u?kaS`e>$N~s#NzEjt?r+AZB{(& zLStFQAM!LAJR;=dEyJgff>8mYr(XxGcrJ#Kv#`}b`^<);EAV^eG*%#tD1(A8PyKY$ z?K$l$9w}MC-5&M%2AAQq#i)l8uHkPt9GL)ho*Q@aZ+v+{kolsjO%~vo;}xn@l)_g( zHQ^VwjfYb;@oBsG7wrWes?pqbj$e=}sV&yc69m^aiThvRVT;}fh z*FZf5@x9?R`q?jv{I#Ft-SuqZWl-|ev}o{`59Qa ze1@si8FR6{V8K0eHpM+w0msItvRvJ**`Rws4wPS0F$7Ief=rB;|9rY@> z5_mFti{j?nE$Yr2fCOdcrWgkNAG~_^nXi~DiIlz1@2%g^Owep6^(;pU4Bf%1jYmG#N7dY{@(;OcY z187)p5r5vbIOa^5^c_ImW0hl0vt|AV91-g;Hx%1ud+qhKBCkDK3i@SO%T;@VM`(Yr zv{%~5`88W>Q3f=em(J}y9sUugNaXe2mMS_>jP3t%N2|4Fxm|Ks#z2zM+ui2`E(mB%uhe=Wl7;||ma z6Ji^~(IhEfnZ_0s3VgEngnK6AoWwrJjq|RpFIt3BoVPk*op^up5>>Gik&hpITuwbt z;BGp1rE0F)rlCk<9UCmrNP1&nOy$I}?NNs7@#TVUhLlLF$*NE@(@Lun5I0e0j8l=N zuT+zOenTas@#%O{6ej9swXbLQ@*G;>fAcjK0iu(9rGNi`ycd$p_Zlar)_O%hQW7qO z(89#TJyWG*RARU$0^h$#oFhW0Eua40y2(y{@gGX00sh2myeFj(MfiRqu3CkAvlscZ{VlNk%gX&kLI z6T)2mf3DG!y!z7}RDeT}`?RSy2H@jW2QZRAIl?Pm&*Sc(ysmD(?eKNCw5Bp~66&1E zz}IYs#C?Y)oK}sNdcTvAKMh-$^+&^ziBKop@z*W;$6boSZMr;t(Swy zdK`@!CB&{s0h{^||&G5;OTRl;^>qfD-yjkQx#|73* z$@<8c1B(jKlHrk5A@Y3~wm#M$h7-+;J7cV17*hxzwUam^r*$AZj<0NQc6v=>76^X% zc50~_lwbqcqGr$)0LZR9ME4NLp<+!Z6z6`C&RL^xE>b0des|Dic{)VM7NFD8x?0$a z0(4!-_SFAHt)UA~%(?cBG-&O ztaR`#3bp^e(-I!mhIXJYE#!2zAGZ>(Wh|u(y>u8Nsxc7=R^>EngTJ2K+5x;13S?^> z>*$}@#Nb#c2hH6dbR0mAICwIE6ImeYu9!yj>E!2R_$A+#r5Je;0x@CG%iMbn&1P0y z7mgCySn>C00Iz`Myni^;lUr{1flM865NN5R#Eh|YiU@2&pkRX{oiD{?0StzSw~VdR zC1~Ijjrb;~{WZf7Gh5j)Sau|MRcviBKW%pY_b+wI)I`Pd#6=bGW}-*U`!_VYpt3r0 z{5AXKC;WFsJVJw|1B}8c1PbgeobcfFbUo_1U6dDTD>WUdEa{_h(oBkYdq`*wn z*NfK`J1zb0>qm<|Pdzo-6UEw>lgyp-dVD>o4e1GTz{>9<9SVK&(rg!GTQ{FRp^}wP5-~B2D2Kc8s98RR4al^a5YL)a5sH{kC__v~?kh z*1k+5hsme}s|s%4k$Qn^fZw~|Rn{E7$>auGM$zjzB?54!Z)!6PST+;9;|V#Sks}m>EpA;ZrANH*}h+rzxd4^%7o*?7pIL zg$GpHg_%&6H z($+!*-;|-vBqjX?oBqwku-{rNybN5XwRmwEniH3_cfC2)t6!j9myC%*es78eC=}I9 zE3RG#8*&*7ZxS}{0XD5*yOHdkX@jX;rs2tAbtFrD>w$xI*yZn~fQCI*W}uHBgo8R5 zGw*^!omTx!n&*GbRKC;s*LAPRu>P7^BJ?vHl$t|`7>7%jd2C+>C9hZ>0lVx3bmFiu zK3#~w($(l$!5%Q4QEQXr29+H^7go&g2m_)v`g&?H_54jmLq!e_XYzeK=nj(ebl-*l zxdu)>b<{jw%w?cy##A4U7{;e8iHVWr!svtM13QkHb#fdi0u)lMZ_(0y~ zH`sI9yz!G)a;B4X!F|b0xVO<0MKMGii6`jun@DHK^9OxMAdn}hSG?gFc6%@|kCKuZ zsg`0hoNgCo#XI%Id(E4Jhl_^mn10i#XEU}-uAw+I$D8doa@UVXGHN|YCoJdBF5 zKp#XTUFDGcXB!Cc>d?}q*NIm-qh$WV;B3I(r+DNbsw$S6H!vZ){Y9ih;=Hf<22c&L zwmjy*5}wA3xM0c7{wvoTFb$u_iWFac915n6a%e-M(=grMLoTY+3Bk?Gu;VgCdLNI! zQt0F#A~@obJjzR5`ioH^I9hcuswA3RQ>Kbz>!iZaPWQd+PsCS9NZ})5zgINmgyWpo zWs4glDU?Axa*0wqgP0*X*@~4lsSL3HELi?R`&x|i8C6T2sKOL^26D)9b7RE^>}ixr zOs|*7z)#^f19ro5ZcF_(4)JD22UQ1crLuH&-*hx#wjH*2n!l{ZLH9I_W|gil@q?)J z14+rMahO;SY(ziRQc8xnnXNg%klFb6|Fm>dMCdA@2WL>!>`YgH5O3bnp z4lk^uMO8fPid@r~ZVR?@CLY-E)Dp8|0i$4^wUrFE-nK=I)0yxslP5a58_oNH!QCb) z{ts`1x?x!tc5lMK;}2&OxCO_C9f?JI5>EvQ8AQWWotW8ckPl#bidD$T-d|clIlC(bLf}+1FGdB6ayOAYtSI_ioX*?`UT6PeH5MN{ za&CRnUNF3nq!?`Id>Pk&b0S9n-m?lknZ8(_(B2(zU7zhS;o&9F zvC;UwoD6UlJ2YEoh2@(COe7LAxd-bOU_=ur(hJK}LO%K3Ny-C{hup%Ol|z$QI_7yh zLisB9S542Y~xii79?*&`M zX+Lbn0U5~c+WMsD%VvhK1RofNrNwAKCL$UsG__&cot)@frCw3B9ywSbMP)eR0l&f1 zOEZD<%SAnSCfL8m>R#LOWII%zH0E)> zwH~|ey`0)o0t+8;1Je{_T4u#i11ur{K0uRlD@*9?ye^5wGJAu~e2Q!V-Y~EuZvD71 zKtxGb3k%jZr89rmBFNuEDJzz)O;DXE@}A!JSu$GdUMhFWI}N+7)qxZ?)}Q&ND#?_|B*w6T#9jK-lX>`EdG1s9=w#;1@JxB0d# zoz;vT6r{BNJQ*>3&BJeigdBkdvm`RW#xmEbAYS z`l?6_w?9i#L;RpwRxLkhs;}hJ1*BKcp2i)7kYFVKQ&LwT)4LMSm#KxHPUSU@pgymS zCmtyr(k(D-#_!aL%uA|d_<)Yi5kBtGh%z$zAI93p*uL0}BTUKNQI%U@US!au)0CoA z3i(PD_q&213eh~yOfbjn6KTs3M`d=tyG9J^%dD)aR5GmM@h-Y z7kKldRs%|};({qjiscZ&8s-3t)TzKE($2LIu7sSw9Wq-$wG^5GS*;RhjQh=iLFb|! zfE00=5`{2;b{mwIrql(-1dL;goUp#;yr>_Rrh=LQ|Jc0(-8F^ohgdLeu|UTG9u<2f z6*+(s0KA%(DEjpAJK-0;enVWrooRP{VXZ1zFsjr@Oh%`o8XB+Gx(dsRcDBDaphk%% zO-n0XU!cxJOKpQIuqc=cuHFRkq$BKIEU9*`L_|a|k)irf1;8VV{668jM|P^u`&)h| zyiCbU<3{VQq9E;$l;GqCpQ+armW9M!f!6kNNvy$53&zTZTw=%Vu0#!PTShPAOQo+W zYwBklo+vWC9Ae)DH%hsIK~Qb6Lu;L=bO%f-%FO~C!DpCc zGloGBNypmF?m=%*Rk;A{PTY~M8O9`_G}|oJgnYya?J=+KwP}J#FU%0op~!R)sY`OuJ77z69sS_CJs=je#?g*a~#5m7}3P4>3mqb1T2tQScb0EBIxh0n$C zuLlz6nvOKeOKI8Y{*}EcRP-2OUpGOTZq6aTz{Zkp!7>q?QALF2Mp!E4yP88nfQJ+n z{2evMO*y-wv+jCo+W!L%oZfvGerCpz*cu8nP+JG^1UpwG1swAeA;+64vL?OGqk}^i z>0B>gSKe{I}Q- zYcNe7qs3bc%eYEQshAByr`;cE^hSCTi6TSe8!f=4dUFK`Txd0@fV9M%DgQR$#pXRN<*h7EL?Z0$)rDX$$yb=O9 zXF;P&4;F;;ef*St2`7Zol?5yQ*GH3}{3;E(N^S$4a_lfOG6c_Z!b{T>#-ia=QfkoL zU_qUHqWMw^9iQxyiHJ7S!0)8cGeQSI1E^B@>Rc5gU~~-vf@SV|c7AP5p~tB^Y6&hT z9>}U$pAo(IP-sY}hUDXg>MU)jjT?=&&jp!1WRWF=lusbXvOyPky$4MQhZeTEneY?-)!tV80{{cUs#NW-0$sSki+FFToB*AECv? zOP>(DI=L@5q%%>ZU!6l_Kwv+(*Vsb!?)NiW$fZUWJ1BTy{r7kq>B4nnWrh z%&wd$(;UnVCDLqy^Xlv5S_L;9lzH305uwo6QLH3!LJAjyDn5Y8&fKx)_o_$cbAS|4 zUQ0rMjRRQppHj^4B+=2OeVXw1KikSK@sZO{0iQe=F=^HJ5j`tkXZgh#L#0`cO@0~b z+)O_SB(DH+{!eUeNI+5xRUPRC_Dx1UOd=vv#PpxiP^_j5T zgaOe5eZoE$A@D&<^X=N6f^d5&WtKCuZA$J6%@% z*&fMS^_^lU8m}tGS3w2MrkJmQ9uFF~9Nsyi#=G;)S^WgEm{bGZi2!{Jb@F{C8k7%5 zFa<-)dGo4pgjr#Mul@|=un{cK4YH0U2lp-8=s=AnVAiu zP+kNV{Fy0B{0ZMaB<^ zncyIaXlhz~C$Scp;+4O$B`l2CMGZPi%>u<@kx_{Uof| zt9ysjYs&|E?sd5c>J~Q>HXsME8IWQ!izEjJqfbq%*9KgY+39n=X!w{WMsrqu%uS@I z`t_oA(asoFRzWFTb)VrM5t;VpW(hBukT-nUn?|vkvRSSqw1FIWnX(zNjup(x8BELw z#7cJ;W}j0S=T6`gB77l&ABir_?7}HktT;bn!=TJN_hG2^zyZQduJ%=tA+03wkOs=p z{$d~()r=hBpULJ2pSxT!7EcBjDt_f1hDnYM7kV!M9|_d-Z-z#_P|Cs14|S{8TM3Fj z&vUYT(1%S^==DBl6<{;y3K=>wu=+fVmez9FMLyx6EE1DG4L-{YtBLKaD zY2`*?4>%@C&HYBAmzV?o*7!~gO(T8$&f7)mSxy(RWO7t&0R2kJb7IYX8Gcu*H+bbg?eAxJxSdwYBZ`>?h4(rN#uDN%z8Lz>H>=@Lx$zZDhrLxf38&kB#T z{dzL}d`srDO?`9u4~spX44kHCKp$iGl@_e#OGvVpSKL3;GF$OSY;;*4(YG#}VhUtw zbMZ&8O&f)pq!vmO;k2lXHD&jTsWhwb%(M9&p{6N)lh^|x1(0##wgLm_IxvlI_Vh;) z5IVn>OEcUa$>AmV6&Xi+6S`XnTtB7@z7=xaAOsOD)U9qi%92-*x9st6yL{mSeXbD* z$Md3}L2?fmDIKZ_`2NZ!j*{v=Qf5>AEWv@v((TK#Zk5NpoyUpQtW)NGo6kiEjZ6lOME#KOF0wo{7y61HkqGqBO(PHbM-ry!`}oxV)46d z3I<&DPnv-N0ks7cVeIq<^RHLqQG{RvzCREL%nWhMKL!qawDB)K$wU|hwhF;N=fXJx zm7%ByNo%oQwKu7lNmGOmeDHcqOO~YK6X((Zn0~Wu8Cc*ip7$*#1ys7OW)5^f)jH6h zh)U&iWXz1hi_@B%$&Hx256GAi@W8RL_I!XMdYYNHv02ibF%LED<=)P|g60WySw1O&e@ zggO$i`N1FRutR%OAe~BBCm{fnQpVT?_V}q}MK`mR_-4az(3FEFA8I9 zryo+*@;waedbZyo;rM(BOf{U%VAkrg_?;C9I}z|^kq zz6Jw@sscsWU1yMt%>0@6uYW3G-YM3AL6YB$ZNP~dgeX>`{2<6u1d%_GN{ixm4*o0m z^(+d5w$;&@!n|#ifepLJ#u5p<(i8%shJ`=S;r`2bT+0}YnFcNnGd&U*u>wv?!@agx z_?JRpT#!)P!o)*L@IsmyN#@VZ#`UWRFq;GOYn3DN&BL&x^AT^`cr{W)O6#;hs7in- z4wy=?)<7m{iJH<>Rg0l@JB&ku8MjB%R8;B*P!RZSxDaTrLX+ihf__Lc&UDDu+W$1| zyy(-0>6xWB1Zh__OaHWN@aFA6ScA;cs(h|JqQ~Qox9kAbA1||4tBc+&mL3Q`rH)JW zL@J`)Bp@ti?;{9AiJ1#{V|kT?s+G!!+NXw(;i00QMD-qx>F4yaC>|QWu3A=vfp3w< z5uO|v^XKO+LeAs4W8}BnLPT07ZEONG@$0vpLHBdxgSqe+jgbR`Rq%Tig;&3~D(fCI$GvR# zIKW3<3vKxT-e!wYAc=e`ri#e5@5O)%xq%^~@|*@InZBGl%*<3VRw7C_bk5M5 ztchpGa82i{8%J}ZK2pTL*?uXj0*j@z!3?lcQV9^joRL#NKzB6($c*9lYUE(qi+0)s z#tGQt7dyGdBntsHe;@sowE(;aO#{%EH?qqotHy%#%%okH_`_vJYj(^cMf@m8H55u1 z7_o)Oedh*Uouje8ubie#dmkkwgdcbAm@D*x>~Qw-$i`b`bR;}X^6&9s=cORCRmA4L z0L}TR$NLOCo1Eu=sR^54p15x{T#}`4spc(4lr94J7I5#80Id}ELz3CrA4b)BEV1~c zVComO79mVU{RS9py{Hg$gR0oa8+xF}%Kv+)+9$OEQo4XkfkV^AzsHJ13wRKI>~#n_ zf*_^Nu5&QrO~$GiQ+);dJ`VSUDc zvh1tJC=ZT1XGLG@lxM%46dAMR5r#Gt_cHUr0EHK{WF{tByp4ifU~19SIMVP1jhjTz zp7A0+Bii|AsI0JB(Fa6r!z5-z6OS zX1G;cM2c2mR`y|;`vQZ;V^>dK$VpNN7RZauHl2?P$WH54X7iUgV2=Dtr+W@H1An?2Na-}0j*j3Ayi`^SY%?5m8yOd z>uWh0E~E#UZ$_|23Cv#P`dxW8mV3V#GAGH$Bc`kV*LDu#!i)k*gUsF)HJ2_ECoR>M zq9NAD)=u~rQ-^7;E2pCYsSSUdR_HCL82qjr9A8i?c`Sb$ki)IZ(!a^00^*h;QJnVj zfp!51kFfnTD?5!^k&#eQqP}ezWz4SU+#wMK-!cSWN*Vx$-gVpTO3(NHJ?;Cud0-(g zs{ngGg>Ne%xq-yIZ!P66kF`w`g|wMDp_u>~ZyIZE{L(ReiVI4N1>@;)&+Uge8ts|> ziZxK`_~`{TcN8CC`0WUxz#ZZYPpI=T*6Va_*PApzxgnW-oh4Xl2j36%#k0s!igdS z+FZ9o5s5bo5%_F8+hn=mC}1`Bu*uIX1tQ9dFI=3U+T2 zsNYoa*{y%C?^y_6fZ@rQ_grWoJR(5ykZZ~O%d#)HO>a{pfu$EOuN%v|&6?l$l(75Z z@~z}Z`{+C2`>n;`7XumzHA5gB4a*xwM9z5+!|B^il_3+*VSm55i^r!1%yQ`q4sj3w zUMKF{QUXIfkpjl#weTJ8V}zh(OBG}_^ygujJY}^=W=0GEN4jK;92Xv+ZIZ=ilLD&D1Mw8X=%h@x z&~eKMKQW<=v8v~%tD^N&vWm+hGnwt@=(Bm>VXt4&9 zho*Y(tIxSoqaQwIfEX&}(vdc&bSKPg`6|!Z+AxwZ<`fHKP#HOD9bCYh%xs4}Y5fzr zzlVzkTmjG#nJT&&^J-m?Jn$M;drYFFqU5ED=(X?OWEkMZX*Q?mlrq2Kuez_o@K4_p zEmQuTQ6(?Nu>G3-j48FRP%>*aeB_%GokTjT8CE6#RWs8V-wAgps));@qo4S-CNrWR zX7mg7THxaXLhc0;v(G&b(7UEeKe7^x{J<{M-F2GRWs$MXj&~q9B$bG%zcqVrAC~3y zyXM&m+ve>HYtuTE?@OonS7fcR-=EKyi?0-T!6--P`TfVFWLEe4&MLDYni!}R6$CN1 ze2Z!ITo*p#1@#7|=Y1&okHae4i(@~7N|iybuZcN~2td%5;S;&juHdl*oH%A%5=5N0rZ`)3vH%te?wZV(Wllk40Q)7!E@Y!SCs%Sd6(tzJy+@2PN zl%Bb}PmC4t8i4D7_zY9}gDy=Km~={BxPq}8z#ancp~PB#yf!g6M6ze^^;zW^TGOP9ITx9ASepjG)(0AZWrC`$-MOc??sOr1t7{O>nHf`#SL$*zkI$A zO+QD1Nv}zqV*>$J5)A8JB3oOl!RLb?{*lSpD|ySR@z3p(s*NdGAXfv$?;AQLs1GTC zUC7#;nH zL(rjEs-`-M3fCnuIW;t!1!sx|ZXPy6Q5$qMPM5n@nrkXCgo^IAto0x>#<=Nj;_hz4 zv&Fzb#fP%;OQ}0GCM+prnaN5C3M5D%(`rRL4nf(2OTQlkFBjbF)B!H85QXBe3jzWJ49W`XLF8#hrtR0GNR#gb~W0{ldX%*>jQsE1w#r9iJ04`OdQa z*X^r>pW#4F0<$nZwG<$$MfJwo42MGlLh;qeouetzdtbS z%6|#!_trof371Dfo|u!I<3Za~c~@Cq*hB1p5Arg(n=XdJ;5|#M5#AG1p~tC~l?Fw6 zfN3h{6Du>x7JGKR-ktj1om!LA2DFsOL;>f&fabzerzDM=MUNSb=dS2+e5Mx!ry1kc z2=i|`dH!Tgx(qzN_f#{c^trxe9EzC93*oB)tUmWEFU@F8MFC@%Au5Gdk%2|{Q|;Wb zVF}nW^EET`r!w-@hmEumBKkk^hhfQ~VSUCIohtSNK_s>P8{bKkcw^&9+4;W>kIy$y&4lFkf;ogf!(DXp?dnp=!s{4du_|LGb7(mBm{ zRC3|~jQMOAf#U`WN3E2GfT6lHT@Lb_q!SC-qy~(^Sx5zIAW-VQ`X;-8hR|INep+Ir z6cnU@#MRwD9ox(^b4J@H{a=`52MhBtj_6&YM39nKgWXe_K_~^aGTcG%l*`Z@J_3G6Q$G-dg$-+ z@m+P=?#d{P;s7x9=meg@k0_82=T$kwbl+&?=5v@fI+UKGY?ZJlMD||H*-8of!b)!E zditj!w||WK_|8JAH#Bzv;;|`CneEG|L?TSWw{V18WzQjrnYxIOx)>^Cz|=A+Kt`xe z>nA$^GIMDQ{Lo&hM+MO^OMHCvN5n*q+liC}w$gHLx>!c^*6FIkM%mfL)P~-TlAC$Rgj4C@ldv(f-$pW!t;?pm#r4h1rho|G-oGzQ-gdUUZ6IEvsJ_%orJ>UW z0b&F~tMf0NTrQy4U3oiP#?bldz`2U=wcYa5A9!e&)7f-R1j7A*yn7?uobKmH^M?u= zYjE|E*H?-*%Kmahrfw_Ff4>!Chy=zU`W6r{tH#UBc2fyNbU7wTMF@wQ+nb>l*083;&bDZC3jzpC%r{_}7-hY#) zOvEPyQ6YV-zbH?4W8NWSFKGVCdxQ(iI|^HtGDDXIZ7=u5rJg`u*QA|C?vm4A@4(43`d3EBJXg%j zXq|s(a~qcO{9!;~GwGB7@`&puor{W%ds@&zU2+#2WRXz94p9C?mEnOc-`dG=xCbhFmpmke{N?_>YHH< z7;w?TN!-gPxJp(J95wN8G{0SfLp|0({{!u|plx;y!?H%65n>NV9SYxFGg#+K3emcZ zh-6Jj0}8(PTv=+bf1*^tGSw+--SGWfprU1oJURu8VlusAY{0EU0RbeP=R{HGbHArzK#sxQDPFR?&cTiB(qZ(T zyH>@ea>?)9ZjNQQ+j{`ZYvjJv^v&3Nl9ItY8LnAT$kdvhEMJ?~NU#f2cUSX*lgdpx ze=XqgASga+&O?O6;QDGs;RWh7ndIREh*?G$aP}5`1t;aDpNH%)Ol*E?y2KyO4YJMsBulgS2QV%v>idBam;>rRWKN<){7#M>h*DCrS zOgjld-XipI)cD#f2^hLs*B&1&JWsv&Kn|J2-hK|A7jnkyqF1ua;`(uv$%a3AgnM8+ z){__s$RTLH&#mi+VVD%r+?A}^i7?fX;nfg~I_}9Ru&u$!?rBfB&BwfU*}@s((ny zMa*?w5xVZJH=APL4Ds$)?gnf{9kY*E%`$*6*l@Y}$lv_-K4{wd%NxUh1u(M zoI0F?CsnAEdcvdJo7r3nnbtvlimm)V`|TnPc*sY%;OOwR{^0Gst8&ujeH;^+s+bQQk+^Dq1D+k+(a%>;7CKmDNJa$BNH0u;<26SUj>P?vc>*WMsqZ_Lb1|-i$KL+l3ZZ`e2KzBHIrhpwTPMxRcfIZV9 z#f#f|o!uffEXKZYgdGdQ;e=iqfs~ zyYHIxO=_*l^ZR>^r>ub<4zS=(<+ZxCevxfAl$}}>Rg+nH1=0<-&F^0R&KPO3u{pRD zuoNCv2S7k4I{%GFXWitHz&B7(^5El-o@Q&lqm8cUX$MXQ%#9(3VarB(ee2)L%P!dCJ5W9MnC^#Qk|m6~LQ#U@VDNGqW%{*EeS6hi^yyo>>4vKLuC&v+UmFdR z#BHhCUU-3kE~G9UDrfRf8||tmQ_oM-`ATn3wuF@s;%2ecZM0}6zaQz|yi4S^qf+Af z()l>SXvOXM(hlx-3GCnIe_wj2-m}A9;seGH{%;8c{^ZCCuQk5JIQiaq@(jB51ve#W zxPC#C<-=-MX>%oo!Z<%)7pq(J&9Yt>chdVB6uQR|6X7P+GycW=Ia2=PNT|V*@YBzo zb>%(PZ)0e{?%fMg8%|I50^*_6u+yL4ai~o^fYO157M#nV&B0_fQ1yZK+wQ;HejFul03IIfhtKg4rGL!Yy!^X;>*cwz{ z?)a_8z?=vOzF)S~rXWH{YhVbfBleXf)17Nu0V+uq<^DC5_fu5f>+R9XqtFQ>w=LHXF9mWs3`D7lAcFA#<`+kkAIieE@QfZ|qt9)< z;OTpL=#4Ve)-F{3<=|Mi1Gnm2MK(tx`1;9Pg>zA7k$b%xWy3aG8k9){uvK{2B--}$ z>Guj&i>l`+f6ZfF=2D^Th~STUYSrHJlTrDrv>T@1G3UViElIfVUvrPz<*_uDl~gZz zvMonFb5VzVZ9W{y0&0qv`>?||pY_0#?=_F4U9h=7sf%oc@2lQY3A~Twd-|tETmX5pRF6)|gMx(1jcB((J^*wnq~K&2^NPf*ne z0TX8#0<7c5TvQenAkGd-Ts1I=;1^okmgCic8a8pXS<`Jb@eTB+{z_?u?qtwYh5gyQ zT7sR<#V@I_$`!qSnWiNSUZqeVzWkG|Dos5&qf4i{NbS4m&%=00xV%wU zA{CuW0}Dp&-{_M%*-it%7U~O|#4=>g%4R`WS4D_N=GKGvczVRoy{&06Y4T}7V9#Xn zy7~|pDqhlG6L^QXz3Q%tD*9+;KE*(t#CagsSE2evQG44w$}gPm?afK2#xb)x_IPwu z;IPk(awQiSokBG9moc@_VZi$ZhWCVy4j(o$t8uMTn}LfGIBi=O-Tnzdngbp4C`kN=_SL=Eje1^CGRQ{to z)^t*Ss`6A4#qqV%bp~!O@GPXrso+io11=i-%)cxWo5?r{O6hr>Rg=&xWVg>_LFM=v z3Y-p~yCKyV!wA#q-@2Hq-5sB7b|bru3dADcB4pjf#giuHGWBF^8WIH#lit_bULy+M z-q_f6GLNeK@dZC_A3T~8aGgevV4T%F2@|-BLr`ni#`ebw8Tp)*41*~Fgg?I5#9ubK zTUH;Li4)9E6Xan4rNAamVEw;mvfBfZwhO$11?m-xf$++BFtwfu3?!kutq_9PO@aOw zn-%GS)k8grt&Q%louzRky|DXRKe=-}wQtLReiP~969+sz4zrgGd{8@6`Ip-RX1Kv^ zoYezq<1Zec9?K{UI4O~p!3+bv20o-biy3vVXAo|#n^SVb7077Q5a*nX)y|vq@}*>& zO;ejF{A))&9Gni&4+yq201@STw9P;$8H#@7znWL`=B>ci9H#Z2KVZUuw;9}wk#4vW z2L9UlQzgw!{syGQ9UBGm70{s@NWI?-_6QJZ3R2iq zt^`$6!K8QHH##h26$HW?N>?%|Qt?Pr@I-Z6Y~n#K*Ic=2@e(pZpDC)5bL?L*Dog|t zja?#9vgjB2qJo2a-{uz5f{--BwS!kg$S`BoEYVR)$vaUhYkLhWJ0K$BkC*=|7gzPu z@z=-syj3|dc~585d0^b?wT)-dPA4p?9y}j^A(q~au$un;4`?ROCD!x&0?~z^0W+go z7#NUY{2Uv(AoYN;zAv<`pxy@P0l@uZt{Z+Y)GdU3p~QFUxx~Sx2jND8@qAY%{nRzB zQiet#y^lpP8@u{zsO0Y|(ClOr$$SvtAqwf=So?75MgCAQ^FCiaD=|ySP=tcO$sXfm zU*bjfv&S*KEWwToz9Hv9e7=IZ#1GI7;i2S|TnDlG}WSb-veqoTc1RYLd=5`*rW3iJEzK5i#gMQa2( zy5z&=+FkGl5yq_)c^`j9>#DEu18H)VIT<4OY)(=67O1~DlM%>#Y(c6$z0IsKIm1f3 z=5OgGh%SVxFT)EAEBhYY_P!U*kWlKiE*`zllH+%N3KfSdfs?t*Mc-=&;mQS?nW|cu zfs+*JTg6M@T*zR_p@{4FsQl){WvZIT9UcZ`Bk8tV`Hb1UNFPJ5n{4L;*(4vIjuKRD z|284|xDWr$SL%7?Tg+STq@bKO!l;QG^Cu|OZM`t6(n_ZGORDZw(x~kB>&>W8187$c zkT>Xkz#pKhpZrg{b$F}*ZNe87DV5EC;wx*C`_KSdRkE{dgCd>kIS#_+aQGx2>0&Zm zdqn8>O-3_fo~#p$chcPO+xRDlAW<^ArZDq@3R@f-V{h2BZbLbp1SI4eUP+xj;4Kw? zRi}key?(<-{*y3OAfZi0RNltsk5^(O?)vIkU*2IQef1o-A;L#}%D*{3h{Fsq<0@4n znXC7P(k1W?d^wkoz-0v-{)=6jY;#BA!vI{c6AP2`gS?~vZb=#%;%S}P#Xh?m*hS2z zBEuwo`wZDjNOK1uH%AJAO@&IU-UBH&?=5Y{|WM)|pgq8A5FLD1IQqka%lYrG=lrJ|X0M|;qd_jC)gX(QGKoKD0ZV|JV)+K{j#+xX zpON9;qZcYJvPF6Gxcv(D`na+r$ksPVcW#^R8$6hKNMwrdgx@=?z44_4Wga*gW;3d@ zWv19Rua(9{Cer6Y4+ka4pw0yvV&5K!aE>ml|7}>+JNicO450mQzn~)s#XlV=BQ%>c z%EfB;pOcIE7_GwsD(&0_w~mlL{g@=4nTn}5S-18Py)XHvx}UUzZh)_OR7L^>6O??2 zXgYSP=->>Mnptqj1Iw&KP`tMqr;dE`u`PE^R;YogX_eUjTJfBgG*GGA;C8+LoHC(g z?(gh9Tx7;)=WnQ&s|TNLRTJL~CUkdfbTc| zb);lv<6)HYJ#a<8hI=6J;hqXGe?YV8K7EyCYMH0`IaKHAUPCsOj#@x2h?WNgn?m)( zH{UjjK7snZ@0J)TOL+SEbtpXw@iUf(QKTNi>n|Q{Og}^b<=2vK%zptT885rM4jd-S zfgNIb0r?LAH7$?7*|K*Vji?AWM)v|*8-CT|9au4c0irE`r&=eh2#gy zM!YA0Uitv;Krp;gx_Zb1D$Hsn;IshQ(3oETZSU0Qs?E;kz&$qva93mxb1@Flbxf%f zAJN>(TbX9zdXBF|fBeP-BX(*awEY9uSCB0E5F>PY1=`yljx-QRGH}~I>swLFHizP* z_@~0fZ)5Xlb>Ys56?E)w6v>h-E|Q}Z)$Y=EAx&#hU<)i9P0`Nr)9Zv#U$hUQqbKk5 zl`Lg!6|!e0u?<0&{KdA&pC7irKB54m>?m zsspS!tw+_{tu^JMdKV`77}p$^%i!kcsmc!71|CgO0Cblw&s*+}72t%^hBw-uu|$Ov zfbP1yh=j0M_8q*VK5f&!eMckc^gnpLMS56Xz>W*zmQGLW99kZw4@ogg`W8eBh$x~1?1#4!&S@tMO zidVoBy4R)gUJaYQo*7*7_585?k5XjMphalG*QI_gto-2TDDH-`3|7^D+lAln4@vjg z07=Va_rm}QA2KFP!0A##o#}xhumU{n`z90|PXy`Mzc0R>UQ>oXiGw#)Qp#!EH?Lrb zh&0!a)^54Rc7IKg0*S0S(34*D=KLJn3#HYn5edbMFg8Au0n!(mOk4Sg6r}n2ID;Dw z7HdYU1d89mRU$AjAWXU~lWOnKt9Dl=#aPLyBX8OYM} zIqm!TPvFHv6hSc9Tw=b%`AeR0(py?RwA9WY>I; zOHxAwsKs}tBh>6AM8W~^9($+Y-Xc|opJMJ>i~uRufo#EM5&f7+5F3j>!U;@c`+V|q z;P0`U1S6cC6U23sf&ae8zBCM&^nU!mp3Xa*>i_-Y$KK)~dxosa9vO*~O~$cR9D7D6 zME2&`h3rkp&Zz87B7~G#MhQ`9`QE3`@A~

*^oJ`@CP{UeCu<#okVb0l_q%sxkK9`;S+)*4-%*PYpi@eV7QvN~C*R zx6E82(rR=EBWR?-v)<(ulxkk(5xO zoT}D2AAw^hM&NV&{j<;a)G_>$$%V&T&xt2GTj_+FX_>cFoP1Z3?uY5;^?D34ZC=i< z#@lh;W$J^HEDfn2ZhU2esvG{}xAO4_zbq>1{si4xDj?+n!H)4mG3@;k%Akf5$L0oO zFf=Y#i~l?V16G)T3dD}XHp0Ej<8I-O*Kg!mroJaY&5I`XbJ2`BwAyW6SJwYp8(qHB zM)%-hVeapPG{!6h>!#DZ*RHm#A6~d$4!u>*0-JPS3;s| zau)YMbyg(&jbn1Y-XYc~C}^s19{a8Nz_`6o-e?*9k~V)Id`c&DetrY7{tckUG&ji?J694i0; zz~PBg&s}2^dRfN9%S^A(kXnQ6hm3_zrAsRoTG(M&bRMryWPvT{|Q~qOTlzo7Ehl^iVuXcmJID z>j-*HhaDYaS4U4zNPLd4sOdZyAf5H60hRiXs~lzxTon5?z+ zo@$jOv-;^D#d2$sZr9HFXqnU1Jd8q+3&)3`&vLx6Y$@Qon6!I(9K@!?k(J%5&S?>` z8~mAe@5HL9X=H!EGWz+HoWxYW-$~?k%=e(iK##Tl(-UB%A6(K~O^T~zKAt%ZVn9$p z^_xm7!-FT9MheLvImJn*S)6cBhhO;9*pTw6{&BcFOk{!i4m}jguPgoPD0szW!1@d} zA|XCUz{lbe6d_ElYsL3%a!z$)3f27NAA-PJlXVTouvPG)#hr=lV7gPZgNGdeWSQgB z=7sUnmpqojRLr9$ByW{2OkPSBnz#wpZfLS4#}P4AC*a`Ol~BAlEMKE4T`1Jgek>R^ zX=guxPY_N*T+A#utn!q@lj76EmG$|@Ow7jeS6JEK6zr=p!r@7A@ewE%qIF$2ikf&? zb;n=myYjcRTAE~sa|Z2-1#Wpi$q~xTVn!}@dT>0vlok3j>TbDe>D$M9`zudGAJ>?D zDdx0y_B#9b?^Ex(Xul^LcCpD>{HXhnkdBu=^gyEs?g*Vt*&@xJ7TN*{6y6bvE4w}_ z&1BUg{`|hdby{Iy^Vqyf7?Sb8l2G#BId3FKyV=jU3lzH$d#Qg?JF9;4yC<5tWhz(n zOkwJD%q*7|)ud?F=p$5&&cZpd39a03R<*IVM*KbeO7=8B8q$ngr-$(x+PFr*xPgEKboFT1rm@~<(&zWmnJdx3r#qHPKn4wwy|&r- z>iA5sTpZ`=Z`G$0<>~9zJ8|<@kFG16iP<1PEN-#&T^B^y!R-;hi z%b-#G7HMd&0$W!NX%whPR;Dvd!v;0VwHuXx{gjgW4yyihHT%55NDwZ(Mt2AM(zh!;A)2nPdo>y|%dqe{SGd$G~yO zcCej2dwfbmP2@C-A`IJp z__ItY{-*zUoAE8UTbXvYo{h%dI%v}^8Za%C?w>gjSuqm8i?+Dwkew`EsZbw6LqjT~ zXt$kiRlax({lW06V!SRHF#P!P0@q^a{-fJu-Dsw9ox$2iAwWw5&39f~Qt)Hvu*$Rh zV~nuet1HIUEz89V8EJ0@*a*XIMTYQOt5Llf*IEk-IzywtpsUS?ul)Kj&GHkr&&zp) zX24+RE^HsXv#ZjI>7m@Il2S*Q{)ViZ+k@b~nJHgR7>&IV-hXHJCNt|>0Cvyd+9q$DRn3|a5=7hP>BMFPheR0YNd+cO&Lox0Ju78>tieodjt=l$aRpctrDgiku% zeV|m{KzcfNqCoylB1dS<_t86W5!{)jyC`fFW?TMvaq0@*;3^gLWdoHN?;R;#V3S2? zWb8SAZ)lm{F>6Lg)!y_qqZ!Vto-KDZuMM|@LcSBYM8qis~yU$OQy+y37BOtGq}8a zf}1@E@HN$+4-~qr)4f0;8llEB-H&&~W>}_#4ebtn?*YAfc@FFSL1IVd~+cJj-_FeMHl=9f(e-1%swQMS>Un&KH7)W9nKf@7vEBOUl?f=l6LH zQzJ9Y4G{7DaigZY2!vjq`@qz-Hz_+)m%f5|skPhU{!RYv#rE~=)*uqz=j41>WyPm( zB5{ClN18ZN|Fvh7uCBZztw_X{)^zaw+53PHTrPC6e{qU6byHIWJeyfTiEeSjHB3XM z_6%ctxdE~o=0s=8XcGjH=a4uF0t~G$)O7N4`_NZ8(K6(BCy3jx!(12*xomp-%e3z1 z9RvYEuD|D-z~Aeo6UV;+J%ya;tfMo1dhh4&AP^rZd7a_|UOjSZ^-D6YvCCFZpk9la z=qBF${$XMRXY#!CcOp6m|2*N+?+@~G!N)=fE(z6yGk*`M)<2c3KI(jz1Y(kygO}KQ zQa3`>sM(Yi%|Dyfxo9e3^FLGk+Jz|t=wu?KYb(FGkbl(z@HdVE3gPL=AH8bYJvlS; z$LjAM;@kDgdSS&{!K_s2^5V4?L0TJJsdy{0=2bo}^o0bY$0VoJB3L%WYn4Z{_0Q@m zp2|9J3N|*B+Y0j9qc!1MW@`ucOrB7IgEU)b&@cTiw}c!K!72xa;y?>7p$&xXP59E=o=>ERPw{pd}YAv5p7hUa@-~!l8&Sw z;@Gg;%@bWN800+vq1gvX7jF-otY;G^g(oZZo!xq7mL<5HucjUImpmUedD z4%}9KBwF}5Gj`Z5Tz@E1%~8+ESm8P(`VOWgYk(dx8Prw&!6cJit)cg+zq#TnFB99W z?QjoU)4i1PF|BJA0$eKBn#cSGoSH6Yu+wejv~I*#h3|}u(^FE^F=*p{Cpy@tB3?RIq_XuJt9#tns!qNsA(TKm zeH^#u%8G^e@A5ib!UiMeEx)HHn91jEFjD$@W;BPww8G;4K*GDBv>3sRt`{?&A(}OI z1yBvHqggurQ4BpX?$hz*WaP75%7S2PHW-)&JI*eTX#F8|wf168tCML34L(h}hG9(e z>b={XUAI4!Vfj@{_P$fvD<1rwz;vBGi|DwJ5xXSyEak$ zTqMcO==F?5!p>3<37Bbv4#)tXOx4R!+$Ha^p$P2})wYIcBHQLeH8O;trh9?wT^Y-XhXyz@c_SGSgZ~ef%Ww$jqov*zUyY%4h=cl_hE%uIpZ$Pc!R`LBv@PVt+##mSw~Kq+go$tk|?Jy zUFyFsf!YXc?}(-EuO@b=_QTz9>;Crs`0k7F;&P8J7u}pMXF>30?jf!=n20BlDQn)a zaC_bUd?{8aK{ShQgOD(+>dDK=J`a*=llv+!MG0&g-=#dtCCnJmX_Kr0FQ)a(QTw#7 z8isf$A=KbiR$QY>#LAxGi?4jm5#D^SOS93dT7XA2@|JYP3P+rKlyVW}kWFKq(pfOj zMRP%tg#Zk;V-XOHf?rea| z-9g?q)P1p*0Yfda!C85UFDgUymxmI|UFoMYK1#JYeMT1s$K2c+D^xZ%pc{ydzZl`I z8hwq!^rKb-<1EP5@c^ncEhv{M-$7_pzFyQM%i1{FnOa|Ib~yq zj{O^Iu=r9UB+b!sa#D(Ie_$DnDVn%aPVlTjn)6XD)f<;P*9R+sRve+Q^0 z5m*PXmpieX6oj$xvhT{=0&p$PNe660x<{r5{H_|G$716j)JI*Mlnuiz-azUaY*uJD zqtA`G%rS{1-QR1UcDz7omsoOxl73yKmqtV~U7QpAvPD+ICLBpl@8APKG)Mfc-6XC= z*zGgm(!7T0rEz)U4QF;L&c6H79igkwt+T|~6k~`u8uE?n!5e7z?ijo)#}<470`{R{ zWFf@Nm3_3K+ko3!d9KM9xKZ>2#dtfXaUKHmJB|Fa3YTs|HQDK)h^y}14sIsCdNZET z01I%2io6WX7Uq(|f)F!=gq+vwDb9F_+hGDZiLGZuwr!->Dkj|3E>4f)3JQ%K z$}UJdH<~=xigmnv?HwsN+rF|*>I+Qxzxo>D_0~Ap98taav!Hof_GW?Yb=eN~3X$ss zx!KR?GIgYZl4%{n|3onl`tll_y8u%qxhpR!?SUF!bSvwAhO=-090jcFj|1_OPLcXu zg(dE>pgq(*e(Cj>E8vsh3_m>JxaZhl3_K(@egXC57Zd4=r~(=uR(m1G4k^l<6@16Dx>s3zNU>5uxA3{hnF~=wASk zJ^`h+#6~X(lpsM>v6W(bOEjf*?kcJzW?;_ez9SuE8;+o{c^HN!%YW$2MC`(yv6pLr z2_KH@rd0%G)W^K}`Y>6}8+UZ4d9Xm4)UqA;vgN#4miBB6f9GbG$rE;SW)&t%lT7C_ zEx9UDoHsXs4N~371)&HC*X-0FV4$5O%Ct9JN^n3Af$dXrV$!^5t+x|YqcwcRv%BBxQJjodhw?I-7{md1dy^XLdyO0$Hr zz@iTA4u6_MJiExl;)@9$0M8*MvJw#B`f`;Ro#_b~la_2M8Z0bScvpEObYoV;0@R|y zp*1Y<`A|a7>We{@E=>C-f8>{-rdZ6Cm^(^=Bl%)lUWdx1(vZ$m0zyWZv;*4p<_yk2DFIn&EV7vwxD-NGw>dx<&9q9- zF%R9_suo5>X11(2pangAZ78Wp`LriEyvLqnxvi`|&&Ry9_dzF95_)0Ng^YCGar<3c z#_4_wfyq^qJ{dz#C{+7ZEe*EU*(X$YvoG9S*ip8qLY$3HY!Q@2%X$D{91b8pw?cft zG|h@+tjx0iv{R0t$jF~T@m@6UaUAfWw3!n6qblIvt|k&oN=}z+yBF!2J}xDd8aH7j zzOVBr!S`&)b&^+Z^)>@dk_$7ouj^^%>{@KrMpPDVxFVQ1CT;z7IclaN6$^V%na;wV zXBzv7)B2+T;+p)XB3HW=_;*m35tK8KVX1+yG`;y!ikGh;(?%cb+gZZA9kR>?xu2^R zqo0J1CQ8(Pmc#45_=vsHNAuRP&oUxp!uFh87DZ&L8Z{MFzYe0{i7DvIRprI#zz=53 zhkHyC+aowbt$8}>^=(=sQB9bSO|z;%KU=g0MP58lo;a%m@ebt4Mhlun?Lo1WDooje}>{l8(O_O|0wfxtIr zz5hfbVrajVE>MY;;*%ZDo)<1kIS|_ZKh)qgM`Z-(B>$s*%6~=8cw9HbB91bW+~Bs_ zjMq7OPp*tTTMNeSWh}Z!y(%*aHjq&x9e-b&ccjNbv0rpsR0=oDuyW_CR(cP9KcX0Y z??jn2T6V)O$^(uq$@eVkcaV<`86wSW#ovK?2TsmD(!`n5ie&{dBDU8uSu^aQ=}+7E zRUc4ENs`%5)G1(0&1WQ(a0`bRUiKhx5qfdF6=7%}_Fthzj{b3o0sHD=+p6GMTT$~Y zBJ#N9QF9}xAZF$h)>0^k(_13Gv4#?afFVvQ%1W{sJ2)$pTDUeUZQKL#Db0>#btF!( zZT>ik;QXe%L75gWc+d3d5L@96+vwh&=1*d9m`S3UJ3|ZB?nlPaP?LIJ;0gaH^n2FM zN4KAF5rrCJh+KHL;*iM{v8MLXds_yd1d;oK1$em{@+4R+;JyH_2U?%pNz-Rp+PFXe$ZGgnuH0O4zb;HI3kYA92u8eVjRZH!ZBmyX+Tzp=&A zcgLvMt4(+IB4Ydkg7RbMA``A`Fd_KPslHcm%dm zhW178wWZ;mgm-I=@|dI~$JCR9aD$Hq|x5_0K zQaIhz;ffw$5R#X9=`#8Is!Vi-OJDJt8jWZq5&>mGP$Dx>STYGHgKmS3I{u;X9{^z0 zLsnkHAlR|aqJ`=*>`x%r7y$$-kv~7q$R4yk^PlvyyKNNVSVKgz^Y*BPPL3palP_lz z50#jtYFFrG>38iT(S_vu$q&@;tqK}Xab7*=iO`pj!Br_hs^#5$&O)D$h1L~k_q@p* zPIql<0 z*;rP7?cG`8IM1(Bh}OmAD77B1O0UdV*|oeXPJ(9HJME0ZE(cEcgQI%Eu-3YUior9Z z)v@60jXQ}(e*Z2EE9TcTH-Jao!EYLiiS%=B$Y(5GXj6k2p2YPeKfpKT&8; zcLd)q(5_bQk_2VYz<~w2qAAu6R$UX`^9R-?o-9_?z(1xTYXdKh9>!a9Ul*x?0mYv!^ zJuD`g9PJpydLtrQMX!($5#AlwfFT2?FaxPL7aVv*#}$f18hWchrA~9#8UxllfDgOZ z8l3%i$SP82`#}tdj})iE6oE__IKR+JcFz6EN{wco=zhH|f-8e**gB9>ru-c#F%=i> zqyCq#HCSfzzW>@*S2Ff9+NViJxcEutFff3jioGE8;f6a)_u>d;!#EeaP_=`gG5?4# zd1|PNeGlxAf6{fWMVd<~vMPn*9B%fW%UL&vt>ghdt5Tw=#TRH%qDjnC4F`%wf8d;3 zh?;tS1?3I6yTA;m+krqtl3t?z-L4vUnt#)7M@AYSk0P?*c}KF!GvgNP1i=J>)Y6cN zwbu`FqO_i)R;~JVL90s_ils|Xdma5pa!tDyE;rVUKJ?fic~8*nkEIB4qb6eQDOD zMvRP|t+VT*&37m%3st9E+SJ}5E2+Bd$mH=I>*ov%9>%@mpmMwWvzpk&_bil|1v@dU zRB8hWb>=%w?H>sHxHlYPMHO7Q-ZCNU&353DFHuJ%sFl@(Txk!O0jxO9y>{a|@s#Xj zuI+~vqAlUw)BXK2wFYuUCfM5V-zh2oLiGwP@Y-ft#3tmIbShdaOLarzDyKBw^(Y$v z$YJ*4qZ9yaLFiaejUuOx&NI-!$6;Pc5gA3H=pMTkoUwehM`#VK%h>Svr z^93W{ApmQt(WZ)eek=akL4#PWzujbh^+72rWKTW3chpm5Yp)*rH| zl~A=>sp85CFinVHdPkXN+p_|aNJ201q`TV@fRwAm1k}bb>zJsg8D@)17O4Xv04Q?+ zc)~*7)HmY!$f|c5d%QB_EC(Juks3n@Z#}t8!Yf5zs)km^YrCZjr9H;yG?Exp_Kmse zo=2XF-V}VSp(6`XfXG?g$nU1?i-0p^kvXju88MP~<-0!B!303zWf;fabk}N*jb&0#(PTIT=%kamBS_)Z_4pM{JK(Fj$!!f*N|m{X`LOT z{hqAjgXK196;eMtYWSR|pK{XCkQTDip;5gs75lJB)Ki8l?LThAu?7YQcu9k0?(`nk zX3-y|{9E5&r6P&aDM7jjq`}UEsR;+71ro><*{CVrXq9g6yiIl2v$B!2V5gAY>S;CC zr3bhk@= zw_AP6e+e0!wacD!`jdMmqiw#$}aM31ZwW#Q^}oj-Z9=&r@LEFa&FscT~quPS|gdNB2XsL*L@5f-cE?Iez) zWbsH?4sGslGA$uwqkaLo=!>mSuw&)SdDK$p&HzlY?(a){m^7xOeAAI{(@L}D5^?DJ zT)AXc?1<c>HnpGmR(YOot1QTQ0$@Py z_p#GY7AiacVSQ(vrd%2@Xxn@J6SLS}91&TzRZ^?Zec7h<@zk&mI>;7~v72^hq^m{x zUY>j3q#K?``D&J->e&AWEo~oUY9$DFMt0`FHReKs=fz?OiS8uRKsi_P=GeA8FBn?z zMATLDGs1PPdbKhAa*-*`jW=gT$~6Rw)imHJ(abay8EDfEw|lH|Iidh4h|PeuZ#l9P zfSP}u-PMaf*mt6Gw=RitLv#m6nU~C~7$-Ife_K+T`ix}rFFK6S+!`Qgez zU_hYzp-MKKJcXnINB*}gkDE>Fq5=x{k0~y9W4TIlqa!*dsC?pVXn?{FVUi`pMG^RV zR@g?2Ik`l=ZKmYQB6-A`B#QMGmZb}&xrIU*3g9W%hye;BWYne?Q)+5~?Y1k8h#_EJ z{Hl-f@|6Zw4lGA!oQE6VfwyN&7AZjicC7@RC16XzkaET;1%~9o7bT~Llj~hhE2>hw zTVT2GqpbH1`+&|*CjPjmhuU410{sjFZ4^|&8)vN9q@qaGmJtQ5TttRqoIt+gsc&ly zdv&S%iw-P|Ro8Dw=Z6=^;Nir-@_S8&XgT+zwLX^!0Vlx*qBcM8bT^~bhknTv&f$%jH z#8aE*rI)T2P_{i_B6d3dg5}*8@=)nQtud`;4a*v@AhjJ+Lc7yqX1Zvh2+-!t6meOYX`KEIND(UGkjj&No<>8BTaD%dRI_%--xNl3cW5|HmzQe_j)IL}2^G%v*6>q6 zO?wXvr!5YcK8=mV43seoB!C}`o&yrdcq3p5h_A$2IaWdvV#}k!P+keJH2kj3UpdjtfK;m@~#epp*@EMtnA<4;e38(Xxu*-X5uFNEV}QUuSVdrL+YD7Mf{@ z&xP7bSeIS4YqFC!@78o$w?kw2xPwgqSRIrlYxRDo1!EXQbg#|>JbkUP(28+e{=!gZ z4vA&JGFNv#4RJF<;;(YVYzUmCHQ+^A;C?03Q0RZ<)M<^|a~>q(QxV|A_UvbEH26#OxRu@G)vJ-hajpV>HNy%bgX*dHROIXJpx)j^v=F`2ejdYg4jzHcW#OPSy9;t7-fPDQRVpNz@LLE=7Co%*PAQw0UQ*@UvI;@ zh(hsgQ!oAlPOH>8%^8J!HE0C`1;f6=}S_&DpH~RDvD5`p&CL!-of!GPSSTM06CaKlw z7|^6&#l9mAd8!uaA06Ui5LquoB=~TPzllgNqk$y5mpA2K_-^B~7&GZ*dkFP_*Rz#< z)5w4pdT8RFJD>K_7Y|seBig0VWP`)K^6<G=49V`~_63Glq{AmiBu}Wr!L(CD zn*lQ*$&X~zL<*K7J)fUu@-O2yDV@5&vg{HWF2h&n;-{5G;8H+``hwZa;updrSC0f1 zGkZuo*z3&PUV5nQge$WEG-*Mb@p}F^HoQd3 zL?Mdm0ZZ3H^pfu?5><2yqIP#M3}9mFDfw59q&f+C^|_5X4RShgH0;k|-l|_^uSOiODVp&@eFb z;Q(>E^ktQNCZ`poXg2)g^~>v#51wm2dQLdGb$FWkouxbt%r@$eERpto%`drg-J=pYjHl;Plb zY+xdCRTB?dJ=rMd-#f9cr?LYnwr~bZM67*k?_x;EyzJ?c$c(QP-7-niuD;^Ylu(#Z zhLNrXCrQY<1%|Y7&U-irk8d!P8Op@GGQp}%-8_~Mw~ci*HDV-Lgf94?@KrV=JlAnw z_9p)R;yE@MRi^LVVsv8MubnP{Q5KN*rA<0YC}AqFVbRV)TgV$mhjmTNoG&8bZ0-ei zwbPD%kKV4)3N#J$wG`lA$g?GOh=rLQ_$yfFRO)C-hE&2vSrK1O$|hGzA2tLr_2k zq)3t8rAqGvlHA3+zx~~P_H)j;=Q)4eJdmucHRq_mImVbJF}m7!XfCo|1OR|W2SkKo4z)ZItk06=x={2v0Ozhwac@=7NIV;^HJO*tD6H(@JV4{JN& z05=ra8UPd!0VpdQ7dszrYdZ%gcSYW`=2I0E{eR$=M1@xwRE{v zJ-qC=rG()^HgLEYx3sLVsFZ}b6kL#73@!#2fs2WViVML-<>1nCVq)C?dhvqXylm~| z9;n^^7ccOeBCn&54@yo%#NXdv*k4@O!^=TLR905@oQ9Z~5ZFSIC;2ppHsB5_VD#l=kDVTwiW$%Ta=@RkB7IT$Nz%#zaRe(3_#OrY5kkW|5O$? zw|}$n_EGl()%ce|{->?I4FXYiA`k4mJ$$`v?9}~0F|VF$gOXGAva|B>@G|i5aQ#n5 z>Heq4++v_6-26sP?zSHO-Z%eC2Rk(@A3H@}&}$Mxa4{hHp9U#EY$!kJbOarLB#ey@!{Z6{xC{o0Wr|2+G}om;2ua zlvDL^_3#2&f^v!f*ZUf(s=8ht_D-(g1MdfSRJb+NRi#8_rKE(!ghl@mR7*=v!`<7* z%H76JLrswvG>fp4ldT-wT1p0PBP}f?CLv`bWG^EjE@UMuEiNQ!Ee4l`TT8>mY_0yY zznX`Q@455O`~Sx{u=TJ3Y5c$9!EL2vtZgNvge0Zl_Cj!L2`eEPaT_ZkJ6mgOJ9}|^ zI9y8lKdI?>Ie|mb%JsieJ(tQBq$pu4V{a`gEd`DfTWKLXQ3+ciS-7a3kfe>ItgNlA zC|pzmeh&D5FeG=^$s6=;;J=2DzMbd4u3VkC|ACF1mCgBZP~^2a$AO(K@4s$4{ZH)i zf5iD;{rw&7K%)N}eEbJ@Zx4GPe=9FLWe3o9|DX61`Ckd|ZRPjB7v0v*)&_(aTnH`= zw-&M%m#`6%wFj{)W-DngEh{A^1^)e~>icLzH#u!`{h-)r)J z2Fm}C^8YVQM9zche_%`Gzr*gIXa6Pef-d~$78uRXFaMoJz%Tz!M0W0=k-We()4wEe z699B`*|*NM{c)vYzpBE*8?Y#33gD0ckYDc(Wlf$j*|^Z3O&{!=UK8mL-fVJ5F0oo9|Hh=#kvtzE<~-& ztk@j1-9D(sQ*i zwjBc0Bv80{gAozQ)mSi0Tl6*R{Mk*zqZaY{g;Xk9{W}z^5T}{T0B{_ADTi%f;&-ki zrpFEx#U_8_k4|=R=DpZlmVuEg*@X~bzq*8=njEHs%7{^c0T|C;=JV*g7r16-dq0-G zq@)I2{Y$%$i-wruD5(L30+wpkug;X)u-D~&^tGMKUZ;z`i&bs7@~$JP6?jgzoKAA! z5!1W0bS@HE@At4Ag3zzUpPC@^h1TK^Nc`j6g}@Uy07S)4e6lD)-}fOJFr7OC3SJK} zpp6_jL6uuQ-{7C&YBgXYcC8Y+aAAq&^)CYHdF@|&^ric~hcAUTCsL4uB<7|JH>HgE z=-|ZnUkA;FhQ2}D?-xYG<&@#%KQK@O)M@rKr986AY+Burw@fde@Ske9JyyY%FFb`{ ztmgl;u>N7?zTT5gY?Ej66-hP^x*)_s1(LnZ*4Id05=3i;DysI5)htitOhPcS04$Ur z*M<@jRivob|JM3-u=p~BpdNy^Np2h^COymfxFZ6#{p=?j9CyAzWxKc}UJF~g&{iWZY#X0}XHa!6kN zi@4kbLW+Ce-Wgsei&0usL=EjobmHb$6>F*>_$Kp22U=;ODZ= zP7wszsoh%a-nSVd!Q%8lr0Mn$4cagmJ&MOv7Rg2l&&eVK}< z(&+6=!nU8#0RhW|xsBkej$MnhKH{A#kE4u&kkMuR*(V5iZnT=-pZZc?eMJ_4l6u(j z^-yJlH@{E*En}uZc9L<{DcA zftgjET!;KDryP7{$Ep&6J!ouIOSPb%Us21O{yR@Kea`qKpqU*ug7E&)>q98w%3>Px&6>D zC=77n#D{txEvC_t)1K~7JF6)R6F5Kz;7YTU{sspHHH^8A>P?bw_zR zo8rX0Asb-k<+w#|lc(&F9X2t8FCc>gEqM)!Y6pe#cl55=Z~tAZ^r`6s-UDPN8}K4; zcQ9ng`-*~LktDA!tR_w$-3yKOg<<&RAjxDZws-FCA%gIt0?%3jWB_VSWO&7%lRKGU za-I75udp4HA;tT6+OtaVq2*n11n*mlH&^eW3oh-4u|;sM8sn$TUE#p2y@W6-+GRt%*Fj9)O57Vw1%d&pGrjg~9 zpYyvsBuYEiu9h(pzfcCv4_m_)#2SZl0^9U@_`cp=sSK?}Q%xgI-IIyUV4kufi|bo0 zDkzi|qx!<$Pt!l#9LeyHgU0dB0bZB>FX7ke4gq-X_r`x zv`RK#|ITwM)aW6z$U=kUCt@C$tu?V)}FOqrZ^P4ct)yjs~H#Mr$*K_$Q2X+f)znV-BI}v45G- zm5&AEr|U>R>GSq*q_Ni?J)Em>3MqNadi+_F^bu%VGfSB-x08;Hk_UJ2d7a0l4Emqn zBK5Vmo)VBq!#i63zaPIlwDG2SOwdapA=$|7JF6%@Bwy2+V;mFX$;+v%wIbN{z=jxb zs{GdkX9W{>Y}gX9dZlX~m((dgot^7slC!z(q{o=ibGv|0BD6^C^;RB`D`h<%_!GaB zBDH$ycnr2(_0Ya9gi}|`k`GKklY>ZnPu77UzROTJ`j*;N9NFGCCr{}%F9Ym@%P!Fo zRrKnc4$b>&4jL+K-s^VfHBE!60KNd1_4XZcW!VGY`IyVKrcp$(?)04q_OO7$U+W^z z^TbMm)f*4d4^zosiKC+w=+g1OXw6k1o{+@KJg&5$!96Ne>{!b9weR?5)Uj0Tf`GC z;&vl(g<^(BOrO5Hcyjux)eCa}tHou6+Z@3FdAKq&>Ozn<9!=Dlu^mQ#Lf0uMqzfG? z$;=Rrh|5G98R5cv9~ztA?b~1{HVy~x=nzh@IR64LXAE@O5?A~b((oeaZ6e+SeXOtL zR3dyoIJV!B|IJfL*2(Zt+)_VSRUjKtx!>M9J}e#X?CcC`CUAvqeP5(a8VixLc#SCA zJ+>eoJCu+Z;)VxkIyq8LosEo)3JVK~YlySCs=do>l4k44S;P@8{LN*D8`JN{iwX@5 z*OVT+sg@Rg4V*I@2MfcxcGR(S-`=pZ??2Hl?2H*}dz99@WIB}9a#Ug2&)9u^o^1#V zseTCRu)e-N*4arsNRswAlZ(AE_~>L%s+%}J72k!dp)1o|4QGEiQDK}^uc=I@u7}*% z?=86q1$z7Q?S5z-6sB>>EiV^(H1|d2KR12wu#q=Ok-@Y$k!F+kMUhiF5hTCAzkhai zR_GDhs(#(`tJ}%1EWFD2*^!aXvw2898JnX{vDW>XQUDY>Ck?k8NcjCdzH7=_S@;5v z9;2dSBR}bA;zTc5ahJ#hVR?BOB>3knapvmMt?lD|Q{3g~8lCL&sEd0J@xGD9?KlnN zW{6^n1(Y0M??>YI6hvaJYbU10ED{7RcH9rwG#J=eyKlOOH6Tv8-@$6zIXy~AZj{`{ znD=L?qSqc#@k#Zs`Imp-G^d~mDKyaZGqb>@)aLS63Jzj-HX0J%vH|cdQo6=dwY(po zQyl(zO>Crur3!WnP~|r!AB+TJ6t(3b)!e1q?!REHp8}@Y`W1`ja>qd|a~2nA%xt^z zvo9vQ2N=o!x;h-b;#ad#K4YO@*##X{yj5HG^#;AcmFerITH`h0zp-`I)-N@|YR0-1jYNyX&A?Uv0h@^-o7C16lprl z0NSAKf@v|qXQ=k|ZqU6i>yIa&$`={aWR!D9cAx$&%@MFLKo2{8N;TN4`S?oBgn58L zU5_rJ%*yW0_l-ZId?-V&H&ss{7^a(VYMYV+(Z8Y8TW@8;K6#5AIXogUB*w*2xi{55 zTo_TqpT5=;isJ0I!^h=Lua?w6V5g7~L$AluECA}l1XD^0f?lCDfAq~Gppq`dR&Ui_ zoMxc<%W9EGDS;*kNM4(X@;MYcX@;`Z^`7jbzsbRyq`s9}0j(Fcx(q|w#GTe4k=Eln zsai`Le!*d3<|WvGeG^5%i7A@DMg4m-B&luq-mTAi#O02fK>J+&i<@|YFE}AOLglqQ zcu_h6O|W}W7lzrdQX~^!>#o6%Nvn(~@*bI~9hCceQ5O#f`22;atq6?G=CikT-Jusf z!n_O}zW_Sy`GcB#H#Dz)gMCSRM%r&d77)&1R^MP)@VY`wkYaKD-V9D-)1rl#iRn||_oGArXEzA(N+n~P%& ziOF#IaCY>q?(yPy3_|(Zj+@9*$eus|Y1AHm%%WJK)j%T6ObwP@ zJqMB40(0y7ullZ)+PkluX-;jp+-$Dj=A2y|(nv8W7q8sIBw!$zDAfGtG1uO{zVfoN z{h(jmFCPEM-CrD~;_o@FTI)*@jQ!kmFN~%)hGs+Y?wo^Bw%1|){=|!Rs3p+Klzt+M zKuEiuUK%51X@k;#2^i%v`&iiZ0(Y;J$Nj@;>&Ft`)drC{T_O z`rhI@4+LPz>HgKd_LewezDH2eXMr(+&*y~UY$e_P&QR*3@;M8C)+b*qk?ATa+BQA~5QcgB7$lD10NAXlrH50tD);3m%W-UZ(75HIhvA*A?;# zb!o^FKsUV~ayw1R$g51BdB~{pM<1bOEqO;;t~$>4lU4Q*wOm`v^$D9r!)m?j*p?hg zIABRyNTB&M`oMbM`{J!zvwv1fuGajybfZz0QHF2qxw+Z%^4s^-Yah7kfca3e05>SD zIMndD?s-UFn>4kDQ@hS*2kFW}4Z5)f;k>DM7^AtLS_nBgATT?GeRN^b;Tay#vK!}w zp~P|>>s1%t9jIxiepwSNKRDyx;Nsucvj7)U`gshA_JdpbbM^x$8_Mc0am~+R24=2{ z;*OuY3}W&|KNYPz%NzhlVk6&J?uVPHXB(n~bKkZ;2au5&kKi0tH~;IFzNb{6o68O-*`aU|_2l z5&I3s>geO?-B5dMW0$N+n={7G*$fY3ZGE45!}>&$X4SZh4X(SxN2&;L7d*h?9v(RQ;@>OgjMBng*ukydf`VKtctT?f#0;-2!wxOrcH{kbzTr5t#a2rU$l=6~ZGx~Cwm$6rhv zD|g*c_>$Ft!1=~tkYYWIBp3+B2T zx8y6c3aZ2l1isyT7dVxFCA0Fn`J?O{wGvvMhvp{l=cEVrizK9q5xz_F?u(GC2_2#m zXZPWh)Rgrpu%r`0`g2mf==Z*h$2XCYuCqp0XO?`kFEa&S_MM?FK|E#Q-}wHzrYp>@ z*FDDi+Ba0Ct64$bmb>@p=iL`OcM)XdaKC$se5kCm5Vc?y?~0KNcMH%j{7nOl_fMP(iykO=PAY+2&$bu*zB^#JIsRNN*7146N#aM$C zNiUJG0G>#)M{fRq&03~6%PL(Pr`-G+%~E3QmI4WzZAsmw8@^+Da_?ddi380M*EB}m zq6BVt?w$QwU2LoB_PU>_o%cp$&N8tnr26?n6x1d#6QO7k8sgzLyyCb(GsuY)l_pQDj$oB)Rv4Z z!xmW^Rx_h{&Ief^%9TQbz*2G7x_R2wzi~=+71v-N6ABQqP7`PrT;Iz9qt3K7~E{wrn1{ct|gbBEi+$z_NYvr07Du zEdl6vUA*CdMVPRE%eXt**XyLTurNOJo$i#!r^D9g{ILTPWWuI72oI8uJCT z)DS~m{<6#u9p<5(9-xjD(db7dg!v3k1KH5cy~0A~Ek#-UN@u5Yvx?Yvom~EO?ZWPp zvk|4E@$%-w-MLKL*4A4v01u+%#)AXqnSIOSxvB4b*pTD>)zz`vJNYcz;EJ~6gChRe zMe9$sreQnDsbLG=<-Ip<>P-MVPuVQX6=DZXB9y`*)O6swlC)xfzSg3vHg0aGW2p~) z(8(8{Z*l1i zFxzw_Ub(Z4(F0{-4LT^q3SBxH2zq9*DK6?2GI_nB>!c{DRh};MG4rQ}n(AeC7)15JkrlYxzMKUKEIjY6& z?QKgm8tuXLWB_uf`^1QAQgJ#XP6~jr@WC;2mSuzwzmqb0h;gOs@iQmG-(g&z{jJpW zs%s}3LqnRd*qH$yOjO^LB5wSa$omVP#5U+{GHOfiX!Li5zAh!q4%d07t+)|JA-!crM8VsO~gKkIzx zgO}b17J^JiThpL2-j4-WFbj)k@>%HXF9V_3drxK|5u|R_+;c-%{)-^)gTvGgL}KSV z*Vc5VgGTCcT%7D-OT&FgJaln&5m&es*8Y;=Q#?&NmWtG^3xkAlE2teXFQ1_|qEp9yPQws7EHdg?hDPsxbs0?R49x*!0f=h2(D9Pcs z=dM2Ujb1oTHZpJa4d}K?Yo#E}8^J`R9<#xa?y{7i`&*~=9vnFMFVrxqdsdF&Q;)(H zw`-m+9E+E?1YwvPXkLBbCv-o9J(}W^9*lXEqC8IlP;)ED95~cgak*EIQ=W8+AW)}u zTct4bv6%=QEQOExZ!IF!1Wsq=oG=ZYr6WM0l+cHh^;fcR)iC^y1D8_J*?w9ZZW8NH zoSkj83U5`N4mw`<`8=n9frBH7l7j^aM6Y4TTn*Mlrb~5%rMJS?ckzT;TG-Zf{X&Vv ztT^`g*pml|CWEm6aA4*<(PfNk-in=f*g`j%l$#Da)EhVL&R#Yufb<0h)t?Lxitpqy zOt7b`%tEF~pxdl^_}Yn&Z~ij-3kNQDj;Z>0W=UvSG!PjwcdlFpqu6sw0TC(5F6IOb z2E;s2N(ju<$1iSv+l79&2tE5T+`hpd>&`V}F36;P51LakZMUTma-)ZYer#Tao7)m- zx$1VB>V~Qj7xt^33GxZ@@>?po(Y?q}O!VCCT}U!@HJ#KX)iW65v<*KaC7%`9C$|~S za$8Lm38quMHyyk`WVnYGOKWrxP)|JDq6~RM*A<10_n7O|V){ z*mxX2!-vU@J4CcseZ6sg;J$you`u&}KJ{?O_KI~|r(&E0y&9upM2gGB=NQPb%K5V7 z((dkq62iK>DVOOafsmwc6#s{vV@k||jE*`pu5_y7b|%??X?4kqY=29@963QSz>mF{ zbTIV5RE6^`r)qE3Ueh>CF`na?oSj>ZiQzVW0&4|ucYdCsL?-lnOAE9r4y5F< zK1sxS-+(voi<5F&Edy#A<||*O_wp2`HS4}yQ zU#sFi3s7CFvB`I1E<*+&Ex5^sV5|qrUhmhtIrnMGa{2uxHX`#P(J%~96+|NAcVv6vCbIQvwDC}xwt#6BdY(W%C@^s^$b=bnQh5?rj@8=Z72`6_6ksAXk59NW4JxSAwCd8 zVpk1953zrra?Dj~V}ucq{_wEWEBg(&&c^6cgE_=Ns$Ux5JDxSQ^Lq_KQ8pON?6+ze zHo7}aNRd`xTpg^WDPGH8%9-oyEe5!`>+X*z(38qR$@_Ve)3Uq_{c{~J86IXswl5X$ zXMC^Gzu5|fI#RVYVfQ<-)P6^NGlP&k$>`8^AA0T;zWGI>|Di6pLA0yGsJ<=^F>SoC z1|v~#ayQ&lR<_8dnB`PGaG9XBC{HslG_vDjJo>MUSUK_g?_4 za<+arKZp2Spl-eXdC0=`qG5|UJqA;>PHGTUt9ug#Wxahnu55cV98wpqkIoPkb`spAp<^njTNpUF!h(T<3s$NyIyU8w9!ZI=$CT0xlvJUE zoFi4>7;*h&8z!+8+;4%`4S`eFrMx)40>Yy;wyOynD14VX^$-dkL#$aYc@;(tJ;#k~ zR9?&aDW?FHD?1hD+3eZ@I}1|Px#z@5;l}g&9By4rugqj4N#WC)aEL_Cm*;(5M~FCV ziF_tC#>kSt8*K@EcFHEaHj(R5^nqp6ZcO*e$^+FQ_emvK9!o&NszzL2Vp``26738j0tbLBgG z13!@!hrWt_5&!g0dT8P`J;lCgzvo7+fTq>(Ol8p$=k{u2YHDg=W@_n2uQT3yqQ_pj z@k0-fbWWkdlt`4?)5&RzI-w*wFaJ!1(Z6%7gxQcIpM}w@>b<{>jO4%H>5QAtOG*xv zCHfqk{*v@$ugY59J9#JXZ;ro6HRdonvMa@xznXC*G5_%(*gVnb2|*+yc$K6e)uFR%rqE+(~m zraeBo=+s^o3LRYxoPN?QD~j=0;$mlK?^nbtyPpQ6hy@4j=A~~384>h-KKPSM0aIXRh4w6!Y66Wft0s9Fd_K3u4P(|&bq^M z0-;9LgI4)5E>~Fnd~YN!ZebxfuH%Uz@g$@Li^ZA>4@wBtrs_So$t9MBopI&H=3Q%zU@>URovPtb8M z+u4(?PLnivzLN?9ZZV%^dHmth%PB-WEWwU;(X%OT$FyJ|KZ@lS26r!}(C&c;1Evkw zpC(9XF~@a`R@*ylS4ljhh>g3Pehto5bbQiC+|lwW*<%#rp33)IfdJRc!^$m+qI4w=N1 z5QCcC&pwp**a;a1u5MhL($0C5Epoe$)hI9jg#=PqD*L14$^w%Tr*-A&R8KI(SF_5NKbNtO7;YqQ32c$2i$@x9mb#5eeSno6+ zC%qp=E7Lv7o-4qNvjBBnUZ8Sz6bOZ zO@Y&TX;|{LPJHJk_ovGxAi4;zBlc?CU^lZ762)EZAqf>$`dp0Y2xM~n8)TW|Cd=g1*f+NfAlcPt z;t>im=P%1Wf|}nWFR#9@ftW6aS-L3z+HV&&8H6!T-(Pc(X055Hd*{ToBENGrqf7sx zkVM5)4XRNG@)=KvVUp9J&0MRySo#Wv01VK$+xZ?!UKE?IT9Y_u&)+i!BV*~mO zSfTgR-&>@c3tK<-bah}KwM&~S#F%`3D@6{xJ)_5_znyFe&1iYi_2=SiCWbUgqO!yD zae(?x=u{A9t@c&!CEebwYlZ`To5(wB=J$o&oV6)$#^!>KY8#N-gj6f8{+NJZW~R`7 zK0Xo|JwHWMimxO8Hd{B5>$>#nlyYW|*Ppevc$hT}Mt?ZcdIkw=Q)l{dV#>`7yVjh& z@*6t7iK;_CzHRRM;a2Ed+oMDK!PTJ$xU28y%0H35QFC}c1hD=*_F06acPlc;K}v$7 z3=YUw^wVeFW@>&Qe*M^ViPt}!Y^?%DX9{bLL8EjesWV}0uB04hqC}Kb24HfU2?1)o(@A6(bpN^}5re`LZmkVH~L_O*3(H6LeMXwHuZ&P2=sKxP95i^z6nA*-&AN;(b!&T~( zu>T6b3-neY-Jd}k-ck;gjLC2TsNH9$Dna$NwbE&tYB78lJ4E!{{wPIbNv7buP|NeA zk)nWXh**=-UsbxFcx`ZX-@Nj3u@H}K#$qp9AuV47Eg#vRMWFR4l`s7)zUzYVDs#C5 z$;YFJPb`?xLx0}+lsQwL5_0;+chg4cy)ta-S(K;FyFcHTFfO}1m1S%c<|215r|AC% zVDJ~=-A_Y85qagrYq5_ml>F8GjJ;^Aa`gNw0H%Q~MIR=m8sdWk$uMtk&S7*Ga*jpYIbHJ(fv)?SV$A-%f&5M;@dB3`sKz#yW-i zpNe_DcHDb>4TOfGm@O2ZwYTt*qDT(1{%zNc{sAI8Ng10OkVfWpE%2QX&5SkS`vHJS z;+$|9ymk1wPfecOauhmSgT$ZORgmlYe{+cE4N`Hs3?f}<)|G6?sKUl# zAq8rn+>2z>jDFE$)wbW`JB)4QWBY`l?!?xL$0@&iFrb||5fE6J(PKYw;d}nQ$%2Uk zH`abCRdEbhyf?nhC^r946?4c zubxbl4*h%8!qE!j8Rzwjh80nc~wU|CpYmDcnGenE#N8UmgvQjQL=t6Q61=vr3G zYIIuN1!mF}-nG|Ao3n{TdF$T&5z%_CJ^_i9s^?!O-hF&|;ID3zVE8ii7eFEqt)tfT z=(W`P z-EnV%PuYqazG;r%YL)&f1+>W`c!H32bv}*Nw~%Gu!wUyjXWlAle(<=G7fA5**SPR^M1B+fc+Rg5w9UxlzCKVHcSi>m}RU|Rwd-#Yw zz^P7yDH?vn^A{pPwLg5^{{F^mdi!wj96&7;h>XK_hna+UrhV$h_HPWpa=9=Slmh@O zVq-BTy76LpGFj}xaZn&e$E`$prym$MXL|pFF=1~Bi@$k%X4`Bkyh=5@!ju$+onxA1 zo4bNptZHeAsXOU4mA{`wHm}k}Ou8of_V)MR-B8!J!mwFe7VdV&glA9yD~b5EjJ`W~ zg4l+_F8l=6XZDdQhk!L4%qe%Fd`X=lN=K+bAo!-MJlW7UnJNz`u=gfp&+hM zt)lnw3VUxbOA8I4IloeSB+^C3y?Wc}S>j~VpY~i!@*Fx0q-ZFMp)8s$icM;-eq*2p zfk*O1gz<&C>g_BCNHP}R3(WRKKU%t zpWHH!@S0m~GO%v+w*Coe23b&Bl{&A`L`P0OhFywS)l^p*Mvl`?=Ms6pP6FV_)ikPJ z-rT0ZQlA&5;jwjrb@p7nPzgm07!c)?bZq`U9@>CodO*>|f!gcx>;TGZ7c146--nj% zBLaOpKLFzcKAxfq*qn9T^Y_OcWf%b}AL*%agJcB@&ek-g@#>cok6EWTR-S->M~nvF zo&l861_wZw1<$fLONc0-jlY>wgxZ=LIEyDp-B{cOI*YXwZ_>&`q}l-GeSn=7U{tq4 zFToPeKHI4|pq+yNHNS9A^)J*<+NhujC5Y?Xm)+%?9^njqQwKO(LzIYYNnp97;pq(Y zJpSIhyr#SRL?+-DL6SCus2zpIDwt_7V}^9&DJ zIgP}H3$k7n3YZ8Zy$8FwCvfEbo7`v$!1NC~Ecm7cfcfz)>=p3}iy|Ac0H=RuG&kK= zP|II>JkXeKAKpUOXw2CfA*c)Xsdaf#&kj^5EqCV!*dS7cft!bbCb);V9mTye2!QKG z3{Ni}aSCf4SM*)*4t;V>gDI}VL+dO^^z93^nkujIw%oTgfJ`9Ak%Rssh?5MW=orPZ zjt4n-Quf?p!E?UPAosd%PR|oH5}TB!*00y_F54=b#A+qxCnpEv-z>DxwR>V~6&7|8 zK8+>a(Pw?FE8hXuB18wpkGbh(-$yXyoshW-S-s8T2o7Ai8J05|e zyb{r&e&8G)@lKoV4|-Hlx!=bM5I86d;LU?lGJBX9GDUBUU%ElH|Df_yF^tDHm6>*; zUMqJ}WLmxzf{}?OOwYx1k|aE>kL^O;C0aU80sH4jy&XX*?L;V*Te7xJHN~=QjwCwJ z2(-qkgC=F&Gv^DWs^d%SyiSVwVaqs(Br9Qa2EST2zV5BqwRg0l;?bF7@Qv7+7Zui);0AdVSNdeH^+`BJC%WxJI!iE?Y-b`15@C^xUyhg z_Hji1WvJtO0wda1;sZ*Q-e4?pdGiolqsZzbe*2ln+$PCRzAZxb$8e7b7Jy!05X`{* zD9h>+g2&9FJOHURoeM^hjnvYbzhWmk6g+N{I;Br*R6Q>KXi{&&$t@B~ercKm+cML^ z>?sM%+LFEDTF=thLkJ*{1cz1`DZr=?7nS>!Gb#>-D{=Sugw>g$-5A~lXvTYAj%@iR zl6YAG>T|OnIP*J}Q+LULF4zFo``yZ?x1L7dht3Wm9Iw#uHdQu@Y*hUX2I4yAgO!9} z{n;DqhrYGCcm`mHRM12XZ_N-F60MPrC;@tVQ2x$C&JBF~nH1)QJLGOMEg{Xl|K`Gqg?( z3a@g{9 zO|?iXMZRZ2FdCXI{uSvx_LH}=yZhAq#wrjd4}pNY-cU#Qf;eafMbmNAAPoBYQMN~W zi*0ypE_jlq0wKsFoO#n*3_vYfLw*?XO>|f*FB4ai%_PV@DZ0A=9Y;st#q#Mh{=A%L z_u2d>%SS_3cEP$XIMH}!DYS>o>rjjr5d>&!FVrqML_q;l%f(0v1Z8Rzx#eE9QMTBc zn@p{qcXXGZ#Ywm@427t7HaAi^-=FXwAT5)pcU*SlO(=MEEDj{7{dsm;xJZ)lnH;#s zz2BA>0|9@FGUe_ar@pH2`WJnaOB5ti1eTCF${sg=jjH-LjL>Ts<>zO+@P%`n>kNQy&65)OYia54tmaFCGkUq7Jmt9T^-G-0?hi?cYn~P zZ(BVy`%=5FhmvGZ7C95VW0V!Qqq2(~fVVh`oQSJ1qZiOPf2CRn_V0rdElE-#c@~|9 zOO`;=5Tm~TUzx)hXayG#O?e{!tk8$_4Zq0|PR{~v;LL)l-r$O*eW_rYwUg>z2(FCr zwKTDmr9fmfARFtuN802-C0KG~4){1EZfITFJB#O(X-|Mlp2L-!L#-H_dA<2JsD?$!7E% zS2roqP0LhJFDtz!#{CI2XC!j~%AAMEmkjkbPJF6@x7t@E?~ujhvQqrHP*Bh;trfOM zRbHw8R5sf0JzWE%a-J)*MCKNkx}3gpUF}@D^}EzDmeZ1J-y%nghynbuUrTS14yjD< z=k?*F!p3i{g!~Bj>cnSRTlH=g_9UvAyq)~15gswi9s@BxuaY>}y#vm73BN6&xP2r> zfFbnuHhF6a8VPC{TDPSAO^fzNkLOkQyD_u8SA?ahk*=qwH*6`TL10kdN0ZuogYQMS z*atMh0mG$2e;(BUp{_;)aGQS4j~wYMYG8h?F81qUJ!EB%z_Z5vb))S|0NtR{x?j=@ zZZ&IbsWrCh1R8l$v~p2j-@9%i&1ZD zv`f!1wE+dawk;GOJrd`B%2JIJrG^_=e^^ucf!c{O=%MNONX7s80kY0DN;yd~96F%< zv`X6f%1iLKq&(WJOnAVkd)v_(xQ66l*9ce?8Xq`)g6u~{v`+f1_FA$?NC5M-v@iK4 z|Ik}(rr4iCq=o@iGUjgqj_yD^ABUPuoR5_BMCu&`P!WAoIf;T~+w}y94rLO1TFYpD z2t^G(VR%6?D|bae^unVbw>iyYG-#QRq;$1tnU-lH!sqM@bf2Emng6}rVMe1SxO}>h zd+{Z+4M3p0@|7dXsOD&=g&Qf_cY<7@hzOC~w&$K)M(r<#U#Po#n+!qq1I?par(`Ng z_TxPY9>A)33V|ZUXofCTo`k`ePs1HGt!;!2`-iP)wK|L zMjzGm_aC~FGU}C@aXN+nkt_ew(tqJ3SEYe+XI{ASwI{EA+pF7Es?mE{JH^g=&te~N zLU8x(8H%}_4P$7)<2LsapC*Ncwt<^D|CHt-N?DpWKvBff=pJ2+K45^~UAqEY(5JY= zP&O7`v+pDvtpx$&OGeN8!JD^sd=)Jlr`&4-QmVOVX=$0_$X-bv7O*YIXb*zZ>cJX> zWW3DF4#tGFs`p~3xY;5%pgov_U8l~<$rO>@biez>qWdHTt)EOzU46bLqdFYJNuP06 z(x;L5dQE5fVrdX;o~PmLb~al&3$UX}K`%UYr-83gZ;iC0=fXAi0t(7SB=J<)bvG`n zo2$EqXe*ZUG0YQP_9wH2X(c*}9I4W;{Pa@Vyf;mtSCywxV4wG3EVzAWWDr~Kd@7Q9 zUmJfR@4!^B-U+&>v>xxOC5P*678@LJ5m8+m?rLQ6jz z(&@yfq7Xc=dD0itvd=#idcB|L=BGHxQa7&i5 zq=-TxW|GFfo2gZ_K(G2*@8{eSp{Icp*ln`W6H(k6ySdGH0(t{H=K zT-Rw0Q`!7>iw8-IH;=tDRwMAb7wSpJ7QWX0dfvoUdliwcuD9%JG)tC_J4CZE3J#h# z7-)SajY5qJrA2GCt(G+ipJjF2ID(Ahip8jk>}CiX`_nyA$M(^!-ki(2o>+T5snTE} zC&_E4vSHAA%vEWU0E7{A(tb4KZ!dlj=g7c1Eq$0O6f@0=Fg4a{Sq-wEq{fya=H}Hu z>@YF@i4)cHRA)ZcC#t@Mhn(PCHT6)erB=Y0$z?F!M-Em*zzEq#ObvnwqUM!W7>o7ZiPR)sxXS-HtYA7sh zpkxTeJow}&I7>bi8}!6(u&Zm3d@7#u2RWbH+%fdkg&{|RIW1pD>O0s$Jo;VhcW7c= znGnsiXQ|@Cj1-yv++f~9 zmUBi)6I>!LCgbI`@&v*G-4ensl;`gxd2(93hK$cE>0Lv&;=mc!PxH^f^Y3kVO_u5E zVZW5eX%rxU>)zq`6&0U!ZvLOqqb1cTf`ehCQnjak9}-*fc97hJGB;t9H}vH}*0vRi&ue&L?+s4n0AFyU2&`NA`!R^b4AG z#(Y^MRB0O++-o0u{*i6UxdQY;@xKqcE;+Apyg6OwaC3YmlEmp){XZVKUyd{;@LGAB@#Iwk-1ZGYie3Lk!NjHD|QhQ%PlkvYyV)&Z)i% z-?BSRX=cufR-WsW6>28&AC4D^W?{vIfOU)8JW2x=7enTz9t27rllu~zqnM;&4JfJT zyO6Fw5V=Am5;6yg`;t=M4!ysp*M46wYJPm~^Qm$cDm|IL=2vw>N(wtv735p$LTJ5G zFm1)3!A+_c&R@?@P@Q^c)_3-fwKDC(4>gicMY2S*Z+%k6$dxvtC#m0G6I>;aSXrFt z>2Y*NGniAsGfvh2o?o-h2)qnF#LJh${jS`L@-(jKuy=h}<+H~np6smTus&|06q?kmU^WT*}(ij`L{DUNy_fceTV$#_P zIp_0A<6vFEfi#^s)T96<_}`;iemu0kUr_f&GVMe>CC!FM*DZCY%#pt+EIYPsfM39y zYPEzum85Kj&!xyny}xa2ZEe-m)GGR>F28EkD)NeAgfldyyBWE2z0Rc8q6;EEG+*&U zBa00F(m!QZ5+C+X_g8_FsZ%vIH5%3pqt#05_??s$Mh&1gfyt27pL?%Q84 zPAvWS@l3fdpaPs9Z+>>*Jh~B4gMTU8)Iuy^cTe2BM`!rZ!eeyGM=ZvFFX_|4u|C&- z$evASo?^hu5lU*T$HF_GV2wB)Bz_Ll{o6)9_0kBg`Pr$KmcV8@J!~7msp%gzCG6y27@@f*Rmgb^QLb~;p_4#TXcM3DMDsV zC07AUx*;UfPCxF>tGPKy+}`dS#UaXJX0KhM16@t$Y*T8^Q4)Vwc2m1NbW%asEV(m2D9&QHsS;lPq5|hpjhx!}M-DfMyJioGsq_Su zG7;X~&WxJHGKVHt{R8ix%6s^Cw2TFP{Go7$N26!o=};W+f@(e1t3^~l1&I>HS2}cj z@JRH_uJ=)~%($p!A)jiQH(it^m(TW7nspY{ISH3{@biO45eyFoH(=}_Sjs7&nh!r@8VgRoouGneR%* z2Mzu^<>jzfyXY;7z((YKGh?dIYr5*^Vy(W%t)?x@?sxyvB9iU&yWPayaIMqp=GrxN zv6v0*JDcNqoTuc!Cg*=I#4Dn;IkifEaAU#2Tz8_mPTb5I3*nKKqn#!$ncr{xV*r0iiN6FK)rkrA$+j3aKYhzaFaWCTmh{tRBwe=9zSWEcrdp(B3nCRR045bhCvTkH78=i z%b9K_9^QPD=4ma6BzbSAqFcCu9Y%?v8?UevLG5(wa31mR#tL;DHBrxyNbS@*9s^Za z2LY{gUM+urlR|O&%ZH3}Zwfd==7R0Rnr8{;4TcHtQh7#wdD5ZDxzNqF=?j&oN(+rW zoqHtoLK@ox4UE03-O+;w^!o2cNIb#zhJ*xra;G3%KCQyiVE&`mPhc6zIWRa@%VHP! zj&OT!O+8P*H5@SJf~-XUi*Phvx8N+ru<+=`Ki|<2PykQJ{{us0{En@Vsd|#o za8k)0_{F<5OnP~9*p+c6L>?5QZKbhDfQ(-A`D{*t3D<_Q4#k(jPexxJa&LI;TJEqFY*^SWc=%2 z;52hw{)0Q4R=V+O4`_wq${Q25Xn^64d%67rcR~H-e{j%PO5i5IV1zbQx8MBbe*nYR z=eE=VPg&{p{+CH?M7L`TNFnQkKr})1m*BkjDD%$XwjCkh$ZtD4fup?b{01CYF&kqr zz|r1zM1fnlWC!7p!W!vBo2|d8PzSIvq%7RZVPBw5;!5- zjtFq@+m0Y`Y_}a=WnS>-hcwU+(glsd2>JjDK`KK*phE>fSlkH^3;{wPzz*j~rbP$= z>?sgc)KxQP6c32%2Z-0bE_)UeM>!F+3Nl#&PseK-y|^k0^A_}%K?@f zLG}Syz%m~o2@V&A2}X*A=$j@?M-1;q>onXKhJ?s}!hPW<6e6nvlfG%T z!F~BRS_6QI7uYZbfQj5hgS9~4G~xVnHi3VD4&Iw+>A;lTMAHQ3=M57Y1L<|bC4tB% zN)*>bL8dxj9m@qx%mqAwf`if<4iXZyAz7|u8dX;k1J&II^LwKxT#)+l5=A{469J{5 zm3gVN_fY)MIM6nea7p*p+v4{^BL%n-_*}tjx*|#w_sM1>zPO^fiPM zZu)XQ;x6YGi91wzq0%xs`oc24SE)XXXEUWDLczmZ64LI^J=<@G9S$_O8U*t;5bG=e zW$p4zsQpPj#w7_vCp4|>CO3T)1<9P2xDWoPfOnQ0o0LOJ%{nj?Edha#rJ0TC6BEzq F{{#JqH_ZS5 diff --git a/addons/cetmix_tower_git/static/description/index.html b/addons/cetmix_tower_git/static/description/index.html deleted file mode 100644 index 89bb1fb..0000000 --- a/addons/cetmix_tower_git/static/description/index.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - - -Cetmix Tower Git - - - -

-

Cetmix Tower Git

- - -

Beta License: AGPL-3 cetmix/cetmix-tower

-

This module implements Git Management functionality for Cetmix -Tower.

-

Please refer to the official -documentation for detailed information.

-

Table of contents

- -
-

Configuration

-

Please refer to the official -documentation for detailed configuration -instructions.

-
-
-

Usage

-

Please refer to the official -documentation for detailed usage -instructions.

-
-
-

Changelog

-
-

16.0.2.0.1 (2025-12-11)

-
    -
  • Features: Improve search views, implement the search panel for -selected views. (5139)
  • -
-
-
-

16.0.2.0.0 (2025-10-27)

-
    -
  • Features: Major refactoring: implement Git repository entity. (4914)
  • -
-
-
-

16.0.1.0.6 (2025-08-18)

-
    -
  • Features: Link or copy a git project when uploading the linked file -using command (4759)
  • -
-
-
-

16.0.1.0.5 (2025-08-17)

-
    -
  • Features: Search servers by git reference (4838)
  • -
-
-
-

16.0.1.0.4 (2025-07-29)

-
    -
  • Features: Export related commands and flight plans together with -server (4849)
  • -
-
-
-

16.0.1.0.3 (2025-05-23)

-
    -
  • Bugfixes: Duplicated file is created when importing a YAML file with a -git project. (4715)
  • -
-
-
-

16.0.1.0.2 (2025-05-16)

-
    -
  • Features: Record references for git relations. (4670)
  • -
-
-
-

16.0.1.0.1 (2025-05-09)

-
    -
  • Bugfixes: Non-critical issues and performance improvements. (4663)
  • -
-
-
-

16.0.1.0.0

-

Release for Odoo 16.0

-
-
-
-

Bug Tracker

-

Bugs are tracked on GitHub Issues. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

-

Do not contact contributors directly about support or help with technical issues.

-
-
-

Credits

-
-

Authors

-
    -
  • Cetmix
  • -
-
-
-

Maintainers

-

This module is part of the cetmix/cetmix-tower project on GitHub.

-

You are welcome to contribute.

-
-
-
- - diff --git a/addons/cetmix_tower_git/tests/__init__.py b/addons/cetmix_tower_git/tests/__init__.py deleted file mode 100644 index 97c9645..0000000 --- a/addons/cetmix_tower_git/tests/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from . import test_remote -from . import test_source -from . import test_project -from . import test_file_rel -from . import test_file_template_rel -from . import test_server -from . import test_repo diff --git a/addons/cetmix_tower_git/tests/common.py b/addons/cetmix_tower_git/tests/common.py deleted file mode 100644 index b3c9643..0000000 --- a/addons/cetmix_tower_git/tests/common.py +++ /dev/null @@ -1,136 +0,0 @@ -from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon - - -class CommonTest(TestTowerCommon): - """Common test class for all tests.""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - - # Models - cls.GitProject = cls.env["cx.tower.git.project"] - cls.GitProjectRel = cls.env["cx.tower.git.project.rel"] - cls.GitProjectFileTemplateRel = cls.env[ - "cx.tower.git.project.file.template.rel" - ] - cls.GitSource = cls.env["cx.tower.git.source"] - cls.GitRemote = cls.env["cx.tower.git.remote"] - - # Data - # Project - cls.git_project_1 = cls.GitProject.create({"name": "Git Project 1"}) - - # Sources - cls.git_source_1 = cls.GitSource.create( - {"name": "Git Source 1", "git_project_id": cls.git_project_1.id} - ) - cls.git_source_2 = cls.GitSource.create( - {"name": "Git Source 2", "git_project_id": cls.git_project_1.id} - ) - # Repositories - cls.Repo = cls.env["cx.tower.git.repo"] - cls.RepoOwner = cls.env["cx.tower.git.repo.owner"] - - cls.repo_cetmix_tower = cls.Repo.create( - { - "name": "Cetmix Tower", - "url": "https://github.com/cetmix-test/cetmix-tower-test.git", - } - ) - cls.repo_oca_web = cls.Repo.create( - { - "name": "OCA Web", - "url": "https://github.com/oca-test/web-test.git", - } - ) - cls.repo_odoo_enterprise = cls.Repo.create( - { - "name": "Odoo Enterprise", - "url": "https://github.com/odoo-test/enterprise-test.git", - "is_private": True, - } - ) - cls.repo_gitlab_private = cls.Repo.create( - { - "name": "GitLab Private", - "url": "git@my.gitlab.com:cetmix-test/cetmix-tower-test.git", - "is_private": True, - } - ) - cls.repo_bitbucket_private = cls.Repo.create( - { - "name": "Bitbucket Private", - "url": "https://bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git", - "is_private": True, - } - ) - - # Same urls, different protocols (intentionally aliased) - cls.repo_other_ssh = cls.Repo.create( - {"url": "git@memegit.com:cetmix-test/cetmix-tower-test.git"} - ) - cls.repo_other_https = cls.repo_other_ssh - - # Remotes - cls.remote_github_https = cls.GitRemote.create( - { - "repo_id": cls.repo_cetmix_tower.id, - "source_id": cls.git_source_1.id, - "head_type": "pr", - "head": "https://github.com/cetmix-test/cetmix-tower-test/pull/123", - "sequence": 1, - } - ) - cls.remote_gitlab_https = cls.GitRemote.create( - { - "repo_id": cls.repo_gitlab_private.id, - "source_id": cls.git_source_1.id, - "head_type": "branch", - "head": "main", - "sequence": 2, - } - ) - cls.remote_gitlab_ssh = cls.GitRemote.create( - { - "repo_id": cls.repo_gitlab_private.id, - "source_id": cls.git_source_1.id, - "head_type": "commit", - "url_protocol": "ssh", - "head": "10000000", - "sequence": 3, - } - ) - cls.remote_bitbucket_https = cls.GitRemote.create( - { - "repo_id": cls.repo_bitbucket_private.id, - "source_id": cls.git_source_2.id, - "head_type": "branch", - "head": "dev", - "sequence": 4, - } - ) - cls.remote_other_ssh = cls.GitRemote.create( - { - "repo_id": cls.repo_other_ssh.id, - "source_id": cls.git_source_2.id, - "head_type": "branch", - "url_protocol": "ssh", - "head": "old", - "sequence": 5, - } - ) - - # File - cls.server_1_file_1 = cls.File.create( - { - "name": "File 1", - "server_id": cls.server_test_1.id, - "source": "tower", - } - ) - cls.file_template_1 = cls.FileTemplate.create( - { - "name": "File Template 1", - } - ) diff --git a/addons/cetmix_tower_git/tests/test_file_rel.py b/addons/cetmix_tower_git/tests/test_file_rel.py deleted file mode 100644 index a0e5215..0000000 --- a/addons/cetmix_tower_git/tests/test_file_rel.py +++ /dev/null @@ -1,390 +0,0 @@ -from odoo.exceptions import AccessError - -from .common import CommonTest - - -class TestFileRel(CommonTest): - """Test class for git file relation.""" - - def setUp(self): - super().setUp() - self.file_1_rel = self.GitProjectRel.create( - { - "server_id": self.server_test_1.id, - "file_id": self.server_1_file_1.id, - "git_project_id": self.git_project_1.id, - "project_format": "git_aggregator", - } - ) - - def test_file_rel_create(self): - """Test if file relation is created correctly""" - - # -- 1 -- - # Check if file content is updated - - # Get code from project - yaml_code_from_project = ( - self.file_1_rel.git_project_id._generate_code_git_aggregator( - self.file_1_rel - ) - ) - - self.assertEqual( - self.server_1_file_1.code, - yaml_code_from_project, - "File content is not updated correctly", - ) - - # Check specific if remote is present in file - self.assertIn( - self.remote_other_ssh.repo_id.url_ssh, - self.server_1_file_1.code, - "Remote is not present in file", - ) - - # -- 2 -- - # Modify remove and check if file content is updated - self.remote_other_ssh.repo_id = self.Repo.create( - { - "url": "https://github.com/cetmix/cetmix-memes.git", - } - ) - self.remote_other_ssh.url_protocol = "https" - - # Must be different from previous project code - self.assertNotEqual( - self.server_1_file_1.code, - yaml_code_from_project, - "File content is not updated correctly", - ) - # New remote must be present in file - self.assertIn( - "https://github.com/cetmix/cetmix-memes.git", - self.server_1_file_1.code, - "Remote is not present in file", - ) - - # -- 3 -- - # Disable source and check if file content is updated - self.git_source_2.active = False - self.assertNotIn( - "https://github.com/cetmix/cetmix-memes.git", - self.server_1_file_1.code, - "Remote is present in file", - ) - - def test_format_git_aggregator(self): - """Test if format git aggregator works correctly""" - - # -- 1 -- - # Check if YAML code is generated correctly - - yaml_code = """# This file is generated with Cetmix Tower https://cetmix.com/tower -# It's designed to be used with git-aggregator tool developed by Acsone. -# Documentation for git-aggregator: https://github.com/acsone/git-aggregator - -# You need to set the following variables in your environment: -# BITBUCKET_TOKEN, GITLAB_TOKEN, GITLAB_TOKEN_NAME -# and run git-aggregator with '--expand-env' parameter. - -./git_project_1_git_source_1: - remotes: - remote_1: https://github.com/cetmix-test/cetmix-tower-test.git - remote_2: https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git - remote_3: git@my.gitlab.com:cetmix-test/cetmix-tower-test.git - merges: - - remote: remote_1 - ref: refs/pull/123/head - - remote: remote_2 - ref: main - - remote: remote_3 - ref: '10000000' - target: remote_1 -./git_project_1_git_source_1_2: - remotes: - remote_1: https://x-token-auth:$BITBUCKET_TOKEN@bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git - remote_2: git@memegit.com:cetmix-test/cetmix-tower-test.git - merges: - - remote: remote_1 - ref: dev - - remote: remote_2 - ref: old - target: remote_1 -""" # noqa: E501 - - # Get code from project - yaml_code_from_project = ( - self.file_1_rel.git_project_id._generate_code_git_aggregator( - self.file_1_rel - ) - ) - self.assertEqual( - yaml_code_from_project, - yaml_code, - "YAML code is not generated correctly", - ) - - # -- 2 -- - # Unlink remote and check if file content is updated - self.remote_github_https.unlink() - yaml_code_from_project = ( - self.file_1_rel.git_project_id._generate_code_git_aggregator( - self.file_1_rel - ) - ) - yaml_code = """# This file is generated with Cetmix Tower https://cetmix.com/tower -# It's designed to be used with git-aggregator tool developed by Acsone. -# Documentation for git-aggregator: https://github.com/acsone/git-aggregator - -# You need to set the following variables in your environment: -# BITBUCKET_TOKEN, GITLAB_TOKEN, GITLAB_TOKEN_NAME -# and run git-aggregator with '--expand-env' parameter. - -./git_project_1_git_source_1: - remotes: - remote_2: https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git - remote_3: git@my.gitlab.com:cetmix-test/cetmix-tower-test.git - merges: - - remote: remote_2 - ref: main - - remote: remote_3 - ref: '10000000' - target: remote_2 -./git_project_1_git_source_1_2: - remotes: - remote_1: https://x-token-auth:$BITBUCKET_TOKEN@bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git - remote_2: git@memegit.com:cetmix-test/cetmix-tower-test.git - merges: - - remote: remote_1 - ref: dev - - remote: remote_2 - ref: old - target: remote_1 -""" # noqa: E501 - - self.assertEqual( - yaml_code_from_project, - yaml_code, - "YAML code is not generated correctly", - ) - - # -- 3 -- - # Unlink source and check if file content is updated - self.git_source_2.unlink() - yaml_code_from_project = ( - self.file_1_rel.git_project_id._generate_code_git_aggregator( - self.file_1_rel - ) - ) - yaml_code = """# This file is generated with Cetmix Tower https://cetmix.com/tower -# It's designed to be used with git-aggregator tool developed by Acsone. -# Documentation for git-aggregator: https://github.com/acsone/git-aggregator - -# You need to set the following variables in your environment: -# GITLAB_TOKEN, GITLAB_TOKEN_NAME -# and run git-aggregator with '--expand-env' parameter. - -./git_project_1_git_source_1: - remotes: - remote_2: https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git - remote_3: git@my.gitlab.com:cetmix-test/cetmix-tower-test.git - merges: - - remote: remote_2 - ref: main - - remote: remote_3 - ref: '10000000' - target: remote_2 -""" # noqa: E501 - self.assertEqual( - yaml_code_from_project, - yaml_code, - "YAML code is not generated correctly", - ) - - def test_user_access(self): - """Test that regular users have no access to git project relations""" - user_rel = self.GitProjectRel.with_user(self.user) - - # Try create - should fail - with self.assertRaises(AccessError): - user_rel.create( - { - "server_id": self.server_test_1.id, - "file_id": self.server_1_file_1.id, - "git_project_id": self.git_project_1.id, - "project_format": "git_aggregator", - } - ) - - # Try read - should fail - with self.assertRaises(AccessError): - user_rel.browse(self.file_1_rel.id).read(["name"]) - - # Try write - should fail - with self.assertRaises(AccessError): - user_rel.browse(self.file_1_rel.id).write( - {"project_format": "git_aggregator"} - ) - - # Try unlink - should fail - with self.assertRaises(AccessError): - user_rel.browse(self.file_1_rel.id).unlink() - - def test_manager_read_access(self): - """Test manager read access rules""" - manager_rel = self.GitProjectRel.with_user(self.manager) - - # Initially manager should not have access - with self.assertRaises(AccessError): - manager_rel.browse(self.file_1_rel.id).read(["name"]) - - # Add manager as project user - should have read access - self.git_project_1.write({"user_ids": [(4, self.manager.id)]}) - self.assertEqual(manager_rel.browse(self.file_1_rel.id).name, "Git Project 1") - - # Remove from project, add as server user - should have read access - self.git_project_1.write({"user_ids": [(3, self.manager.id)]}) - self.server_test_1.write({"user_ids": [(4, self.manager.id)]}) - self.assertEqual(manager_rel.browse(self.file_1_rel.id).name, "Git Project 1") - - # Remove from server users, add as project manager - should have read access - self.server_test_1.write({"user_ids": [(3, self.manager.id)]}) - self.git_project_1.write({"manager_ids": [(4, self.manager.id)]}) - self.assertEqual(manager_rel.browse(self.file_1_rel.id).name, "Git Project 1") - - # Remove from project, add as server manager - should have read access - self.git_project_1.write({"manager_ids": [(3, self.manager.id)]}) - self.server_test_1.write({"manager_ids": [(4, self.manager.id)]}) - self.assertEqual(manager_rel.browse(self.file_1_rel.id).name, "Git Project 1") - - def test_manager_write_access(self): - """Test manager write/create access rules""" - manager_rel = self.GitProjectRel.with_user(self.manager) - - # Create new file to avoid unique constraint violation - file_2 = self.File.create( - { - "name": "test_file_2", - "server_id": self.server_test_1.id, - "source": "tower", - "file_type": "text", - } - ) - - # Try create without being project and server manager - should fail - with self.assertRaises(AccessError): - manager_rel.create( - { - "server_id": self.server_test_1.id, - "file_id": file_2.id, - "git_project_id": self.git_project_1.id, - "project_format": "git_aggregator", - } - ) - - # Add as project manager only - should still fail - file_3 = self.File.create( - { - "name": "test_file_3", - "server_id": self.server_test_1.id, - "source": "tower", - "file_type": "text", - } - ) - self.git_project_1.write({"manager_ids": [(4, self.manager.id)]}) - with self.assertRaises(AccessError): - manager_rel.create( - { - "server_id": self.server_test_1.id, - "file_id": file_3.id, - "git_project_id": self.git_project_1.id, - "project_format": "git_aggregator", - } - ) - - # Add as server manager - should succeed - file_4 = self.File.create( - { - "name": "test_file_4", - "server_id": self.server_test_1.id, - "source": "tower", - "file_type": "text", - } - ) - self.server_test_1.write({"manager_ids": [(4, self.manager.id)]}) - rel = manager_rel.create( - { - "server_id": self.server_test_1.id, - "file_id": file_4.id, - "git_project_id": self.git_project_1.id, - "project_format": "git_aggregator", - } - ) - self.assertTrue(rel.exists()) - - # Test write access - rel.write({"project_format": "git_aggregator"}) - - # Remove server manager access - should fail to write - self.server_test_1.write({"manager_ids": [(3, self.manager.id)]}) - with self.assertRaises(AccessError): - rel.write({"project_format": "git_aggregator"}) - - # Remove project manager access - should fail to write - self.git_project_1.write({"manager_ids": [(3, self.manager.id)]}) - with self.assertRaises(AccessError): - rel.write({"project_format": "git_aggregator"}) - - def test_manager_unlink_access(self): - """Test manager unlink access rules""" - manager_rel = self.GitProjectRel.with_user(self.manager) - - # Try delete without being project and server manager - should fail - with self.assertRaises(AccessError): - manager_rel.browse(self.file_1_rel.id).unlink() - - # Add as project manager only - should fail - self.git_project_1.write({"manager_ids": [(4, self.manager.id)]}) - with self.assertRaises(AccessError): - manager_rel.browse(self.file_1_rel.id).unlink() - - # Add as server manager - should succeed - self.server_test_1.write({"manager_ids": [(4, self.manager.id)]}) - self.file_1_rel.unlink() - self.assertFalse(self.file_1_rel.exists()) - - def test_root_access(self): - """Test root access rules""" - root_rel = self.GitProjectRel.with_user(self.root) - - # Create new file to avoid unique constraint violation - file_3 = self.File.create( - { - "name": "test_file_3", - "server_id": self.server_test_1.id, - "source": "tower", - "file_type": "text", - } - ) - - # Create - should succeed - rel = root_rel.create( - { - "server_id": self.server_test_1.id, - "file_id": file_3.id, - "git_project_id": self.git_project_1.id, - "project_format": "git_aggregator", - } - ) - self.assertTrue(rel.exists()) - - # Read - should succeed - self.assertEqual(root_rel.browse(rel.id).name, "Git Project 1") - - # Write - should succeed - root_rel.browse(rel.id).write({"project_format": "git_aggregator"}) - - # Delete - should succeed - rel.unlink() - self.assertFalse(rel.exists()) diff --git a/addons/cetmix_tower_git/tests/test_file_template_rel.py b/addons/cetmix_tower_git/tests/test_file_template_rel.py deleted file mode 100644 index 11e8849..0000000 --- a/addons/cetmix_tower_git/tests/test_file_template_rel.py +++ /dev/null @@ -1,308 +0,0 @@ -from odoo.exceptions import AccessError - -from .common import CommonTest - - -class TestFileTemplateRel(CommonTest): - """Test class for git file template relation.""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.file_template_1_rel = cls.GitProjectFileTemplateRel.create( - { - "git_project_id": cls.git_project_1.id, - "file_template_id": cls.file_template_1.id, - "project_format": "git_aggregator", - } - ) - - def test_file_template_rel_create(self): - """Test if file template relation is created correctly""" - - # -- 1 -- - # Check if file content is updated - - # Get code from project - yaml_code_from_project = ( - self.file_template_1_rel.git_project_id._generate_code_git_aggregator( - self.file_template_1_rel - ) - ) - - self.assertEqual( - self.file_template_1.code, - yaml_code_from_project, - "File template content is not updated correctly", - ) - - # Check specific if remote is present in file - self.assertIn( - self.remote_other_ssh.repo_id.url_ssh, - self.file_template_1.code, - "Remote is not present in file template", - ) - - # -- 2 -- - # Modify remove and check if file template content is updated - self.remote_other_ssh.repo_id = self.Repo.create( - { - "url": "https://github.com/cetmix/cetmix-memes.git", - } - ) - self.remote_other_ssh.url_protocol = "https" - - # Must be different from previous project code - self.assertNotEqual( - self.file_template_1.code, - yaml_code_from_project, - "File template content is not updated correctly", - ) - # New remote must be present in file - self.assertIn( - "https://github.com/cetmix/cetmix-memes.git", - self.file_template_1.code, - "Remote is not present in file template", - ) - - # -- 3 -- - # Disable source and check if file content is updated - self.git_source_2.active = False - self.assertNotIn( - "https://github.com/cetmix/cetmix-memes.git", - self.file_template_1.code, - "Remote is present in file template", - ) - - def test_format_git_aggregator(self): - """Test if format git aggregator works correctly""" - - # -- 1 -- - # Check if YAML code is generated correctly - - yaml_code = """# This file is generated with Cetmix Tower https://cetmix.com/tower -# It's designed to be used with git-aggregator tool developed by Acsone. -# Documentation for git-aggregator: https://github.com/acsone/git-aggregator - -# You need to set the following variables in your environment: -# BITBUCKET_TOKEN, GITLAB_TOKEN, GITLAB_TOKEN_NAME -# and run git-aggregator with '--expand-env' parameter. - -./git_project_1_git_source_1: - remotes: - remote_1: https://github.com/cetmix-test/cetmix-tower-test.git - remote_2: https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git - remote_3: git@my.gitlab.com:cetmix-test/cetmix-tower-test.git - merges: - - remote: remote_1 - ref: refs/pull/123/head - - remote: remote_2 - ref: main - - remote: remote_3 - ref: '10000000' - target: remote_1 -./git_project_1_git_source_1_2: - remotes: - remote_1: https://x-token-auth:$BITBUCKET_TOKEN@bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git - remote_2: git@memegit.com:cetmix-test/cetmix-tower-test.git - merges: - - remote: remote_1 - ref: dev - - remote: remote_2 - ref: old - target: remote_1 -""" # noqa: E501 - - # Get code from project - yaml_code_from_project = ( - self.file_template_1_rel.git_project_id._generate_code_git_aggregator( - self.file_template_1_rel - ) - ) - self.assertEqual( - yaml_code_from_project, - yaml_code, - "YAML code is not generated correctly", - ) - - def test_user_access(self): - """Test that regular users have no access to git project relations""" - user_rel = self.GitProjectFileTemplateRel.with_user(self.user) - - # Try create - should fail - with self.assertRaises(AccessError): - user_rel.create( - { - "git_project_id": self.git_project_1.id, - "file_template_id": self.file_template_1.id, - "project_format": "git_aggregator", - } - ) - - # Try read - should fail - with self.assertRaises(AccessError): - user_rel.browse(self.file_template_1_rel.id).read(["name"]) - - # Try write - should fail - with self.assertRaises(AccessError): - user_rel.browse(self.file_template_1_rel.id).write( - {"project_format": "git_aggregator"} - ) - - # Try unlink - should fail - with self.assertRaises(AccessError): - user_rel.browse(self.file_template_1_rel.id).unlink() - - def test_manager_read_access(self): - """Test manager read access rules""" - manager_rel = self.GitProjectFileTemplateRel.with_user(self.manager) - - # Initially manager should not have access - with self.assertRaises(AccessError): - manager_rel.browse(self.file_template_1_rel.id).read(["name"]) - - # Add manager as project user - should have read access - self.git_project_1.write({"user_ids": [(4, self.manager.id)]}) - self.assertEqual( - manager_rel.browse(self.file_template_1_rel.id).name, "Git Project 1" - ) - - # Remove from project, add as file template user - # should have read access - self.git_project_1.write({"user_ids": [(3, self.manager.id)]}) - self.file_template_1.write({"user_ids": [(4, self.manager.id)]}) - self.assertEqual( - manager_rel.browse(self.file_template_1_rel.id).name, "Git Project 1" - ) - - # Remove from file template users, add as project manager - # should have read access - self.file_template_1.write({"user_ids": [(3, self.manager.id)]}) - self.git_project_1.write({"manager_ids": [(4, self.manager.id)]}) - self.assertEqual( - manager_rel.browse(self.file_template_1_rel.id).name, "Git Project 1" - ) - - # Remove from project, add as file template manager - # should have read access - self.git_project_1.write({"manager_ids": [(3, self.manager.id)]}) - self.file_template_1.write({"manager_ids": [(4, self.manager.id)]}) - self.assertEqual( - manager_rel.browse(self.file_template_1_rel.id).name, "Git Project 1" - ) - - def test_manager_write_access(self): - """Test manager write/create access rules""" - manager_rel = self.GitProjectFileTemplateRel.with_user(self.manager) - - # Create new file template to avoid unique constraint violation - file_template_2 = self.FileTemplate.create( - { - "name": "test_file_template_2", - } - ) - - # Try create without being project and file template manager - should fail - with self.assertRaises(AccessError): - manager_rel.create( - { - "git_project_id": self.git_project_1.id, - "file_template_id": file_template_2.id, - "project_format": "git_aggregator", - } - ) - - # Add as project manager only - should still fail - file_template_3 = self.FileTemplate.create( - { - "name": "test_file_template_3", - } - ) - self.git_project_1.write({"manager_ids": [(4, self.manager.id)]}) - with self.assertRaises(AccessError): - manager_rel.create( - { - "git_project_id": self.git_project_1.id, - "file_template_id": file_template_3.id, - "project_format": "git_aggregator", - } - ) - - # Add as file template manager - should succeed - file_template_4 = self.FileTemplate.create( - { - "name": "test_file_template_4", - } - ) - file_template_4.write({"manager_ids": [(4, self.manager.id)]}) - rel = manager_rel.create( - { - "git_project_id": self.git_project_1.id, - "file_template_id": file_template_4.id, - "project_format": "git_aggregator", - } - ) - self.assertTrue(rel.exists()) - - # Test write access - rel.write({"project_format": "git_aggregator"}) - - # Remove file template manager access - should fail to write - file_template_4.write({"manager_ids": [(3, self.manager.id)]}) - with self.assertRaises(AccessError): - rel.write({"project_format": "git_aggregator"}) - - # Remove project manager access - should fail to write - self.git_project_1.write({"manager_ids": [(3, self.manager.id)]}) - file_template_4.write({"manager_ids": [(4, self.manager.id)]}) - with self.assertRaises(AccessError): - rel.write({"project_format": "git_aggregator"}) - - def test_manager_unlink_access(self): - """Test manager unlink access rules""" - manager_rel = self.GitProjectFileTemplateRel.with_user(self.manager) - - # Try delete without being project and server manager - should fail - with self.assertRaises(AccessError): - manager_rel.browse(self.file_template_1_rel.id).unlink() - - # Add as project manager only - should fail - self.git_project_1.write({"manager_ids": [(4, self.manager.id)]}) - with self.assertRaises(AccessError): - manager_rel.browse(self.file_template_1_rel.id).unlink() - - # Add as file template manager - should succeed - self.file_template_1.write({"manager_ids": [(4, self.manager.id)]}) - self.file_template_1_rel.unlink() - self.assertFalse(self.file_template_1_rel.exists()) - - def test_root_access(self): - """Test root access rules""" - root_rel = self.GitProjectFileTemplateRel.with_user(self.root) - - # Create new file to avoid unique constraint violation - file_template_3 = self.FileTemplate.create( - { - "name": "test_file_template_3", - } - ) - - # Create - should succeed - rel = root_rel.create( - { - "git_project_id": self.git_project_1.id, - "file_template_id": file_template_3.id, - "project_format": "git_aggregator", - } - ) - self.assertTrue(rel.exists()) - - # Read - should succeed - self.assertEqual(root_rel.browse(rel.id).name, "Git Project 1") - - # Write - should succeed - root_rel.browse(rel.id).write({"project_format": "git_aggregator"}) - - # Delete - should succeed - rel.unlink() - self.assertFalse(rel.exists()) diff --git a/addons/cetmix_tower_git/tests/test_project.py b/addons/cetmix_tower_git/tests/test_project.py deleted file mode 100644 index 588d520..0000000 --- a/addons/cetmix_tower_git/tests/test_project.py +++ /dev/null @@ -1,315 +0,0 @@ -from odoo.exceptions import AccessError - -from .common import CommonTest - - -class TestProject(CommonTest): - """Test class for git project.""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - # Remove user bob from all groups - cls.remove_from_group( - cls.user_bob, - [ - "cetmix_tower_server.group_user", - "cetmix_tower_server.group_manager", - "cetmix_tower_server.group_root", - ], - ) - - # Create another manager for testing - cls.manager_2 = cls.Users.create( - { - "name": "Second Manager", - "login": "manager2", - "email": "manager2@test.com", - "groups_id": [(4, cls.env.ref("cetmix_tower_server.group_manager").id)], - } - ) - - # Create test project as root - cls.project = cls.GitProject.create( - { - "name": "Test Project", - } - ) - - def test_user_access(self): - """Test that regular users have no access to git projects""" - user_project = self.GitProject.with_user(self.user) - - # Test CRUD operations - with self.assertRaises(AccessError): - user_project.create({"name": "New Project"}) - with self.assertRaises(AccessError): - user_project.browse(self.project.id).read(["name"]) - with self.assertRaises(AccessError): - user_project.browse(self.project.id).write({"name": "Updated Name"}) - with self.assertRaises(AccessError): - user_project.browse(self.project.id).unlink() - - def test_manager_read_access(self): - """Test manager read access rules""" - manager_project = self.GitProject.with_user(self.manager) - - # Manager not in user_ids or manager_ids - should not read - with self.assertRaises(AccessError): - manager_project.browse(self.project.id).read(["name"]) - - # Add manager to user_ids - should read - self.project.write({"user_ids": [(4, self.manager.id)]}) - self.assertEqual(manager_project.browse(self.project.id).name, "Test Project") - - # Remove from user_ids, add to manager_ids - should read - self.project.write( - {"user_ids": [(3, self.manager.id)], "manager_ids": [(4, self.manager.id)]} - ) - self.assertEqual(manager_project.browse(self.project.id).name, "Test Project") - - def test_manager_write_access(self): - """Test manager write/create access rules""" - manager_project = self.GitProject.with_user(self.manager) - - # Create - should succeed as manager is added by default - new_project = manager_project.create({"name": "New Project"}) - self.assertTrue(new_project.exists()) - self.assertIn(self.manager, new_project.manager_ids) - - # Write - not in manager_ids, should fail - with self.assertRaises(AccessError): - manager_project.browse(self.project.id).write({"name": "Updated Name"}) - - # Add to manager_ids - should write - self.project.write({"manager_ids": [(4, self.manager.id)]}) - manager_project.browse(self.project.id).write({"name": "Updated Name"}) - self.assertEqual(self.project.name, "Updated Name") - - def test_manager_unlink_access(self): - """Test manager unlink access rules""" - # Create project as manager_2 - project = self.GitProject.with_user(self.manager_2).create( - {"name": "Project to Delete"} - ) - manager_project = self.GitProject.with_user(self.manager) - - # Try delete as different manager - should fail - with self.assertRaises(AccessError): - manager_project.browse(project.id).unlink() - - # Add to manager_ids but not creator - should fail - project.write({"manager_ids": [(4, self.manager.id)]}) - with self.assertRaises(AccessError): - manager_project.browse(project.id).unlink() - - # Create as manager and try delete - should succeed - own_project = manager_project.create({"name": "Own Project"}) - self.assertTrue(own_project.exists()) - own_project.unlink() - self.assertFalse(own_project.exists()) - - def test_root_access(self): - """Test root access rules""" - root_project = self.GitProject.with_user(self.root) - - # Create - new_project = root_project.create({"name": "Root Project"}) - self.assertTrue(new_project.exists()) - - # Read - self.assertEqual(root_project.browse(self.project.id).name, "Test Project") - - # Write - root_project.browse(self.project.id).write({"name": "Updated by Root"}) - self.assertEqual(self.project.name, "Updated by Root") - - # Delete - new_project.unlink() - self.assertFalse(new_project.exists()) - - def test_compute_user_ids(self): - """Test computation of user_ids and manager_ids for git projects""" - # Add users "Bob" and "user" to the group "cetmix_tower_server.group_manager" - self.add_to_group(self.user_bob, "cetmix_tower_server.group_manager") - self.add_to_group(self.user, "cetmix_tower_server.group_manager") - - # -- 1 -- - # Create project as manager - project_as_manager = self.GitProject.with_user(self.manager).create( - { - "name": "Project As Manager", - } - ) - # Check that manager is added to both user_ids and manager_ids by default - self.assertEqual(len(project_as_manager.user_ids), 1) - self.assertIn(self.manager, project_as_manager.user_ids) - self.assertEqual(len(project_as_manager.manager_ids), 1) - self.assertIn(self.manager, project_as_manager.manager_ids) - - # -- 2 -- - # Create servers with multiple users and managers - server_1 = self.Server.create( - { - "name": "Test Server 1", - "ip_v4_address": "localhost", - "ssh_username": "admin", - "ssh_password": "password", - "os_id": self.os_debian_10.id, - "user_ids": [(6, 0, [self.user_bob.id, self.user.id])], # Two users - "manager_ids": [ - (6, 0, [self.manager.id, self.manager_2.id]) - ], # Two managers - } - ) - - server_2 = self.Server.create( - { - "name": "Test Server 2", - "ip_v4_address": "localhost", - "ssh_username": "admin", - "ssh_password": "password", - "os_id": self.os_debian_10.id, - "user_ids": [ - (6, 0, [self.user_bob.id, self.user.id]) - ], # Same two users - "manager_ids": [ - (6, 0, [self.manager.id, self.manager_2.id]) - ], # Same two managers - } - ) - - # Create project and link servers - project = self.GitProject.create( - { - "name": "Test Project", - } - ) - - # Create files and link them to the project - for server in [server_1, server_2]: - file = self.File.create( - { - "name": f"test_file_{server.name}", - "server_id": server.id, - } - ) - self.GitProjectRel.create( - { - "server_id": server.id, - "file_id": file.id, - "git_project_id": project.id, - "project_format": "git_aggregator", - } - ) - - # Invalidate cache to ensure computed fields are updated - project.invalidate_recordset(["server_ids", "user_ids", "manager_ids"]) - - # -- 3 -- - # Test computed values with linked servers - # Each user/manager should be counted only once even if present in both servers - self.assertEqual(len(project.server_ids), 2) - self.assertEqual(len(project.user_ids), 2) # Two unique users - self.assertIn(self.user_bob, project.user_ids) - self.assertIn(self.user, project.user_ids) - self.assertEqual(len(project.manager_ids), 2) # Two unique managers - self.assertIn(self.manager, project.manager_ids) - self.assertIn(self.manager_2, project.manager_ids) - - # -- 4 -- - # Add server with different users/managers - server_3 = self.Server.create( - { - "name": "Test Server 3", - "ip_v4_address": "localhost", - "ssh_username": "admin", - "ssh_password": "password", - "os_id": self.os_debian_10.id, - "user_ids": [(6, 0, [self.user_bob.id])], # Only one user - "manager_ids": [(6, 0, [self.manager_2.id])], # Only second manager - } - ) - file_3 = self.File.create( - { - "name": "test_file_3", - "server_id": server_3.id, - } - ) - self.GitProjectRel.create( - { - "server_id": server_3.id, - "file_id": file_3.id, - "git_project_id": project.id, - "project_format": "git_aggregator", - } - ) - - # Invalidate cache to ensure computed fields are updated - project.invalidate_recordset(["server_ids", "user_ids", "manager_ids"]) - - # Test that computed values are updated correctly - # Only users/managers present in all servers should remain - self.assertEqual(len(project.server_ids), 3) - self.assertEqual(len(project.user_ids), 1) # Only bob is in all servers - self.assertIn(self.user_bob, project.user_ids) - self.assertEqual( - len(project.manager_ids), 1 - ) # Only manager_2 is in all servers - self.assertIn(self.manager_2, project.manager_ids) - - # -- 5 -- - # Verify that first manager can still access the project - project_as_manager_1 = self.GitProject.with_user(self.manager).browse( - project.id - ) - self.assertTrue(project_as_manager_1.exists()) - self.assertEqual(project_as_manager_1.name, "Test Project") - - def test_manager_server_based_access(self): - """Test manager access through server relationships""" - manager_project = self.GitProject.with_user(self.manager) - - # Create a server where manager is a user - server = self.Server.create( - { - "name": "Test Server", - "ip_v4_address": "localhost", - "ssh_username": "admin", - "ssh_password": "password", - "os_id": self.os_debian_10.id, - "user_ids": [(4, self.manager.id)], - } - ) - - # Create a file and link project to server - file = self.File.create( - { - "name": "test_file", - "server_id": server.id, - } - ) - self.GitProjectRel.create( - { - "server_id": server.id, - "file_id": file.id, - "git_project_id": self.project.id, - "project_format": "git_aggregator", - } - ) - - # Manager should be able to read project through server relationship - self.assertEqual(manager_project.browse(self.project.id).name, "Test Project") - - # Remove manager from server users - server.write({"user_ids": [(3, self.manager.id)]}) - - # Manager should not be able to read project anymore - with self.assertRaises(AccessError): - manager_project.browse(self.project.id).read(["name"]) - - # Add manager to server managers - server.write({"manager_ids": [(4, self.manager.id)]}) - - # Manager should be able to read project again - self.assertEqual(manager_project.browse(self.project.id).name, "Test Project") diff --git a/addons/cetmix_tower_git/tests/test_remote.py b/addons/cetmix_tower_git/tests/test_remote.py deleted file mode 100644 index e66811b..0000000 --- a/addons/cetmix_tower_git/tests/test_remote.py +++ /dev/null @@ -1,462 +0,0 @@ -from odoo.exceptions import AccessError - -from .common import CommonTest - - -class TestRemote(CommonTest): - """Test class for git remote.""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - # Create another manager for testing - cls.manager_2 = cls.Users.create( - { - "name": "Second Manager", - "login": "manager2", - "email": "manager2@test.com", - "groups_id": [(4, cls.env.ref("cetmix_tower_server.group_manager").id)], - } - ) - - # Create test project and source as root - cls.project = cls.GitProject.create( - { - "name": "Test Project", - } - ) - cls.source = cls.GitSource.create( - { - "name": "Test Source", - "git_project_id": cls.project.id, - } - ) - cls.repo_cetmix_tower = cls.Repo.create( - { - "name": "Cetmix Tower", - "url": "https://github.com/cetmix-test/cetmix-tower.git", - } - ) - cls.remote = cls.GitRemote.create( - { - "repo_id": cls.repo_cetmix_tower.id, - "source_id": cls.source.id, - "head_type": "branch", - "head": "main", - } - ) - cls.repo_test = cls.Repo.create( - { - "name": "Test Repository", - "url": "https://github.com/cetmix-test/test.git", - } - ) - - def test_user_access(self): - """Test that regular users have no access to git remotes""" - user_remote = self.GitRemote.with_user(self.user) - - # Test CRUD operations - with self.assertRaises(AccessError): - user_remote.create( - { - "repo_id": self.repo_test.id, - "url_protocol": "https", - "source_id": self.source.id, - "head": "main", - } - ) - with self.assertRaises(AccessError): - user_remote.search([("id", "=", self.remote.id)]) - with self.assertRaises(AccessError): - self.remote.with_user(self.user).write({"head": "dev"}) - with self.assertRaises(AccessError): - self.remote.with_user(self.user).unlink() - - def test_manager_read_access(self): - """Test manager read access rules""" - manager_remote = self.GitRemote.with_user(self.manager) - - # Manager not in project user_ids or manager_ids - should not read - self.assertFalse(manager_remote.search([("id", "=", self.remote.id)])) - - # Add manager to project user_ids - should read - self.project.write({"user_ids": [(4, self.manager.id)]}) - remote = manager_remote.search([("id", "=", self.remote.id)]) - self.assertTrue(remote) - self.assertEqual(remote.head, "main") - - # Remove from user_ids, add to manager_ids - should read - self.project.write( - {"user_ids": [(3, self.manager.id)], "manager_ids": [(4, self.manager.id)]} - ) - remote = manager_remote.search([("id", "=", self.remote.id)]) - self.assertTrue(remote.exists()) - - def test_manager_write_access(self): - """Test manager write/create access rules""" - manager_remote = self.GitRemote.with_user(self.manager) - - # Create project as manager - should be added to manager_ids automatically - project = self.GitProject.with_user(self.manager).create( - { - "name": "Manager Project", - } - ) - source = self.GitSource.create( - { - "name": "Manager Source", - "git_project_id": project.id, - } - ) - - # Create remote in own project - should succeed - new_remote = manager_remote.create( - { - "repo_id": self.repo_test.id, - "url_protocol": "https", - "source_id": source.id, - "head_type": "branch", - "head": "main", - } - ) - self.assertTrue(new_remote.exists()) - - # Write to own remote - should succeed - new_remote.write({"head": "dev"}) - self.assertEqual(new_remote.head, "dev") - - # Write to other's remote - should fail - with self.assertRaises(AccessError): - self.remote.with_user(self.manager).write({"head": "dev"}) - - def test_manager_unlink_access(self): - """Test manager unlink access rules""" - # Create project and remote as manager_2 - project = self.GitProject.with_user(self.manager_2).create( - { - "name": "Manager 2 Project", - } - ) - source = self.GitSource.create( - { - "name": "Manager 2 Source", - "git_project_id": project.id, - } - ) - remote = self.GitRemote.with_user(self.manager_2).create( - { - "repo_id": self.repo_test.id, - "url_protocol": "https", - "source_id": source.id, - "head_type": "branch", - "head": "main", - } - ) - - # Try delete as different manager - should fail even if added to manager_ids - project.write({"manager_ids": [(4, self.manager.id)]}) - with self.assertRaises(AccessError): - remote.with_user(self.manager).unlink() - - # Create remote as manager and try delete - should succeed - own_remote = self.GitRemote.with_user(self.manager).create( - { - "repo_id": self.repo_test.id, - "url_protocol": "https", - "source_id": source.id, - "head_type": "branch", - "head": "main", - } - ) - self.assertTrue(own_remote.exists()) - own_remote.with_user(self.manager).unlink() - self.assertFalse(own_remote.exists()) - - def test_root_access(self): - """Test root access rules""" - root_remote = self.GitRemote.with_user(self.root) - - # Create - new_remote = root_remote.create( - { - "repo_id": self.repo_test.id, - "url_protocol": "https", - "source_id": self.source.id, - "head_type": "branch", - "head": "main", - } - ) - self.assertTrue(new_remote.exists()) - - # Read - remote = root_remote.search([("id", "=", self.remote.id)]) - self.assertTrue(remote) - self.assertEqual(remote.head, "main") - - # Write - self.remote.with_user(self.root).write({"head": "dev"}) - self.assertEqual(self.remote.head, "dev") - - # Delete - new_remote.with_user(self.root).unlink() - self.assertFalse(new_remote.exists()) - - def test_remote_provider_protocol_and_name(self): - """Test if remote provider is detected correctly""" - - # -- 1-- - # GitHub + https - # Check if remote provider is detected correctly - self.assertEqual( - self.remote_github_https.repo_provider, - "github", - "Provider is not detected correctly", - ) - self.assertEqual( - self.remote_github_https.url_protocol, - "https", - "Protocol is not detected correctly", - ) - self.assertEqual( - self.remote_github_https.name, - "remote_1", - "Name is not prepared correctly", - ) - - # -- 2 -- - # GitLab + ssh - # Check if remote provider is detected correctly - self.assertEqual( - self.remote_gitlab_ssh.repo_provider, - "gitlab", - "Provider is not detected correctly", - ) - self.assertEqual( - self.remote_gitlab_ssh.url_protocol, - "ssh", - "Protocol is not detected correctly", - ) - self.assertEqual( - self.remote_gitlab_ssh.name, - "remote_3", - "Name is not prepared correctly", - ) - - # -- 3 -- - # Bitbucket + https - # Check if remote provider is detected correctly - self.assertEqual( - self.remote_bitbucket_https.repo_provider, - "bitbucket", - "Provider is not detected correctly", - ) - self.assertEqual( - self.remote_bitbucket_https.url_protocol, - "https", - "Protocol is not detected correctly", - ) - self.assertEqual( - self.remote_bitbucket_https.name, - "remote_1", - "Name is not prepared correctly", - ) - - # -- 4 -- - # Other + ssh - # Check if remote provider is detected correctly - self.assertEqual( - self.remote_other_ssh.repo_provider, - "gitlab", # this is how giturlparse detects the provider - "Provider is not detected correctly", - ) - self.assertEqual( - self.remote_other_ssh.url_protocol, - "ssh", - "Protocol is not detected correctly", - ) - self.assertEqual( - self.remote_other_ssh.name, - "remote_2", - "Name is not prepared correctly", - ) - - def test_git_aggregator_prepare_url(self): - """Test if url is prepared correctly""" - - # -- 1 -- - # GitHub + https - self.remote_github_https.repo_id.is_private = False - self.assertEqual( - self.remote_github_https._git_aggregator_prepare_url(), - self.remote_github_https.repo_id.url, - "URL is not prepared correctly", - ) - - # -- 2 -- - # GitHub + https -> private - self.remote_github_https.repo_id.is_private = True - self.assertEqual( - self.remote_github_https._git_aggregator_prepare_url(), - "https://$GITHUB_TOKEN:x-oauth-basic@github.com/cetmix-test/cetmix-tower-test.git", - "URL is not prepared correctly", - ) - - # -- 3 -- - # Gitlab + https - self.remote_gitlab_https.repo_id.is_private = False - self.assertEqual( - self.remote_gitlab_https._git_aggregator_prepare_url(), - self.remote_gitlab_https.repo_id.url, - "URL is not prepared correctly", - ) - - # -- 4 -- - # Gitlab + https -> private - self.remote_gitlab_https.repo_id.is_private = True - self.assertEqual( - self.remote_gitlab_https._git_aggregator_prepare_url(), - "https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git", - "URL is not prepared correctly", - ) - - # -- 5 -- - # Bitbucket + https - self.remote_bitbucket_https.repo_id.is_private = False - self.assertEqual( - self.remote_bitbucket_https._git_aggregator_prepare_url(), - self.remote_bitbucket_https.repo_id.url, - "URL is not prepared correctly", - ) - - # -- 6 -- - # Bitbucket + https -> private - self.remote_bitbucket_https.repo_id.is_private = True - self.assertEqual( - self.remote_bitbucket_https._git_aggregator_prepare_url(), - "https://x-token-auth:$BITBUCKET_TOKEN@bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git", - "URL is not prepared correctly", - ) - - # -- 7 -- - # Other + ssh - self.remote_other_ssh.repo_id.is_private = False - self.assertEqual( - self.remote_other_ssh._git_aggregator_prepare_url(), - self.remote_other_ssh.repo_id.url_ssh, - "URL is not prepared correctly", - ) - - def test_git_aggregator_prepare_head(self): - """Test if head is prepared correctly""" - - # -- 1 -- - # GitHub + PR/MR as link - self.assertEqual( - self.remote_github_https._git_aggregator_prepare_head(), - "refs/pull/123/head", - "Head is not prepared correctly", - ) - - # -- 2 -- - # GitHub + PR/MR as number - self.remote_github_https.write({"head": "123", "head_type": "pr"}) - self.assertEqual( - self.remote_github_https._git_aggregator_prepare_head(), - "refs/pull/123/head", - "Head is not prepared correctly", - ) - - # -- 3 -- - # GitHub + branch as name - self.remote_github_https.write({"head": "main", "head_type": "branch"}) - self.assertEqual( - self.remote_github_https._git_aggregator_prepare_head(), - self.remote_github_https.head, - "Head is not prepared correctly", - ) - - # -- 4 -- - # GitHub + branch as link - self.remote_github_https.write( - { - "head": "https://github.com/cetmix-test/cetmix-tower/tree/14.0-demo-branch", - "head_type": "branch", - } - ) - self.assertEqual( - self.remote_github_https._git_aggregator_prepare_head(), - "14.0-demo-branch", - "Head is not prepared correctly", - ) - - # -- 5 -- - # GitHub + commit as number - self.remote_github_https.write({"head": "1234567890", "head_type": "commit"}) - self.assertEqual( - self.remote_github_https._git_aggregator_prepare_head(), - "1234567890", - "Head is not prepared correctly", - ) - - # -- 6 -- - # GitHub + commit as link - self.remote_github_https.head = ( - "https://github.com/cetmix-test/cetmix-tower/commit/1234567890" - ) - self.assertEqual( - self.remote_github_https._git_aggregator_prepare_head(), - "1234567890", - "Head is not prepared correctly", - ) - - def test_manager_server_based_access(self): - """Test manager access to remotes through server relationships""" - manager_remote = self.GitRemote.with_user(self.manager) - - # Create a server where manager is a user - server = self.Server.create( - { - "name": "Test Server", - "ip_v4_address": "localhost", - "ssh_username": "admin", - "ssh_password": "password", - "os_id": self.os_debian_10.id, - "user_ids": [(4, self.manager.id)], - } - ) - - # Link project to server - file = self.File.create( - { - "name": "test_file", - "server_id": server.id, - } - ) - self.GitProjectRel.create( - { - "server_id": server.id, - "file_id": file.id, - "git_project_id": self.project.id, - "project_format": "git_aggregator", - } - ) - - # Manager should be able to read remote through server relationship - remote = manager_remote.search([("id", "=", self.remote.id)]) - self.assertTrue(remote) - self.assertEqual(remote.head, "main") - - # Remove manager from server users - server.write({"user_ids": [(3, self.manager.id)]}) - - # Manager should not be able to read remote anymore - self.assertFalse(manager_remote.search([("id", "=", self.remote.id)])) - - # Add manager to server managers - server.write({"manager_ids": [(4, self.manager.id)]}) - - # Manager should be able to read remote again - remote = manager_remote.search([("id", "=", self.remote.id)]) - self.assertTrue(remote) - self.assertEqual(remote.head, "main") diff --git a/addons/cetmix_tower_git/tests/test_repo.py b/addons/cetmix_tower_git/tests/test_repo.py deleted file mode 100644 index 05db569..0000000 --- a/addons/cetmix_tower_git/tests/test_repo.py +++ /dev/null @@ -1,84 +0,0 @@ -from odoo.exceptions import ValidationError - -from .common import CommonTest - - -class TestRepo(CommonTest): - """Test class for git repository.""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - - def test_repo_create_from_url_https_success(self): - """Test if repository is created correctly""" - # -- 1 -- - # Valid HTTPS URL - repo = self.Repo.create( - { - "url": "https://github.com/memes-demo/doge-memes.git", - } - ) - repo.invalidate_recordset() - - self.assertEqual(repo.name, "github.com/memes-demo/doge-memes") - self.assertEqual(repo.host, "github.com") - self.assertEqual(repo.owner_id.name, "memes-demo") - self.assertEqual(repo.provider, "github") - self.assertEqual(repo.is_private, False) - self.assertEqual(repo.url_ssh, "git@github.com:memes-demo/doge-memes.git") - self.assertEqual(repo.url_git, "git://github.com/memes-demo/doge-memes.git") - - def test_repo_create_from_url_ssh_success(self): - """Test if repository is created correctly""" - # -- 1 -- - # Valid SSH URL - repo = self.Repo.create( - { - "url": "git@gitlab.com:chad-guy/chad-guy.git", - } - ) - repo.invalidate_recordset() - - self.assertEqual(repo.name, "gitlab.com/chad-guy/chad-guy") - self.assertEqual(repo.host, "gitlab.com") - self.assertEqual(repo.owner_id.name, "chad-guy") - self.assertEqual(repo.provider, "gitlab") - self.assertEqual(repo.is_private, False) - self.assertEqual(repo.url, "https://gitlab.com/chad-guy/chad-guy.git") - self.assertEqual(repo.url_git, "git://gitlab.com/chad-guy/chad-guy.git") - - def test_repo_create_from_url_git_success(self): - """Test if repository is created correctly""" - # -- 1 -- - # Valid GIT URL - repo = self.Repo.create( - { - "url": "git://bitbucket.com/much-pepe/pepe-memes.git", - } - ) - self.assertEqual(repo.name, "bitbucket.com/much-pepe/pepe-memes") - self.assertEqual(repo.host, "bitbucket.com") - self.assertEqual(repo.owner_id.name, "much-pepe") - self.assertEqual(repo.provider, "bitbucket") - self.assertEqual(repo.is_private, False) - self.assertEqual(repo.url_ssh, "git@bitbucket.com:much-pepe/pepe-memes.git") - self.assertEqual(repo.url, "https://bitbucket.com/much-pepe/pepe-memes.git") - - def test_repo_create_from_url_fails(self): - """Test if repository creation fails with invalid URLs""" - - # Invalid URL 1 - with self.assertRaises(ValidationError): - self.Repo.create( - { - "url": "something.com", - } - ) - # Invalid URL 2 - with self.assertRaises(ValidationError): - self.Repo.create( - { - "url": "random string", - } - ) diff --git a/addons/cetmix_tower_git/tests/test_server.py b/addons/cetmix_tower_git/tests/test_server.py deleted file mode 100644 index a509811..0000000 --- a/addons/cetmix_tower_git/tests/test_server.py +++ /dev/null @@ -1,198 +0,0 @@ -try: - from odoo.addons.queue_job.tests.common import trap_jobs -except ImportError: - trap_jobs = None - -from .common import CommonTest - - -class TestServer(CommonTest): - """Test setting git project to server from plan line.""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - - cls.GitProjectRel.create( - { - "git_project_id": cls.git_project_1.id, - "server_id": cls.server_test_1.id, - "file_id": cls.server_1_file_1.id, - } - ) - - def test_server_creation_running_flight_plan(self): - """Test that server is created with git project from plan line.""" - git_project = self.GitProject.create( - { - "name": "Test Git Project", - "manager_ids": [(4, self.manager.id)], - } - ) - - file_template = self.FileTemplate.create( - { - "name": "Git Config Template", - "file_name": "repos.yaml", - "server_dir": "/var/test", - "code": "repositories:\n test_repo:\n " - "url: https://github.com/test/repo.git\n target: main", - } - ) - - command = self.Command.create( - { - "name": "Create Git Config File", - "action": "file_using_template", - "file_template_id": file_template.id, - } - ) - - flight_plan = self.Plan.create( - { - "name": "Git Project Setup Plan", - "note": "Sets up a git project on the server", - } - ) - - self.plan_line.create( - { - "plan_id": flight_plan.id, - "command_id": command.id, - "sequence": 10, - "git_project_id": git_project.id, - } - ) - - server_template = self.ServerTemplate.create( - { - "name": "Git Server Template", - "ssh_port": 22, - "ssh_username": "admin", - "ssh_password": "password", - "ssh_auth_mode": "p", - "os_id": self.os_debian_10.id, - "flight_plan_id": flight_plan.id, - "manager_ids": [(4, self.manager.id)], - } - ) - - action = server_template.action_create_server() - - # Open the wizard and fill in the data - wizard = ( - self.env["cx.tower.server.template.create.wizard"] - .with_context(**action["context"]) - .create( - { - "name": "Git Server", - "ip_v4_address": "192.168.1.10", - "server_template_id": server_template.id, - } - ) - ) - - # If cetmix_tower_server_queue module is installed, test async processing - if self.env["ir.module.module"].search_count( - [("name", "=", "cetmix_tower_server_queue"), ("state", "=", "installed")] - ): - with trap_jobs() as trap: - wizard.action_confirm() - - # Verify that jobs were created - self.assertGreater( - len(trap.enqueued_jobs), 0, "Jobs should have been enqueued" - ) - - # Execute all trapped jobs to simulate async processing - trap.perform_enqueued_jobs() - else: - wizard.action_confirm() - - # Now search for the created records after jobs have been executed - server = self.Server.search( - [ - ("name", "=", "Git Server"), - ("server_template_id", "=", server_template.id), - ] - ) - self.assertEqual(len(server), 1, "Exactly one server should have been created") - - # Verify the file was created - file = self.File.search( - [("server_id", "=", server.id), ("name", "=", "repos.yaml")] - ) - - self.assertEqual( - len(file), 1, "Exactly one git config file should have been created" - ) - - # Verify the git project relation exists - git_project_rel = self.GitProjectRel.search( - [ - ("server_id", "=", server.id), - ("git_project_id", "=", git_project.id), - ("file_id", "=", file.id), - ] - ) - - self.assertEqual( - len(git_project_rel), 1, "Exactly one git project relation should exist" - ) - self.assertEqual( - git_project_rel.file_id, - file, - "The related file should be the git config file", - ) - self.assertEqual( - git_project_rel.git_project_id, - git_project, - "The related git project should match the one in the flight plan", - ) - self.assertEqual( - git_project_rel.project_format, - git_project._default_project_format(), - "Project format should match the default format", - ) - - def test_server_get_servers_by_git_ref_success(self): - """Check the success case of server.get_servers_by_git_ref""" - - # 1. URL only - servers = self.Server.get_servers_by_git_ref( - self.remote_github_https.repo_id.url - ) - self.assertEqual(servers, self.server_test_1) - - # 2. Specific URL with specific head - servers = self.Server.get_servers_by_git_ref( - self.remote_github_https.repo_id.url, "123" - ) - self.assertEqual(servers, self.server_test_1) - - # 2. Specific URL with specific head and head type - servers = self.Server.get_servers_by_git_ref( - self.remote_github_https.repo_id.url, "123", "pr" - ) - self.assertEqual(servers, self.server_test_1) - - def test_server_get_servers_by_git_ref_no_match(self): - """Check the no match case of server.get_servers_by_git_ref""" - - # 1. Repo link does not exist - servers = self.Server.get_servers_by_git_ref( - "https://github.com/other-org/other-repo.git", "main", "branch" - ) - self.assertFalse(servers) - - # 2. Repo link exists, but remote does not exist - servers = self.Server.get_servers_by_git_ref( - self.repo_cetmix_tower.url, "3311", "pr" - ) - self.assertFalse(servers) - - # 3. Repo link exists, but remote type does not exist - servers = self.Server.get_servers_by_git_ref( - self.repo_cetmix_tower.url, "main", "commit" - ) - self.assertFalse(servers) diff --git a/addons/cetmix_tower_git/tests/test_source.py b/addons/cetmix_tower_git/tests/test_source.py deleted file mode 100644 index de67eef..0000000 --- a/addons/cetmix_tower_git/tests/test_source.py +++ /dev/null @@ -1,226 +0,0 @@ -from odoo.exceptions import AccessError - -from .common import CommonTest - - -class TestSource(CommonTest): - """Test class for git source.""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - # Create another manager for testing - cls.manager_2 = cls.Users.create( - { - "name": "Second Manager", - "login": "manager2", - "email": "manager2@test.com", - "groups_id": [(4, cls.env.ref("cetmix_tower_server.group_manager").id)], - } - ) - - # Create test project and source as root - cls.project = cls.GitProject.create( - { - "name": "Test Project", - } - ) - cls.source = cls.GitSource.create( - { - "name": "Test Source", - "git_project_id": cls.project.id, - } - ) - - def test_user_access(self): - """Test that regular users have no access to git sources""" - user_source = self.GitSource.with_user(self.user) - - # Test CRUD operations - with self.assertRaises(AccessError): - user_source.create( - { - "name": "New Source", - "git_project_id": self.project.id, - } - ) - with self.assertRaises(AccessError): - user_source.browse(self.source.id).read(["name"]) - with self.assertRaises(AccessError): - user_source.browse(self.source.id).write({"name": "Updated Name"}) - with self.assertRaises(AccessError): - user_source.browse(self.source.id).unlink() - - def test_manager_read_access(self): - """Test manager read access rules""" - manager_source = self.GitSource.with_user(self.manager) - - # Manager not in project user_ids or manager_ids - should not read - with self.assertRaises(AccessError): - manager_source.browse(self.source.id).read(["name"]) - - # Add manager to project user_ids - should read - self.project.write({"user_ids": [(4, self.manager.id)]}) - self.assertEqual(manager_source.browse(self.source.id).name, "Test Source") - - # Remove from user_ids, add to manager_ids - should read - self.project.write( - {"user_ids": [(3, self.manager.id)], "manager_ids": [(4, self.manager.id)]} - ) - self.assertEqual(manager_source.browse(self.source.id).name, "Test Source") - - def test_manager_write_access(self): - """Test manager write/create access rules""" - manager_source = self.GitSource.with_user(self.manager) - - # Create project as manager - should be added to manager_ids automatically - project = self.GitProject.with_user(self.manager).create( - { - "name": "Manager Project", - } - ) - self.assertIn(self.manager, project.manager_ids) - - # Create source in own project - should succeed - new_source = manager_source.create( - { - "name": "New Source", - "git_project_id": project.id, - } - ) - self.assertTrue(new_source.exists()) - - # Write to own source - should succeed - new_source.write({"name": "Updated Name"}) - self.assertEqual(new_source.name, "Updated Name") - - # Write to other's source - should fail - with self.assertRaises(AccessError): - manager_source.browse(self.source.id).write({"name": "Updated Name"}) - - def test_manager_unlink_access(self): - """Test manager unlink access rules""" - # Create project and source as manager_2 - project = self.GitProject.with_user(self.manager_2).create( - { - "name": "Manager 2 Project", - } - ) - source = self.GitSource.with_user(self.manager_2).create( - { - "name": "Source to Delete", - "git_project_id": project.id, - } - ) - manager_source = self.GitSource.with_user(self.manager) - - # Try delete as different manager - should fail even if added to manager_ids - project.write({"manager_ids": [(4, self.manager.id)]}) - with self.assertRaises(AccessError): - manager_source.browse(source.id).unlink() - - # Create source as manager and try delete - should succeed - own_source = manager_source.create( - { - "name": "Own Source", - "git_project_id": project.id, - } - ) - self.assertTrue(own_source.exists()) - own_source.unlink() - self.assertFalse(own_source.exists()) - - def test_root_access(self): - """Test root access rules""" - root_source = self.GitSource.with_user(self.root) - - # Create - new_source = root_source.create( - { - "name": "Root Source", - "git_project_id": self.project.id, - } - ) - self.assertTrue(new_source.exists()) - - # Read - self.assertEqual(root_source.browse(self.source.id).name, "Test Source") - - # Write - root_source.browse(self.source.id).write({"name": "Updated by Root"}) - self.assertEqual(self.source.name, "Updated by Root") - - # Delete - new_source.unlink() - self.assertFalse(new_source.exists()) - - def test_source_git_aggregator_prepare_record(self): - """Test if source prepare record method works correctly.""" - - # -- 1 -- - # Source 1 - expected_result = { - "remotes": { - "remote_1": "https://github.com/cetmix-test/cetmix-tower-test.git", - "remote_2": "https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git", - "remote_3": "git@my.gitlab.com:cetmix-test/cetmix-tower-test.git", - }, - "merges": [ - {"remote": "remote_1", "ref": "refs/pull/123/head"}, - {"remote": "remote_2", "ref": "main"}, - {"remote": "remote_3", "ref": "10000000"}, - ], - "target": "remote_1", - } - prepared_result = self.git_source_1._git_aggregator_prepare_record() - self.assertEqual( - prepared_result, expected_result, "Prepared result is not correct" - ) - - def test_manager_server_based_access(self): - """Test manager access to sources through server relationships""" - manager_source = self.GitSource.with_user(self.manager) - - # Create a server where manager is a user - server = self.Server.create( - { - "name": "Test Server", - "ip_v4_address": "localhost", - "ssh_username": "admin", - "ssh_password": "password", - "os_id": self.os_debian_10.id, - "user_ids": [(4, self.manager.id)], - } - ) - - # Link project to server - file = self.File.create( - { - "name": "test_file", - "server_id": server.id, - } - ) - self.GitProjectRel.create( - { - "server_id": server.id, - "file_id": file.id, - "git_project_id": self.project.id, - "project_format": "git_aggregator", - } - ) - - # Manager should be able to read source through server relationship - self.assertEqual(manager_source.browse(self.source.id).name, "Test Source") - - # Remove manager from server users - server.write({"user_ids": [(3, self.manager.id)]}) - - # Manager should not be able to read source anymore - with self.assertRaises(AccessError): - manager_source.browse(self.source.id).read(["name"]) - - # Add manager to server managers - server.write({"manager_ids": [(4, self.manager.id)]}) - - # Manager should be able to read source again - self.assertEqual(manager_source.browse(self.source.id).name, "Test Source") diff --git a/addons/cetmix_tower_git/tools/git_aggregator.py b/addons/cetmix_tower_git/tools/git_aggregator.py deleted file mode 100644 index e69de29..0000000 diff --git a/addons/cetmix_tower_git/views/cx_tower_file_template_views.xml b/addons/cetmix_tower_git/views/cx_tower_file_template_views.xml deleted file mode 100644 index 6f0ec86..0000000 --- a/addons/cetmix_tower_git/views/cx_tower_file_template_views.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - cx.tower.file.template.view.form - cx.tower.file.template - - - - - - - - - diff --git a/addons/cetmix_tower_git/views/cx_tower_file_views.xml b/addons/cetmix_tower_git/views/cx_tower_file_views.xml deleted file mode 100644 index 917bbf4..0000000 --- a/addons/cetmix_tower_git/views/cx_tower_file_views.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - cx.tower.file.view.form - cx.tower.file - - - - - - - - - diff --git a/addons/cetmix_tower_git/views/cx_tower_git_project_views.xml b/addons/cetmix_tower_git/views/cx_tower_git_project_views.xml deleted file mode 100644 index 9a67e74..0000000 --- a/addons/cetmix_tower_git/views/cx_tower_git_project_views.xml +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - cx.tower.git.project.tree - cx.tower.git.project - - - - - - - - - - - - cx.tower.git.project.form - cx.tower.git.project - -
- - -
-

- -

-

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-

- * Sources where all remotes are private -

-
-
-

- * Sources where some remotes are private -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - cx.tower.git.repo.search - cx.tower.git.repo - - - - - - - - - - - - - - - - - - - - - - - - - - Repositories - cx.tower.git.repo - tree,form - -

- Create your first repository! -

-

- Repositories represent git repositories with their metadata and configuration. - They can be linked to remotes to automatically populate URL information. -

-
-
- -
diff --git a/addons/cetmix_tower_git/views/cx_tower_git_source_views.xml b/addons/cetmix_tower_git/views/cx_tower_git_source_views.xml deleted file mode 100644 index 871649f..0000000 --- a/addons/cetmix_tower_git/views/cx_tower_git_source_views.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - cx.tower.git.source.tree - cx.tower.git.source - - - - - - - - - - - cx.tower.git.source.form - cx.tower.git.source - -
- - -
-

- -

-

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -
diff --git a/addons/cetmix_tower_git/views/cx_tower_plan_line_view.xml b/addons/cetmix_tower_git/views/cx_tower_plan_line_view.xml deleted file mode 100644 index d4a69a2..0000000 --- a/addons/cetmix_tower_git/views/cx_tower_plan_line_view.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - cx.tower.plan.line.view.form - cx.tower.plan.line - - - - - - - - - - - diff --git a/addons/cetmix_tower_git/views/cx_tower_server_view.xml b/addons/cetmix_tower_git/views/cx_tower_server_view.xml deleted file mode 100644 index 39d2878..0000000 --- a/addons/cetmix_tower_git/views/cx_tower_server_view.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - cx.tower.server.view.form.shortcuts - cx.tower.server - - - - - - - - - - - -