From ca38df1f30ddedbbdbf73d1f8d4ddd98b12d3740 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 23 Jun 2025 23:51:54 +0300 Subject: Add --sync-github-public flag to sync GitHub repos to Codeberg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement GitHub API client methods to list public repositories - Add --sync-github-public flag to sync all public GitHub repos to Codeberg - Add --create-codeberg-repos flag (placeholder for future implementation) - Support pagination for GitHub API to handle users with many repos - Filter GitHub repos to exclude forks, archived, and private repos - Update README with new sync direction and features - Add dry-run support for GitHub->Codeberg sync 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- internal/github/github.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'internal/github') diff --git a/internal/github/github.go b/internal/github/github.go index 38aafa0..7d6213a 100644 --- a/internal/github/github.go +++ b/internal/github/github.go @@ -197,4 +197,79 @@ func (c *Client) CreateRepo(repoName, description string, private bool) error { // HasToken returns whether a token is configured func (c *Client) HasToken() bool { return c.token != "" +} + +// Repository represents a GitHub repository +type Repository struct { + Name string `json:"name"` + Description string `json:"description"` + Private bool `json:"private"` + Fork bool `json:"fork"` + Archived bool `json:"archived"` + Disabled bool `json:"disabled"` + Size int `json:"size"` +} + +// ListPublicRepos lists all public repositories for the user/org +func (c *Client) ListPublicRepos() ([]Repository, error) { + if c.token == "" { + return nil, fmt.Errorf("GitHub token required to list repositories") + } + + var allRepos []Repository + page := 1 + perPage := 100 + + for { + url := fmt.Sprintf("https://api.github.com/users/%s/repos?page=%d&per_page=%d&type=owner", c.org, page, perPage) + fmt.Printf(" Fetching page %d...\n", page) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+c.token) + req.Header.Set("Accept", "application/vnd.github.v3+json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("failed to list repos: status %d: %s", resp.StatusCode, string(body)) + } + + var repos []Repository + if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + // Filter for public, non-fork, non-archived, non-empty repos + for _, repo := range repos { + if !repo.Private && !repo.Fork && !repo.Archived && !repo.Disabled && repo.Size > 0 { + allRepos = append(allRepos, repo) + } + } + + // Check if there are more pages + if len(repos) < perPage { + break + } + page++ + } + + return allRepos, nil +} + +// GetRepoNames extracts repository names from a list of repos +func GetRepoNames(repos []Repository) []string { + names := make([]string, len(repos)) + for i, repo := range repos { + names[i] = repo.Name + } + return names } \ No newline at end of file -- cgit v1.2.3