Skip to content

feat: add default reviewers to pr edit#150

Merged
avivsinai merged 8 commits intoavivsinai:masterfrom
ekrako:feat/pr-edit-default-reviewers
Apr 12, 2026
Merged

feat: add default reviewers to pr edit#150
avivsinai merged 8 commits intoavivsinai:masterfrom
ekrako:feat/pr-edit-default-reviewers

Conversation

@ekrako
Copy link
Copy Markdown
Contributor

@ekrako ekrako commented Apr 10, 2026

Summary

This change adds --with-default-reviewers support to bkt pr edit for both Bitbucket Data Center and Bitbucket Cloud so existing pull requests can be updated to include repository defaults without recreating them. It reuses the existing default-reviewer lookup behavior from bkt pr create, preserves current reviewer-edit semantics, and fixes the boolean flag handling so --with-default-reviewers=false does not trigger reviewer changes.

Changes

  • CLI behavior:
    • Added --with-default-reviewers to bkt pr edit.
    • On Data Center, pr edit now fetches default reviewers using the PR's current source and target refs and merges them into the existing reviewer set.
    • On Cloud, pr edit now fetches effective default reviewers, excludes the PR author, preserves reviewer UUIDs in update payloads, and applies explicit --reviewer / --remove-reviewer changes on top.
  • Shared logic:
    • Refactored default-reviewer fetch and filtering so pr create and pr edit use the same host-specific helper paths instead of duplicating the behavior.
    • Extended the Cloud pull request model to expose author UUID so author exclusion during edit is reliable.
  • Tests and docs:
    • Added regression coverage for Data Center and Cloud edit flows, including the --with-default-reviewers=false case so explicit false values do not trigger reviewer mutations.
    • Regenerated the bkt skill docs so the pr edit reference now includes the new flag and example usage.

Testing

  • pre-commit run --files pkg/bbcloud/pullrequests.go pkg/cmd/pr/pr.go pkg/cmd/pr/pr_test.go skills/bkt/rules/pr.md
  • go test ./...
  • No new environment variables were introduced.

Risks

  • Low. The main area worth reviewer attention is reviewer merge behavior on pr edit, especially the Cloud path that excludes the PR author and preserves UUID-based reviewers.
  • Explicit --with-default-reviewers=false handling is covered by regression tests.

@sentry
Copy link
Copy Markdown

sentry Bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 98.24561% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/cmd/pr/pr.go 98.13% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@ekrako ekrako marked this pull request as ready for review April 10, 2026 14:43
Copy link
Copy Markdown
Owner

@avivsinai avivsinai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feature is solid, refactor is clean, one real blocker on the Cloud path.

Blocking

Cloud mixed-identity handling is incomplete in the explicit --reviewer/--remove-reviewer path, plus a silent data-loss bug on AccountID-only reviewers.

The new helpers (cloudUserKeys, sameCloudUser, mergeCloudPRReviewers, cloudReviewerIDs) reconcile all three identity formats and are used for the default-reviewer merge and author exclusion. But matchesCloudUser at pkg/cmd/pr/pr.go:794-799 still only checks UUID or username, and editCloudReviewers at pr.go:808-880 uses it exclusively. The runtime flow at pr.go:1573-1591 merges defaults first (identity-aware), then calls editCloudReviewers (not identity-aware) against the merged reviewer list — so the brittleness surfaces in the explicit add/remove path even when the merge worked.

Repro against the diff:

  • Reviewer returned as {UUID: "x", AccountID: "abc", Username: ""} — real for GDPR-era Cloud accounts that never had a username
  • --with-default-reviewers --remove-reviewer alice (alice has account_id: "abc"): matchesCloudUser returns false for every check → remove is a silent no-op, alice stays on the PR
  • --with-default-reviewers --reviewer alice same case: alreadyOnPR is false → alice appended as a duplicate entry

Plus a silent-drop bug at pr.go:842-857:

if u.UUID != "" {
    result = append(result, u.UUID)
} else if u.Username != "" {
    result = append(result, u.Username)
}

Reviewers with only AccountID populated are silently dropped from the result. cloudReviewerIDs at pr.go:1378-1408 has the same drop. bbcloud.User at pkg/bbcloud/client.go:55-61 confirms all four fields are optional JSON with no guarantees.

Fix: plumb sameCloudUser through editCloudReviewers (replace matchesCloudUser calls), and extend the keep-loop + cloudReviewerIDs serialization to fall back to AccountID when UUID and Username are both empty. Extend the mixed-identity tests you already added (pr_test.go 3116-3156, 4466-4519) to the explicit --reviewer/--remove-reviewer flow.

Non-blocking

  1. Author exclusion fragility (pr.go:1575-1579). If Cloud returns an author with no usable identity (rare but possible), cloudUserKeys(excluded) is empty → sameCloudUser returns false → author not filtered → Cloud rejects server-side. Worth a warning + fallback to client.CurrentUser(ctx).

  2. Spurious "already on this pull request" warnings at pr.go:1518-1520 (DC) and 1586-1591 (Cloud). --with-default-reviewers --reviewer alice where alice is already in the default set produces noisy stderr. Correct behavior, bad UX — consider suppressing when the overlap is only with freshly-merged defaults, not with pre-existing reviewers.

  3. ios, _ := f.Streams() at pr.go:1469 breaks codebase convention — every other runX does ios, err := f.Streams(); if err != nil { return err }. The var err error on line 1472 reads like a workaround for the dropped shadow. Streams() currently never returns a non-nil error so it's harmless today, but please restore the pattern.

  4. Four merge functions (mergeReviewers, mergeCloudReviewers, mergeDCPRReviewers, mergeCloudPRReviewers) with different shapes all called "merge reviewers" — rename for callsite clarity.

  5. TestRunEditDataCenterReviewers PUT-body assertion is shallow — only checks len(reviewers) == 3, not the actual names. Could miss a regression where the merge produces the right count but the wrong set.

