Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ issues:
max-issues-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0
exclude-rules:
- path: pkg/prx/types/
text: "var-naming: avoid meaningless package names"

formatters:
enable:
Expand Down Expand Up @@ -188,6 +191,8 @@ linters:
disabled: true
- name: bare-return
disabled: true
- name: var-naming
disabled: true # types is a perfectly valid package name

rowserrcheck:
# database/sql is always checked.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ Go library for fetching pull request data from GitHub, GitLab, and Gitea/Codeber
## Quick Start

```go
import "github.com/codeGROOVE-dev/prx/pkg/prx/fetch"
import "github.com/codeGROOVE-dev/prx/pkg/prx"

data, err := fetch.Fetch(ctx, "https://github.com/golang/go/pull/12345")
data, err := prx.Fetch(ctx, "https://github.com/golang/go/pull/12345")
// Works with: GitHub, GitLab, Codeberg, self-hosted instances
```
Auto-detects platform and resolves authentication from `GITHUB_TOKEN`/`GITLAB_TOKEN`/`GITEA_TOKEN` or CLI tools (`gh`, `glab`, `tea`, `berg`).
Expand Down
15 changes: 8 additions & 7 deletions cmd/prx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/codeGROOVE-dev/prx/pkg/prx/gitea"
"github.com/codeGROOVE-dev/prx/pkg/prx/github"
"github.com/codeGROOVE-dev/prx/pkg/prx/gitlab"
"github.com/codeGROOVE-dev/prx/pkg/prx/types"
)

