ptl-tool: recover cleanly from merge conflicts instead of crashing
build_branch()'s per-PR merge loop called G.git.merge() with no error
handling. When a PR conflicts with changes already merged earlier in
the same run (two PRs in one label touching the same file is a real,
reproducible case), GitCommandError propagated all the way up through
main() as a raw traceback -- and left the git index in an unresolved
"needs merge" state. The next invocation against that same checkout
then failed a later, unrelated git checkout with "you need to resolve
your current index first", a confusing secondary symptom of the real
problem. Already reported as feedback on
https://github.com/ceph/ceph/pull/70549#issuecomment-
5123585401.
Extract the merge call into merge_pr_or_abort(), which catches
GitCommandError, runs `git merge --abort` to restore a clean working
tree (guarded so an abort failure can't mask the original error), logs
which PR failed, and exits via SystemExit with an actionable message.
Also add ensure_clean_checkout(G), called at the start of
build_branch() right after G = git.Repo(args.git). It detects a
checkout already stuck from a previous run that crashed or was
killed -- an in-progress merge (MERGE_HEAD), an in-progress
cherry-pick (CHERRY_PICK_HEAD), or uncommitted tracked changes -- and
raises SystemExit with manual-cleanup instructions rather than
auto-mutating the checkout on the operator's behalf (per batrick's
review on this PR). A clean checkout (the normal case) is untouched.
Verified against real conflicting merges (not just mocks): the repo is
left clean after merge_pr_or_abort() aborts, and MERGE_HEAD is
confirmed to still exist after ensure_clean_checkout() raises, proving
no auto-abort occurs there. All 20 unit tests pass.
Fixes: https://tracker.ceph.com/issues/78991
Signed-off-by: Yuri Weinstein <yweinste@redhat.com>