feat(bitbucket): add guarded cloud PR merge - #1471
KatalKavya96 wants to merge 4 commits into
Conversation
|
Hi @potiuk — next narrow #606 follow-up after #1194. This adds guarded Bitbucket Cloud Focused/full Bitbucket tests, Ruff, mypy, repository hooks, and workspace pytest are green. Would appreciate your review when you get a chance. |
Kaap10
left a comment
There was a problem hiding this comment.
Thanks for working on this @KatalKavya96!
The overall structure and Data Center fail-closed handling look solid. However, there are two discrepancies between the documented contract shape and the implementation:
-
Missing
--strategyin CLI & Payload:README.mdand the PR summary documentpr merge <id> --strategy {merge,squash,rebase}, but--strategyis missing from_build_parser()incli.py.cloud.merge_pull_request()currently hardcodespayload={"type": "pullrequest"}without accepting or mapping the strategy parameter to Bitbucket's API values (merge_commit,squash,fast_forward).
-
Missing
landed_refnormalization:- The documentation mentions returning the merge commit as
landed_refwhen available, but innormalize.py(merged_pull_request),landed_refis not extracted fromresult_data.get("merge_commit", {}).get("hash").
- The documentation mentions returning the merge commit as
Could you add --strategy to cli.py / cloud.py and extract landed_ref in normalize.py along with matching tests?
|
Thanks @Kaap10 I updated the PR to:
Thanks for catching the contract/implementation mismatch. |
potiuk
left a comment
There was a problem hiding this comment.
Thanks — --strategy is now wired end to end and landed_ref comes from merge_commit.hash, so the earlier review points are addressed. A merge is the highest-blast-radius write this bridge exposes, though, and a few things need tightening before it's approvable (details inline):
- Async merges (major). Bitbucket Cloud answers a slow merge with 202 and a
Locationpointing at the merge task-status resource.write_requestdrops the headers, an empty-body 202 makes this raise after the merge was actually submitted, and a finished task carries the hash undermerge_result.merge_commit.hash, which the normalizer doesn't read. - Head pinning, the
rebasemapping, README consistency, and failure-path tests (minor).
One contract question to settle in the README either way: pr merge performs no pre-merge gating — it doesn't check approvals, build status or merge checks, and Bitbucket Cloud only enforces merge checks server-side on Premium with the setting enabled. Either the adapter should check change_request.gates before POSTing, or the README row should say plainly that the caller must run pr merge-checks first.
This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. The findings
below are observations, not blockers; an Apache Magpie
maintainer — a real person — will take the next look at the
PR. If you think a finding is mis-applied, please reply on
the PR and a maintainer will weigh in.More on how Apache Magpie handles maintainer review:
CONTRIBUTING.md.
| "merge_strategy": merge_strategy, | ||
| }, | ||
| ) | ||
| if result is None: |
There was a problem hiding this comment.
major — Bitbucket Cloud's merge endpoint isn't always synchronous: past its synchronous window (or with ?async=true) it answers 202 with a Location header pointing at .../pullrequests/{id}/merge/task-status/{task_id}, and that resource carries task_status and merge_result (with merge_commit.hash). Today write_request drops headers so the task URL is lost; an empty-body 202 raises here and the CLI exits non-zero after the merge was submitted (inviting a confused retry); the 30s client timeout sits right at the synchronous window; and normalize.merged_pull_request reads merge_commit only at top level, so a finished task reports landed_ref: None. Please surface status + Location from the merge call, treat 202 as submitted (returning the task URL, or polling it with a bounded loop), read merge_result.merge_commit.hash, and add tests for the 202-with-Location and empty-body cases — the current queued-merge test uses a hand-built dict that no endpoint returns.
| "pull_request_id", | ||
| help="Pull request ID to merge.", | ||
| ) | ||
| pr_merge.add_argument( |
There was a problem hiding this comment.
minor — Nothing pins the head the maintainer confirmed: if the author pushes between confirmation and this call, the new commits land unreviewed. Bitbucket's merge API has no expected-hash parameter, so consider an --expected-source-commit <sha> that GETs the PR first and fails closed if source.commit.hash doesn't match (prefix-compare — Cloud returns short hashes). That narrows the window from human think-time to the GET→POST gap and makes "guarded" concrete.
| strategy_map = { | ||
| "merge": "merge_commit", | ||
| "squash": "squash", | ||
| "rebase": "fast_forward", |
There was a problem hiding this comment.
minor — fast_forward isn't a rebase: it fails unless the source already contains the destination tip, whereas rebase replays the commits. Bitbucket Cloud now appears to offer rebase strategies (rebase_fast_forward / rebase_merge) — please check the current API reference and map to the closer one. Separately, the output echoes the requested strategy; the land contract asks backends to report the strategy they actually used when they can't honour the request.
| and pull-request approve/unapprove, request-changes/remove-request-changes, and decline actions after the calling skill has obtained | ||
| and pull-request approve/unapprove, request-changes/remove-request-changes, decline, and merge actions after the calling skill has obtained | ||
| explicit user confirmation. Other writes, such as editing/deleting comments, | ||
| merging, creating/updating issues, changing branches, or triggering |
There was a problem hiding this comment.
minor — This paragraph now lists merge as supported and "merging" as out of scope two lines later. Please drop "merging," here (or say "Data Center merging"). The Write-path discipline section further down still lists only the older mutations and says "All other Bitbucket mutations remain out of scope", and the Invocation block has no pr merge example.
|
|
||
|
|
||
| @patch("magpie_bitbucket.client.urllib.request.build_opener") | ||
| def test_cloud_merge_pull_request_posts_default_payload( |
There was a problem hiding this comment.
minor — All new tests are happy-path (plus the Data Center refusal). Please add: a 409/400 from the merge endpoint surfacing as a non-zero CLI exit with Bitbucket's message; the result is None branch; argparse rejecting a missing or invalid --strategy before any request (assert no opener call); and pr merge via the CLI with BITBUCKET_KIND=datacenter exiting non-zero without an outbound request. The ..._posts_default_payload name is also stale now that --strategy is required.
Summary
magpie-bitbucket pr merge <id> --strategy {merge,squash,rebase}.land(id, strategy) -> landed_refcontract shape, including extraction of the resulting merge commit when available.Type of change
.claude/skills/<name>/) — eval fixtures updated belowtools/<system>/*.md)tools/*/withpyproject.toml)docs/,README.md,CONTRIBUTING.md)projects/_template/)prek, workflows, validators)Test plan
prek run --all-filespassesuv run pytest/ruff check/mypypasses(
PYTHONPATH=tools/skill-evals/src python3 -m skill_evals.runner tools/skill-evals/evals/<skill>/)(a regression test for the bug fixed / the behaviour added — see CONTRIBUTING.md)
landed_ref, Data Center fail-closed behavior, and CLI dispatchgit diff --checkpassesRFC-AI-0004 compliance
landcontract rather than introducing a new framework-level vendor