Skip to content

Instantly share code, notes, and snippets.

@szampardi
Last active April 22, 2022 20:14
Show Gist options
  • Save szampardi/d4cc5bad7563a5baa624779b469d75fe to your computer and use it in GitHub Desktop.
Save szampardi/d4cc5bad7563a5baa624779b469d75fe to your computer and use it in GitHub Desktop.
migrate user/org repos to gitea user/org
package main
import (
"bufio"
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strconv"
"strings"
"time"
"code.gitea.io/sdk/gitea"
"github.com/google/go-github/github"
"golang.org/x/oauth2"
)
var (
i = flag.Bool("i", false, "run interactively")
x = flag.Bool("x", false, "execute (just prints repos that would be migrated by default)")
mirrors = flag.Bool("mirrors", false, "set up migrations as mirrors")
// github
ghAPI = flag.String("github", "https://api.github.com/", "GitHub API endpoint")
ghT = os.Getenv("GITHUB_TOKEN")
ghTarget string
ghTargetType = flag.String("github-target-type", "user", "user or org")
// gitea
gtAPI = flag.String("gitea", "https://gitea.com", "Gitea endpoint")
gtT = os.Getenv("GITEA_TOKEN")
gtTarget string
//giteaTargetType = flag.String("gitea-target-type", "user", "user or org")
// misc
errArgs = fmt.Errorf("must specify github org/user with the first argument and gitea org/user with the second")
)
func init() {
if ghT == "" || gtT == "" {
panic("GITHUB_TOKEN and GITEA_TOKEN env vars need to be set")
}
flag.Parse()
args := flag.Args()
if len(args) < 2 {
panic(errArgs)
}
ghTarget = args[0]
gtTarget = args[1]
if ghTarget == "" || gtTarget == "" {
panic(errArgs)
}
if *ghAPI == "" || *gtAPI == "" {
panic("must provide valid Gitea/GitHub endpoints with the -gitea and -github parameters")
}
}
func main() {
// github client
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: ghT},
)
client, err := github.NewEnterpriseClient(*ghAPI, *ghAPI, oauth2.NewClient(ctx, ts))
if err != nil {
panic(err)
}
// gitea client
giteaclient, err := gitea.NewClient(*gtAPI, gitea.SetToken(gtT))
if err != nil {
panic(err)
}
// get all GitHub repos
var ghRepos []*github.Repository
switch *ghTargetType {
case "user":
opt := &github.RepositoryListOptions{
ListOptions: github.ListOptions{PerPage: 100},
}
for {
repos, resp, err := client.Repositories.List(ctx, ghTarget, opt)
if err != nil {
fmt.Println(err)
return
}
ghRepos = append(ghRepos, repos...)
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
case "org":
opt := &github.RepositoryListByOrgOptions{
ListOptions: github.ListOptions{PerPage: 100},
}
for {
repos, resp, err := client.Repositories.ListByOrg(ctx, ghTarget, opt)
if err != nil {
fmt.Println(err)
return
}
ghRepos = append(ghRepos, repos...)
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
default:
panic("only user or org target types are supported")
}
if !*x {
json.NewEncoder(os.Stdout).Encode(ghRepos)
os.Exit(0)
}
var gtMigrations []gitea.MigrateRepoOption
for _, r := range ghRepos {
var (
skip = false
private = *r.Private
mirror = *mirrors
)
if *i {
br := bufio.NewReader(os.Stdin)
skip, err = iRead(br, fmt.Sprintf("%s/%s: skip? [t/f] (%v): ", ghTarget, *r.Name, skip), skip)
if err != nil {
panic(err)
}
if !skip {
private, err = iRead(br, fmt.Sprintf("%s/%s: migrate to private repository? [t/f] (%v): ", ghTarget, *r.Name, private), private)
if err != nil {
panic(err)
}
mirror, err = iRead(br, fmt.Sprintf("%s/%s: set up migration as mirror? [t/f]] (%v): ", ghTarget, *r.Name, mirror), mirror)
if err != nil {
panic(err)
}
}
}
if !skip {
description := ""
if r.Description != nil { // will throw a nil pointer error if description is passed directly to the below struct
description = *r.Description
}
mig := gitea.MigrateRepoOption{
CloneAddr: *r.CloneURL,
RepoName: *r.Name,
Private: private,
Mirror: mirror,
Description: description,
RepoOwner: gtTarget,
}
if *r.Private {
mig.AuthToken = ghT
}
gtMigrations = append(gtMigrations, mig)
}
}
for _, i := range gtMigrations {
fmt.Println(time.Now(), ": migrating", i.RepoName)
repo, _, err := giteaclient.MigrateRepo(i)
if err != nil {
fmt.Println(err)
} else {
json.NewEncoder(os.Stderr).Encode(repo)
}
}
}
func iRead(br *bufio.Reader, prompt string, def bool) (bool, error) {
fmt.Fprint(os.Stderr, prompt)
i, err := br.ReadString('\n')
if err != nil {
return false, err
}
i = strings.ToLower(strings.TrimSpace(i))
if i == "" {
return def, nil
}
return strconv.ParseBool(i)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment