Files
zeekayandHanzo Dev b497511c04 no hogs: stamphog and review_hog are stamp and review
Both are Django apps, and both pin `label` in apps.py — "stamp" and "review".
django_migrations keys a row on (app_label, name), and app_label comes from that
pin and not from the import path, so moving the directory does not orphan a
single migration. All six stay where they are, untouched.

  products/stamphog -> products/stamp     name = products.stamp.backend
  products/review_hog -> products/review  name = products.review.backend
  StamphogScene/ -> StampScene/, and stamphogSceneLogic.ts with it

189 files follow. An earlier attempt at this move failed twice for reasons worth
recording: `git mv A B` where B exists puts A INSIDE B, and a stray
products/stamp/node_modules from an abandoned run was enough to make B exist —
the app ended up nested two deep. And `git ls-files` does not list a file moved
with plain mv and not yet staged, so a pass over "every tracked file" silently
skips it; that is how StampScene.tsx kept importing ./stampSceneLogic after the
file beside it had been renamed back.

bin/check-imports-are-committed: all first-party imports across 15,843 files
resolve.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 10:18:18 -07:00

67 lines
2.9 KiB
Python

"""Shared retraction of stale stamp approvals when a PR's head moves.
GitHub never auto-dismisses an APPROVE review when new commits land, so a prior run's approval at an
old head keeps satisfying required reviews after a push. Two paths must retract it, and they share this
one implementation:
* the review workflow's dismiss_stale_approvals activity (runs before re-reviewing), and
* the Celery task's LABEL-mode skip path — a head-changing event whose trigger label is absent never
enters the workflow, so the standing approval would otherwise survive an unreviewed push.
"""
from __future__ import annotations
from django.db import router
from django.utils import timezone
from ..models import PullRequest, ReviewRun, StampRepoConfig
from .github_client import StampGitHubClient
DEFAULT_DISMISS_MESSAGE = (
"New commits were pushed — dismissing the stamp approval from an earlier head; a re-review runs automatically."
)
def dismiss_stale_approvals_for_head(
team_id: int,
pull_request: PullRequest,
repo_config: StampRepoConfig,
current_head_sha: str,
message: str = DEFAULT_DISMISS_MESSAGE,
) -> int:
"""Retract every un-dismissed stamp approval posted at a head other than ``current_head_sha``.
Keyed off ``posted_review_id`` alone — only the approve path ever sets it, and filtering on
``verdict=APPROVED`` too would miss a run that posted its approval but crashed (or lost a
supersession race) before the verdict was saved, leaving that orphan standing forever.
Returns the number dismissed. Idempotent via ``approval_dismissed_at``: a run already stamped is
excluded, so a retry (or the activity re-running after the task skip already handled it) won't
re-dismiss. Each dismissal is stamped right after its GitHub call so a mid-loop failure resumes
cleanly on retry.
"""
# Writer pin: a head-changing delivery can run this sweep moments after post_verdict committed
# posted_review_id — a lagged reader would see no approval and skip the retraction entirely.
stale_runs = list(
ReviewRun.objects.for_team(team_id)
.using(router.db_for_write(ReviewRun))
.filter(
pull_request=pull_request,
posted_review_id__isnull=False,
approval_dismissed_at__isnull=True,
)
.exclude(head_sha=current_head_sha)
)
if not stale_runs:
return 0
client = StampGitHubClient(repo_config.installation_id)
dismissed = 0
for stale in stale_runs:
if stale.posted_review_id is None:
continue # excluded by the query filter; narrows the Optional for the type checker
client.dismiss_pr_review(repo_config.repository, pull_request.pr_number, stale.posted_review_id, message)
stale.approval_dismissed_at = timezone.now()
stale.save(update_fields=["approval_dismissed_at", "updated_at"])
dismissed += 1
return dismissed