Documentation
¶
Overview ¶
Package review owns PR-review orchestration: inline comments, review submission with attached pending comments, dismissal, reviewer requests, thread resolution, and the required-reviews gate consulted by the merge handler.
Diff-position anchoring model (matches GitHub's contract): each comment captures (file_path, side, original_commit_sha, original_line, original_position). The PR synchronize pipeline re-walks the diff against the new head and updates each comment's current_position; comments whose anchor line no longer exists go to current_position=NULL ("outdated"). The position mapping helper here is invoked from `pulls.Synchronize`.
Index ¶
- Constants
- Variables
- func AddComment(ctx context.Context, deps Deps, p CommentParams) (pullsdb.PrReviewComment, error)
- func Dismiss(ctx context.Context, deps Deps, actorUserID, reviewID int64, reason string) error
- func EditComment(ctx context.Context, deps Deps, commentID int64, body string) error
- func RemapAllForPR(ctx context.Context, deps Deps, gitDir string, prID int64, ...) error
- func Reopen(ctx context.Context, deps Deps, commentID int64) error
- func Request(ctx context.Context, deps Deps, p RequestParams) (pullsdb.PrReviewRequest, error)
- func Resolve(ctx context.Context, deps Deps, actorUserID, commentID int64) error
- func Submit(ctx context.Context, deps Deps, p SubmitParams) (pullsdb.PrReview, error)
- type CommentParams
- type Deps
- type GateInputs
- type GateResult
- type RequestParams
- type SubmitParams
Constants ¶
const MaxReviewersPerPR = 20
MaxReviewersPerPR caps active review requests per PR. Matches the spec pitfall section.
Variables ¶
var ( ErrEmptyBody = errors.New("review: comment body is required") ErrBodyTooLong = errors.New("review: body too long") ErrAuthorCannotApprove = errors.New("review: author cannot approve their own PR") ErrInvalidState = errors.New("review: state must be comment, approve, or request_changes") ErrCommentNotOnPR = errors.New("review: comment does not belong to this PR") ErrAlreadyResolved = errors.New("review: thread already resolved") ErrNotResolved = errors.New("review: thread is not resolved") ErrReviewerLimitReached = errors.New("review: 20 reviewers max per PR") ErrReviewerAlreadyPending = errors.New("review: reviewer already requested") ErrReviewNotFound = errors.New("review: review not found") )
Errors surfaced to handlers.
Functions ¶
func AddComment ¶
func AddComment(ctx context.Context, deps Deps, p CommentParams) (pullsdb.PrReviewComment, error)
AddComment inserts a single inline comment. When InReplyToID is set the new comment threads under that one (validated to belong to the same PR). Markdown is rendered to body_html_cached at insert time.
func Dismiss ¶
Dismiss flips a review's dismissed_at + reason. Caller has already gated on policy (typically repo admin only).
func EditComment ¶
EditComment updates the body of an existing comment. Caller is expected to have already enforced "actor == comment.author OR repo admin" via policy.
func RemapAllForPR ¶
func RemapAllForPR(ctx context.Context, deps Deps, gitDir string, prID int64, newBaseOID, newHeadOID string) error
RemapAllForPR re-anchors every non-draft review comment on a PR against the new (base, head) snapshot. For each comment we do a content-aware check:
- Read the line text at (original_commit_sha, file_path, original_line). This is what the comment was written against.
- Read the same line number in the new blob (newHeadOID for side=right; newBaseOID for side=left).
- If the bytes are identical → keep current_position = original_line. If not → current_position = NULL (outdated). The comment still renders in the conversation timeline; the Files tab hides it until the user clicks "Show outdated."
This is the conservative v1 mapper. Lines that have been re-indented, shifted by an insertion above, or merely had a comma added all outdate — that's the right default. The spec calls out `git blame --porcelain` as a richer mapper for rebase-heavy PRs; add that when the simple presence check proves too aggressive.
Idempotent: re-running converges on the same answer.
func Request ¶
func Request(ctx context.Context, deps Deps, p RequestParams) (pullsdb.PrReviewRequest, error)
Request creates a pr_review_requests row. Bounded by MaxReviewersPerPR per the spec pitfalls section.
func Resolve ¶
Resolve marks the thread (root + replies) resolved. The "thread" is keyed off the comment id; UI walks `in_reply_to_id` to render replies, so resolving the root collapses the whole conversation. Default-collapsed in the Files tab; "Show resolved" toggle reopens.
Types ¶
type CommentParams ¶
type CommentParams struct {
PRIssueID int64
AuthorUserID int64
FilePath string
Side string // "left" | "right"
OriginalCommitSHA string
OriginalLine int32
OriginalPosition int32
CurrentPosition int32 // -1 means already outdated (rare on insert)
Body string
InReplyToID int64 // 0 means top-level
Pending bool
}
CommentParams describes a single inline comment on a PR. When `Pending` is true the comment is filed as a draft attached to the author's pending review (review_id stays NULL); SubmitReview later flips pending=false and sets review_id.
type GateInputs ¶
GateInputs is the small struct the merge gate cares about.
type GateResult ¶
type GateResult struct {
// Satisfied — the PR meets the review requirements (or none apply).
Satisfied bool
// Reason — short human-readable cause when not satisfied. Used by
// the UI banner ("Approval required (1/1 reviews missing)").
Reason string
// RequiredCount — the rule's required_review_count.
RequiredCount int
// CurrentApprovals — count of "latest review per author" with state
// approve. Always excludes the PR author.
CurrentApprovals int
// HasRequestChanges — at least one undismissed `request_changes`
// review whose author hasn't superseded it with a later approve.
HasRequestChanges bool
}
GateResult mirrors the spec's mergeable_state vocabulary for review-side blocks.
func Evaluate ¶
func Evaluate(ctx context.Context, pool *pgxpool.Pool, in GateInputs, prAuthorUserID int64) (GateResult, error)
Evaluate computes the gate against the matching branch-protection rule. Returns Satisfied=true when no rule matches the base ref or when the rule's required_review_count == 0 and no unresolved request_changes reviews exist.
"Latest review per author" semantics: when an author submits a request_changes and later submits an approve, only the approve counts. When two distinct authors approve and one of them later submits a comment, both still count for the approval tally — only approve and request_changes shift the per-author state.
type RequestParams ¶
RequestParams describes a review-request action.
type SubmitParams ¶
type SubmitParams struct {
PRIssueID int64
AuthorUserID int64
State string // "comment" | "approve" | "request_changes"
Body string
// PRAuthorUserID is the issues.author_user_id of the PR — used to
// reject self-approval. Caller passes it in to keep this package
// from cross-importing the pulls orchestrator.
PRAuthorUserID int64
}
SubmitParams describes the submit-a-review action.