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

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

commit 1f854244619c80288afb5f9db06070e35f50e5ce
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Feb 4 18:32:01 2026 +0000

    Clone source from GitHub in the task to compare source trees
---
 atr/tasks/checks/compare.py | 77 ++++++++++++++++++++++++++++++++++++++++++++-
 pyproject.toml              |  1 +
 uv.lock                     | 23 +++++++++++++-
 3 files changed, 99 insertions(+), 2 deletions(-)

diff --git a/atr/tasks/checks/compare.py b/atr/tasks/checks/compare.py
index 52f8d1b..44a2d80 100644
--- a/atr/tasks/checks/compare.py
+++ b/atr/tasks/checks/compare.py
@@ -15,11 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import asyncio
 import json
-from typing import Any
+import os
+import pathlib
+import secrets
+import shutil
+from typing import Any, Final
 
 import aiofiles
 import aiofiles.os
+import dulwich.objectspec as objectspec
+import dulwich.porcelain as porcelain
 import pydantic
 
 import atr.attestable as attestable
@@ -27,6 +34,10 @@ import atr.log as log
 import atr.models.results as results
 import atr.sbom.models.github as github_models
 import atr.tasks.checks as checks
+import atr.util as util
+
+_DEFAULT_EMAIL: Final[str] = "atr@localhost"
+_DEFAULT_USER: Final[str] = "atr"
 
 
 async def source_trees(args: checks.FunctionArguments) -> results.Results | 
None:
@@ -43,6 +54,9 @@ async def source_trees(args: checks.FunctionArguments) -> 
results.Results | None
         return None
 
     payload = await _load_tp_payload(args.project_name, args.version_name, 
args.revision_number)
+    checkout_dir: str | None = None
+    if payload is not None:
+        checkout_dir = await _checkout_github_source(payload)
     payload_summary = _payload_summary(payload)
     log.info(
         "Ran compare.source_trees successfully",
@@ -51,10 +65,65 @@ async def source_trees(args: checks.FunctionArguments) -> 
results.Results | None
         revision=args.revision_number,
         path=args.primary_rel_path,
         github_payload=payload_summary,
+        github_checkout=checkout_dir,
     )
     return None
 
 
+async def _checkout_github_source(payload: 
github_models.TrustedPublisherPayload) -> str | None:
+    tmp_dir = util.get_tmp_dir()
+    await aiofiles.os.makedirs(tmp_dir, exist_ok=True)
+    checkout_dir = tmp_dir / f"github-{secrets.token_hex(12)}"
+    repo_url = f"https://github.com/{payload.repository}.git";
+    branch = _ref_to_branch(payload.ref)
+    try:
+        await asyncio.to_thread(_clone_repo, repo_url, payload.sha, branch, 
checkout_dir)
+    except Exception:
+        log.exception(
+            "Failed to clone GitHub repo for compare.source_trees",
+            repo_url=repo_url,
+            sha=payload.sha,
+            branch=branch,
+            checkout_dir=str(checkout_dir),
+            git_author_name=os.environ.get("GIT_AUTHOR_NAME"),
+            git_author_email=os.environ.get("GIT_AUTHOR_EMAIL"),
+            git_committer_name=os.environ.get("GIT_COMMITTER_NAME"),
+            git_committer_email=os.environ.get("GIT_COMMITTER_EMAIL"),
+            user=os.environ.get("USER"),
+            logname=os.environ.get("LOGNAME"),
+            email=os.environ.get("EMAIL"),
+        )
+        return None
+    return str(checkout_dir)
+
+
+def _clone_repo(repo_url: str, sha: str, branch: str | None, checkout_dir: 
pathlib.Path) -> None:
+    _ensure_clone_identity_env()
+    repo = porcelain.clone(
+        repo_url,
+        str(checkout_dir),
+        checkout=True,
+        depth=1,
+        branch=branch,
+    )
+    try:
+        commit = objectspec.parse_commit(repo, sha)
+        repo.get_worktree().reset_index(tree=commit.tree)
+    except (KeyError, ValueError) as exc:
+        raise RuntimeError(f"Commit {sha} not found in shallow clone") from exc
+    git_dir = pathlib.Path(repo.controldir())
+    if git_dir.exists():
+        shutil.rmtree(git_dir)
+    tmp_dir = pathlib.Path(repo.path)
+    if tmp_dir.exists():
+        shutil.rmtree(tmp_dir)
+
+
+def _ensure_clone_identity_env() -> None:
+    os.environ["USER"] = _DEFAULT_USER
+    os.environ["EMAIL"] = _DEFAULT_EMAIL
+
+
 async def _load_tp_payload(
     project_name: str, version_name: str, revision_number: str
 ) -> github_models.TrustedPublisherPayload | None:
@@ -88,3 +157,9 @@ def _payload_summary(payload: 
github_models.TrustedPublisherPayload | None) -> d
         "actor": payload.actor,
         "actor_id": payload.actor_id,
     }
