fix: make pull merges durably auditable (Closes #1028)
CI / lint (pull_request) Successful in 2m54s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 2m8s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-17 15:28:00 +00:00
1 parent f687389046
commit 6729359ba2
2 files changed
+178 -13

No files matched your search

+46 -13
View File
@@ -5918,31 +5918,60 @@ async def merge_assigned_pull(
target=f"{repository}#{number}",
)
async def merge_pull():
if not await gitea_proxy.is_assigned_pull(repository, number):
raise HTTPException(status_code=404, detail="Assigned pull request not found")
return await gitea_proxy.merge_assigned_pull(
repository, number, submission.expected_head_sha
try:
assigned = await asyncio.wait_for(
gitea_proxy.is_assigned_pull(repository, number),
timeout=ISSUE_ACTION_TIMEOUT_SECONDS,
)
except Exception:
return JSONResponse(
{"error": "The pull request assignment could not be verified. Nothing was merged."},
status_code=503,
headers={"Retry-After": "1"},
)
if not assigned:
raise HTTPException(status_code=404, detail="Assigned pull request not found")
journal = _security_event_store()
try:
operation_id = await asyncio.to_thread(
journal.reserve,
"pull_merged",
target=f"{repository}#{number}",
)
except SecurityEventStoreError:
return JSONResponse(
{"error": "Security activity is temporarily unavailable. Nothing was merged."},
status_code=503,
headers={"Retry-After": "1"},
)
try:
result = await asyncio.wait_for(
merge_pull(), timeout=ISSUE_ACTION_TIMEOUT_SECONDS
)
await asyncio.to_thread(
_security_event_store().record,
"pull_merged",
target=f"{repository}#{number}",
gitea_proxy.merge_assigned_pull(
repository, number, submission.expected_head_sha
),
timeout=ISSUE_ACTION_TIMEOUT_SECONDS,
)
try:
await asyncio.to_thread(journal.finalize, operation_id)
except SecurityEventStoreError:
pass
return result
except HTTPException:
raise
except gitea_proxy.StalePullError:
try:
await asyncio.to_thread(journal.discard, operation_id)
except SecurityEventStoreError:
pass
return JSONResponse(
{"error": "New commits were pushed. Refresh before merging."},
status_code=409,
)
except gitea_proxy.PullNotMergeableError:
try:
await asyncio.to_thread(journal.discard, operation_id)
except SecurityEventStoreError:
pass
return JSONResponse(
{"error": "This pull request is not currently safe to merge. Refresh its status."},
status_code=409,
@@ -5958,6 +5987,10 @@ async def merge_assigned_pull(
except Exception:
merged = False
if merged:
try:
await asyncio.to_thread(journal.finalize, operation_id)
except SecurityEventStoreError:
pass
return {"number": number, "merged": True, "state": "closed"}
return JSONResponse(
{