From c09a1b7594ee942ce2777be25dbb4f5528bb6092 Mon Sep 17 00:00:00 2001 From: randypanding <66171646+randypanding@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:51:16 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(backend):=20create=5Fcommit=20=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=BC=95=E7=94=A8=E7=BB=8F=E5=85=B8=E7=A9=BA=E6=A0=91?= =?UTF-8?q?=20SHA=E2=80=94=E2=80=94POST=20/git/trees=20=E7=A9=BA=E6=A0=91?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E8=A2=AB=20GitHub=20=E4=B8=80=E5=BE=8B=20422?= =?UTF-8?q?=EF=BC=88e2e=20.github#206=20=E5=AE=9E=E6=B5=8B=EF=BC=8Cconduct?= =?UTF-8?q?or=20run=2032493680834=EF=BC=89(1/2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arbiter/backend.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/arbiter/backend.py b/arbiter/backend.py index e84c73c..fcf12c3 100644 --- a/arbiter/backend.py +++ b/arbiter/backend.py @@ -114,16 +114,16 @@ def delete_ref(self, ref: str) -> None: raise NotFoundError(f"ref 不存在: {ref}") raise InfraError(f"deleteRef HTTP {status}: {payload.get('message', '')}") + # 经典空树 SHA(git mktree str: """空树 commit,message 内嵌 JSON(租约/台账载体,ADR-0054 §2)。""" - status, payload = self._request( - "POST", f"/repos/{self._repo}/git/trees", {}) - if status != 201: - raise InfraError(f"createTree HTTP {status}: {payload.get('message', '')}") - tree = payload.get("sha") - if not tree: - raise InfraError(f"createTree 未返回 sha: {payload}") - body = {"message": message, "tree": tree} + body = {"message": message, "tree": self.EMPTY_TREE_SHA} if parent: body["parents"] = [parent] status, payload = self._request( From 50ec39e0a6b46b05c1d06f48476ac415588bf93c Mon Sep 17 00:00:00 2001 From: randypanding <66171646+randypanding@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:51:30 +0800 Subject: [PATCH 2/2] =?UTF-8?q?test:=20GitHubRefBackend=20=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=BD=A2=E7=8A=B6=E5=9B=9E=E5=BD=92=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=EF=BC=88=E7=A9=BA=E6=A0=91=20SHA=20=E5=B8=B8=E9=87=8F/trees=20?= =?UTF-8?q?=E9=9B=B6=E8=B0=83=E7=94=A8/parent=20=E9=80=8F=E4=BC=A0?= =?UTF-8?q?=EF=BC=8C3=20=E4=BE=8B=EF=BC=89(2/2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_github_backend_wire.py | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/test_github_backend_wire.py diff --git a/tests/test_github_backend_wire.py b/tests/test_github_backend_wire.py new file mode 100644 index 0000000..470c7d9 --- /dev/null +++ b/tests/test_github_backend_wire.py @@ -0,0 +1,66 @@ +"""GitHubRefBackend 请求形状回归测试(W1-C2 补充)。 + +背景:create_commit 原实现先 POST /git/trees 空 body 建"空树",但 GitHub 对 +空树创建一律 422 "Invalid tree info"(e2e .github#206 演习实测, +conductor run 32493680834)——该路径在真实 API 上从未成功,单测因走 +LocalGitBackend 而未暴露。本测试在 HTTP 请求层(mock _request)锁定请求形状, +防再次引入"创建空树"调用。 +""" + +import unittest +from unittest.mock import patch + +from arbiter.backend import GitHubRefBackend + + +def make_backend(): + return GitHubRefBackend(token="t-test", repo="o/r") + + +class TestCreateCommitWire(unittest.TestCase): + def test_create_commit_uses_canonical_empty_tree_no_trees_call(self): + """create_commit 不得调用 /git/trees;commit 请求体 tree=经典空树 SHA。""" + calls = [] + + def fake_request(method, path, body=None): + calls.append((method, path, body)) + if path.endswith("/git/commits"): + return 201, {"sha": "c0ffee"} + raise AssertionError(f"意外调用: {method} {path}") + + b = make_backend() + with patch.object(b, "_request", side_effect=fake_request): + sha = b.create_commit("lease-meta-json", parent=None) + self.assertEqual(sha, "c0ffee") + self.assertEqual(len(calls), 1, "create_commit 应只发一次 POST /git/commits") + method, path, body = calls[0] + self.assertEqual(method, "POST") + self.assertTrue(path.endswith("/git/commits")) + self.assertEqual(body["tree"], GitHubRefBackend.EMPTY_TREE_SHA) + self.assertEqual(body["message"], "lease-meta-json") + self.assertNotIn("parents", body) # 无父提交(新租约) + + def test_create_commit_with_parent(self): + calls = [] + + def fake_request(method, path, body=None): + calls.append((method, path, body)) + return 201, {"sha": "beef"} + + b = make_backend() + with patch.object(b, "_request", side_effect=fake_request): + sha = b.create_commit("renew", parent="aaa") + self.assertEqual(sha, "beef") + self.assertEqual(calls[0][2]["parents"], ["aaa"]) # 接管=以旧租约为父 + + def test_empty_tree_sha_is_the_git_canonical_constant(self): + # 4b825dc…是空树的恒定 SHA(git mktree