fix(gitea-sync): filter milestone issues with milestones=, adopt phase issues left by an interrupted run

Gitea ignores the singular milestone= query parameter and returns the
newest issues repo-wide, so list_issues() never filtered by milestone:
phase-state updates only saw issues on the first page. Use milestones=.

sync_spec() adopted an existing milestone but always created its three
phase issues, so a run killed between creating an issue and writing
spec.json created duplicates on the next run. Reuse an issue already in
the milestone with the same title.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-29 07:26:16 +00:00
parent cdc7b46d2d
commit 196eb90be6
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C

View file

@ -296,9 +296,11 @@ class GiteaAPI:
def list_issues(self, milestone_id=None, state="all"):
"""List issues, optionally filtered by milestone."""
params = [f"state={state}"]
params = [f"state={state}", "type=issues"]
if milestone_id is not None:
params.append(f"milestone={milestone_id}")
# Gitea filters on `milestones` (names or ids); it silently
# ignores `milestone` and returns the newest issues repo-wide.
params.append(f"milestones={milestone_id}")
url = "?" + "&".join(params)
_, issues = request("GET", f"/issues{url}")
return issues or []
@ -682,15 +684,27 @@ def sync_spec(api, spec_name, dry_run=False, quiet=False):
warn(f" could not find or create milestone for '{spec_name}'")
return
# Create phase issues
# Create phase issues, adopting any a run interrupted before its
# spec.json write-back already created in this milestone.
milestone_id = milestone["id"]
phase_issues = {}
existing_by_title = {
i["title"]: i["number"]
for i in api.list_issues(milestone_id=milestone_id, state="all")
}
for phase_def in PHASE_DEFS:
title = f"[{spec_name}] {phase_def['title_suffix']}"
state = _spec_desired_states(spec_data)[0][phase_def["key"]]
if title in existing_by_title:
phase_issues[phase_def["key"]] = existing_by_title[title]
if not quiet:
print(f" adopting existing issue "
f"#{existing_by_title[title]}: {title}")
continue
# Build body
if phase_def.get("body_fn_extra"):
body = phase_def["body_fn_extra"](spec_name, spec_data)