BBC2-15 add bb pr merge - #22

Open
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge
Open

BBC2-15 add bb pr merge#22
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge

Conversation

@b2l

@b2lb2l commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bb pr merge [id] that merges an open PR. Flags: --strategy/-s, --message/-m, --delete. Defaults to the PR for the current branch.
  • getPullRequest now requests destination.branch.default_merge_strategy and destination.branch.merge_strategies additively via fields=+... — both are omitted from Bitbucket's default serialization (see docs/bb-notes.md → "Default merge strategy"). Exposed on PullRequestDetail as defaultMergeStrategy / allowedMergeStrategies.
  • --strategy is validated client-side against the branch's allow-list before the POST, so users get a clean message listing permitted strategies rather than an opaque 400.
  • Without --strategy, the destination branch's configured default is used; when the branch has none, we fall back to Bitbucket's own default merge_commit.
  • 202 responses are polled against the Location header's task-status URL with capped exponential backoff (15 attempts, 500ms → 4s). 409 and 555 surface as retry-friendly errors from the backend. No auto-retry.
  • --delete sends close_source_branch: true (remote-side cleanup) and, client-side, deletes the matching local branch with git branch -d. Guards: only when the local name matches the PR's source; switch to destination first when currently on the source; warn (non-fatal) if git refuses.
  • Extends GitRunner with hasLocalBranch, checkoutExistingBranch, deleteLocalBranch; adds GitError which carries git's stderr.

Test plan

  • bun test — 170 tests pass (13 new backend tests: merge happy path, message/delete body shaping, 202 + Location polling, 202 missing Location, 409, 555, task-status PENDING / SUCCESS / FAILED / malformed / non-2xx; 2 new fields-mapping tests on getPullRequest)
  • bun run lint clean
  • bunx tsc --noEmit clean
  • Manual: bb pr merge --help shows all flags
  • Manual: bb pr merge in a non-Bitbucket repo fails with the -R hint at the repo-resolve step (as expected)

Manual test path

Pick a throwaway open PR you're willing to merge. Replace <N> with its id.

  1. bb pr merge <N> — merges using the destination branch's default strategy. Expect Merged pull request #<N>: <url>.
  2. bb pr merge <N> --strategy squash — explicit strategy. If the branch allows squash, it merges. If not, expect Merge strategy 'squash' is not allowed on branch 'main'. Allowed: merge_commit.
  3. bb pr merge <N> --strategy bogus — expect Unknown merge strategy 'bogus'. Allowed: merge_commit, squash, fast_forward, squash_fast_forward, rebase_fast_forward, rebase_merge.
  4. bb pr merge <N> --message "custom merge msg" — merges with your message as the merge commit message.
  5. On a branch that is the PR's source branch, with a clean working tree: bb pr merge <N> --delete. Expect Merged pull request #<N>: <url> then Deleted local branch '<source>'. — you should end up on the destination branch.
  6. On a branch that is NOT the source, with the source branch present locally: bb pr merge <N> --delete. Same outcome; no branch switching.
  7. Without a local source branch: bb pr merge <N> --delete merges remotely and skips the local delete silently.
  8. With a dirty working tree: bb pr merge <N> --delete merges remotely, then the local delete fails with a clear Warning: could not clean up local branch '<source>': ... line; exit code still 0.
  9. Attempt to merge an already-merged or declined PR: expect Pull request #<N> is merged; cannot merge. (or declined) — no API call made.
  10. bb --json pr merge <N> emits the full merged PR detail object.

For the 202 path, no reliable manual setup — covered by backend tests. Keep an eye on timing if you do hit it in the wild; the polling budget is ~30s.

Closes BBC2-15.

