From 488859fadaf25e02f605d41a957dd9555c6e74fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Tue, 1 Oct 2024 12:19:32 +0200 Subject: [PATCH 1/2] Ability to port module located in any subfolder --- oca_port/app.py | 26 ++++++++++++++++++-------- oca_port/cli/main.py | 6 +++--- oca_port/migrate_addon.py | 2 +- oca_port/port_addon_pr.py | 12 +++++++++--- oca_port/tests/common.py | 2 +- oca_port/utils/git.py | 28 ++++++++++++++++++---------- 6 files changed, 50 insertions(+), 26 deletions(-) diff --git a/oca_port/app.py b/oca_port/app.py index 8272bdd..c9b4f9b 100644 --- a/oca_port/app.py +++ b/oca_port/app.py @@ -26,8 +26,8 @@ class App(Output): string representation of the source branch, e.g. 'origin/15.0' target: string representation of the target branch, e.g. 'origin/16.0' - addon: - the name of the module to process + addon_path: + the path of the addon to process destination: string representation of the destination branch, e.g. 'camptocamp/16.0-addon-dev' @@ -62,7 +62,7 @@ class App(Output): source: str target: str - addon: str + addon_path: str destination: str = None source_version: str = None target_version: str = None @@ -149,6 +149,11 @@ def _prepare_parameters(self): if self.repo.is_dirty(untracked_files=True): raise ValueError("changes not committed detected in this repository.") + # Module name + self.addon_path = pathlib.Path(self.addon_path) + self.addons_rootdir = self.addon_path.parent + self.addon = self.addon_path.name + # Convert them to full remote info if needed for key in ("source", "target", "destination"): value = getattr(self, key) @@ -263,14 +268,19 @@ def _check_branch_exists(self, branch, raise_exc=False): def _check_addon_exists(self, branch, raise_exc=False): repo = self.repo - addon = self.addon - branch_addons = [t.path for t in repo.commit(branch.ref()).tree.trees] - if addon not in branch_addons: + addons_tree = repo.commit(branch.ref()).tree + if self.addons_rootdir and self.addons_rootdir.name: + addons_tree /= str(self.addons_rootdir) + branch_addons = [t.path for t in addons_tree.trees] + if str(self.addon_path) not in branch_addons: if not raise_exc: return False - error = f"{addon} does not exist on {branch.ref()}" + error = f"{self.addon_path} does not exist on {branch.ref()}" if self.cli: - error = f"{bc.FAIL}{addon}{bc.ENDC} does not exist on {branch.ref()}" + error = ( + f"{bc.FAIL}{self.addon_path}{bc.ENDC} " + "does not exist on {branch.ref()}" + ) raise ValueError(error) return True diff --git a/oca_port/cli/main.py b/oca_port/cli/main.py index 3616c26..5e6ce0a 100644 --- a/oca_port/cli/main.py +++ b/oca_port/cli/main.py @@ -62,7 +62,7 @@ @click.command() @click.argument("source", required=True) @click.argument("target", required=True) -@click.argument("addon", required=True) +@click.argument("addon_path", required=True) @click.option( "--destination", help=("Git reference where work will be pushed, e.g. 'camptocamp/16.0-dev'."), @@ -99,7 +99,7 @@ @click.option("--no-cache", is_flag=True, help="Disable user's cache.") @click.option("--clear-cache", is_flag=True, help="Clear the user's cache.") def main( - addon: str, + addon_path: str, source: str, target: str, destination: str, @@ -132,7 +132,7 @@ def main( """ try: app = App( - addon=addon, + addon_path=addon_path, source=source, target=target, destination=destination, diff --git a/oca_port/migrate_addon.py b/oca_port/migrate_addon.py index 599c3f9..1b5258a 100644 --- a/oca_port/migrate_addon.py +++ b/oca_port/migrate_addon.py @@ -189,7 +189,7 @@ def _generate_patches(self, patches_dir): patches_dir, f"{self.app.to_branch.ref()}..{self.app.from_branch.ref()}", "--", - self.app.addon, + self.app.addon_path, ) def _apply_patches(self, patches_dir): diff --git a/oca_port/port_addon_pr.py b/oca_port/port_addon_pr.py index dfc336e..14081bd 100644 --- a/oca_port/port_addon_pr.py +++ b/oca_port/port_addon_pr.py @@ -607,7 +607,7 @@ class BranchesDiff(Output): def __init__(self, app): self.app = app - self.path = self.app.addon + self.path = self.app.addon_path self.from_branch_path_commits, _ = self._get_branch_commits( self.app.from_branch.ref(), self.path ) @@ -649,7 +649,9 @@ def _get_branch_commits(self, branch, path="."): for commit in commits: if self.app.cache.is_commit_ported(commit.hexsha): continue - com = g.Commit(commit, cache=self.app.cache) + com = g.Commit( + commit, addons_path=self.app.addons_rootdir, cache=self.app.cache + ) if self._skip_commit(com): continue commits_list.append(com) @@ -751,7 +753,11 @@ def get_commits_diff(self): # Ignore commits referenced by a PR but not present # in the stable branches continue - pr_commit = g.Commit(raw_commit, cache=self.app.cache) + pr_commit = g.Commit( + raw_commit, + addons_path=self.app.addons_rootdir, + cache=self.app.cache, + ) if self._skip_commit(pr_commit): continue pr_commit_paths = { diff --git a/oca_port/tests/common.py b/oca_port/tests/common.py index 4aec9be..c071582 100644 --- a/oca_port/tests/common.py +++ b/oca_port/tests/common.py @@ -115,7 +115,7 @@ def _create_app(self, source, target, destination=None, **kwargs): "source": source, "target": target, "destination": destination, - "addon": self.addon, + "addon_path": self.addon, "source_version": None, "target_version": None, "repo_path": self.repo_path, diff --git a/oca_port/utils/git.py b/oca_port/utils/git.py index 230931c..d054c3b 100644 --- a/oca_port/utils/git.py +++ b/oca_port/utils/git.py @@ -2,6 +2,7 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl) import contextlib +import pathlib import re import subprocess from collections import abc @@ -38,10 +39,12 @@ def ref(self): class CommitPath(str): """Helper class to know if a base path is a directory or a file.""" - def __new__(cls, value): - new_value = value.split("/", maxsplit=1)[0] - obj = super().__new__(cls, new_value) - obj.isdir = "/" in value + def __new__(cls, addons_path, value): + file_path = pathlib.Path(value).relative_to(addons_path) + root_node = file_path.parts[0] + obj = super().__new__(cls, root_node) + # As soon as `file_path` has a parent, the root node is obviously a folder + obj.isdir = bool(file_path.parent.name) return obj @@ -58,9 +61,10 @@ class Commit: other_equality_attrs = ("paths",) eq_strict = True - def __init__(self, commit, cache=None): + def __init__(self, commit, addons_path=".", cache=None): """Initializes a new Commit instance from a GitPython Commit object.""" self.raw_commit = commit + self.addons_path = addons_path self.cache = cache self.author_name = commit.author.name self.author_email = commit.author.email @@ -87,11 +91,15 @@ def files(self): @property def paths(self): - """Returns list of `CommitPath` objects.""" - # Access git storage or cache only on demand to avoid too much IO - paths = {CommitPath(f) for f in self.files} - if not self._paths: - self._paths = paths + """Return the folders/files updated in `addons_path`. + + If a commit updates files 'x/a/b/c.py', 'x/d/e.py' and 'x/f.txt', knowing + the `addons_path` is `x`, the root nodes updated by this commit are + `a` (folder), `d` (folder) and `f.txt` (file). + """ + if self._paths: + return self._paths + self._paths = {CommitPath(self.addons_path, f) for f in self.files} return self._paths def _get_files(self): From 3708158ddde2176d4e428a12dd3421f8fc8171e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Mon, 14 Oct 2024 08:23:47 +0200 Subject: [PATCH 2/2] Display a warning if original PR could not be detected --- oca_port/port_addon_pr.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/oca_port/port_addon_pr.py b/oca_port/port_addon_pr.py index 14081bd..94cf824 100644 --- a/oca_port/port_addon_pr.py +++ b/oca_port/port_addon_pr.py @@ -11,6 +11,7 @@ import click import git +import requests from .utils import git as g, misc from .utils.session import Session @@ -845,12 +846,16 @@ def _get_original_pr(self, commit: g.Commit): # Request GitHub to get them if not any("github.com" in remote.url for remote in self.app.repo.remotes): return - raw_data = self.app.github.get_original_pr( - self.app.upstream_org, - self.app.repo_name, - self.app.from_branch.name, - commit.hexsha, - ) + try: + raw_data = self.app.github.get_original_pr( + self.app.upstream_org, + self.app.repo_name, + self.app.from_branch.name, + commit.hexsha, + ) + except requests.exceptions.ConnectionError: + self._print("⚠️ Unable to detect original PR (connection error)") + return if raw_data: # Get all commits of the PR as they could update others addons # than the one the user is interested in.