Skip to content

Commit 9ab86c3

Browse files
authored
fix: avoid upstream pin-only refreshes
* fix: avoid upstream pin churn without content changes Signed-off-by: Vitor Mattos <vitor@php.rio> * test: keep immutable pin when upstream content is unchanged Signed-off-by: Vitor Mattos <vitor@php.rio> --------- Signed-off-by: Vitor Mattos <vitor@php.rio>
1 parent c689409 commit 9ab86c3

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

‎scripts/sync_upstream.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,9 @@ def refresh(manifest_path: Path, root: Path, token: str | None = None) -> None:
130130
content = _download(url)
131131
digest = hashlib.sha256(content).hexdigest()
132132

133-
raw["url"] = url
134-
raw["sha256"] = digest
133+
if digest != raw["sha256"]:
134+
raw["url"] = url
135+
raw["sha256"] = digest
135136

136137
destination = _safe_destination(root, Path(str(raw["destination"])))
137138
destination.parent.mkdir(parents=True, exist_ok=True)

‎tests/test_sync_upstream.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,53 @@ def test_refresh_updates_pin_hash_and_vendor_copy(
220220
"example/project", "master", "workflow.yml", "token"
221221
)
222222

223+
@patch("scripts.sync_upstream._download")
224+
@patch("scripts.sync_upstream._latest_commit")
225+
def test_refresh_keeps_existing_pin_when_content_is_unchanged(
226+
self, latest_commit, download
227+
) -> None:
228+
old_commit = "0" * 40
229+
new_commit = "1" * 40
230+
content = b"name: Same\n"
231+
digest = hashlib.sha256(content).hexdigest()
232+
latest_commit.return_value = new_commit
233+
download.return_value = content
234+
235+
with tempfile.TemporaryDirectory() as directory:
236+
root = Path(directory)
237+
manifest = root / "sources.json"
238+
original_url = (
239+
"https://raw.githubusercontent.com/example/project/"
240+
+ old_commit
241+
+ "/workflow.yml"
242+
)
243+
manifest.write_text(
244+
json.dumps(
245+
{
246+
"sources": [
247+
{
248+
"name": "workflow",
249+
"repository": "example/project",
250+
"ref": "master",
251+
"path": "workflow.yml",
252+
"url": original_url,
253+
"sha256": digest,
254+
"destination": "templates/workflow.yml",
255+
}
256+
]
257+
}
258+
),
259+
encoding="utf-8",
260+
)
261+
262+
refresh(manifest, root, token="token")
263+
264+
payload = json.loads(manifest.read_text(encoding="utf-8"))
265+
[source] = payload["sources"]
266+
self.assertEqual(source["url"], original_url)
267+
self.assertEqual(source["sha256"], digest)
268+
self.assertEqual((root / "templates/workflow.yml").read_bytes(), content)
269+
223270
def test_rejects_destination_escape(self) -> None:
224271
content = b"name: Example\n"
225272
source = Source(

0 commit comments

Comments
 (0)