prx

package
v0.0.0-...-52ee643 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 16, 2026 License: Apache-2.0 Imports: 15 Imported by: 2

Documentation

Overview

Package prx provides a client for fetching pull request events from code hosting platforms. It supports GitHub, GitLab, and Codeberg (Gitea), with caching to improve performance and reduce API rate limit consumption.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/codeGROOVE-dev/prx/pkg/prx/pr"
)

func main() {
	// The simplest way: just pass a URL
	// Authentication is automatically resolved from environment or CLI tools
	ctx := context.Background()
	data, err := pr.Fetch(ctx, "https://github.com/owner/repo/pull/123")
	if err != nil {
		log.Fatal(err)
	}

	// Show PR metadata
	fmt.Printf("PR #%d: %s\n", data.PullRequest.Number, data.PullRequest.Title)
	fmt.Printf("Author: %s (write access: %d)\n", data.PullRequest.Author, data.PullRequest.AuthorWriteAccess)
	fmt.Printf("Status: %s, Mergeable: %v\n", data.PullRequest.State, data.PullRequest.MergeableState)

	// Process events
	for i := range data.Events {
		event := &data.Events[i]
		fmt.Printf("%s: %s by %s\n",
			event.Timestamp.Format("2006-01-02 15:04:05"),
			event.Kind,
			event.Actor,
		)
	}
}

Index

Examples

Constants

View Source
const (
	TestStateNone    = ""        // No tests or unknown state
	TestStateQueued  = "queued"  // Tests are queued to run
	TestStateRunning = "running" // Tests are currently executing
	TestStatePassing = "passing" // All tests passed
	TestStateFailing = "failing" // Some tests failed
	TestStatePending = "pending" // Some tests are pending
)

TestState represents the overall testing status of a pull request.

View Source
const (
	EventKindCommit        = "commit"         // EventKindCommit represents a commit event.
	EventKindComment       = "comment"        // EventKindComment represents a comment event.
	EventKindReview        = "review"         // EventKindReview represents a review event.
	EventKindReviewComment = "review_comment" // EventKindReviewComment represents a review comment event.

	EventKindLabeled   = "labeled"   // EventKindLabeled represents a label added event.
	EventKindUnlabeled = "unlabeled" // EventKindUnlabeled represents a label removed event.

	EventKindAssigned   = "assigned"   // EventKindAssigned represents an assignment event.
	EventKindUnassigned = "unassigned" // EventKindUnassigned represents an unassignment event.

	EventKindMilestoned   = "milestoned"   // EventKindMilestoned represents a milestone added event.
	EventKindDemilestoned = "demilestoned" // EventKindDemilestoned represents a milestone removed event.

	EventKindReviewRequested      = "review_requested"       // EventKindReviewRequested represents a review request event.
	EventKindReviewRequestRemoved = "review_request_removed" // EventKindReviewRequestRemoved represents a review request removed event.

	EventKindPROpened       = "pr_opened"        // EventKindPROpened represents a PR opened event.
	EventKindPRClosed       = "pr_closed"        // EventKindPRClosed represents a PR closed event.
	EventKindPRMerged       = "pr_merged"        // EventKindPRMerged represents a PR merge event.
	EventKindMerged         = "merged"           // EventKindMerged represents a merge event from timeline.
	EventKindReadyForReview = "ready_for_review" // EventKindReadyForReview represents a ready for review event.
	EventKindConvertToDraft = "convert_to_draft" // EventKindConvertToDraft represents a convert to draft event.
	EventKindClosed         = "closed"           // EventKindClosed represents a PR closed event.
	EventKindReopened       = "reopened"         // EventKindReopened represents a PR reopened event.
	EventKindRenamedTitle   = "renamed_title"    // EventKindRenamedTitle represents a title rename event.

	EventKindMentioned       = "mentioned"        // EventKindMentioned represents a mention event.
	EventKindReferenced      = "referenced"       // EventKindReferenced represents a reference event.
	EventKindCrossReferenced = "cross_referenced" // EventKindCrossReferenced represents a cross-reference event.

	EventKindPinned      = "pinned"      // EventKindPinned represents a pin event.
	EventKindUnpinned    = "unpinned"    // EventKindUnpinned represents an unpin event.
	EventKindTransferred = "transferred" // EventKindTransferred represents a transfer event.

	EventKindSubscribed   = "subscribed"   // EventKindSubscribed represents a subscription event.
	EventKindUnsubscribed = "unsubscribed" // EventKindUnsubscribed represents an unsubscription event.

	EventKindHeadRefDeleted     = "head_ref_deleted"      // EventKindHeadRefDeleted represents a head ref deletion event.
	EventKindHeadRefRestored    = "head_ref_restored"     // EventKindHeadRefRestored represents a head ref restoration event.
	EventKindHeadRefForcePushed = "head_ref_force_pushed" // EventKindHeadRefForcePushed represents a head ref force push event.

	EventKindBaseRefChanged     = "base_ref_changed"      // EventKindBaseRefChanged represents a base ref change event.
	EventKindBaseRefForcePushed = "base_ref_force_pushed" // EventKindBaseRefForcePushed represents a base ref force push event.

	EventKindReviewDismissed = "review_dismissed" // EventKindReviewDismissed represents a review dismissed event.

	EventKindLocked   = "locked"   // EventKindLocked represents a lock event.
	EventKindUnlocked = "unlocked" // EventKindUnlocked represents an unlock event.

	EventKindAutoMergeEnabled      = "auto_merge_enabled"       // EventKindAutoMergeEnabled represents an auto merge enabled event.
	EventKindAutoMergeDisabled     = "auto_merge_disabled"      // EventKindAutoMergeDisabled represents an auto merge disabled event.
	EventKindAddedToMergeQueue     = "added_to_merge_queue"     // EventKindAddedToMergeQueue represents an added to merge queue event.
	EventKindRemovedFromMergeQueue = "removed_from_merge_queue" // EventKindRemovedFromMergeQueue represents removal from merge queue.

	// EventKindAutomaticBaseChangeSucceeded represents a successful base change.
	EventKindAutomaticBaseChangeSucceeded = "automatic_base_change_succeeded"
	// EventKindAutomaticBaseChangeFailed represents a failed base change.
	EventKindAutomaticBaseChangeFailed = "automatic_base_change_failed"

	EventKindDeployed = "deployed" // EventKindDeployed represents a deployment event.
	// EventKindDeploymentEnvironmentChanged represents a deployment environment change event.
	EventKindDeploymentEnvironmentChanged = "deployment_environment_changed"

	EventKindConnected    = "connected"    // EventKindConnected represents a connected event.
	EventKindDisconnected = "disconnected" // EventKindDisconnected represents a disconnected event.
	EventKindUserBlocked  = "user_blocked" // EventKindUserBlocked represents a user blocked event.

	EventKindStatusCheck = "status_check" // EventKindStatusCheck represents a status check event (from APIs).
	EventKindCheckRun    = "check_run"    // EventKindCheckRun represents a check run event (from APIs).
)

