Skip to content

Commit 870e5d2

Browse files
authored
Merge pull request #111 from eduralph/fix/108-republish-force-with-lease
fix(publish): force-with-lease when re-publishing a rebuilt bundle
2 parents 2237921 + f65bfd3 commit 870e5d2

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

template/src/pdca_harness/publish.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,14 @@ def publish(
151151
# target checkout, so a DCO-gated host accepts the PR by construction; harmless
152152
# on non-DCO hosts (issue #81).
153153
git("commit", "-s", "-F", str((d / COMMIT_MSG).resolve())),
154-
git("push", "-u", "origin", branch),
154+
# `--force-with-lease`, not a plain push: re-publishing a rebuilt bundle (after
155+
# `signoff --iterate-do`) commits a FRESH `checkout -B branch origin/<base>` off
156+
# the current base, which is not a fast-forward of the previous attempt already on
157+
# the PR branch — a plain push is rejected and the re-Done bundle never publishes
158+
# (#108). The lease is safe: `fetch` above refreshed `origin/<branch>`, so the
159+
# force refuses if the remote moved unexpectedly, and it still creates the branch
160+
# on a first publish.
161+
git("push", "--force-with-lease", "-u", "origin", branch),
155162
]
156163
# A fork-based PR's --head must be OWNER:BRANCH — `gh` resolves a bare branch name
157164
# against the *base* repo (where the fork branch doesn't exist) and fails with

template/tests/test_publish_slice.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,45 @@ def test_publish_succeeds_with_dirty_target_tree(self) -> None:
229229
capture_output=True, text=True).stdout
230230
self.assertIn("fix/DIRTY-my-fix", refs)
231231

232+
def test_republish_force_updates_existing_pr_branch(self) -> None:
233+
# iterate-do (#108): re-publishing a rebuilt bundle commits a FRESH branch off the
234+
# current base and pushes it to the EXISTING PR branch — not a fast-forward of the
235+
# prior attempt. A plain push is rejected (the re-Done bundle never publishes);
236+
# publish must force-with-lease so the branch is updated to the rebuilt commit.
237+
import subprocess as sp
238+
repo = self.tmp / "checkout"
239+
origin = self.tmp / "origin.git"
240+
sp.run(["git", "init", "-q", "--bare", str(origin)], check=True)
241+
sp.run(["git", "clone", "-q", str(origin), str(repo)], check=True)
242+
run = lambda *a: sp.run(["git", "-C", str(repo), *a], check=True, capture_output=True)
243+
run("config", "user.email", "t@example.com")
244+
run("config", "user.name", "T")
245+
run("config", "commit.gpgsign", "false")
246+
(repo / "file.txt").write_text("base\n", encoding="utf-8")
247+
run("add", "-A"); run("commit", "-q", "-m", "base")
248+
run("branch", "-M", "main"); run("push", "-q", "-u", "origin", "main")
249+
250+
self.cfg.base_remote = "origin"
251+
self.cfg.repo_checkouts = {"example-org/example-repo": str(repo)}
252+
d = _bundle(self.cfg, "REDO", brief_body=_FIX_BRIEF, accepted=True)
253+
254+
def publish_fix(line: str) -> str:
255+
# A distinct fix off the same base → a sibling (non-ff) commit on re-publish.
256+
(d / "patch.diff").write_text(
257+
"diff --git a/file.txt b/file.txt\n--- a/file.txt\n+++ b/file.txt\n"
258+
f"@@ -1 +1,2 @@\n base\n+{line}\n", encoding="utf-8")
259+
buf = io.StringIO()
260+
with redirect_stdout(buf), redirect_stderr(buf):
261+
rc = publish.publish(self.cfg, "REDO", open_pr=False, by="T", today="2026-06-05")
262+
self.assertEqual(rc, 0, buf.getvalue())
263+
return sp.run(["git", "-C", str(repo), "ls-remote", "origin", "fix/REDO-my-fix"],
264+
capture_output=True, text=True).stdout.split()[0]
265+
266+
tip1 = publish_fix("first fix")
267+
tip2 = publish_fix("second fix") # was rejected (non-fast-forward) before #108
268+
self.assertTrue(tip1 and tip2)
269+
self.assertNotEqual(tip1, tip2) # the PR branch was force-updated to the rebuild
270+
232271
def test_pr_head_is_fork_owner_qualified(self) -> None:
233272
"""Regression (#23b): a fork-based PR's --head must be OWNER:BRANCH, else gh
234273
resolves the branch against the base repo and fails 'Head ref must be a

0 commit comments

Comments
 (0)