+
+
+def _ref_to_branch(ref: str) -> str | None:
+    if ref.startswith("refs/heads/"):
+        return ref.removeprefix("refs/heads/")
+    return None
diff --git a/pyproject.toml b/pyproject.toml
index 3148339..8ff5474 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,6 +26,7 @@ dependencies = [
   "cyclonedx-python-lib[json-validation]>=11.0.0",
   # "dkimpy @ git+https://github.com/sbp/dkimpy.git@main";,
   "dnspython>=2.7.0,<3.0.0",
+  "dulwich>=1.0.0",
   "dunamai>=1.23.0",
   "email-validator~=2.2.0",
   "gitignore-parser (>=0.1.12,<0.2.0)",
diff --git a/uv.lock b/uv.lock
index 7cfd9dc..210c5c7 100644
--- a/uv.lock
+++ b/uv.lock
@@ -3,7 +3,7 @@ revision = 3
 requires-python = "==3.13.*"
 
 [options]
-exclude-newer = "2026-02-04T16:49:21Z"
+exclude-newer = "2026-02-04T17:11:34Z"
 
 [[package]]
 name = "aiofiles"
@@ -517,6 +517,25 @@ wheels = [
     { url = 
"https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl";,
 hash = 
"sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size 
= 331094, upload-time = "2025-09-07T18:57:58.071Z" },
 ]
 
+[[package]]
+name = "dulwich"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple"; }
+dependencies = [
+    { name = "urllib3" },
+]
+sdist = { url = 
"https://files.pythonhosted.org/packages/ee/df/4178b6465e118e6e74fd78774b451953dd53c09fdec18f2c4b3319dd0485/dulwich-1.0.0.tar.gz";,
 hash = 
"sha256:3d07104735525f22bfec35514ac611cf328c89b7acb059316a4f6e583c8f09bc", size 
= 1135862, upload-time = "2026-01-17T23:44:16.357Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/97/82/5ce63c7a2ac8d756bc7477298633e420632eed97ea645ecea13210e9b1a7/dulwich-1.0.0-cp313-cp313-android_21_arm64_v8a.whl";,
 hash = 
"sha256:ff94f47f0b5787d4e6a0105daf51ff9cdb4e5b9d4e9f8dd01b58ba9a5b79bbd9", size 
= 1417766, upload-time = "2026-01-17T23:43:57.855Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b9/71/7d4ecdf9e0da21ceec3ac05b03c2cac8cf2271a52172fd55dd65a9faa9e7/dulwich-1.0.0-cp313-cp313-android_21_x86_64.whl";,
 hash = 
"sha256:1d95663441c930631d9d1765dc4f427dcc0662af45f42a0831357e60055ddb84", size 
= 1417760, upload-time = "2026-01-17T23:43:59.42Z" },
+    { url = 
"https://files.pythonhosted.org/packages/09/3d/0486cefda75c7e9ea8d8dbdeaa014d618e694bc75734f073927135b37a4b/dulwich-1.0.0-cp313-cp313-macosx_11_0_arm64.whl";,
 hash = 
"sha256:78542a62fabea894943a1d01c9c477a56eee5f7d58d3bdee42c7e0622ddf6893", size 
= 1316186, upload-time = "2026-01-17T23:44:01.334Z" },
+    { url = 
"https://files.pythonhosted.org/packages/f7/a7/a24c6e1e9f7e5a2ee8f9e362e2c3e5d864cc2b69f04d02bedf82673f31c3/dulwich-1.0.0-cp313-cp313-manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:d1c33f6456e4335dfe6f4d3917fa7d77050d6470bbbaf8054b5c5084ee8e8cd1", size 
= 1392530, upload-time = "2026-01-17T23:44:03.655Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d4/03/1ff9dbda655fc714528786e3fdbbe16278bbefc02b9836e91a38620aa616/dulwich-1.0.0-cp313-cp313-manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:581330cf799577f194fda2b5384b7ba50e095de7ff088779c027a6de63642de2", size 
= 1420386, upload-time = "2026-01-17T23:44:05.844Z" },
+    { url = 
"https://files.pythonhosted.org/packages/f0/ca/72e7cdde2ee0a4f858166ba8eb81a0d89f61762d9114bd7a358798892fc9/dulwich-1.0.0-cp313-cp313-win32.whl";,
 hash = 
"sha256:276ff18ae734fe4a1be66d4267216a51d2deab0ac981d722db3d32fcc2ac4ff8", size 
= 981425, upload-time = "2026-01-17T23:44:07.373Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d7/27/8d4bed76ce983052e259da25255fed85b48ad30a34b4e4b7c8f518fdbc30/dulwich-1.0.0-cp313-cp313-win_amd64.whl";,
 hash = 
"sha256:cc0ab4ba7fd8617bebe20294dedaa8f713d1767ce059bfbefd971b911b702726", size 
= 998055, upload-time = "2026-01-17T23:44:08.908Z" },
+    { url = 
"https://files.pythonhosted.org/packages/f9/99/4543953d2f7c1a940c1373362a70d253b85860be64b4ef8885bf8bfb340b/dulwich-1.0.0-py3-none-any.whl";,
 hash = 
"sha256:221be803b71b060c928e9faae4ab3e259ff5beac6e0c251ba3c176b51b5c2ffb", size 
= 647950, upload-time = "2026-01-17T23:44:14.449Z" },
+]
+
 [[package]]
 name = "dunamai"
 version = "1.25.0"
@@ -1874,6 +1893,7 @@ dependencies = [
     { name = "cvss" },
     { name = "cyclonedx-python-lib", extra = ["json-validation"] },
     { name = "dnspython" },
+    { name = "dulwich" },
     { name = "dunamai" },
     { name = "email-validator" },
     { name = "gitignore-parser" },
@@ -1937,6 +1957,7 @@ requires-dist = [
     { name = "cvss", specifier = "~=3.6" },
     { name = "cyclonedx-python-lib", extras = ["json-validation"], specifier = 
">=11.0.0" },
     { name = "dnspython", specifier = ">=2.7.0,<3.0.0" },
+    { name = "dulwich", specifier = ">=1.0.0" },
     { name = "dunamai", specifier = ">=1.23.0" },
     { name = "email-validator", specifier = "~=2.2.0" },
     { name = "gitignore-parser", specifier = ">=0.1.12,<0.2.0" },


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

Reply via email to