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
|
# Build prefix filter for pipeline-owned branches only
|
||||||
# Agent branches stay approved but are NOT auto-merged (Leo: PRs #2141, #157, #2142, #2180)
|
# Agent branches stay approved but are NOT auto-merged (Leo: PRs #2141, #157, #2142, #2180)
|
||||||
prefix_clauses = " OR ".join(
|
prefix_clauses = " OR ".join("p.branch LIKE ?" for _ in PIPELINE_OWNED_PREFIXES)
|
||||||
f"p.branch LIKE '{pfx}%'" for pfx in PIPELINE_OWNED_PREFIXES
|
prefix_params = [f"{pfx}%" for pfx in PIPELINE_OWNED_PREFIXES]
|
||||||
)
|
|
||||||
row = conn.execute(
|
row = conn.execute(
|
||||||
f"""UPDATE prs SET status = 'merging', last_attempt = datetime('now')
|
f"""UPDATE prs SET status = 'merging', last_attempt = datetime('now')
|
||||||
WHERE number = (
|
WHERE number = (
|
||||||
|
|
@ -210,7 +209,7 @@ async def _claim_next_pr(conn, domain: str) -> dict | None:
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)
|
)
|
||||||
RETURNING number, source_path, branch, domain""",
|
RETURNING number, source_path, branch, domain""",
|
||||||
(domain,),
|
(domain, *prefix_params),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
return dict(row) if row else None
|
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):
|
if conflict_files and all(f.startswith("entities/") for f in conflict_files):
|
||||||
# Entity conflicts: take main's version (entities are recoverable)
|
# 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:
|
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)
|
await _git("add", cf, cwd=worktree_path)
|
||||||
dropped_entities.update(conflict_files)
|
dropped_entities.update(conflict_files)
|
||||||
rc_cont, cont_out = await _git(
|
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
|
# Force-push clean branch as the original branch name
|
||||||
# Capture expected SHA for force-with-lease
|
# Capture expected SHA for force-with-lease
|
||||||
rc, expected_sha = await _git("rev-parse", f"origin/{branch}")
|
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(
|
rc, out = await _git(
|
||||||
"push",
|
"push",
|
||||||
|
|
@ -972,8 +975,8 @@ async def _merge_domain_queue(conn, domain: str) -> tuple[int, int]:
|
||||||
failed += 1
|
failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Local ff-merge: push rebased branch as main (Rhea's approach, Leo+Rhea: local primary)
|
# Local ff-merge: push cherry-picked branch as main (Rhea's approach, Leo+Rhea: local primary)
|
||||||
# The branch was just rebased onto origin/main by _rebase_and_push,
|
# The branch was just cherry-picked onto origin/main,
|
||||||
# so origin/{branch} is a descendant of origin/main. Push it as main.
|
# so origin/{branch} is a descendant of origin/main. Push it as main.
|
||||||
await _git("fetch", "origin", branch, timeout=15)
|
await _git("fetch", "origin", branch, timeout=15)
|
||||||
rc, main_sha = await _git("rev-parse", "origin/main")
|
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]:
|
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).
|
Design: Ganymede (extend merge stage), Rhea (safety guards), Leo (re-eval required).
|
||||||
- Pick up PRs with status='conflict' and both approvals
|
- Pick up PRs with status='conflict' and both approvals
|
||||||
- Attempt fresh rebase onto origin/main
|
- Cherry-pick extraction commits onto fresh branch from origin/main
|
||||||
- If rebase succeeds: force-push, reset to 'open' with verdicts cleared for re-eval
|
- If cherry-pick succeeds: force-push, reset to 'open' with verdicts cleared for re-eval
|
||||||
- If rebase fails: increment attempt counter, leave as 'conflict'
|
- If cherry-pick fails: increment attempt counter, leave as 'conflict'
|
||||||
- After MAX_CONFLICT_REBASE_ATTEMPTS failures: mark 'conflict_permanent'
|
- After MAX_CONFLICT_REBASE_ATTEMPTS failures: mark 'conflict_permanent'
|
||||||
- Skip branches with new commits since conflict was set (Rhea: someone is working on it)
|
- 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", branch, timeout=30)
|
||||||
await _git("fetch", "origin", "main", timeout=30)
|
await _git("fetch", "origin", "main", timeout=30)
|
||||||
|
|
||||||
# Attempt rebase
|
# Attempt cherry-pick onto fresh main (replaces rebase — Leo+Cory directive)
|
||||||
ok, msg = await _rebase_and_push(branch)
|
ok, msg = await _cherry_pick_onto_main(branch)
|
||||||
|
|
||||||
if ok:
|
if ok:
|
||||||
# Rebase succeeded — reset for re-eval (Ganymede: approvals are stale after rebase)
|
# Rebase succeeded — reset for re-eval (Ganymede: approvals are stale after rebase)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue