1
0
Fork 0
mirror of https://github.com/Luzifer/github2gitea.git synced 2024-12-22 20:11:19 +00:00
github2gitea/main.go

190 lines
5.6 KiB
Go

package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
"strings"
"github.com/Luzifer/rconfig/v2"
"github.com/google/go-github/github"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
)
var (
cfg = struct {
DryRun bool `flag:"dry-run,n" default:"false" description:"Only report actions to be done, don't execute them"`
GiteaToken string `flag:"gitea-token" default:"" description:"Token to interact with Gitea instance" validate:"nonzero"`
GiteaURL string `flag:"gitea-url" default:"" description:"URL of the Gitea instance" validate:"nonzero"`
GithubToken string `flag:"github-token" default:"" description:"Github access token" validate:"nonzero"`
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
MigrateArchived bool `flag:"migrate-archived" default:"false" description:"Create migrations for archived repos"`
MigrateForks bool `flag:"migrate-forks" default:"false" description:"Create migrations for forked repos"`
MigratePrivate bool `flag:"migrate-private" default:"true" description:"Migrate private repos (the given Github Token will be entered as sync credential!)"`
SourceExpression string `flag:"source-expression" default:"" description:"Regular expression to match the full name of the source repo (i.e. '^Luzifer/.*$')" validate:"nonzero"`
TargetUser int64 `flag:"target-user" default:"0" description:"ID of the User / Organization in Gitea to assign the repo to" validate:"nonzero"`
TargetUserName string `flag:"target-user-name" default:"" description:"Username of the given ID (to check whether repo already exists)" validate:"nonzero"`
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
}{}
version = "dev"
)
func init() {
rconfig.AutoEnv(true)
if err := rconfig.ParseAndValidate(&cfg); err != nil {
log.Fatalf("Unable to parse commandline options: %s", err)
}
if cfg.VersionAndExit {
fmt.Printf("create-gitea-migration %s\n", version)
os.Exit(0)
}
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
log.WithError(err).Fatal("Unable to parse log level")
} else {
log.SetLevel(l)
}
}
func main() {
log.WithFields(log.Fields{
"dry-run": cfg.DryRun,
"source": cfg.SourceExpression,
"target-user": cfg.TargetUserName,
"version": version,
}).Info("create-gitea-migration started")
log.Info("Collecting source repos...")
repos, err := fetchGithubRepos()
if err != nil {
log.WithError(err).Fatal("Failed to fetch repos")
}
log.Info("Creating target repos...")
for _, r := range repos {
if err := giteaCreateMigration(r); err != nil {
log.WithError(err).Error("Unable to create mirror")
}
}
}
func fetchGithubRepos() ([]*github.Repository, error) {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: cfg.GithubToken},
)
tc := oauth2.NewClient(ctx, ts)
client := github.NewClient(tc)
opt := &github.RepositoryListOptions{
ListOptions: github.ListOptions{PerPage: 100},
}
sourceExpr := regexp.MustCompile(cfg.SourceExpression)
// get all pages of results
var allRepos []*github.Repository
for {
repos, resp, err := client.Repositories.List(ctx, "", opt)
if err != nil {
return nil, errors.Wrap(err, "Unable to list repos")
}
for _, r := range repos {
if !sourceExpr.MatchString(*r.FullName) {
log.WithField("repo", *r.FullName).Debug("Skip: Name does not match")
continue
}
if !cfg.MigrateArchived && boolFromPtr(r.Archived) {
log.WithField("repo", *r.FullName).Debug("Skip: Archived")
continue
}
if !cfg.MigrateForks && boolFromPtr(r.Fork) {
log.WithField("repo", *r.FullName).Debug("Skip: Fork")
continue
}
allRepos = append(allRepos, r)
}
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
return allRepos, nil
}
func giteaCreateMigration(gr *github.Repository) error {
logger := log.WithFields(log.Fields{
"repo": strFromPtr(gr.Name),
"private": boolFromPtr(gr.Private),
})
req, _ := http.NewRequest(http.MethodGet, giteaURL(strings.Join([]string{"api/v1/repos", cfg.TargetUserName, strFromPtr(gr.Name)}, "/")), nil)
req.Header.Set("Authorization", "token "+cfg.GiteaToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "Unable to create repo in Gitea")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
logger.Info("Repo already exists, no action required")
return nil
}
cmr := createMigrationRequestFromGithubRepo(gr)
body := new(bytes.Buffer)
if err := json.NewEncoder(body).Encode(cmr); err != nil {
return errors.Wrap(err, "Unable to marshal creation request")
}
if cfg.DryRun {
logger.Warn("Repo not found, will be created in real run (dry-run enabled)")
return nil
}
logger.Info("Repo not found, creating")
req, _ = http.NewRequest(http.MethodPost, giteaURL("api/v1/repos/migrate"), body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "token "+cfg.GiteaToken)
resp, err = http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "Unable to create repo in Gitea")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
body, _ := ioutil.ReadAll(resp.Body)
return errors.Errorf("Unable to create repo in Gitea: Status %d: %s", resp.StatusCode, body)
}
return nil
}
func giteaURL(path string) string {
return strings.Join([]string{
strings.TrimRight(cfg.GiteaURL, "/"),
strings.TrimLeft(path, "/"),
}, "/")
}