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 c4f1c989e80083c04776e7e1cc5ecfc0486de2c8 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..3cf73a2 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_GIT_AUTHOR_EMAIL: Final[str] = "no-reply@localhost" +_DEFAULT_GIT_AUTHOR_NAME: Final[str] = "ATR Compare Task" 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["GIT_AUTHOR_NAME"] = _DEFAULT_GIT_AUTHOR_NAME + os.environ["GIT_AUTHOR_EMAIL"] = _DEFAULT_GIT_AUTHOR_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]
