主線成員讀不到任何一條 /dependencies 時不准印出完整名單:set 不印 ✅ 並離開碼 3、refresh/adopt 保留舊快取並講出來(inkstone/ISEP#130 → c6973)
總管 09-13 實撞:Gitea 握手逾時,hub 票 InkStoneCo#132 本身讀得到、它的 /dependencies 讀不到, `mainline set` 仍印「✅ 成員 6 張」,底下三張前置全漏;`refresh --members` 零輸出、名單沒變。 雲端工人靠這份名單抓票,漏收=以為抓盡=收工,等於 v0.30.0 換個入口又停。 - collect_members:清單讀取拿不到就重讀(ISEP_MEMBER_RETRIES 預設 2、退避 2s×n); 仍讀不到的票記進 info["unread"],info["complete"]=沒有 unread 且沒被上限截斷 - set:不完整時印 ⚠️(🔴 成員不完整)+點名讀不到的票,主線檔寫 members_complete/members_unread, 同一條線保留舊快取成員,離開碼 3(主線本身照樣標定) - refresh --members:完整才蓋快取並印成員張數;不完整保留舊快取並講出來;讀不到里程碑也講 - adopt:不完整時與舊快取取聯集 重現(改之前跑新測試):F1–F5、F9–F11、F13–F14 共 11 條紅——set 印 ✅、arcrun-rag#104 漏收、 refresh --members 零輸出且把快取蓋成少兩張的名單。 修後:mainline-members-by-dependency 70/71(唯一紅的 ㊽ 在 main 上同樣紅)、repo-mirror 17/17、 gitea-fallback 31/31、focus-guard 24/24、ticket-pick 58/58、debt-worklist 47/47。 真 Gitea 唯讀跑(不寫主線檔):InkStoneCo#65 成員 9 張、complete=True、hub #132 底下三張都收到。 沒動 InkStoneCo 的 system-dev/mainline.json。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+31
-5
@@ -364,18 +364,42 @@ def collect_members(api, ms, cap=MEMBER_WALK_CAP):
|
||||
回 (members, info):
|
||||
members 排好序的 `owner/repo#N` 清單(含已關的——進度與 `has` 都要看得到它們)
|
||||
info {"milestone": [...], "via_deps": [...], "hub_ticket": ref|None,
|
||||
"reached": bool, "capped": bool}
|
||||
"reached": bool, "capped": bool, "unread": [...], "complete": bool}
|
||||
🔴 `reached=False` = 連 hub 里程碑那一頁都讀不到 ⇒ 呼叫端**不要拿空清單蓋掉舊快取**
|
||||
(讀不到 ≠ 沒有成員)。
|
||||
🔴 `unread` = 讀不到 `/dependencies` 的票(重讀過仍失敗)。**只要有一張,名單就不完整**:
|
||||
那張底下的前置全部沒收到(inkstone/ISEP#130 c6973:Gitea 握手逾時,hub 票底下三張全漏,
|
||||
`set` 卻印 ✅)。`complete`=reached 且沒有 unread 且沒被上限截斷——呼叫端只准在它為真時
|
||||
把名單當成完整的寫出去。
|
||||
判準只有兩個 Gitea 事實:票的 `milestone.id` 等於錨的 id、以及相依邊。標題只拿來查詢。
|
||||
"""
|
||||
import time
|
||||
import urllib.parse
|
||||
try:
|
||||
retries = max(0, int(os.environ.get("ISEP_MEMBER_RETRIES", "2")))
|
||||
except ValueError:
|
||||
retries = 2
|
||||
try:
|
||||
backoff = max(0.0, float(os.environ.get("ISEP_MEMBER_BACKOFF", "2")))
|
||||
except ValueError:
|
||||
backoff = 2.0
|
||||
|
||||
def read_list(path):
|
||||
"""讀一頁清單;拿不到 list 就重讀(握手逾時常常下一次就好)。仍拿不到回 None。"""
|
||||
for n in range(retries + 1):
|
||||
got = api(path)
|
||||
if isinstance(got, list):
|
||||
return got
|
||||
if n < retries and backoff:
|
||||
time.sleep(backoff * (n + 1))
|
||||
return None
|
||||
|
||||
owner, repo, mid = ms.get("owner"), ms.get("repo"), ms.get("id")
|
||||
title = ms.get("title") or ""
|
||||
info = {"milestone": [], "via_deps": [], "hub_ticket": hub_ticket(ms),
|
||||
"reached": False, "capped": False}
|
||||
"reached": False, "capped": False, "unread": [], "complete": False}
|
||||
q = urllib.parse.urlencode({"state": "all", "type": "issues", "milestones": title, "limit": 100})
|
||||
rows = api("/repos/%s/%s/issues?%s" % (owner, repo, q))
|
||||
rows = read_list("/repos/%s/%s/issues?%s" % (owner, repo, q))
|
||||
if not isinstance(rows, list):
|
||||
return [], info
|
||||
info["reached"] = True
|
||||
@@ -400,9 +424,10 @@ def collect_members(api, ms, cap=MEMBER_WALK_CAP):
|
||||
parts = split_ref(cur)
|
||||
if not parts:
|
||||
continue
|
||||
deps = api("/repos/%s/%s/issues/%d/dependencies?limit=100" % parts)
|
||||
deps = read_list("/repos/%s/%s/issues/%d/dependencies?limit=100" % parts)
|
||||
if not isinstance(deps, list):
|
||||
continue # 這一張讀不到:跳過它,不當成「沒有相依」以外的事
|
||||
info["unread"].append(cur) # 讀不到 ≠ 沒有相依:記下來,名單就不算完整
|
||||
continue
|
||||
for d in deps:
|
||||
r = issue_ref(d)
|
||||
if not r or r in seen:
|
||||
@@ -412,6 +437,7 @@ def collect_members(api, ms, cap=MEMBER_WALK_CAP):
|
||||
break
|
||||
seen.add(r); out.append(r); queue.append(r)
|
||||
info["via_deps"].append(r)
|
||||
info["complete"] = not info["unread"] and not info["capped"]
|
||||
return sorted(set(out)), info
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user