Skip to content

Commit a7e0f6e

Browse files
committed
Add support for GitLab/Gitea
1 parent c98f366 commit a7e0f6e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+6930
-1528
lines changed

cmd/prx/main.go

Lines changed: 77 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
// Package main provides the prx command-line tool for analyzing GitHub pull requests.
1+
// Package main provides the prx command-line tool for analyzing pull requests
2+
// from GitHub, GitLab, and Codeberg/Gitea.
23
package main
34

45
import (
@@ -7,41 +8,43 @@ import (
78
"errors"
89
"flag"
910
"fmt"
10-
"log"
1111
"log/slog"
12-
"net/url"
1312
"os"
14-
"os/exec"
15-
"strconv"
16-
"strings"
1713
"time"
1814

1915
"github.com/codeGROOVE-dev/fido/pkg/store/null"
2016
"github.com/codeGROOVE-dev/prx/pkg/prx"
17+
"github.com/codeGROOVE-dev/prx/pkg/prx/auth"
18+
"github.com/codeGROOVE-dev/prx/pkg/prx/gitea"
19+
"github.com/codeGROOVE-dev/prx/pkg/prx/github"
20+
"github.com/codeGROOVE-dev/prx/pkg/prx/gitlab"
2121
)
2222

23-
const (
24-
expectedURLParts = 4
25-
pullPathIndex = 2
26-
pullPathValue = "pull"
23+
var (
24+
debug = flag.Bool("debug", false, "Enable debug logging")
25+
noCache = flag.Bool("no-cache", false, "Disable caching")
26+
referenceTimeStr = flag.String("reference-time", "", "Reference time for cache validation (RFC3339 format)")
2727
)
2828

2929
func main() {
30-
debug := flag.Bool("debug", false, "Enable debug logging")
31-
noCache := flag.Bool("no-cache", false, "Disable caching")
32-
referenceTimeStr := flag.String("reference-time", "", "Reference time for cache validation (RFC3339 format, e.g., 2025-03-16T06:18:08Z)")
3330
flag.Parse()
3431

32+
if err := run(); err != nil {
33+
fmt.Fprintf(os.Stderr, "prx: %v\n", err)
34+
os.Exit(1)
35+
}
36+
}
37+
38+
func run() error {
3539
if *debug {
3640
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
3741
Level: slog.LevelDebug,
3842
})))
3943
}
4044

4145
if flag.NArg() != 1 {
42-
fmt.Fprintf(os.Stderr, "Usage: %s [--debug] [--no-cache] [--reference-time=TIME] <pull-request-url>\n", os.Args[0])
43-
fmt.Fprintf(os.Stderr, "Example: %s https://github.com/golang/go/pull/12345\n", os.Args[0])
44-
os.Exit(1)
46+
printUsage()
47+
return errors.New("expected exactly one argument (pull request URL)")
4548
}
4649

4750
// Parse reference time if provided
@@ -50,91 +53,89 @@ func main() {
5053
var err error
5154
referenceTime, err = time.Parse(time.RFC3339, *referenceTimeStr)
5255
if err != nil {
53-
log.Printf("Invalid reference time format (use RFC3339, e.g., 2025-03-16T06:18:08Z): %v", err)
54-
os.Exit(1)
56+
return fmt.Errorf("invalid reference time format (use RFC3339, e.g., 2025-03-16T06:18:08Z): %w", err)
5557
}
5658
}
5759

5860
prURL := flag.Arg(0)
5961

60-
owner, repo, prNumber, err := parsePRURL(prURL)
62+
parsed, err := prx.ParseURL(prURL)
6163
if err != nil {
62-
log.Printf("Invalid PR URL: %v", err)
63-
os.Exit(1)
64+
return fmt.Errorf("invalid PR URL: %w", err)
6465
}
6566

