216 lines
4.2 KiB
Go
216 lines
4.2 KiB
Go
package gitea
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Client struct {
|
|
BaseURL string
|
|
Token string
|
|
Client *http.Client
|
|
}
|
|
|
|
func NewClient(baseURL, token string) *Client {
|
|
baseURL = strings.TrimRight(baseURL, "/")
|
|
|
|
if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
|
|
baseURL = "https://" + baseURL
|
|
}
|
|
|
|
if strings.HasSuffix(baseURL, "/api/v1") {
|
|
baseURL = strings.TrimSuffix(baseURL, "/api/v1")
|
|
}
|
|
if strings.HasSuffix(baseURL, "/api") {
|
|
baseURL = strings.TrimSuffix(baseURL, "/api")
|
|
}
|
|
|
|
return &Client{
|
|
BaseURL: baseURL,
|
|
Token: token,
|
|
Client: &http.Client{Timeout: 60 * time.Second},
|
|
}
|
|
}
|
|
|
|
type GiteaRepo struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
FullName string `json:"full_name"`
|
|
HTMLURL string `json:"html_url"`
|
|
Language string `json:"language"`
|
|
Topics []string `json:"topics"`
|
|
}
|
|
|
|
type GiteaCommit struct {
|
|
SHA string `json:"sha"`
|
|
RepoName string `json:"-"`
|
|
Commit struct {
|
|
Author struct {
|
|
Name string `json:"name"`
|
|
Email string `json:"email"`
|
|
Date time.Time `json:"date"`
|
|
} `json:"author"`
|
|
Message string `json:"message"`
|
|
} `json:"commit"`
|
|
}
|
|
|
|
type RepoRef struct {
|
|
Owner string
|
|
Name string
|
|
}
|
|
|
|
func (c *Client) do(method, path string) ([]byte, error) {
|
|
url := fmt.Sprintf("%s/api/v1%s", c.BaseURL, path)
|
|
|
|
req, err := http.NewRequest(method, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request for %s: %w", url, err)
|
|
}
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("token %s", c.Token))
|
|
req.Header.Set("Accept", "application/json")
|
|
req.Header.Set("User-Agent", "wrapped-cli/1.0")
|
|
|
|
resp, err := c.Client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("API request failed (%s %s): %w", method, url, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
errMsg := string(body)
|
|
if len(errMsg) > 500 {
|
|
errMsg = errMsg[:500]
|
|
}
|
|
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, errMsg)
|
|
}
|
|
|
|
return body, nil
|
|
}
|
|
|
|
func (c *Client) GetUserRepos() ([]GiteaRepo, error) {
|
|
var repos []GiteaRepo
|
|
page := 1
|
|
pageSize := 50
|
|
maxPages := 100
|
|
|
|
for page <= maxPages {
|
|
path := fmt.Sprintf("/user/repos?page=%d&limit=%d", page, pageSize)
|
|
body, err := c.do("GET", path)
|
|
if err != nil {
|
|
if len(repos) > 0 {
|
|
return repos, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var pageRepos []GiteaRepo
|
|
if err := json.Unmarshal(body, &pageRepos); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(pageRepos) == 0 {
|
|
break
|
|
}
|
|
|
|
repos = append(repos, pageRepos...)
|
|
|
|
if len(pageRepos) < pageSize {
|
|
break
|
|
}
|
|
|
|
page++
|
|
}
|
|
|
|
return repos, nil
|
|
}
|
|
|
|
func (c *Client) GetRepoCommits(owner, repo string) ([]GiteaCommit, error) {
|
|
var commits []GiteaCommit
|
|
page := 1
|
|
pageSize := 50
|
|
maxPages := 1000
|
|
|
|
for page <= maxPages {
|
|
path := fmt.Sprintf("/repos/%s/%s/commits?page=%d&limit=%d", owner, repo, page, pageSize)
|
|
body, err := c.do("GET", path)
|
|
if err != nil {
|
|
if len(commits) > 0 {
|
|
return commits, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var pageCommits []GiteaCommit
|
|
if err := json.Unmarshal(body, &pageCommits); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(pageCommits) == 0 {
|
|
break
|
|
}
|
|
|
|
commits = append(commits, pageCommits...)
|
|
|
|
if len(pageCommits) < pageSize {
|
|
break
|
|
}
|
|
|
|
page++
|
|
}
|
|
|
|
return commits, nil
|
|
}
|
|
|
|
func (c *Client) GetReposCommitsParallel(repos []RepoRef) ([]GiteaCommit, error) {
|
|
var allCommits []GiteaCommit
|
|
var mu sync.Mutex
|
|
var wg sync.WaitGroup
|
|
|
|
semaphore := make(chan struct{}, 5)
|
|
|
|
for _, repo := range repos {
|
|
wg.Add(1)
|
|
go func(owner, repoName string) {
|
|
defer wg.Done()
|
|
|
|
semaphore <- struct{}{}
|
|
defer func() { <-semaphore }()
|
|
|
|
commits, err := c.GetRepoCommits(owner, repoName)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for i := range commits {
|
|
commits[i].RepoName = repoName
|
|
}
|
|
|
|
mu.Lock()
|
|
allCommits = append(allCommits, commits...)
|
|
mu.Unlock()
|
|
}(repo.Owner, repo.Name)
|
|
}
|
|
|
|
wg.Wait()
|
|
return allCommits, nil
|
|
}
|
|
|
|
func (c *Client) TestConnection() error {
|
|
_, err := c.do("GET", "/user")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|