var (
Expand Down Expand Up @@ -59,7 +60,7 @@ func run() error {

prURL := flag.Arg(0)

parsed, err := prx.ParseURL(prURL)
parsed, err := types.ParseURL(prURL)
if err != nil {
return fmt.Errorf("invalid PR URL: %w", err)
}
Expand All @@ -74,7 +75,7 @@ func run() error {
token, err := resolver.Resolve(ctx, platform, parsed.Host)
// Authentication is optional for public repos on GitLab/Gitea/Codeberg
// Only GitHub strictly requires authentication for most API calls
tokenOptional := parsed.Platform != prx.PlatformGitHub
tokenOptional := parsed.Platform != types.PlatformGitHub

if err != nil && !tokenOptional {
return fmt.Errorf("authentication failed: %w", err)
Expand All @@ -89,13 +90,13 @@ func run() error {
}

// Create platform-specific client
var prxPlatform prx.Platform
var prxPlatform types.Platform
switch parsed.Platform {
case prx.PlatformGitHub:
case types.PlatformGitHub:
prxPlatform = github.NewPlatform(tokenValue)
case prx.PlatformGitLab:
case types.PlatformGitLab:
prxPlatform = gitlab.NewPlatform(tokenValue, gitlab.WithBaseURL("https://"+parsed.Host))
case prx.PlatformCodeberg:
case types.PlatformCodeberg:
prxPlatform = gitea.NewCodebergPlatform(tokenValue)
default:
// Self-hosted Gitea
Expand All @@ -108,7 +109,7 @@ func run() error {
opts = append(opts, prx.WithLogger(slog.Default()))
}
if *noCache {
opts = append(opts, prx.WithCacheStore(null.New[string, prx.PullRequestData]()))
opts = append(opts, prx.WithCacheStore(null.New[string, types.PullRequestData]()))
}

client := prx.NewClientWithPlatform(prxPlatform, opts...)
Expand Down
33 changes: 17 additions & 16 deletions cmd/prx_compare/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

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

const (
Expand Down Expand Up @@ -63,7 +64,7 @@ func main() {
fmt.Println("\nFull data saved to rest_output.json and graphql_output.json")
}

func comparePullRequestData(rest, graphql *prx.PullRequestData) {
func comparePullRequestData(rest, graphql *types.PullRequestData) {
// Compare PullRequest fields
fmt.Println("=== Pull Request Metadata ===")
comparePullRequest(&rest.PullRequest, &graphql.PullRequest)
Expand All @@ -73,7 +74,7 @@ func comparePullRequestData(rest, graphql *prx.PullRequestData) {
compareEvents(rest.Events, graphql.Events)
}

func comparePullRequest(rest, graphql *prx.PullRequest) {
func comparePullRequest(rest, graphql *types.PullRequest) {
differences, matches := compareFields(rest, graphql)

if len(differences) > 0 {
Expand All @@ -86,7 +87,7 @@ func comparePullRequest(rest, graphql *prx.PullRequest) {
fmt.Printf("\nMatching fields: %s\n", strings.Join(matches, ", "))
}

func compareFields(rest, graphql *prx.PullRequest) (differences, matches []string) {
func compareFields(rest, graphql *types.PullRequest) (differences, matches []string) {
restVal := reflect.ValueOf(*rest)
graphqlVal := reflect.ValueOf(*graphql)
restType := restVal.Type()
Expand Down Expand Up @@ -131,7 +132,7 @@ func comparePointerField(name string, restField, graphqlField reflect.Value) str
return ""
}

func compareCheckSummary(rest, graphql *prx.PullRequest) {
func compareCheckSummary(rest, graphql *types.PullRequest) {
if rest.CheckSummary == nil || graphql.CheckSummary == nil {
return
}
Expand All @@ -149,7 +150,7 @@ func compareCheckSummary(rest, graphql *prx.PullRequest) {
compareCheckSummaryMaps(rest.CheckSummary, graphql.CheckSummary)
}

func compareCheckSummaryMaps(rest, graphql *prx.CheckSummary) {
func compareCheckSummaryMaps(rest, graphql *types.CheckSummary) {
compareSummaryMap("Success", rest.Success, graphql.Success)
compareSummaryMap("Failing", rest.Failing, graphql.Failing)
compareSummaryMap("Pending", rest.Pending, graphql.Pending)
Expand Down Expand Up @@ -186,7 +187,7 @@ func compareStatusMaps(rest, graphql map[string]string) {
}
}

func compareEvents(restEvents, graphqlEvents []prx.Event) {
func compareEvents(restEvents, graphqlEvents []types.Event) {
// Count events by type
restCounts := countEventsByType(restEvents)
graphqlCounts := countEventsByType(graphqlEvents)
Expand All @@ -200,13 +201,13 @@ func compareEvents(restEvents, graphqlEvents []prx.Event) {
allTypes[k] = true
}

var types []string
var eventTypes []string
for t := range allTypes {
types = append(types, t)
eventTypes = append(eventTypes, t)
}
sort.Strings(types)
sort.Strings(eventTypes)

for _, eventType := range types {
for _, eventType := range eventTypes {
restCount := restCounts[eventType]
graphqlCount := graphqlCounts[eventType]
if restCount != graphqlCount {
Expand All @@ -226,7 +227,7 @@ func compareEvents(restEvents, graphqlEvents []prx.Event) {
restByType := groupEventsByType(restEvents)
graphqlByType := groupEventsByType(graphqlEvents)

for _, eventType := range types {
for _, eventType := range eventTypes {
restTypeEvents := restByType[eventType]
graphqlTypeEvents := graphqlByType[eventType]

Expand Down Expand Up @@ -294,23 +295,23 @@ func compareEvents(restEvents, graphqlEvents []prx.Event) {
}
}

func countEventsByType(events []prx.Event) map[string]int {
func countEventsByType(events []types.Event) map[string]int {
counts := make(map[string]int)
for i := range events {
counts[events[i].Kind]++
}
return counts
}

func groupEventsByType(events []prx.Event) map[string][]prx.Event {
grouped := make(map[string][]prx.Event)
func groupEventsByType(events []types.Event) map[string][]types.Event {
grouped := make(map[string][]types.Event)
for i := range events {
grouped[events[i].Kind] = append(grouped[events[i].Kind], events[i])
}
return grouped
}

func extractWriteAccess(events []prx.Event) map[string]int {
func extractWriteAccess(events []types.Event) map[string]int {
access := make(map[string]int)
for i := range events {
e := &events[i]
Expand All @@ -324,7 +325,7 @@ func extractWriteAccess(events []prx.Event) map[string]int {
return access
}

func extractBots(events []prx.Event) map[string]bool {
func extractBots(events []types.Event) map[string]bool {
bots := make(map[string]bool)
for i := range events {
e := &events[i]
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ require (
github.com/codeGROOVE-dev/fido/pkg/store/localfs v1.10.0
github.com/codeGROOVE-dev/fido/pkg/store/null v1.10.0
github.com/codeGROOVE-dev/retry v1.3.1
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/codeGROOVE-dev/fido/pkg/store/compress v1.10.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/puzpuzpuz/xsync/v4 v4.2.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
37 changes: 25 additions & 12 deletions pkg/prx/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/codeGROOVE-dev/fido"
"github.com/codeGROOVE-dev/fido/pkg/store/localfs"
"github.com/codeGROOVE-dev/prx/pkg/prx/types"
)

const (
Expand All @@ -29,13 +30,13 @@ const (

// PRStore is the interface for PR cache storage backends.
// This is an alias for fido.Store with the appropriate type parameters.
type PRStore = fido.Store[string, PullRequestData]
type PRStore = fido.Store[string, types.PullRequestData]

// Client provides methods to fetch pull request events from various platforms.
type Client struct {
platform Platform
platform types.Platform
logger *slog.Logger
prCache *fido.TieredCache[string, PullRequestData]
prCache *fido.TieredCache[string, types.PullRequestData]
}

// Option is a function that configures a Client.
Expand Down Expand Up @@ -67,13 +68,13 @@ func WithCacheStore(store PRStore) Option {
// For GitHub: NewClientWithPlatform(github.NewPlatform(token), opts...)
// For GitLab: NewClientWithPlatform(gitlab.NewPlatform(token), opts...)
// For Gitea: NewClientWithPlatform(gitea.NewPlatform(token), opts...)
func NewClient(platform Platform, opts ...Option) *Client {
func NewClient(platform types.Platform, opts ...Option) *Client {
return NewClientWithPlatform(platform, opts...)
}

// NewClientWithPlatform creates a new Client with the given platform.
// Use this to create clients for GitLab, Codeberg, or other platforms.
func NewClientWithPlatform(platform Platform, opts ...Option) *Client {
func NewClientWithPlatform(platform types.Platform, opts ...Option) *Client {
c := &Client{
platform: platform,
logger: slog.Default(),
Expand All @@ -90,7 +91,7 @@ func NewClientWithPlatform(platform Platform, opts ...Option) *Client {
return c
}

func createDefaultCache(log *slog.Logger) *fido.TieredCache[string, PullRequestData] {
func createDefaultCache(log *slog.Logger) *fido.TieredCache[string, types.PullRequestData] {
dir, err := os.UserCacheDir()
if err != nil {
dir = os.TempDir()
Expand All @@ -100,7 +101,7 @@ func createDefaultCache(log *slog.Logger) *fido.TieredCache[string, PullRequestD
log.Warn("failed to create cache directory, caching disabled", "error", err)
return nil
}
store, err := localfs.New[string, PullRequestData]("prx-pr", dir)
store, err := localfs.New[string, types.PullRequestData]("prx-pr", dir)
if err != nil {
log.Warn("failed to create cache store, caching disabled", "error", err)
return nil
Expand All @@ -114,7 +115,7 @@ func createDefaultCache(log *slog.Logger) *fido.TieredCache[string, PullRequestD
}

// PullRequest fetches a pull request with all its events and metadata.
func (c *Client) PullRequest(ctx context.Context, owner, repo string, prNumber int) (*PullRequestData, error) {
func (c *Client) PullRequest(ctx context.Context, owner, repo string, prNumber int) (*types.PullRequestData, error) {
return c.PullRequestWithReferenceTime(ctx, owner, repo, prNumber, time.Now())
}

Expand All @@ -124,7 +125,7 @@ func (c *Client) PullRequestWithReferenceTime(
owner, repo string,
pr int,
refTime time.Time,
) (*PullRequestData, error) {
) (*types.PullRequestData, error) {
if c.prCache == nil {
return c.platform.FetchPR(ctx, owner, repo, pr, refTime)
}
Expand All @@ -150,10 +151,10 @@ func (c *Client) PullRequestWithReferenceTime(
"platform", c.platform.Name(), "owner", owner, "repo", repo, "pr", pr)
}

result, err := c.prCache.Fetch(ctx, key, func(ctx context.Context) (PullRequestData, error) {
result, err := c.prCache.Fetch(ctx, key, func(ctx context.Context) (types.PullRequestData, error) {
data, err := c.platform.FetchPR(ctx, owner, repo, pr, refTime)
if err != nil {
return PullRequestData{}, err
return types.PullRequestData{}, err
}
data.CachedAt = time.Now()
return *data, nil
Expand Down Expand Up @@ -182,7 +183,7 @@ func NewCacheStore(dir string) (PRStore, error) {
if err := os.MkdirAll(dir, 0o700); err != nil {
return nil, fmt.Errorf("creating cache directory: %w", err)
}
store, err := localfs.New[string, PullRequestData]("prx-pr", dir)
store, err := localfs.New[string, types.PullRequestData]("prx-pr", dir)
if err != nil {
return nil, fmt.Errorf("creating PR cache store: %w", err)
}
Expand All @@ -205,3 +206,15 @@ func collaboratorsCacheKey(owner, repo string) string {
func rulesetsCacheKey(owner, repo string) string {
return fmt.Sprintf("%s/%s", owner, repo)
}

// isHexString returns true if the string contains only hex characters.
func isHexString(s string) bool {
for i := range s {
c := s[i]
if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') {
continue
}
return false
}
return true
}
Loading