66-
token, err := githubToken()
67-
if err != nil {
68-
log.Printf("Failed to get GitHub token: %v", err)
69-
os.Exit(1)
67+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
68+
defer cancel()
69+
70+
// Resolve token based on platform
71+
resolver := auth.NewResolver()
72+
platform := auth.DetectPlatform(parsed.Platform)
73+
74+
token, err := resolver.Resolve(ctx, platform, parsed.Host)
75+
// Authentication is optional for public repos on GitLab/Gitea/Codeberg
76+
// Only GitHub strictly requires authentication for most API calls
77+
tokenOptional := parsed.Platform != prx.PlatformGitHub
78+
79+
if err != nil && !tokenOptional {
80+
return fmt.Errorf("authentication failed: %w", err)
7081
}
7182

72-
var opts []prx.Option
73-
if *debug {
74-
opts = append(opts, prx.WithLogger(slog.Default()))
83+
var tokenValue string
84+
if token != nil {
85+
tokenValue = token.Value
86+
slog.Debug("Using token", "source", token.Source, "host", token.Host)
87+
} else {
88+
slog.Debug("No authentication token found, proceeding without authentication")
7589
}
7690

77-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
78-
defer cancel()
91+
// Create platform-specific client
92+
var prxPlatform prx.Platform
93+
switch parsed.Platform {
94+
case prx.PlatformGitHub:
95+
prxPlatform = github.NewPlatform(tokenValue)
96+
case prx.PlatformGitLab:
97+
prxPlatform = gitlab.NewPlatform(tokenValue, gitlab.WithBaseURL("https://"+parsed.Host))
98+
case prx.PlatformCodeberg:
99+
prxPlatform = gitea.NewCodebergPlatform(tokenValue)
100+
default:
101+
// Self-hosted Gitea
102+
prxPlatform = gitea.NewPlatform(tokenValue, gitea.WithBaseURL("https://"+parsed.Host))
103+
}
79104

80105
// Configure client options
106+
var opts []prx.Option
107+
if *debug {
108+
opts = append(opts, prx.WithLogger(slog.Default()))
109+
}
81110
if *noCache {
82111
opts = append(opts, prx.WithCacheStore(null.New[string, prx.PullRequestData]()))
83112
}
84113

85-
client := prx.NewClient(token, opts...)
86-
data, err := client.PullRequestWithReferenceTime(ctx, owner, repo, prNumber, referenceTime)
114+
client := prx.NewClientWithPlatform(prxPlatform, opts...)
115+
data, err := client.PullRequestWithReferenceTime(ctx, parsed.Owner, parsed.Repo, parsed.Number, referenceTime)
87116
if err != nil {
88-
log.Printf("Failed to fetch PR data: %v", err)
89-
cancel()
90-
os.Exit(1) //nolint:gocritic // False positive: cancel() is called immediately before os.Exit()
117+
return fmt.Errorf("failed to fetch PR data: %w", err)
91118
}
92119

93120
encoder := json.NewEncoder(os.Stdout)
94121
if err := encoder.Encode(data); err != nil {
95-
log.Printf("Failed to encode pull request: %v", err)
96-
cancel()
97-
os.Exit(1)
98-
}
99-
100-
cancel() // Ensure context is cancelled before exit
101-
}
102-
103-
func githubToken() (string, error) {
104-
cmd := exec.CommandContext(context.Background(), "gh", "auth", "token")
105-
output, err := cmd.Output()
106-
if err != nil {
107-
return "", fmt.Errorf("failed to run 'gh auth token': %w", err)
108-
}
109-
110-
token := strings.TrimSpace(string(output))
111-
if token == "" {
112-
return "", errors.New("no token returned by 'gh auth token'")
122+
return fmt.Errorf("failed to encode pull request: %w", err)
113123
}
114124

115-
return token, nil
125+
return nil
116126
}
117127

118-
//nolint:revive // function-result-limit: function needs all 4 return values
119-
func parsePRURL(prURL string) (owner, repo string, prNumber int, err error) {
120-
u, err := url.Parse(prURL)
121-
if err != nil {
122-
return "", "", 0, err
123-
}
124-
125-
if u.Host != "github.com" {
126-
return "", "", 0, errors.New("not a GitHub URL")
127-
}
128-
129-
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
130-
if len(parts) != expectedURLParts || parts[pullPathIndex] != pullPathValue {
131-
return "", "", 0, errors.New("invalid PR URL format")
132-
}
133-
134-
prNumber, err = strconv.Atoi(parts[3])
135-
if err != nil {
136-
return "", "", 0, fmt.Errorf("invalid PR number: %w", err)
137-
}
138-
139-
return parts[0], parts[1], prNumber, nil
128+
func printUsage() {
129+
fmt.Fprintf(os.Stderr, "Usage: %s [options] <pull-request-url>\n\n", os.Args[0])
130+
fmt.Fprint(os.Stderr, "Supported platforms:\n")
131+
fmt.Fprint(os.Stderr, " GitHub: https://github.com/owner/repo/pull/123\n")
132+
fmt.Fprint(os.Stderr, " GitLab: https://gitlab.com/owner/repo/-/merge_requests/123\n")
133+
fmt.Fprint(os.Stderr, " Codeberg: https://codeberg.org/owner/repo/pulls/123\n\n")
134+
fmt.Fprint(os.Stderr, "Authentication:\n")
135+
fmt.Fprint(os.Stderr, " GitHub: GITHUB_TOKEN env or 'gh auth login'\n")
136+
fmt.Fprint(os.Stderr, " GitLab: GITLAB_TOKEN env or 'glab auth login'\n")
137+
fmt.Fprint(os.Stderr, " Codeberg: CODEBERG_TOKEN env\n")
138+
fmt.Fprint(os.Stderr, " Gitea: GITEA_TOKEN env\n\n")
139+
fmt.Fprint(os.Stderr, "Options:\n")
140+
flag.PrintDefaults()
140141
}

cmd/prx_compare/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414

1515
"github.com/codeGROOVE-dev/prx/pkg/prx"
16+
"github.com/codeGROOVE-dev/prx/pkg/prx/github"
1617
)
1718

1819
const (
@@ -38,15 +39,15 @@ func main() {
3839

3940
// Both now use GraphQL, but we'll compare two fetches to ensure consistency
4041
fmt.Println("Fetching first time...")
41-
restClient := prx.NewClient(token)
42+
restClient := prx.NewClientWithPlatform(github.NewPlatform(token))
4243
restData, err := restClient.PullRequest(context.TODO(), owner, repo, prNumber)
4344
if err != nil {
4445
log.Fatalf("First fetch failed: %v", err)
4546
}
4647

4748
// Fetch again to compare consistency
4849
fmt.Println("Fetching second time...")
49-
graphqlClient := prx.NewClient(token)
50+
graphqlClient := prx.NewClientWithPlatform(github.NewPlatform(token))
5051
graphqlData, err := graphqlClient.PullRequest(context.TODO(), owner, repo, prNumber)
5152
if err != nil {
5253
log.Fatalf("Second fetch failed: %v", err)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ require (
1313
github.com/codeGROOVE-dev/fido/pkg/store/compress v1.10.0 // indirect
1414
github.com/klauspost/compress v1.18.2 // indirect
1515
github.com/puzpuzpuz/xsync/v4 v4.2.0 // indirect
16+
gopkg.in/yaml.v3 v3.0.1 // indirect
1617
)

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU
1414
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
1515
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
1616
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
19+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)