Merges an open PR via POST /pullrequests/{id}/merge with the destination
branch's default strategy, or an override via --strategy. Supports
--message for a custom merge commit message and --delete for server-side
+ local source branch cleanup.
Strategy resolution:
- --strategy validates against the destination branch's merge_strategies
allow-list (surfaced client-side with a helpful message listing the
permitted set) before the POST, rather than taking an opaque 400 from
the API.
- Without --strategy, use destination.branch.default_merge_strategy (set
via the branch's merge settings in the web UI); fall back to the API's
own default merge_commit when unset.
Both strategy fields are omitted from the default /pullrequests/{id}
serialization. Request them additively via fields=+... (see docs/bb-notes
under "Default merge strategy"). That's done in getPullRequest globally —
the extra fields are also useful to future consumers (e.g. BBC2-44) and
`+` additive syntax doesn't drop existing fields.
Response handling:
- 200 → synchronous success, print PR URL.
- 202 → poll task-status with capped exponential backoff (15 attempts,
500ms → 4s), resolve to SUCCESS or FAILED or time out with a retry
message.
- 409 / 555 → clean retry-friendly messages from the backend layer.
--delete local cleanup is best-effort. Guards: only delete when local
branch name matches the PR's source; switch to destination first when
currently on the source; fall back to a warning (non-fatal) if git
refuses (dirty tree, non-FF, etc.) — the server-side merge already
landed.
Extends GitRunner with the write primitives checkoutExistingBranch and
deleteLocalBranch (plus the hasLocalBranch query), and adds GitError
whose message carries git's stderr so users see git's own diagnostics.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

BBC2-15 add bb pr merge - #22

Open
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge
Open

BBC2-15 add bb pr merge#22
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge

Conversation

@b2l

@b2lb2l commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bb pr merge [id] that merges an open PR. Flags: --strategy/-s, --message/-m, --delete. Defaults to the PR for the current branch.
  • getPullRequest now requests destination.branch.default_merge_strategy and destination.branch.merge_strategies additively via fields=+... — both are omitted from Bitbucket's default serialization (see docs/bb-notes.md → "Default merge strategy"). Exposed on PullRequestDetail as defaultMergeStrategy / allowedMergeStrategies.
  • --strategy is validated client-side against the branch's allow-list before the POST, so users get a clean message listing permitted strategies rather than an opaque 400.
  • Without --strategy, the destination branch's configured default is used; when the branch has none, we fall back to Bitbucket's own default merge_commit.
  • 202 responses are polled against the Location header's task-status URL with capped exponential backoff (15 attempts, 500ms → 4s). 409 and 555 surface as retry-friendly errors from the backend. No auto-retry.
  • --delete sends close_source_branch: true (remote-side cleanup) and, client-side, deletes the matching local branch with git branch -d. Guards: only when the local name matches the PR's source; switch to destination first when currently on the source; warn (non-fatal) if git refuses.
  • Extends GitRunner with hasLocalBranch, checkoutExistingBranch, deleteLocalBranch; adds GitError which carries git's stderr.

Test plan

  • bun test — 170 tests pass (13 new backend tests: merge happy path, message/delete body shaping, 202 + Location polling, 202 missing Location, 409, 555, task-status PENDING / SUCCESS / FAILED / malformed / non-2xx; 2 new fields-mapping tests on getPullRequest)
  • bun run lint clean
  • bunx tsc --noEmit clean
  • Manual: bb pr merge --help shows all flags
  • Manual: bb pr merge in a non-Bitbucket repo fails with the -R hint at the repo-resolve step (as expected)

Manual test path

Pick a throwaway open PR you're willing to merge. Replace <N> with its id.

  1. bb pr merge <N> — merges using the destination branch's default strategy. Expect Merged pull request #<N>: <url>.
  2. bb pr merge <N> --strategy squash — explicit strategy. If the branch allows squash, it merges. If not, expect Merge strategy 'squash' is not allowed on branch 'main'. Allowed: merge_commit.
  3. bb pr merge <N> --strategy bogus — expect Unknown merge strategy 'bogus'. Allowed: merge_commit, squash, fast_forward, squash_fast_forward, rebase_fast_forward, rebase_merge.
  4. bb pr merge <N> --message "custom merge msg" — merges with your message as the merge commit message.
  5. On a branch that is the PR's source branch, with a clean working tree: bb pr merge <N> --delete. Expect Merged pull request #<N>: <url> then Deleted local branch '<source>'. — you should end up on the destination branch.
  6. On a branch that is NOT the source, with the source branch present locally: bb pr merge <N> --delete. Same outcome; no branch switching.
  7. Without a local source branch: bb pr merge <N> --delete merges remotely and skips the local delete silently.
  8. With a dirty working tree: bb pr merge <N> --delete merges remotely, then the local delete fails with a clear Warning: could not clean up local branch '<source>': ... line; exit code still 0.
  9. Attempt to merge an already-merged or declined PR: expect Pull request #<N> is merged; cannot merge. (or declined) — no API call made.
  10. bb --json pr merge <N> emits the full merged PR detail object.

For the 202 path, no reliable manual setup — covered by backend tests. Keep an eye on timing if you do hit it in the wild; the polling budget is ~30s.

Closes BBC2-15.

Merges an open PR via POST /pullrequests/{id}/merge with the destination
branch's default strategy, or an override via --strategy. Supports
--message for a custom merge commit message and --delete for server-side
+ local source branch cleanup.
Strategy resolution:
- --strategy validates against the destination branch's merge_strategies
allow-list (surfaced client-side with a helpful message listing the
permitted set) before the POST, rather than taking an opaque 400 from
the API.
- Without --strategy, use destination.branch.default_merge_strategy (set
via the branch's merge settings in the web UI); fall back to the API's
own default merge_commit when unset.
Both strategy fields are omitted from the default /pullrequests/{id}
serialization. Request them additively via fields=+... (see docs/bb-notes
under "Default merge strategy"). That's done in getPullRequest globally —
the extra fields are also useful to future consumers (e.g. BBC2-44) and
`+` additive syntax doesn't drop existing fields.
Response handling:
- 200 → synchronous success, print PR URL.
- 202 → poll task-status with capped exponential backoff (15 attempts,
500ms → 4s), resolve to SUCCESS or FAILED or time out with a retry
message.
- 409 / 555 → clean retry-friendly messages from the backend layer.
--delete local cleanup is best-effort. Guards: only delete when local
branch name matches the PR's source; switch to destination first when
currently on the source; fall back to a warning (non-fatal) if git
refuses (dirty tree, non-FF, etc.) — the server-side merge already
landed.
Extends GitRunner with the write primitives checkoutExistingBranch and
deleteLocalBranch (plus the hasLocalBranch query), and adds GitError
whose message carries git's stderr so users see git's own diagnostics.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

BBC2-15 add bb pr merge - #22

Open
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge
Open

BBC2-15 add bb pr merge#22
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge

Conversation

@b2l

@b2lb2l commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bb pr merge [id] that merges an open PR. Flags: --strategy/-s, --message/-m, --delete. Defaults to the PR for the current branch.
  • getPullRequest now requests destination.branch.default_merge_strategy and destination.branch.merge_strategies additively via fields=+... — both are omitted from Bitbucket's default serialization (see docs/bb-notes.md → "Default merge strategy"). Exposed on PullRequestDetail as defaultMergeStrategy / allowedMergeStrategies.
  • --strategy is validated client-side against the branch's allow-list before the POST, so users get a clean message listing permitted strategies rather than an opaque 400.
  • Without --strategy, the destination branch's configured default is used; when the branch has none, we fall back to Bitbucket's own default merge_commit.
  • 202 responses are polled against the Location header's task-status URL with capped exponential backoff (15 attempts, 500ms → 4s). 409 and 555 surface as retry-friendly errors from the backend. No auto-retry.
  • --delete sends close_source_branch: true (remote-side cleanup) and, client-side, deletes the matching local branch with git branch -d. Guards: only when the local name matches the PR's source; switch to destination first when currently on the source; warn (non-fatal) if git refuses.
  • Extends GitRunner with hasLocalBranch, checkoutExistingBranch, deleteLocalBranch; adds GitError which carries git's stderr.

Test plan

  • bun test — 170 tests pass (13 new backend tests: merge happy path, message/delete body shaping, 202 + Location polling, 202 missing Location, 409, 555, task-status PENDING / SUCCESS / FAILED / malformed / non-2xx; 2 new fields-mapping tests on getPullRequest)
  • bun run lint clean
  • bunx tsc --noEmit clean
  • Manual: bb pr merge --help shows all flags
  • Manual: bb pr merge in a non-Bitbucket repo fails with the -R hint at the repo-resolve step (as expected)

Manual test path

Pick a throwaway open PR you're willing to merge. Replace <N> with its id.

  1. bb pr merge <N> — merges using the destination branch's default strategy. Expect Merged pull request #<N>: <url>.
  2. bb pr merge <N> --strategy squash — explicit strategy. If the branch allows squash, it merges. If not, expect Merge strategy 'squash' is not allowed on branch 'main'. Allowed: merge_commit.
  3. bb pr merge <N> --strategy bogus — expect Unknown merge strategy 'bogus'. Allowed: merge_commit, squash, fast_forward, squash_fast_forward, rebase_fast_forward, rebase_merge.
  4. bb pr merge <N> --message "custom merge msg" — merges with your message as the merge commit message.
  5. On a branch that is the PR's source branch, with a clean working tree: bb pr merge <N> --delete. Expect Merged pull request #<N>: <url> then Deleted local branch '<source>'. — you should end up on the destination branch.
  6. On a branch that is NOT the source, with the source branch present locally: bb pr merge <N> --delete. Same outcome; no branch switching.
  7. Without a local source branch: bb pr merge <N> --delete merges remotely and skips the local delete silently.
  8. With a dirty working tree: bb pr merge <N> --delete merges remotely, then the local delete fails with a clear Warning: could not clean up local branch '<source>': ... line; exit code still 0.
  9. Attempt to merge an already-merged or declined PR: expect Pull request #<N> is merged; cannot merge. (or declined) — no API call made.
  10. bb --json pr merge <N> emits the full merged PR detail object.

For the 202 path, no reliable manual setup — covered by backend tests. Keep an eye on timing if you do hit it in the wild; the polling budget is ~30s.

Closes BBC2-15.

Merges an open PR via POST /pullrequests/{id}/merge with the destination
branch's default strategy, or an override via --strategy. Supports
--message for a custom merge commit message and --delete for server-side
+ local source branch cleanup.
Strategy resolution:
- --strategy validates against the destination branch's merge_strategies
allow-list (surfaced client-side with a helpful message listing the
permitted set) before the POST, rather than taking an opaque 400 from
the API.
- Without --strategy, use destination.branch.default_merge_strategy (set
via the branch's merge settings in the web UI); fall back to the API's
own default merge_commit when unset.
Both strategy fields are omitted from the default /pullrequests/{id}
serialization. Request them additively via fields=+... (see docs/bb-notes
under "Default merge strategy"). That's done in getPullRequest globally —
the extra fields are also useful to future consumers (e.g. BBC2-44) and
`+` additive syntax doesn't drop existing fields.
Response handling:
- 200 → synchronous success, print PR URL.
- 202 → poll task-status with capped exponential backoff (15 attempts,
500ms → 4s), resolve to SUCCESS or FAILED or time out with a retry
message.
- 409 / 555 → clean retry-friendly messages from the backend layer.
--delete local cleanup is best-effort. Guards: only delete when local
branch name matches the PR's source; switch to destination first when
currently on the source; fall back to a warning (non-fatal) if git
refuses (dirty tree, non-FF, etc.) — the server-side merge already
landed.
Extends GitRunner with the write primitives checkoutExistingBranch and
deleteLocalBranch (plus the hasLocalBranch query), and adds GitError
whose message carries git's stderr so users see git's own diagnostics.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

BBC2-15 add bb pr merge - #22

Open
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge
Open

BBC2-15 add bb pr merge#22
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge

Conversation

@b2l

@b2lb2l commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bb pr merge [id] that merges an open PR. Flags: --strategy/-s, --message/-m, --delete. Defaults to the PR for the current branch.
  • getPullRequest now requests destination.branch.default_merge_strategy and destination.branch.merge_strategies additively via fields=+... — both are omitted from Bitbucket's default serialization (see docs/bb-notes.md → "Default merge strategy"). Exposed on PullRequestDetail as defaultMergeStrategy / allowedMergeStrategies.
  • --strategy is validated client-side against the branch's allow-list before the POST, so users get a clean message listing permitted strategies rather than an opaque 400.
  • Without --strategy, the destination branch's configured default is used; when the branch has none, we fall back to Bitbucket's own default merge_commit.
  • 202 responses are polled against the Location header's task-status URL with capped exponential backoff (15 attempts, 500ms → 4s). 409 and 555 surface as retry-friendly errors from the backend. No auto-retry.
  • --delete sends close_source_branch: true (remote-side cleanup) and, client-side, deletes the matching local branch with git branch -d. Guards: only when the local name matches the PR's source; switch to destination first when currently on the source; warn (non-fatal) if git refuses.
  • Extends GitRunner with hasLocalBranch, checkoutExistingBranch, deleteLocalBranch; adds GitError which carries git's stderr.

Test plan

  • bun test — 170 tests pass (13 new backend tests: merge happy path, message/delete body shaping, 202 + Location polling, 202 missing Location, 409, 555, task-status PENDING / SUCCESS / FAILED / malformed / non-2xx; 2 new fields-mapping tests on getPullRequest)
  • bun run lint clean
  • bunx tsc --noEmit clean
  • Manual: bb pr merge --help shows all flags
  • Manual: bb pr merge in a non-Bitbucket repo fails with the -R hint at the repo-resolve step (as expected)

Manual test path

Pick a throwaway open PR you're willing to merge. Replace <N> with its id.

  1. bb pr merge <N> — merges using the destination branch's default strategy. Expect Merged pull request #<N>: <url>.
  2. bb pr merge <N> --strategy squash — explicit strategy. If the branch allows squash, it merges. If not, expect Merge strategy 'squash' is not allowed on branch 'main'. Allowed: merge_commit.
  3. bb pr merge <N> --strategy bogus — expect Unknown merge strategy 'bogus'. Allowed: merge_commit, squash, fast_forward, squash_fast_forward, rebase_fast_forward, rebase_merge.
  4. bb pr merge <N> --message "custom merge msg" — merges with your message as the merge commit message.
  5. On a branch that is the PR's source branch, with a clean working tree: bb pr merge <N> --delete. Expect Merged pull request #<N>: <url> then Deleted local branch '<source>'. — you should end up on the destination branch.
  6. On a branch that is NOT the source, with the source branch present locally: bb pr merge <N> --delete. Same outcome; no branch switching.
  7. Without a local source branch: bb pr merge <N> --delete merges remotely and skips the local delete silently.
  8. With a dirty working tree: bb pr merge <N> --delete merges remotely, then the local delete fails with a clear Warning: could not clean up local branch '<source>': ... line; exit code still 0.
  9. Attempt to merge an already-merged or declined PR: expect Pull request #<N> is merged; cannot merge. (or declined) — no API call made.
  10. bb --json pr merge <N> emits the full merged PR detail object.

For the 202 path, no reliable manual setup — covered by backend tests. Keep an eye on timing if you do hit it in the wild; the polling budget is ~30s.

Closes BBC2-15.

Merges an open PR via POST /pullrequests/{id}/merge with the destination
branch's default strategy, or an override via --strategy. Supports
--message for a custom merge commit message and --delete for server-side
+ local source branch cleanup.
Strategy resolution:
- --strategy validates against the destination branch's merge_strategies
allow-list (surfaced client-side with a helpful message listing the
permitted set) before the POST, rather than taking an opaque 400 from
the API.
- Without --strategy, use destination.branch.default_merge_strategy (set
via the branch's merge settings in the web UI); fall back to the API's
own default merge_commit when unset.
Both strategy fields are omitted from the default /pullrequests/{id}
serialization. Request them additively via fields=+... (see docs/bb-notes
under "Default merge strategy"). That's done in getPullRequest globally —
the extra fields are also useful to future consumers (e.g. BBC2-44) and
`+` additive syntax doesn't drop existing fields.
Response handling:
- 200 → synchronous success, print PR URL.
- 202 → poll task-status with capped exponential backoff (15 attempts,
500ms → 4s), resolve to SUCCESS or FAILED or time out with a retry
message.
- 409 / 555 → clean retry-friendly messages from the backend layer.
--delete local cleanup is best-effort. Guards: only delete when local
branch name matches the PR's source; switch to destination first when
currently on the source; fall back to a warning (non-fatal) if git
refuses (dirty tree, non-FF, etc.) — the server-side merge already
landed.
Extends GitRunner with the write primitives checkoutExistingBranch and
deleteLocalBranch (plus the hasLocalBranch query), and adds GitError
whose message carries git's stderr so users see git's own diagnostics.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

BBC2-15 add bb pr merge - #22

Open
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge
Open

BBC2-15 add bb pr merge#22
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge

Conversation

@b2l

@b2lb2l commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bb pr merge [id] that merges an open PR. Flags: --strategy/-s, --message/-m, --delete. Defaults to the PR for the current branch.
  • getPullRequest now requests destination.branch.default_merge_strategy and destination.branch.merge_strategies additively via fields=+... — both are omitted from Bitbucket's default serialization (see docs/bb-notes.md → "Default merge strategy"). Exposed on PullRequestDetail as defaultMergeStrategy / allowedMergeStrategies.
  • --strategy is validated client-side against the branch's allow-list before the POST, so users get a clean message listing permitted strategies rather than an opaque 400.
  • Without --strategy, the destination branch's configured default is used; when the branch has none, we fall back to Bitbucket's own default merge_commit.
  • 202 responses are polled against the Location header's task-status URL with capped exponential backoff (15 attempts, 500ms → 4s). 409 and 555 surface as retry-friendly errors from the backend. No auto-retry.
  • --delete sends close_source_branch: true (remote-side cleanup) and, client-side, deletes the matching local branch with git branch -d. Guards: only when the local name matches the PR's source; switch to destination first when currently on the source; warn (non-fatal) if git refuses.
  • Extends GitRunner with hasLocalBranch, checkoutExistingBranch, deleteLocalBranch; adds GitError which carries git's stderr.

Test plan

  • bun test — 170 tests pass (13 new backend tests: merge happy path, message/delete body shaping, 202 + Location polling, 202 missing Location, 409, 555, task-status PENDING / SUCCESS / FAILED / malformed / non-2xx; 2 new fields-mapping tests on getPullRequest)
  • bun run lint clean
  • bunx tsc --noEmit clean
  • Manual: bb pr merge --help shows all flags
  • Manual: bb pr merge in a non-Bitbucket repo fails with the -R hint at the repo-resolve step (as expected)

Manual test path

Pick a throwaway open PR you're willing to merge. Replace <N> with its id.

  1. bb pr merge <N> — merges using the destination branch's default strategy. Expect Merged pull request #<N>: <url>.
  2. bb pr merge <N> --strategy squash — explicit strategy. If the branch allows squash, it merges. If not, expect Merge strategy 'squash' is not allowed on branch 'main'. Allowed: merge_commit.
  3. bb pr merge <N> --strategy bogus — expect Unknown merge strategy 'bogus'. Allowed: merge_commit, squash, fast_forward, squash_fast_forward, rebase_fast_forward, rebase_merge.
  4. bb pr merge <N> --message "custom merge msg" — merges with your message as the merge commit message.
  5. On a branch that is the PR's source branch, with a clean working tree: bb pr merge <N> --delete. Expect Merged pull request #<N>: <url> then Deleted local branch '<source>'. — you should end up on the destination branch.
  6. On a branch that is NOT the source, with the source branch present locally: bb pr merge <N> --delete. Same outcome; no branch switching.
  7. Without a local source branch: bb pr merge <N> --delete merges remotely and skips the local delete silently.
  8. With a dirty working tree: bb pr merge <N> --delete merges remotely, then the local delete fails with a clear Warning: could not clean up local branch '<source>': ... line; exit code still 0.
  9. Attempt to merge an already-merged or declined PR: expect Pull request #<N> is merged; cannot merge. (or declined) — no API call made.
  10. bb --json pr merge <N> emits the full merged PR detail object.

For the 202 path, no reliable manual setup — covered by backend tests. Keep an eye on timing if you do hit it in the wild; the polling budget is ~30s.

Closes BBC2-15.

Merges an open PR via POST /pullrequests/{id}/merge with the destination
branch's default strategy, or an override via --strategy. Supports
--message for a custom merge commit message and --delete for server-side
+ local source branch cleanup.
Strategy resolution:
- --strategy validates against the destination branch's merge_strategies
allow-list (surfaced client-side with a helpful message listing the
permitted set) before the POST, rather than taking an opaque 400 from
the API.
- Without --strategy, use destination.branch.default_merge_strategy (set
via the branch's merge settings in the web UI); fall back to the API's
own default merge_commit when unset.
Both strategy fields are omitted from the default /pullrequests/{id}
serialization. Request them additively via fields=+... (see docs/bb-notes
under "Default merge strategy"). That's done in getPullRequest globally —
the extra fields are also useful to future consumers (e.g. BBC2-44) and
`+` additive syntax doesn't drop existing fields.
Response handling:
- 200 → synchronous success, print PR URL.
- 202 → poll task-status with capped exponential backoff (15 attempts,
500ms → 4s), resolve to SUCCESS or FAILED or time out with a retry
message.
- 409 / 555 → clean retry-friendly messages from the backend layer.
--delete local cleanup is best-effort. Guards: only delete when local
branch name matches the PR's source; switch to destination first when
currently on the source; fall back to a warning (non-fatal) if git
refuses (dirty tree, non-FF, etc.) — the server-side merge already
landed.
Extends GitRunner with the write primitives checkoutExistingBranch and
deleteLocalBranch (plus the hasLocalBranch query), and adds GitError
whose message carries git's stderr so users see git's own diagnostics.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

BBC2-15 add bb pr merge - #22

Open
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge
Open

BBC2-15 add bb pr merge#22
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge

Conversation

@b2l

@b2lb2l commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bb pr merge [id] that merges an open PR. Flags: --strategy/-s, --message/-m, --delete. Defaults to the PR for the current branch.
  • getPullRequest now requests destination.branch.default_merge_strategy and destination.branch.merge_strategies additively via fields=+... — both are omitted from Bitbucket's default serialization (see docs/bb-notes.md → "Default merge strategy"). Exposed on PullRequestDetail as defaultMergeStrategy / allowedMergeStrategies.
  • --strategy is validated client-side against the branch's allow-list before the POST, so users get a clean message listing permitted strategies rather than an opaque 400.
  • Without --strategy, the destination branch's configured default is used; when the branch has none, we fall back to Bitbucket's own default merge_commit.
  • 202 responses are polled against the Location header's task-status URL with capped exponential backoff (15 attempts, 500ms → 4s). 409 and 555 surface as retry-friendly errors from the backend. No auto-retry.
  • --delete sends close_source_branch: true (remote-side cleanup) and, client-side, deletes the matching local branch with git branch -d. Guards: only when the local name matches the PR's source; switch to destination first when currently on the source; warn (non-fatal) if git refuses.
  • Extends GitRunner with hasLocalBranch, checkoutExistingBranch, deleteLocalBranch; adds GitError which carries git's stderr.

Test plan

  • bun test — 170 tests pass (13 new backend tests: merge happy path, message/delete body shaping, 202 + Location polling, 202 missing Location, 409, 555, task-status PENDING / SUCCESS / FAILED / malformed / non-2xx; 2 new fields-mapping tests on getPullRequest)
  • bun run lint clean
  • bunx tsc --noEmit clean
  • Manual: bb pr merge --help shows all flags
  • Manual: bb pr merge in a non-Bitbucket repo fails with the -R hint at the repo-resolve step (as expected)

Manual test path

Pick a throwaway open PR you're willing to merge. Replace <N> with its id.

  1. bb pr merge <N> — merges using the destination branch's default strategy. Expect Merged pull request #<N>: <url>.
  2. bb pr merge <N> --strategy squash — explicit strategy. If the branch allows squash, it merges. If not, expect Merge strategy 'squash' is not allowed on branch 'main'. Allowed: merge_commit.
  3. bb pr merge <N> --strategy bogus — expect Unknown merge strategy 'bogus'. Allowed: merge_commit, squash, fast_forward, squash_fast_forward, rebase_fast_forward, rebase_merge.
  4. bb pr merge <N> --message "custom merge msg" — merges with your message as the merge commit message.
  5. On a branch that is the PR's source branch, with a clean working tree: bb pr merge <N> --delete. Expect Merged pull request #<N>: <url> then Deleted local branch '<source>'. — you should end up on the destination branch.
  6. On a branch that is NOT the source, with the source branch present locally: bb pr merge <N> --delete. Same outcome; no branch switching.
  7. Without a local source branch: bb pr merge <N> --delete merges remotely and skips the local delete silently.
  8. With a dirty working tree: bb pr merge <N> --delete merges remotely, then the local delete fails with a clear Warning: could not clean up local branch '<source>': ... line; exit code still 0.
  9. Attempt to merge an already-merged or declined PR: expect Pull request #<N> is merged; cannot merge. (or declined) — no API call made.
  10. bb --json pr merge <N> emits the full merged PR detail object.

For the 202 path, no reliable manual setup — covered by backend tests. Keep an eye on timing if you do hit it in the wild; the polling budget is ~30s.

Closes BBC2-15.

Merges an open PR via POST /pullrequests/{id}/merge with the destination
branch's default strategy, or an override via --strategy. Supports
--message for a custom merge commit message and --delete for server-side
+ local source branch cleanup.
Strategy resolution:
- --strategy validates against the destination branch's merge_strategies
allow-list (surfaced client-side with a helpful message listing the
permitted set) before the POST, rather than taking an opaque 400 from
the API.
- Without --strategy, use destination.branch.default_merge_strategy (set
via the branch's merge settings in the web UI); fall back to the API's
own default merge_commit when unset.
Both strategy fields are omitted from the default /pullrequests/{id}
serialization. Request them additively via fields=+... (see docs/bb-notes
under "Default merge strategy"). That's done in getPullRequest globally —
the extra fields are also useful to future consumers (e.g. BBC2-44) and
`+` additive syntax doesn't drop existing fields.
Response handling:
- 200 → synchronous success, print PR URL.
- 202 → poll task-status with capped exponential backoff (15 attempts,
500ms → 4s), resolve to SUCCESS or FAILED or time out with a retry
message.
- 409 / 555 → clean retry-friendly messages from the backend layer.
--delete local cleanup is best-effort. Guards: only delete when local
branch name matches the PR's source; switch to destination first when
currently on the source; fall back to a warning (non-fatal) if git
refuses (dirty tree, non-FF, etc.) — the server-side merge already
landed.
Extends GitRunner with the write primitives checkoutExistingBranch and
deleteLocalBranch (plus the hasLocalBranch query), and adds GitError
whose message carries git's stderr so users see git's own diagnostics.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

BBC2-15 add bb pr merge - #22

Open
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge
Open

BBC2-15 add bb pr merge#22
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge

Conversation

@b2l

@b2lb2l commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bb pr merge [id] that merges an open PR. Flags: --strategy/-s, --message/-m, --delete. Defaults to the PR for the current branch.
  • getPullRequest now requests destination.branch.default_merge_strategy and destination.branch.merge_strategies additively via fields=+... — both are omitted from Bitbucket's default serialization (see docs/bb-notes.md → "Default merge strategy"). Exposed on PullRequestDetail as defaultMergeStrategy / allowedMergeStrategies.
  • --strategy is validated client-side against the branch's allow-list before the POST, so users get a clean message listing permitted strategies rather than an opaque 400.
  • Without --strategy, the destination branch's configured default is used; when the branch has none, we fall back to Bitbucket's own default merge_commit.
  • 202 responses are polled against the Location header's task-status URL with capped exponential backoff (15 attempts, 500ms → 4s). 409 and 555 surface as retry-friendly errors from the backend. No auto-retry.
  • --delete sends close_source_branch: true (remote-side cleanup) and, client-side, deletes the matching local branch with git branch -d. Guards: only when the local name matches the PR's source; switch to destination first when currently on the source; warn (non-fatal) if git refuses.
  • Extends GitRunner with hasLocalBranch, checkoutExistingBranch, deleteLocalBranch; adds GitError which carries git's stderr.

Test plan

  • bun test — 170 tests pass (13 new backend tests: merge happy path, message/delete body shaping, 202 + Location polling, 202 missing Location, 409, 555, task-status PENDING / SUCCESS / FAILED / malformed / non-2xx; 2 new fields-mapping tests on getPullRequest)
  • bun run lint clean
  • bunx tsc --noEmit clean
  • Manual: bb pr merge --help shows all flags
  • Manual: bb pr merge in a non-Bitbucket repo fails with the -R hint at the repo-resolve step (as expected)

Manual test path

Pick a throwaway open PR you're willing to merge. Replace <N> with its id.

  1. bb pr merge <N> — merges using the destination branch's default strategy. Expect Merged pull request #<N>: <url>.
  2. bb pr merge <N> --strategy squash — explicit strategy. If the branch allows squash, it merges. If not, expect Merge strategy 'squash' is not allowed on branch 'main'. Allowed: merge_commit.
  3. bb pr merge <N> --strategy bogus — expect Unknown merge strategy 'bogus'. Allowed: merge_commit, squash, fast_forward, squash_fast_forward, rebase_fast_forward, rebase_merge.
  4. bb pr merge <N> --message "custom merge msg" — merges with your message as the merge commit message.
  5. On a branch that is the PR's source branch, with a clean working tree: bb pr merge <N> --delete. Expect Merged pull request #<N>: <url> then Deleted local branch '<source>'. — you should end up on the destination branch.
  6. On a branch that is NOT the source, with the source branch present locally: bb pr merge <N> --delete. Same outcome; no branch switching.
  7. Without a local source branch: bb pr merge <N> --delete merges remotely and skips the local delete silently.
  8. With a dirty working tree: bb pr merge <N> --delete merges remotely, then the local delete fails with a clear Warning: could not clean up local branch '<source>': ... line; exit code still 0.
  9. Attempt to merge an already-merged or declined PR: expect Pull request #<N> is merged; cannot merge. (or declined) — no API call made.
  10. bb --json pr merge <N> emits the full merged PR detail object.

For the 202 path, no reliable manual setup — covered by backend tests. Keep an eye on timing if you do hit it in the wild; the polling budget is ~30s.

Closes BBC2-15.

Merges an open PR via POST /pullrequests/{id}/merge with the destination
branch's default strategy, or an override via --strategy. Supports
--message for a custom merge commit message and --delete for server-side
+ local source branch cleanup.
Strategy resolution:
- --strategy validates against the destination branch's merge_strategies
allow-list (surfaced client-side with a helpful message listing the
permitted set) before the POST, rather than taking an opaque 400 from
the API.
- Without --strategy, use destination.branch.default_merge_strategy (set
via the branch's merge settings in the web UI); fall back to the API's
own default merge_commit when unset.
Both strategy fields are omitted from the default /pullrequests/{id}
serialization. Request them additively via fields=+... (see docs/bb-notes
under "Default merge strategy"). That's done in getPullRequest globally —
the extra fields are also useful to future consumers (e.g. BBC2-44) and
`+` additive syntax doesn't drop existing fields.
Response handling:
- 200 → synchronous success, print PR URL.
- 202 → poll task-status with capped exponential backoff (15 attempts,
500ms → 4s), resolve to SUCCESS or FAILED or time out with a retry
message.
- 409 / 555 → clean retry-friendly messages from the backend layer.
--delete local cleanup is best-effort. Guards: only delete when local
branch name matches the PR's source; switch to destination first when
currently on the source; fall back to a warning (non-fatal) if git
refuses (dirty tree, non-FF, etc.) — the server-side merge already
landed.
Extends GitRunner with the write primitives checkoutExistingBranch and
deleteLocalBranch (plus the hasLocalBranch query), and adds GitError
whose message carries git's stderr so users see git's own diagnostics.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

BBC2-15 add bb pr merge - #22

Open
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge
Open

BBC2-15 add bb pr merge#22
b2l wants to merge 1 commit into
mainfrom
bbc2-15-pr-merge

Conversation

@b2l

@b2lb2l commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bb pr merge [id] that merges an open PR. Flags: --strategy/-s, --message/-m, --delete. Defaults to the PR for the current branch.
  • getPullRequest now requests destination.branch.default_merge_strategy and destination.branch.merge_strategies additively via fields=+... — both are omitted from Bitbucket's default serialization (see docs/bb-notes.md → "Default merge strategy"). Exposed on PullRequestDetail as defaultMergeStrategy / allowedMergeStrategies.
  • --strategy is validated client-side against the branch's allow-list before the POST, so users get a clean message listing permitted strategies rather than an opaque 400.
  • Without --strategy, the destination branch's configured default is used; when the branch has none, we fall back to Bitbucket's own default merge_commit.
  • 202 responses are polled against the Location header's task-status URL with capped exponential backoff (15 attempts, 500ms → 4s). 409 and 555 surface as retry-friendly errors from the backend. No auto-retry.
  • --delete sends close_source_branch: true (remote-side cleanup) and, client-side, deletes the matching local branch with git branch -d. Guards: only when the local name matches the PR's source; switch to destination first when currently on the source; warn (non-fatal) if git refuses.
  • Extends GitRunner with hasLocalBranch, checkoutExistingBranch, deleteLocalBranch; adds GitError which carries git's stderr.

Test plan

  • bun test — 170 tests pass (13 new backend tests: merge happy path, message/delete body shaping, 202 + Location polling, 202 missing Location, 409, 555, task-status PENDING / SUCCESS / FAILED / malformed / non-2xx; 2 new fields-mapping tests on getPullRequest)
  • bun run lint clean
  • bunx tsc --noEmit clean
  • Manual: bb pr merge --help shows all flags
  • Manual: bb pr merge in a non-Bitbucket repo fails with the -R hint at the repo-resolve step (as expected)

Manual test path

Pick a throwaway open PR you're willing to merge. Replace <N> with its id.

  1. bb pr merge <N> — merges using the destination branch's default strategy. Expect Merged pull request #<N>: <url>.
  2. bb pr merge <N> --strategy squash — explicit strategy. If the branch allows squash, it merges. If not, expect Merge strategy 'squash' is not allowed on branch 'main'. Allowed: merge_commit.
  3. bb pr merge <N> --strategy bogus — expect Unknown merge strategy 'bogus'. Allowed: merge_commit, squash, fast_forward, squash_fast_forward, rebase_fast_forward, rebase_merge.
  4. bb pr merge <N> --message "custom merge msg" — merges with your message as the merge commit message.
  5. On a branch that is the PR's source branch, with a clean working tree: bb pr merge <N> --delete. Expect Merged pull request #<N>: <url> then Deleted local branch '<source>'. — you should end up on the destination branch.
  6. On a branch that is NOT the source, with the source branch present locally: bb pr merge <N> --delete. Same outcome; no branch switching.
  7. Without a local source branch: bb pr merge <N> --delete merges remotely and skips the local delete silently.
  8. With a dirty working tree: bb pr merge <N> --delete merges remotely, then the local delete fails with a clear Warning: could not clean up local branch '<source>': ... line; exit code still 0.
  9. Attempt to merge an already-merged or declined PR: expect Pull request #<N> is merged; cannot merge. (or declined) — no API call made.
  10. bb --json pr merge <N> emits the full merged PR detail object.

For the 202 path, no reliable manual setup — covered by backend tests. Keep an eye on timing if you do hit it in the wild; the polling budget is ~30s.

Closes BBC2-15.

Merges an open PR via POST /pullrequests/{id}/merge with the destination
branch's default strategy, or an override via --strategy. Supports
--message for a custom merge commit message and --delete for server-side
+ local source branch cleanup.
Strategy resolution:
- --strategy validates against the destination branch's merge_strategies
allow-list (surfaced client-side with a helpful message listing the
permitted set) before the POST, rather than taking an opaque 400 from
the API.
- Without --strategy, use destination.branch.default_merge_strategy (set
via the branch's merge settings in the web UI); fall back to the API's
own default merge_commit when unset.
Both strategy fields are omitted from the default /pullrequests/{id}
serialization. Request them additively via fields=+... (see docs/bb-notes
under "Default merge strategy"). That's done in getPullRequest globally —
the extra fields are also useful to future consumers (e.g. BBC2-44) and
`+` additive syntax doesn't drop existing fields.
Response handling:
- 200 → synchronous success, print PR URL.
- 202 → poll task-status with capped exponential backoff (15 attempts,
500ms → 4s), resolve to SUCCESS or FAILED or time out with a retry
message.
- 409 / 555 → clean retry-friendly messages from the backend layer.
--delete local cleanup is best-effort. Guards: only delete when local
branch name matches the PR's source; switch to destination first when
currently on the source; fall back to a warning (non-fatal) if git
refuses (dirty tree, non-FF, etc.) — the server-side merge already
landed.
Extends GitRunner with the write primitives checkoutExistingBranch and
deleteLocalBranch (plus the hasLocalBranch query), and adds GitError
whose message carries git's stderr so users see git's own diagnostics.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l