Last active
April 22, 2022 20:14
-
-
Save szampardi/d4cc5bad7563a5baa624779b469d75fe to your computer and use it in GitHub Desktop.
migrate user/org repos to gitea user/org
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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