Skip to content

Commit 9a916c4

Browse files
eduralphclaude
andcommitted
fix(publish): treat an empty patch.diff as a close, not a broken contribution (#95)
The close-disposition guard checked `patch.diff.is_file()` but not its content. A verify-first close can leave a 0-byte (or whitespace-only) patch.diff behind — `is_file()` let it past the guard, after which `git apply` was a no-op and the commit failed with "nothing to commit", aborting publish and leaving an empty local fix/ branch. Fold an emptiness check into the existing guard so a present-but-empty patch.diff short-circuits to the same non-fatal return 0 as a missing one. Add a red->green regression over both empty shapes (0-byte and whitespace-only), each of which the state machine reads as past-Do (COMPLETE) and so reaches publish. Closes #95. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Eduard Ralph <15236434+eduralph@users.noreply.github.com>
1 parent 7777f76 commit 9a916c4

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

template/src/pdca_harness/publish.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,16 @@ def publish(
7676
# patch.diff, so there is nothing to `git apply` / open a PR for. This is not a
7777
# failure — close the tracker item by hand. Return 0 so the continuous flow's
7878
# publish-on-accept doesn't error (mirrors skip_if_no_target).
79-
if not (d / "patch.diff").is_file():
80-
print(f"publish: {d.name} has no patch.diff (close / no-fix disposition) — "
81-
"nothing to contribute; close the tracker item by hand.", file=sys.stderr)
79+
#
80+
# A 0-byte / whitespace-only patch.diff counts as "no patch" too (issue #95): a
81+
# verify-first close can leave an empty patch.diff behind, and `is_file()` alone
82+
# would let it past this guard — after which `git apply` is a no-op and the commit
83+
# fails with "nothing to commit". Treat empty content the same as a missing file.
84+
patch = d / "patch.diff"
85+
if not patch.is_file() or not patch.read_text(encoding="utf-8").strip():
86+
print(f"publish: {d.name} has no (non-empty) patch.diff (close / no-fix "
87+
"disposition) — nothing to contribute; close the tracker item by hand.",
88+
file=sys.stderr)
8289
return 0
8390

8491
# Resolve the target from the brief (the contribution's where).

template/tests/test_publish_slice.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,27 @@ def test_skip_if_no_target_is_nonfatal(self) -> None:
130130
# …but a standalone publish (no skip) treats the missing target as an error.
131131
self.assertEqual(publish.publish(self.cfg, "NOTGT", dry_run=True), 1)
132132

133+
def test_empty_patch_is_treated_as_close_disposition(self) -> None:
134+
"""Regression (#95): a 0-byte / whitespace-only patch.diff is a close, not a
135+
broken contribution. `is_file()` alone let an empty patch past the guard, after
136+
which `git apply` was a no-op and the commit failed with 'nothing to commit'.
137+
138+
The #95 shape is an *empty patch.diff present* — which the state machine reads
139+
as past-Do, so the bundle is COMPLETE and reaches publish (unlike a missing
140+
patch.diff + close marker, the issue #60 path). Both empty shapes must
141+
short-circuit to a non-fatal 0 and plan nothing."""
142+
for iid, content in (("CLOSE0", ""), ("CLOSE1", "\n \n")):
143+
d = _bundle(self.cfg, iid, brief_body=_FIX_BRIEF, accepted=True)
144+
(d / "patch.diff").write_text(content, encoding="utf-8")
145+
self.assertEqual(state.state(d), state.COMPLETE) # empty patch ⇒ past-Do
146+
buf = io.StringIO()
147+
with redirect_stderr(buf):
148+
self.assertEqual(publish.publish(self.cfg, iid, dry_run=True), 0)
149+
self.assertIn("no (non-empty) patch.diff", buf.getvalue())
150+
# the guard returns before any contribution is planned/recorded
151+
self.assertFalse((d / "commit-msg.txt").exists())
152+
self.assertFalse((d / "publish.json").exists())
153+
133154
def test_commit_stages_patch_added_files(self) -> None:
134155
"""Regression (#23a): the commit must stage patch-ADDED files (the new
135156
regression test), not only modified-tracked ones — `git apply` + `add --all`

0 commit comments

Comments
 (0)