Event kind constants for PR timeline events.

View Source
const (
	WriteAccessNo         = -2 // User confirmed to not have write access
	WriteAccessUnlikely   = -1 // User unlikely to have write access (CONTRIBUTOR, NONE, etc.)
	WriteAccessNA         = 0  // Not applicable/not set (omitted from JSON)
	WriteAccessLikely     = 1  // User likely has write access but unable to confirm (MEMBER with 403 API response)
	WriteAccessDefinitely = 2  // User definitely has write access (OWNER, COLLABORATOR, or confirmed via API)
)

WriteAccess constants for the Event.WriteAccess field.

View Source
const (
	PlatformGitHub   = "github"
	PlatformGitLab   = "gitlab"
	PlatformCodeberg = "codeberg"
)

Common platform hosts.

Variables

This section is empty.

Functions

func BuildCodebergURL

func BuildCodebergURL(owner, repo string, number int) string

BuildCodebergURL constructs a Codeberg PR URL from components.

func BuildGitHubURL

func BuildGitHubURL(owner, repo string, number int) string

BuildGitHubURL constructs a GitHub PR URL from components.

func BuildGitLabURL

func BuildGitLabURL(host, owner, repo string, number int) string

BuildGitLabURL constructs a GitLab MR URL from components.

func BuildGiteaURL

func BuildGiteaURL(host, owner, repo string, number int) string

BuildGiteaURL constructs a Gitea PR URL from components. For Codeberg, use BuildCodebergURL instead.

func CalculateParticipantAccess

func CalculateParticipantAccess(events []Event, pr *PullRequest) map[string]int

CalculateParticipantAccess builds a map of all PR participants to their write access levels. Includes the PR author, assignees, reviewers, and all event actors.

func ContainsQuestion

func ContainsQuestion(text string) bool

ContainsQuestion determines if text contains a question based on: 1. Presence of a question mark 2. Common question patterns with proper word boundaries.

func DetectPlatform deprecated

func DetectPlatform(host string) string

DetectPlatform attempts to detect the platform from a host name. Returns the platform identifier or empty string if unknown.