What's done well

  • --with-default-reviewers=false is exactly right — tests verify the endpoint is NOT called AND that the Cloud PUT body has no reviewers field.
  • DC uses the PR's actual FromRef.ID / ToRef.ID (not repo defaults) — spec-correct.
  • Cloud preserves existing reviewer UUIDs in the update payload — matches Cloud's stable identity model.
  • Explicit --reviewer/--remove-reviewer applies AFTER the default merge on both backends — explicit user intent wins.
  • Default-reviewer fetch errors abort the edit rather than silently continuing.

Once editCloudReviewers and cloudReviewerIDs pick up the account_id-aware logic that the rest of the diff already introduces, this is an approve.

@ekrako ekrako force-pushed the feat/pr-edit-default-reviewers branch from a8a5569 to bfb1912 Compare April 12, 2026 09:23
- matchesCloudUser now checks AccountID in addition to UUID/Username
- Serialization fallback to AccountID when UUID and Username are empty
- Author exclusion falls back to CurrentUser when author has no identity
- Suppress spurious "already on PR" warnings for default-merged reviewers
- Restore ios error check in runEdit to match codebase convention
- Rename merge functions for callsite clarity (create vs edit)
- Strengthen DC reviewer PUT-body assertion to check actual names
@ekrako
Copy link
Copy Markdown
Contributor Author

ekrako commented Apr 12, 2026

All points addressed in b6d708a:

Blocking — Cloud mixed-identity handling:

  • matchesCloudUser now checks AccountID in addition to UUID/Username
  • Serialization in editCloudReviewers and cloudReviewerIDs falls back to AccountID when UUID and Username are both empty (priority: UUID > Username > AccountID — preserves existing behavior, AccountID as final fallback for GDPR-era accounts)
  • 5 test cases added covering: remove by account_id, add-when-present warns, remove-not-present warns, AccountID-only serialization, cross-identity overlap by account_id

Non-blocking 1 — Author exclusion fragility:
When PR author has no usable identity (empty UUID/Username/AccountID), warns on stderr and falls back to client.CurrentUser(ctx) for default-reviewer exclusion. Integration test added.

Non-blocking 2 — Spurious warnings:
editDCReviewers and editCloudReviewers now accept a preExisting parameter. When non-nil, "already on this pull request" warnings are suppressed for reviewers that came from the defaults merge only. 4 unit tests added.

Non-blocking 3 — ios, _ := f.Streams():
Restored ios, err := f.Streams() with error check, matching codebase convention.

Non-blocking 4 — Merge function names:
Renamed for callsite clarity: mergeCreateReviewers (DC create), mergeCloudCreateReviewers (Cloud create), mergeDCEditReviewers (DC edit), mergeCloudEditReviewers (Cloud edit).

Non-blocking 5 — Shallow PUT-body assertion:
TestRunEditDataCenterReviewers "add default reviewers" now asserts actual reviewer names (alice, bob, charlie) in order, not just count.

@ekrako ekrako requested a review from avivsinai April 12, 2026 13:04
Copy link
Copy Markdown
Owner

@avivsinai avivsinai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issue still left in the Cloud edit path.

pkg/cmd/pr/pr.go now preserves/emits AccountID reviewer identities (editCloudReviewers, cloudReviewerIDs), but pkg/bbcloud/pullrequests.go:346-352 still serializes every non-UUID reviewer as {\"username\": reviewer}. For AccountID-only reviewers, bkt pr edit --with-default-reviewers / --remove-reviewer will send an account ID under the username field instead of account_id, which can 400 or silently lose reviewers on Cloud repos that no longer expose usernames.

Please fix the serializer so the update payload emits account_id when the reviewer token is an account ID, and add an end-to-end request-shape test covering the edit flow so this path is exercised outside the helper-level tests.

The UpdatePullRequest and CreatePullRequest serializers only handled UUID
vs username, sending AccountID-only reviewers under the wrong field.
Add LooksLikeAccountID detection and a shared reviewerIdentity helper
so the API payload emits {"account_id": ...} for Atlassian Account IDs.
@ekrako
Copy link
Copy Markdown
Contributor Author

ekrako commented Apr 12, 2026

Fixed in dcd8da4. Added LooksLikeAccountID to detect Atlassian Account ID format (\d+:uuid), extracted a shared reviewerIdentity helper used by both CreatePullRequest and UpdatePullRequest, so the serializer now emits {"account_id": ...} for account IDs, {"uuid": ...} for UUIDs, and {"username": ...} otherwise.

Tests added:

  • TestLooksLikeAccountID — 11 cases covering valid AAIDs, UUIDs, usernames, edge cases
  • TestCreatePullRequestReviewerAutoDetect — extended with account_id and mixed all three cases
  • TestUpdatePullRequestReviewerAutoDetect — new table-driven test covering uuid, username, account_id, and mixed serialization in the update (edit) flow
  • TestUpdatePullRequestWithReviewers — extended to verify account_id serialization

@ekrako ekrako requested a review from avivsinai April 12, 2026 14:54
Copy link
Copy Markdown
Owner

@avivsinai avivsinai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the latest head after the serializer fix. The Cloud reviewer identity path now correctly emits account_id for AccountID-only reviewers, the create/update serializers are aligned through a shared helper, and the request-shape coverage is in place. LGTM.

@avivsinai avivsinai merged commit 2f4be89 into avivsinai:master Apr 12, 2026
7 checks passed
Sign up for free to 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.

2 participants