diff --git a/vcs/check_test.go b/vcs/check_test.go new file mode 100644 index 0000000..c64e954 --- /dev/null +++ b/vcs/check_test.go @@ -0,0 +1,27 @@ +//go:build phase2 + +package vcs_test + +import ( + "gitea.weiker.me/rodin/review-bot/gitea" + "gitea.weiker.me/rodin/review-bot/vcs" +) + +// Compile-time assertion: documents the gap between gitea.Client and vcs.Client. +// Guarded by the "phase2" build tag — enable once the Gitea adapter bridges these gaps: +// +// 1. PostReview signature mismatch: +// gitea.Client: PostReview(ctx, owner, repo, number, event, body string, comments []gitea.ReviewComment) +// vcs.Reviewer: PostReview(ctx, owner, repo, number, req vcs.ReviewRequest) +// +// 2. GetFileContent signature mismatch: +// gitea.Client: GetFileContent(ctx, owner, repo, filepath string) [no ref; uses default branch] +// vcs.FileReader: GetFileContent(ctx, owner, repo, path, ref string) +// (gitea.Client has GetFileContentRef for the ref variant) +// +// 3. ReviewComment type mismatch: +// gitea.ReviewComment uses NewPosition int64 (Gitea line-number convention) +// vcs.ReviewComment uses Position int (GitHub diff-position convention) +// +// The Gitea adapter (Phase 2) will wrap gitea.Client to bridge these gaps. +var _ vcs.Client = (*gitea.Client)(nil) diff --git a/vcs/interfaces.go b/vcs/interfaces.go new file mode 100644 index 0000000..ecada13 --- /dev/null +++ b/vcs/interfaces.go @@ -0,0 +1,40 @@ +// Package vcs defines the shared VCS client interface and supporting types. +// Platform adapters (gitea, github) implement these interfaces so the core +// review logic can work with any VCS platform without platform-specific code. +package vcs + +import "context" + +// PRReader can fetch pull request metadata, diffs, and changed files. +type PRReader interface { + GetPullRequest(ctx context.Context, owner, repo string, number int) (*PullRequest, error) + GetPullRequestDiff(ctx context.Context, owner, repo string, number int) (string, error) + GetPullRequestFiles(ctx context.Context, owner, repo string, number int) ([]ChangedFile, error) +} + +// FileReader can fetch file contents and list directory entries. +type FileReader interface { + GetFileContent(ctx context.Context, owner, repo, path, ref string) (string, error) + ListContents(ctx context.Context, owner, repo, path string) ([]ContentEntry, error) +} + +// Reviewer can post, list, and delete pull request reviews. +type Reviewer interface { + PostReview(ctx context.Context, owner, repo string, number int, req ReviewRequest) (*Review, error) + ListReviews(ctx context.Context, owner, repo string, number int) ([]Review, error) + DeleteReview(ctx context.Context, owner, repo string, number int, reviewID int64) error +} + +// Identity can report who the authenticated user is. +type Identity interface { + GetAuthenticatedUser(ctx context.Context) (string, error) +} + +// Client is the full VCS interface: PR reads, file reads, review management, and identity. +// Platform adapters (gitea, github) implement this interface. +type Client interface { + PRReader + FileReader + Reviewer + Identity +} diff --git a/vcs/types.go b/vcs/types.go new file mode 100644 index 0000000..6dfd932 --- /dev/null +++ b/vcs/types.go @@ -0,0 +1,82 @@ +package vcs + +// ReviewEvent is the event type for a pull request review action. +// Adapters must translate these action constants to/from platform-native values. +// For example, Gitea uses "APPROVED" as both action and state, while GitHub +// uses "APPROVE" for the action and returns "approved" as the state. +type ReviewEvent string + +const ( + // ReviewEventApprove approves the pull request. + ReviewEventApprove ReviewEvent = "APPROVE" + // ReviewEventRequestChanges requests changes to the pull request. + ReviewEventRequestChanges ReviewEvent = "REQUEST_CHANGES" + // ReviewEventComment posts a review comment without approval or rejection. + ReviewEventComment ReviewEvent = "COMMENT" +) + +// HeadRef identifies the source branch and latest commit of a pull request. +type HeadRef struct { + SHA string `json:"sha"` + Ref string `json:"ref"` +} + +// UserInfo identifies a user by login name. +type UserInfo struct { + Login string `json:"login"` +} + +// PullRequest holds relevant PR metadata. +type PullRequest struct { + Title string `json:"title"` + Body string `json:"body"` + Head HeadRef `json:"head"` +} + +// ChangedFile represents a file modified in a PR. +type ChangedFile struct { + Filename string `json:"filename"` + Status string `json:"status"` +} + +// ContentEntry represents a file or directory entry from the contents API. +type ContentEntry struct { + Name string `json:"name"` + Path string `json:"path"` + Type string `json:"type"` // "file" or "dir" +} + +// Review represents a pull request review. +type Review struct { + ID int64 `json:"id"` + Body string `json:"body"` + User UserInfo `json:"user"` + State string `json:"state"` + Stale bool `json:"stale"` + CommitID string `json:"commit_id"` +} + +// ReviewComment represents an inline comment in a review. +// All adapters use GitHub diff-position convention: +// - Position is a 1-indexed offset from the @@ hunk line in the unified diff. +// - CommitID identifies the commit the comment is anchored to. +// It is optional; omit (empty string) for review-level comments that are +// not attached to a specific commit. +// +// Adapters are responsible for translating to/from platform-native formats +// (e.g. Gitea uses line numbers; GitHub uses diff positions natively). +type ReviewComment struct { + Path string `json:"path"` + Position int `json:"position"` // diff-position: 1-indexed offset from @@ hunk line + CommitID string `json:"commit_id"` + Body string `json:"body"` +} + +// ReviewRequest is the payload for posting a review. +type ReviewRequest struct { + // Body is the top-level review comment. + Body string `json:"body"` + // Event is the review action (approve, request changes, or comment). + Event ReviewEvent `json:"event"` + Comments []ReviewComment `json:"comments,omitempty"` +}