diff --git a/docs/TESTING.md b/docs/TESTING.md index c3e8636..be1e573 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -1019,12 +1019,12 @@ bash scripts/test-worktree.sh - (31)(32) 紅 ⇒ 把「離線」當成「沒推」——拿雜訊懲罰謹慎。 -### A41 — 主線成員由相依邊決定,不靠同名里程碑:56 條 +### A41 — 主線成員由相依邊決定,不靠同名里程碑:57 條 ``` bash hooks/tests/mainline-members-by-dependency.test.sh ``` -**該看到**:`通過 56 條,失敗 0 條`。**全程離線**:Gitea 是本機一個假伺服器(`TICKET_HOST` 指過去, -照真 Gitea 回:相依 POST 201、已存在 409、issue 列表用 `milestones=` 名字查),狀態走 +**該看到**:`通過 57 條,失敗 0 條`。**全程離線**:Gitea 是本機一個假伺服器(`TICKET_HOST` 指過去, +照真 Gitea 回:相依 POST 201、**已存在回 500**(2026-09-07 實測,不是想像中的 409)、issue 列表用 `milestones=` 名字查),狀態走 `ISEP_COUNTDOWN_STATE_DIR`、repo 那份走 TMP 底下的假薄殼、`HOME` 也指到 TMP。 **不碰真家目錄、不碰真 Gitea、不碰真 InkStoneCo。** @@ -1048,7 +1048,7 @@ adopt 同 repo ⇒ 掛進 hub 里程碑;別 repo ⇒ POST 相依到 hub 票 - A 群 ⑥⑦ 紅 ⇒ **同名里程碑裡的票又被當成成員**——被推翻的判準回來了 - ⑪ 紅 ⇒ `set` 又去別的 repo 找同名的了 - ⑮⑯⑰ 任一紅 ⇒ `adopt` 別 repo 的票不是加相依:要嘛改了那張票的 milestone、要嘛建了里程碑 -- ⑳ 紅 ⇒ 重複 adopt 炸了(Gitea 409 沒被當成「邊本來就在」) +- ⑳⑳′ 紅 ⇒ 重複 adopt 炸了或重複 POST 了(冪等要靠「先讀 /dependencies」,真 Gitea 對重複 POST 回 500) - ㉔–㉙ 任一紅 ⇒ 沒 hub 票時沒擋、沒給路、或給的路**貼上去跑不動**(㉙ 把訊息裡那行原樣執行) - ㉛–㉞ 任一紅 ⇒ **票上驗法第 1 條破了**:同名里程碑關掉後成員掉出主線,或 `pick` 抓不到靠邊進來的票 - ㊱㊴㊵ 任一紅 ⇒ `has`/focus guard 沒去問 `/blocks`,只看 milestone 就下結論(那是「只知一半」) @@ -1061,6 +1061,7 @@ adopt 同 repo ⇒ 掛進 hub 里程碑;別 repo ⇒ POST 相依到 hub 票 (里程碑裡 4 + 相依收到 20),其中 `mira#6`、`ISEP#130`、`ISEP#140`、`arcrun-rag#104` 都在 ——**不必新增任何邊**,現有的相依圖已經連得到;`has` 四張都回「在」; `ticket pick --all` 主線組回 `Arcrun#197`、`mira#5`(後者靠 `Arcrun#86` 的邊,沒掛任何 hub 里程碑)。 +**寫入那半也真跑過一次**:`adopt inkstone/ISEP#133`(hub=InkStoneCo#44)POST 成功、`/blocks` 回 `InkStoneCo#44`、清空快取後 `has` 靠 `/blocks` 判「在」;重複 POST 真 Gitea 回 **500**(假伺服器原本猜 409——這就是「離線全綠之後一定要真跑一次」),修成先讀 `/dependencies`;測完 DELETE 復原(回 201,`/blocks` 空)。 這一走 19 秒(約 30 次序列 GET)⇒ SessionStart 的 `refresh` **不帶** `--members`(成本不變), `set`/`adopt`/`refresh --members` 才重收;`pick`/`has`/閘每次現問 Gitea,快取只餵 `show`。 diff --git a/hooks/tests/mainline-members-by-dependency.test.sh b/hooks/tests/mainline-members-by-dependency.test.sh index a32fc2f..3d1a610 100755 --- a/hooks/tests/mainline-members-by-dependency.test.sh +++ b/hooks/tests/mainline-members-by-dependency.test.sh @@ -14,7 +14,7 @@ # # 用法:bash hooks/tests/mainline-members-by-dependency.test.sh # 🔴 全程離線:Gitea 是本機一個假伺服器(TICKET_HOST 指過去),它照真 Gitea 回:相依 POST 201、 -# 已存在 409、issue 列表用 `milestones=` 名字查。狀態走 ISEP_COUNTDOWN_STATE_DIR、repo 那份走 +# 已存在 **500**(2026-09-07 實測,不是 409)、issue 列表用 `milestones=` 名字查。狀態走 ISEP_COUNTDOWN_STATE_DIR、repo 那份走 # TMP 底下的假薄殼、HOME 也指到 TMP。不碰真家目錄、不碰真 Gitea、不碰真 InkStoneCo。 set -u ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" @@ -105,7 +105,7 @@ class H(BaseHTTPRequestHandler): m = re.match(r"^/api/v1/repos/([^/]+)/([^/]+)/issues/(\d+)/dependencies$", p) if m: parent = "%s/%s#%s" % m.groups(); child = "%s/%s#%s" % (body["owner"], body["repo"], body["index"]) - if child in w["deps"].get(parent, []): return self._send(409, {"message": "dependency already exists"}) + if child in w["deps"].get(parent, []): return self._send(500, {"message": "", "url": "…/api/swagger"}) # 真 Gitea 2026-09-07 實測:重複相依回 500,不是 409 w["deps"].setdefault(parent, []).append(child); save(w) return self._send(201, issue_obj(w, parent)) if re.match(r"^/api/v1/repos/([^/]+)/([^/]+)/milestones$", p): @@ -209,7 +209,8 @@ M=$(members) case "$M" in *"inkstone/ISEP#130"*"inkstone/ISEP#140"*) ok "⑱ 成員多了 ISEP#130,**還有它的相依 ISEP#140**(邊會一路走下去)" ;; *) no "⑱ 成員沒更新到 #130/#140" "$M" ;; esac case "$OUT" in *"沒有多出任何里程碑"*) ok "⑲ 訊息明講那個 repo 沒有多出里程碑" ;; *) no "⑲ 訊息沒講" "$OUT" ;; esac OUT=$(ml adopt inkstone/ISEP#130 --via inkstone/Arcrun#86); RC=$? -[ "$RC" = 0 ] && ok "⑳ 同一張再 adopt 一次(Gitea 回 409)⇒ 仍是離開碼 0(冪等,不炸)" || no "⑳ 重複 adopt 炸了" "$OUT" +[ "$RC" = 0 ] && ok "⑳ 同一張再 adopt 一次 ⇒ 仍是離開碼 0(冪等:先讀 /dependencies,邊在就不 POST;真 Gitea 對重複 POST 回 500)" || no "⑳ 重複 adopt 炸了" "$OUT" +grep -c '^POST /api/v1/repos/inkstone/Arcrun/issues/86/dependencies' "$TMP/log" | grep -qx 1 && ok "⑳′ 第二次沒有再 POST(判準是 Gitea 有沒有這條邊,不是回應碼)" || no "⑳′ 重複 POST 了" "$(grep POST "$TMP/log")" : > "$TMP/log" OUT=$(ml adopt inkstone/arcrun-rag#37); RC=$? [ "$RC" = 0 ] && grep -q '^POST /api/v1/repos/inkstone/InkStoneCo/issues/44/dependencies' "$TMP/log" \ diff --git a/scripts/ticket b/scripts/ticket index 4093806..340f8e6 100755 --- a/scripts/ticket +++ b/scripts/ticket @@ -670,13 +670,22 @@ def _deps(owner, repo, num): def add_dependency(parent, child): - """讓 `child` 成為 `parent` 的相依(兩個都是 (owner, repo, num))。**冪等**:邊已經在 - (Gitea 回 409)就當成功,回 False——`mainline adopt` 補收同一張票兩次不該炸。 + """讓 `child` 成為 `parent` 的相依(兩個都是 (owner, repo, num))。**冪等**: + 先讀 parent 的 `/dependencies`,邊已經在就不 POST、回 False——`mainline adopt` + 補收同一張票兩次不該炸。 + 🔴 冪等**不能靠回應碼**:2026-09-07 對真 Gitea 實測,重複相依回的是 **500**(不是想像中的 409) + ——判準改成「Gitea 現在有沒有這條邊」這個事實;409 仍容忍(別的版本可能這樣回)。 `subtask` 與 `mainline adopt`(inkstone/ISEP#133)都走這一支:掛相依只有一條路, 而且走的是 `api()`——離線測試把 `api()` 換成錄音機時才錄得到它。""" global _TOLERATE po, pr, pn = parent co, cr, cn = child + want = f"{co}/{cr}#{cn}" + have = api(f"/repos/{po}/{pr}/issues/{pn}/dependencies?limit=100") or [] + for d in have: + full = ((d.get("repository") or {}).get("full_name")) if isinstance(d, dict) else None + if full and f"{full}#{d.get('number')}" == want: + return False # 已經是相依 _TOLERATE = (409,) try: r = api(f"/repos/{po}/{pr}/issues/{pn}/dependencies", {"owner": co, "repo": cr, "index": cn})