Deprecated: Use detectPlatformFromHost instead, which defaults to Gitea.

func DetectPlatformFromHost

func DetectPlatformFromHost(host string) string

DetectPlatformFromHost detects platform from hostname, defaulting to Gitea for unknown hosts.

func ExtractShortRef

func ExtractShortRef(input string) (string, error)

ExtractShortRef returns a short reference string like "owner/repo#123".

func FinalizePullRequest

func FinalizePullRequest(pullRequest *PullRequest, events []Event, requiredChecks []string, testStateFromAPI string)

FinalizePullRequest applies final calculations and consistency fixes.

func FixTestState

func FixTestState(pullRequest *PullRequest)

FixTestState ensures test_state is consistent with check_summary. If CheckSummary has data, it takes precedence. Otherwise, preserve the existing TestState (which may have been set from platform-specific data like GitLab pipelines).

func IsValidPRURL

func IsValidPRURL(input string) bool

IsValidPRURL returns true if the input appears to be a valid PR/MR URL.

func NormalizeURL

func NormalizeURL(input string) (string, error)

NormalizeURL takes any supported URL format and returns a normalized URL string.

func SetBlockedDescription

func SetBlockedDescription(pullRequest *PullRequest)

SetBlockedDescription determines what's blocking the PR and sets appropriate description.

func SetMergeableDescription

func SetMergeableDescription(pullRequest *PullRequest)

SetMergeableDescription adds human-readable description for mergeable state.

func Truncate

func Truncate(s string) string

Truncate returns the first maxTruncateLength characters of s.

func UpgradeWriteAccess

func UpgradeWriteAccess(events []Event)

UpgradeWriteAccess scans through events and upgrades write_access from 1 (likely) to 2 (definitely) for actors who have performed actions that require write access.

Types

type ApprovalSummary

type ApprovalSummary struct {
	// Approvals from users confirmed to have write access (owners, collaborators, members with confirmed access)
	ApprovalsWithWriteAccess int `json:"approvals_with_write_access"`

	// Approvals from users with unknown or likely write access (members, uncertain cases)
	ApprovalsWithUnknownAccess int `json:"approvals_with_unknown_access"`

	// Approvals from users confirmed to not have write access (contributors, outside collaborators)
	ApprovalsWithoutWriteAccess int `json:"approvals_without_write_access"`

	// Outstanding change requests from any reviewer
	ChangesRequested int `json:"changes_requested"`
}

ApprovalSummary tracks PR review approvals and change requests.

func CalculateApprovalSummary

func CalculateApprovalSummary(events []Event) *ApprovalSummary

CalculateApprovalSummary analyzes review events and categorizes approvals by reviewer's write access.

type CheckSummary

type CheckSummary struct {
	Success   map[string]string `json:"success"`   // Map of successful check names to their status descriptions
	Failing   map[string]string `json:"failing"`   // Map of failing check names to their status descriptions (excludes cancelled)
	Pending   map[string]string `json:"pending"`   // Map of pending check names to their status descriptions
	Cancelled map[string]string `json:"cancelled"` // Map of cancelled check names to their status descriptions
	Skipped   map[string]string `json:"skipped"`   // Map of skipped check names to their status descriptions
	Stale     map[string]string `json:"stale"`     // Map of stale check names to their status descriptions
	Neutral   map[string]string `json:"neutral"`   // Map of neutral check names to their status descriptions
}

CheckSummary aggregates all status checks and check runs.

func CalculateCheckSummary

func CalculateCheckSummary(events []Event, requiredChecks []string) *CheckSummary

CalculateCheckSummary analyzes check/status events and categorizes them by outcome.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client provides methods to fetch pull request events from various platforms.

func NewClient

func NewClient(platform Platform, opts ...Option) *Client

NewClient creates a new Client with the given platform. For GitHub: NewClient(github.NewPlatform(token), opts...) For GitLab: NewClient(gitlab.NewPlatform(token), opts...) For Gitea: NewClient(gitea.NewPlatform(token), opts...)

func (*Client) Close

func (c *Client) Close() error

Close releases cache resources.

func (*Client) PullRequest

func (c *Client) PullRequest(ctx context.Context, owner, repo string, prNumber int) (*PullRequestData, error)

PullRequest fetches a pull request with all its events and metadata.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/codeGROOVE-dev/prx/pkg/prx/pr"
)

