From 1409ccfaad40555122b3a016252a0bd4ad288f5b Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sun, 20 Sep 2026 18:39:37 -0300 Subject: [PATCH 1/2] fix: avoid upstream pin churn without content changes Signed-off-by: Vitor Mattos --- scripts/sync_upstream.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/sync_upstream.py b/scripts/sync_upstream.py index a09546e..18f8c6f 100755 --- a/scripts/sync_upstream.py +++ b/scripts/sync_upstream.py @@ -130,8 +130,9 @@ def refresh(manifest_path: Path, root: Path, token: str | None = None) -> None: content = _download(url) digest = hashlib.sha256(content).hexdigest() - raw["url"] = url - raw["sha256"] = digest + if digest != raw["sha256"]: + raw["url"] = url + raw["sha256"] = digest destination = _safe_destination(root, Path(str(raw["destination"]))) destination.parent.mkdir(parents=True, exist_ok=True) From 77d0318e748984cfefb2227d2bcb1f99c043b9b9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sun, 20 Sep 2026 18:39:40 -0300 Subject: [PATCH 2/2] test: keep immutable pin when upstream content is unchanged Signed-off-by: Vitor Mattos --- tests/test_sync_upstream.py | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/test_sync_upstream.py b/tests/test_sync_upstream.py index 6b86f50..9769717 100644 --- a/tests/test_sync_upstream.py +++ b/tests/test_sync_upstream.py @@ -220,6 +220,53 @@ def test_refresh_updates_pin_hash_and_vendor_copy( "example/project", "master", "workflow.yml", "token" ) + @patch("scripts.sync_upstream._download") + @patch("scripts.sync_upstream._latest_commit") + def test_refresh_keeps_existing_pin_when_content_is_unchanged( + self, latest_commit, download + ) -> None: + old_commit = "0" * 40 + new_commit = "1" * 40 + content = b"name: Same\n" + digest = hashlib.sha256(content).hexdigest() + latest_commit.return_value = new_commit + download.return_value = content + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + manifest = root / "sources.json" + original_url = ( + "https://raw.githubusercontent.com/example/project/" + + old_commit + + "/workflow.yml" + ) + manifest.write_text( + json.dumps( + { + "sources": [ + { + "name": "workflow", + "repository": "example/project", + "ref": "master", + "path": "workflow.yml", + "url": original_url, + "sha256": digest, + "destination": "templates/workflow.yml", + } + ] + } + ), + encoding="utf-8", + ) + + refresh(manifest, root, token="token") + + payload = json.loads(manifest.read_text(encoding="utf-8")) + [source] = payload["sources"] + self.assertEqual(source["url"], original_url) + self.assertEqual(source["sha256"], digest) + self.assertEqual((root / "templates/workflow.yml").read_bytes(), content) + def test_rejects_destination_escape(self) -> None: content = b"name: Example\n" source = Source(