all repos — reposync @ main

Go program that clones all of my public repos from GitHub to my git server.

main.go (view raw)

  1package main
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"io"
  7	"log"
  8	"net/http"
  9	"os"
 10	"os/exec"
 11	"path/filepath"
 12
 13	"github.com/joho/godotenv"
 14)
 15
 16type Repo struct {
 17	Name        string `json:"name"`
 18	HTMLUrl     string `json:"html_url"`
 19	Description string `json:"description"`
 20}
 21
 22func getRepos(username, token string) ([]Repo, error) {
 23	var allRepos []Repo
 24	page := 1
 25
 26	for {
 27		url := fmt.Sprintf("https://api.github.com/users/%s/repos?page=%d&per_page=100", username, page)
 28		req, err := http.NewRequest("GET", url, nil)
 29		if err != nil {
 30			return nil, err
 31		}
 32		req.SetBasicAuth(username, token)
 33
 34		client := &http.Client{}
 35		resp, err := client.Do(req)
 36		if err != nil {
 37			return nil, err
 38		}
 39		defer resp.Body.Close()
 40
 41		if resp.StatusCode != http.StatusOK {
 42			return nil, fmt.Errorf("failed to fetch repositories: %s", resp.Status)
 43		}
 44
 45		body, err := io.ReadAll(resp.Body)
 46		if err != nil {
 47			return nil, err
 48		}
 49
 50		var repos []Repo
 51		if err := json.Unmarshal(body, &repos); err != nil {
 52			return nil, err
 53		}
 54
 55		if len(repos) == 0 {
 56			break
 57		}
 58
 59		allRepos = append(allRepos, repos...)
 60		page++
 61	}
 62
 63	return allRepos, nil
 64}
 65
 66func runGitCommand(args ...string) error {
 67	cmd := exec.Command("git", args...)
 68	cmd.Stdout = os.Stdout
 69	cmd.Stderr = os.Stderr
 70	return cmd.Run()
 71}
 72
 73func writeDescription(repoPath, description string) error {
 74	descriptionPath := filepath.Join(repoPath, "description")
 75	return os.WriteFile(descriptionPath, []byte(description), 0644)
 76}
 77
 78func syncRepo(repo Repo, targetDir string) error {
 79	repoPath := filepath.Join(targetDir, repo.Name+".git")
 80	if _, err := os.Stat(repoPath); os.IsNotExist(err) {
 81		fmt.Printf("Cloning repository %s as bare...\n", repo.Name)
 82		if err := runGitCommand("clone", "--bare", repo.HTMLUrl, repoPath); err != nil {
 83			return err
 84		}
 85	} else {
 86		fmt.Printf("Fetching updates for bare repository %s...\n", repo.Name)
 87		cmd := exec.Command("git", "--git-dir", repoPath, "fetch", "--all")
 88		cmd.Stdout = os.Stdout
 89		cmd.Stderr = os.Stderr
 90		if err := cmd.Run(); err != nil {
 91			return err
 92		}
 93	}
 94
 95	// Write the repository description
 96	if err := writeDescription(repoPath, repo.Description); err != nil {
 97		return fmt.Errorf("failed to write description for %s: %v", repo.Name, err)
 98	}
 99
100	return nil
101}
102
103func main() {
104	err := godotenv.Load()
105	if err != nil {
106		println("Failed to load .env file: %v\n", err)
107	}
108
109	ghUser := os.Getenv("GITHUB_USERNAME")
110	ghToken := os.Getenv("GITHUB_TOKEN")
111	repoDir := os.Getenv("REPO_DIR")
112
113	if ghUser == "" || ghToken == "" || repoDir == "" {
114		log.Fatalf("GitHub username and token must be provided\n")
115	}
116
117	repos, err := getRepos(ghUser, ghToken)
118	if err != nil {
119		log.Fatalf("Failed to fetch repositories: %v\n", err)
120	}
121
122	for _, repo := range repos {
123		if err := syncRepo(repo, repoDir); err != nil {
124			log.Printf("Failed to sync repository %s: %v\n", repo.Name, err)
125		}
126	}
127}