func main() {
	// For advanced use cases where you need to fetch multiple PRs from the same platform,
	// you can still use the client-based API
	ctx := context.Background()

	// Fetch using the simple API
	data, err := pr.Fetch(ctx, "https://github.com/golang/go/pull/123")
	if err != nil {
		log.Fatal(err)
	}

	// Show PR size
	fmt.Printf("PR #%d: +%d -%d in %d files\n",
		data.PullRequest.Number,
		data.PullRequest.Additions,
		data.PullRequest.Deletions,
		data.PullRequest.ChangedFiles)

	// Count events by type
	eventCounts := make(map[string]int)
	for i := range data.Events {
		eventCounts[data.Events[i].Kind]++
	}

	// Print summary
	fmt.Printf("Total events: %d\n", len(data.Events))
	for eventKind, count := range eventCounts {
		fmt.Printf("%s: %d\n", eventKind, count)
	}
}

func (*Client) PullRequestWithReferenceTime

func (c *Client) PullRequestWithReferenceTime(
	ctx context.Context,
	owner, repo string,
	pr int,
	refTime time.Time,
) (*PullRequestData, error)

PullRequestWithReferenceTime fetches a pull request using the given reference time for caching decisions.

type Event

type Event struct {
	Timestamp   time.Time `json:"timestamp"`
	Kind        string    `json:"kind"`
	Actor       string    `json:"actor"`
	Target      string    `json:"target,omitempty"`
	Outcome     string    `json:"outcome,omitempty"`
	Body        string    `json:"body,omitempty"`
	Description string    `json:"description,omitempty"`
	WriteAccess int       `json:"write_access,omitempty"`
	Bot         bool      `json:"bot,omitempty"`
	TargetIsBot bool      `json:"target_is_bot,omitempty"`
	Question    bool      `json:"question,omitempty"`
	Required    bool      `json:"required,omitempty"`
	Outdated    bool      `json:"outdated,omitempty"` // For review comments: indicates comment is on outdated code
}

Event represents a single event that occurred on a pull request. Each event captures who did what and when, with additional context depending on the event type.

func FilterEvents

func FilterEvents(events []Event) []Event

FilterEvents removes non-essential events to reduce noise. Currently filters out successful status_check events (keeps failures).

type Option

type Option func(*Client)

Option is a function that configures a Client.

func WithCacheStore

func WithCacheStore(store PRStore) Option

WithCacheStore sets a custom cache store for PR data. Use null.New[string, prx.PullRequestData]() to disable persistence.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets a custom logger for the client.

type PRStore

type PRStore = fido.Store[string, PullRequestData]

PRStore is the interface for PR cache storage backends. This is an alias for fido.Store with the appropriate type parameters.

func NewCacheStore

func NewCacheStore(dir string) (PRStore, error)

NewCacheStore creates a cache store backed by the given directory. This is a convenience function for use with WithCacheStore.

type ParsedPR

type ParsedPR struct {
	Owner    string
	Repo     string
	Platform string // Empty if parsed from short ref
	Number   int
}

ParsedPR represents the result of parsing a PR reference in any format.

func ParseOwnerRepoPR

func ParseOwnerRepoPR(input string) (*ParsedPR, error)

ParseOwnerRepoPR is a convenience function that accepts multiple input formats: full URL, short ref with hash, or short ref with slash.

type ParsedURL

type ParsedURL struct {
	Platform string // "github", "gitlab", "codeberg"
	Host     string // e.g., "github.com", "gitlab.com"
	Owner    string
	Repo     string
	Number   int // PR or MR number
}

ParsedURL represents a parsed code hosting URL.

func ParseURL

func ParseURL(input string) (*ParsedURL, error)

