fix: replace broken _rebase_and_push call with cherry-pick in conflict retry
_retry_conflict_prs called _rebase_and_push which was never defined, causing NameError on every conflict retry. Now uses _cherry_pick_onto_main consistent with the primary merge path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
686ef3fd7f
commit
f25a4093c2
1 changed files with 17 additions and 14 deletions
31
lib/merge.py
31
lib/merge.py
|
|
@ -178,9 +178,8 @@ async def _claim_next_pr(conn, domain: str) -> dict | None:
|
|||
"""
|
||||
# Build prefix filter for pipeline-owned branches only
|
||||
# Agent branches stay approved but are NOT auto-merged (Leo: PRs #2141, #157, #2142, #2180)
|
||||
prefix_clauses = " OR ".join(
|
||||
f"p.branch LIKE '{pfx}%'" for pfx in PIPELINE_OWNED_PREFIXES
|
||||
)
|
||||
prefix_clauses = " OR ".join("p.branch LIKE ?" for _ in PIPELINE_OWNED_PREFIXES)
|
||||
prefix_params = [f"{pfx}%" for pfx in PIPELINE_OWNED_PREFIXES]
|
||||
row = conn.execute(
|
||||
f"""UPDATE prs SET status = 'merging', last_attempt = datetime('now')
|
||||
WHERE number = (
|
||||
|
|
@ -210,7 +209,7 @@ async def _claim_next_pr(conn, domain: str) -> dict | None:
|
|||
LIMIT 1
|
||||
)
|
||||
RETURNING number, source_path, branch, domain""",
|
||||
(domain,),
|
||||
(domain, *prefix_params),
|
||||
).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
|
@ -329,8 +328,10 @@ async def _cherry_pick_onto_main(branch: str) -> tuple[bool, str]:
|
|||
|
||||
if conflict_files and all(f.startswith("entities/") for f in conflict_files):
|
||||
# Entity conflicts: take main's version (entities are recoverable)
|
||||
# In cherry-pick: --ours = branch we're ON (clean branch from origin/main)
|
||||
# --theirs = commit being cherry-picked (extraction branch)
|
||||
for cf in conflict_files:
|
||||
await _git("checkout", "--theirs", cf, cwd=worktree_path)
|
||||
await _git("checkout", "--ours", cf, cwd=worktree_path)
|
||||
await _git("add", cf, cwd=worktree_path)
|
||||
dropped_entities.update(conflict_files)
|
||||
rc_cont, cont_out = await _git(
|
||||
|
|
@ -366,7 +367,9 @@ async def _cherry_pick_onto_main(branch: str) -> tuple[bool, str]:
|
|||
# Force-push clean branch as the original branch name
|
||||
# Capture expected SHA for force-with-lease
|
||||
rc, expected_sha = await _git("rev-parse", f"origin/{branch}")
|
||||
expected_sha = expected_sha.strip().split("\n")[0] if rc == 0 else ""
|
||||
if rc != 0:
|
||||
return False, f"rev-parse origin/{branch} failed: {expected_sha}"
|
||||
expected_sha = expected_sha.strip().split("\n")[0]
|
||||
|
||||
rc, out = await _git(
|
||||
"push",
|
||||
|
|
@ -972,8 +975,8 @@ async def _merge_domain_queue(conn, domain: str) -> tuple[int, int]:
|
|||
failed += 1
|
||||
continue
|
||||
|
||||
# Local ff-merge: push rebased branch as main (Rhea's approach, Leo+Rhea: local primary)
|
||||
# The branch was just rebased onto origin/main by _rebase_and_push,
|
||||
# Local ff-merge: push cherry-picked branch as main (Rhea's approach, Leo+Rhea: local primary)
|
||||
# The branch was just cherry-picked onto origin/main,
|
||||
# so origin/{branch} is a descendant of origin/main. Push it as main.
|
||||
await _git("fetch", "origin", branch, timeout=15)
|
||||
rc, main_sha = await _git("rev-parse", "origin/main")
|
||||
|
|
@ -1256,13 +1259,13 @@ async def _handle_permanent_conflicts(conn) -> int:
|
|||
|
||||
|
||||
async def _retry_conflict_prs(conn) -> tuple[int, int]:
|
||||
"""Retry rebase on conflict PRs that were previously approved.
|
||||
"""Retry conflict PRs via cherry-pick onto fresh main.
|
||||
|
||||
Design: Ganymede (extend merge stage), Rhea (safety guards), Leo (re-eval required).
|
||||
- Pick up PRs with status='conflict' and both approvals
|
||||
- Attempt fresh rebase onto origin/main
|
||||
- If rebase succeeds: force-push, reset to 'open' with verdicts cleared for re-eval
|
||||
- If rebase fails: increment attempt counter, leave as 'conflict'
|
||||
- Cherry-pick extraction commits onto fresh branch from origin/main
|
||||
- If cherry-pick succeeds: force-push, reset to 'open' with verdicts cleared for re-eval
|
||||
- If cherry-pick fails: increment attempt counter, leave as 'conflict'
|
||||
- After MAX_CONFLICT_REBASE_ATTEMPTS failures: mark 'conflict_permanent'
|
||||
- Skip branches with new commits since conflict was set (Rhea: someone is working on it)
|
||||
"""
|
||||
|
|
@ -1293,8 +1296,8 @@ async def _retry_conflict_prs(conn) -> tuple[int, int]:
|
|||
await _git("fetch", "origin", branch, timeout=30)
|
||||
await _git("fetch", "origin", "main", timeout=30)
|
||||
|
||||
# Attempt rebase
|
||||
ok, msg = await _rebase_and_push(branch)
|
||||
# Attempt cherry-pick onto fresh main (replaces rebase — Leo+Cory directive)
|
||||
ok, msg = await _cherry_pick_onto_main(branch)
|
||||
|
||||
if ok:
|
||||
# Rebase succeeded — reset for re-eval (Ganymede: approvals are stale after rebase)
|
||||
|
|
|
|||
Loading…
Reference in a new issue