This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git


The following commit(s) were added to refs/heads/main by this push:
     new c7cddfb  Use a more advanced matcher for binary and source artifact 
paths
c7cddfb is described below

commit c7cddfb966e1f101eb85969ff851befaf3223391
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon Jun 23 17:27:31 2025 +0100

    Use a more advanced matcher for binary and source artifact paths
---
 atr/routes/projects.py       | 23 +--------------------
 atr/tasks/checks/__init__.py | 48 ++++++++++++++++++++++++++++----------------
 atr/tasks/checks/rat.py      |  1 +
 poetry.lock                  | 13 +++++++++++-
 pyproject.toml               |  1 +
 5 files changed, 46 insertions(+), 40 deletions(-)

diff --git a/atr/routes/projects.py b/atr/routes/projects.py
index a86d613..b7d6e57 100644
--- a/atr/routes/projects.py
+++ b/atr/routes/projects.py
@@ -392,28 +392,7 @@ async def _metadata_edit(
 def _parse_artifact_paths(artifact_paths: str) -> list[str]:
     if not artifact_paths:
         return []
-    lines = artifact_paths.split("\n")
-    # This is similar to announce._download_path_suffix_validated
-    paths = []
-    for path in lines:
-        path = path.strip()
-        if not path:
-            continue
-        if (".." in path) or ("//" in path):
-            raise ValueError("Artifact path must not contain .. or //")
-        if path.startswith("./"):
-            path = path[1:]
-        elif path == ".":
-            path = "/"
-        if not path.startswith("/"):
-            path = "/" + path
-        # We differ from _download_path_suffix_validated in that we don't add 
a trailing slash
-        # if not path.endswith("/"):
-        #     path = path + "/"
-        if "/." in path:
-            raise ValueError("Artifact path must not contain /.")
-        paths.append(path)
-    return sorted(paths)
+    return [path.strip() for path in artifact_paths.split("\n") if 
path.strip()]
 
 
 async def _policy_edit(
diff --git a/atr/tasks/checks/__init__.py b/atr/tasks/checks/__init__.py
index 17ed569..7e07d0c 100644
--- a/atr/tasks/checks/__init__.py
+++ b/atr/tasks/checks/__init__.py
@@ -19,11 +19,11 @@ from __future__ import annotations
 
 import dataclasses
 import datetime
-import fnmatch
 import functools
 import pathlib
 from typing import TYPE_CHECKING, Any
 
+import gitignore_parser
 import sqlmodel
 
 if TYPE_CHECKING:
@@ -136,11 +136,6 @@ class Recorder:
 
     async def abs_path(self, rel_path: str | None = None) -> pathlib.Path | 
None:
         """Construct the absolute path using the required revision."""
-        base_dir = util.get_unfinished_dir()
-        project_part = self.project_name
-        version_part = self.version_name
-        revision_part = self.revision_number
-
         # Determine the relative path part
         rel_path_part: str | None = None
         if rel_path is not None:
@@ -148,11 +143,12 @@ class Recorder:
         elif self.primary_rel_path is not None:
             rel_path_part = self.primary_rel_path
 
-        # Construct the absolute path
-        abs_path_parts: list[str | pathlib.Path] = [base_dir, project_part, 
version_part, revision_part]
-        if isinstance(rel_path_part, str):
-            abs_path_parts.append(rel_path_part)
-        return pathlib.Path(*abs_path_parts)
+        if rel_path_part is None:
+            return self.abs_path_base()
+        return self.abs_path_base() / rel_path_part
+
+    def abs_path_base(self) -> pathlib.Path:
+        return pathlib.Path(util.get_unfinished_dir(), self.project_name, 
self.version_name, self.revision_number)
 
     async def project(self) -> models.Project:
         # TODO: Cache project
@@ -167,9 +163,11 @@ class Recorder:
         project = await self.project()
         if not project.policy_binary_artifact_paths:
             return False
-        paths = project.policy_binary_artifact_paths
-        slash_path = "/" + self.primary_rel_path
-        return any(fnmatch.fnmatch(slash_path, path) for path in paths)
+        matches = _create_path_matcher(
+            project.policy_binary_artifact_paths, self.abs_path_base() / 
".ignore", self.abs_path_base()
+        )
+        abs_path = await self.abs_path()
+        return matches(str(abs_path))
 
     async def primary_path_is_source(self) -> bool:
         if self.primary_rel_path is None:
@@ -177,9 +175,11 @@ class Recorder:
         project = await self.project()
         if not project.policy_source_artifact_paths:
             return False
-        paths = project.policy_source_artifact_paths
-        slash_path = "/" + self.primary_rel_path
-        return any(fnmatch.fnmatch(slash_path, path) for path in paths)
+        matches = _create_path_matcher(
+            project.policy_source_artifact_paths, self.abs_path_base() / 
".ignore", self.abs_path_base()
+        )
+        abs_path = await self.abs_path()
+        return matches(str(abs_path))
 
     async def clear(self, primary_rel_path: str | None = None, 
member_rel_path: str | None = None) -> None:
         async with db.session() as data:
@@ -254,3 +254,17 @@ def with_model(cls: type[schema.Strict]) -> 
Callable[[Callable[..., Any]], Calla
         return wrapper
 
     return decorator
+
+
+def _create_path_matcher(lines: list[str], full_path: pathlib.Path, base_dir: 
pathlib.Path) -> Callable[[str], bool]:
+    rules = []
+    negation = False
+    for line_no, line in enumerate(lines, start=1):
+        rule = gitignore_parser.rule_from_pattern(line.rstrip("\n"), 
base_path=base_dir, source=(full_path, line_no))
+        if rule:
+            rules.append(rule)
+            if rule.negation:
+                negation = True
+    if not negation:
+        return lambda file_path: any(r.match(file_path) for r in rules)
+    return lambda file_path: gitignore_parser.handle_negation(file_path, rules)
diff --git a/atr/tasks/checks/rat.py b/atr/tasks/checks/rat.py
index ec6d125..90f8094 100644
--- a/atr/tasks/checks/rat.py
+++ b/atr/tasks/checks/rat.py
@@ -48,6 +48,7 @@ async def check(args: checks.FunctionArguments) -> str | None:
     if not (artifact_abs_path := await recorder.abs_path()):
         return None
     if await recorder.primary_path_is_binary():
+        _LOGGER.info(f"Skipping RAT check for binary artifact 
{artifact_abs_path} (rel: {args.primary_rel_path})")
         return None
 
     _LOGGER.info(f"Checking RAT licenses for {artifact_abs_path} (rel: 
{args.primary_rel_path})")
diff --git a/poetry.lock b/poetry.lock
index 7ebf38f..e217940 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1045,6 +1045,17 @@ files = [
     {file = "frozenlist-1.7.0.tar.gz", hash = 
"sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"},
 ]
 
+[[package]]
+name = "gitignore-parser"
+version = "0.1.12"
+description = "A spec-compliant gitignore parser for Python 3.5+"
+optional = false
+python-versions = "*"
+groups = ["main"]
+files = [
+    {file = "gitignore_parser-0.1.12.tar.gz", hash = 
"sha256:78b22243adc0f02102c56c5e8c9a1d9121394142ca6143a90daa7f8d7a07a17e"},
+]
+
 [[package]]
 name = "greenlet"
 version = "3.2.3"
@@ -3184,4 +3195,4 @@ propcache = ">=0.2.1"
 [metadata]
 lock-version = "2.1"
 python-versions = "~=3.13"
-content-hash = 
"b79174b297f7612b074db005aa6399eace902e49302d56e0168540befd71a8f1"
+content-hash = 
"54eafdba2809654d5c799509841e7fb6e3cb67635d88f7770de3217dc5c68c44"
diff --git a/pyproject.toml b/pyproject.toml
index 27d71ed..2153c78 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,6 +25,7 @@ dependencies = [
   "dnspython>=2.7.0,<3.0.0",
   "dunamai>=1.23.0",
   "email-validator~=2.2.0",
+  "gitignore-parser (>=0.1.12,<0.2.0)",
   "greenlet>=3.1.1,<4.0.0",
   "httpx~=0.27",
   "hypercorn~=0.17",


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to