ParseURL parses a pull request or merge request URL and returns its components. It detects the platform based on the URL structure and host. URL fragments (#...) and query parameters (?...) are automatically stripped.

type Platform

type Platform interface {
	// FetchPR retrieves a pull request with all events and metadata.
	// The refTime parameter is used for cache validation decisions.
	FetchPR(ctx context.Context, owner, repo string, number int, refTime time.Time) (*PullRequestData, error)

	// Name returns the platform identifier (e.g., "github", "gitlab", "codeberg").
	Name() string
}

Platform fetches pull request data from a code hosting service. Each platform (GitHub, GitLab, Codeberg) implements its own fetching strategy.

type PullRequest

type PullRequest struct {
	// 16-byte fields (time.Time)
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	// 8-byte pointer fields
	ClosedAt        *time.Time       `json:"closed_at,omitempty"`
	MergedAt        *time.Time       `json:"merged_at,omitempty"`
	ApprovalSummary *ApprovalSummary `json:"approval_summary,omitempty"`
	CheckSummary    *CheckSummary    `json:"check_summary,omitempty"`
	Mergeable       *bool            `json:"mergeable,omitempty"`
	// 24-byte slice/map fields
	Assignees         []string               `json:"assignees,omitempty"`
	Labels            []string               `json:"labels,omitempty"`
	Commits           []string               `json:"commits,omitempty"` // List of commit SHAs in chronological order (oldest to newest)
	Reviewers         map[string]ReviewState `json:"reviewers,omitempty"`
	ParticipantAccess map[string]int         `json:"participant_access,omitempty"` // Map of username to WriteAccess level
	// 16-byte string fields
	MergeableState            string `json:"mergeable_state"`
	MergeableStateDescription string `json:"mergeable_state_description,omitempty"`
	Author                    string `json:"author"`
	Body                      string `json:"body"`
	Title                     string `json:"title"`
	MergedBy                  string `json:"merged_by,omitempty"`
	State                     string `json:"state"`
	TestState                 string `json:"test_state,omitempty"`
	HeadSHA                   string `json:"head_sha,omitempty"`
	// 8-byte int fields
	Number            int `json:"number"`
	ChangedFiles      int `json:"changed_files"`
	Deletions         int `json:"deletions"`
	Additions         int `json:"additions"`
	AuthorWriteAccess int `json:"author_write_access,omitempty"`
	// 1-byte bool fields
	AuthorBot bool `json:"author_bot"`
	Merged    bool `json:"merged"`
	Draft     bool `json:"draft"`
}

PullRequest represents a GitHub pull request with its essential metadata.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/codeGROOVE-dev/prx/pkg/prx/pr"
)

func main() {
	ctx := context.Background()

	// Works with GitHub
	data, err := pr.Fetch(ctx, "https://github.com/owner/repo/pull/123")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("GitHub PR #%d\n", data.PullRequest.Number)

	// Works with GitLab
	data, err = pr.Fetch(ctx, "https://gitlab.com/owner/repo/-/merge_requests/456")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("GitLab MR #%d\n", data.PullRequest.Number)

	// Works with Codeberg
	data, err = pr.Fetch(ctx, "https://codeberg.org/owner/repo/pulls/789")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Codeberg PR #%d\n", data.PullRequest.Number)

	// Works with any Gitea instance
	data, err = pr.Fetch(ctx, "https://gitea.example.com/owner/repo/pulls/100")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Gitea PR #%d\n", data.PullRequest.Number)

	// URL fragments and query parameters are automatically stripped
	data, err = pr.Fetch(ctx, "https://github.com/owner/repo/pull/123?tab=checks#issuecomment-456")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("PR #%d (cleaned URL)\n", data.PullRequest.Number)
}

type PullRequestData

type PullRequestData struct {
	CachedAt    time.Time   `json:"cached_at,omitzero"` // When this data was cached
	Events      []Event     `json:"events"`
	PullRequest PullRequest `json:"pull_request"`
}

PullRequestData contains a pull request and all its associated events.

type ReviewState

type ReviewState string

ReviewState represents the current state of a reviewer's review.

const (
	ReviewStatePending          ReviewState = "pending"           // Review requested but not yet submitted
	ReviewStateApproved         ReviewState = "approved"          // Approved
	ReviewStateChangesRequested ReviewState = "changes_requested" // Changes requested
	ReviewStateCommented        ReviewState = "commented"         // Reviewed with comments only
)

Review state constants.

type ShortRef

type ShortRef struct {
	Owner  string
	Repo   string
	Number int
}

ShortRef represents a parsed short reference like "owner/repo#123".

func ParseShortRef

func ParseShortRef(ref string) (*ShortRef, error)

ParseShortRef parses a short reference like "owner/repo#123" or "owner/repo/123". It does not include platform information - that must be provided separately.

Directories

Path Synopsis
Package auth provides token resolution for different git hosting platforms.
Package auth provides token resolution for different git hosting platforms.
Package gitea provides a Gitea/Codeberg platform implementation for fetching pull request data from Gitea-based forges.
Package gitea provides a Gitea/Codeberg platform implementation for fetching pull request data from Gitea-based forges.
Package github provides a low-level client for the GitHub REST and GraphQL APIs.
Package github provides a low-level client for the GitHub REST and GraphQL APIs.
Package gitlab provides a GitLab platform implementation for fetching merge request data from GitLab instances.
Package gitlab provides a GitLab platform implementation for fetching merge request data from GitLab instances.
Package pr provides convenience functions for fetching pull requests with automatic platform detection and authentication resolution.
Package pr provides convenience functions for fetching pull requests with automatic platform detection and authentication resolution.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL