From 006724744a943aad877a92406a5e2b4d5d12acd3 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 23 Jun 2025 23:26:52 +0300 Subject: Add GitHub repository creation and improve error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- internal/codeberg/codeberg.go | 133 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 internal/codeberg/codeberg.go (limited to 'internal/codeberg') 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 +} -- cgit v1.2.3