summaryrefslogtreecommitdiff
path: root/internal/codeberg
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-23 23:26:52 +0300
committerPaul Buetow <paul@buetow.org>2025-06-23 23:26:52 +0300
commit006724744a943aad877a92406a5e2b4d5d12acd3 (patch)
treece79e6481d3a9ae38bebf3a7acd1d3a7edd520a8 /internal/codeberg
parent125e2a2c50bcb3eaa5dfb8802c6de3b2f406b3d2 (diff)
Add GitHub repository creation and improve error handling
- Add --create-github-repos flag to automatically create missing GitHub repositories - Implement GitHub API client with token support from config/env/file - Add Codeberg API integration to sync all public repositories - Make sync operations stop on first error for better debugging - Support GitHub repo creation for all sync commands (--sync, --sync-all, --sync-codeberg-public) - Add comprehensive error messages and debug logging 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'internal/codeberg')
-rw-r--r--internal/codeberg/codeberg.go133
1 files changed, 133 insertions, 0 deletions
diff --git a/internal/codeberg/codeberg.go b/internal/codeberg/codeberg.go
new file mode 100644
index 0000000..288d1e7
--- /dev/null
+++ b/internal/codeberg/codeberg.go
@@ -0,0 +1,133 @@
+package codeberg
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "time"
+)
+
+// Repository represents a Codeberg/Gitea repository
+type Repository struct {
+ ID int64 `json:"id"`
+ Name string `json:"name"`
+ FullName string `json:"full_name"`
+ Description string `json:"description"`
+ Private bool `json:"private"`
+ Fork bool `json:"fork"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ CloneURL string `json:"clone_url"`
+ SSHURL string `json:"ssh_url"`
+ Size int `json:"size"`
+ Archived bool `json:"archived"`
+ Empty bool `json:"empty"`
+}
+
+// Client handles Codeberg API operations
+type Client struct {
+ baseURL string
+ org string
+}
+
+// CLAUDE: Is there a rason, that we return a pointer of Client?
+// NewClient creates a new Codeberg API client
+func NewClient(org string) *Client {
+ return &Client{
+ baseURL: "https://codeberg.org/api/v1",
+ org: org,
+ }
+}
+
+// ListPublicRepos lists all public repositories for an organization
+func (c *Client) ListPublicRepos() ([]Repository, error) {
+ var allRepos []Repository
+ page := 1
+ perPage := 50
+
+ for {
+ url := fmt.Sprintf("%s/orgs/%s/repos?page=%d&limit=%d", c.baseURL, c.org, page, perPage)
+
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to fetch repositories: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
+ }
+
+ var repos []Repository
+ if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil {
+ return nil, fmt.Errorf("failed to parse response: %w", err)
+ }
+
+ // Filter only public, non-fork, non-archived, non-empty repos
+ for _, repo := range repos {
+ if !repo.Private && !repo.Fork && !repo.Archived && !repo.Empty {
+ allRepos = append(allRepos, repo)
+ }
+ }
+
+ // If we got fewer repos than requested, we've reached the end
+ if len(repos) < perPage {
+ break
+ }
+
+ page++
+ }
+
+ return allRepos, nil
+}
+
+// ListUserPublicRepos lists all public repositories for a user
+func (c *Client) ListUserPublicRepos() ([]Repository, error) {
+ var allRepos []Repository
+ page := 1
+ perPage := 50
+
+ for {
+ url := fmt.Sprintf("%s/users/%s/repos?page=%d&limit=%d", c.baseURL, c.org, page, perPage)
+
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to fetch repositories: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
+ }
+
+ var repos []Repository
+ if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil {
+ return nil, fmt.Errorf("failed to parse response: %w", err)
+ }
+
+ // Filter only public, non-fork, non-archived, non-empty repos
+ for _, repo := range repos {
+ if !repo.Private && !repo.Fork && !repo.Archived && !repo.Empty {
+ allRepos = append(allRepos, repo)
+ }
+ }
+
+ // If we got fewer repos than requested, we've reached the end
+ if len(repos) < perPage {
+ break
+ }
+
+ page++
+ }
+
+ return allRepos, nil
+}
+
+// GetRepoNames returns just the repository names
+func GetRepoNames(repos []Repository) []string {
+ names := make([]string, 0, len(repos))
+ for _, repo := range repos {
+ names = append(names, repo.Name)
+ }
+ return names
+}