mirror of
https://github.com/Luzifer/github2gitea.git
synced 2024-12-22 20:11:19 +00:00
Allow specifying multiple actions at once
in order to speed up the execution and to lower the need of Github tokens. Instead of running the tool multiple times it can now be run once while specifying more than one mapping using a mapping file. Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
69b41f5035
commit
494dbf16dd
6 changed files with 119 additions and 18 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
.env
|
.env
|
||||||
github2gitea
|
github2gitea
|
||||||
|
mapping.yaml
|
||||||
|
|
4
gitea.go
4
gitea.go
|
@ -23,7 +23,7 @@ type createMigrationRequest struct {
|
||||||
Wiki bool `json:"wiki"`
|
Wiki bool `json:"wiki"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMigrationRequestFromGithubRepo(gr *github.Repository) createMigrationRequest {
|
func createMigrationRequestFromGithubRepo(gr *github.Repository, repoMapping *mapping) createMigrationRequest {
|
||||||
cmr := createMigrationRequest{
|
cmr := createMigrationRequest{
|
||||||
CloneAddr: strFromPtr(gr.CloneURL),
|
CloneAddr: strFromPtr(gr.CloneURL),
|
||||||
Description: strFromPtr(gr.Description),
|
Description: strFromPtr(gr.Description),
|
||||||
|
@ -32,7 +32,7 @@ func createMigrationRequestFromGithubRepo(gr *github.Repository) createMigration
|
||||||
Private: boolFromPtr(gr.Private),
|
Private: boolFromPtr(gr.Private),
|
||||||
PullRequests: boolFromPtr(gr.HasIssues),
|
PullRequests: boolFromPtr(gr.HasIssues),
|
||||||
RepoName: strFromPtr(gr.Name),
|
RepoName: strFromPtr(gr.Name),
|
||||||
UID: cfg.TargetUser,
|
UID: repoMapping.TargetUser,
|
||||||
Wiki: boolFromPtr(gr.HasWiki),
|
Wiki: boolFromPtr(gr.HasWiki),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -12,4 +12,5 @@ require (
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sirupsen/logrus v1.4.2
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||||
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
)
|
)
|
||||||
|
|
57
main.go
57
main.go
|
@ -8,7 +8,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Luzifer/rconfig/v2"
|
"github.com/Luzifer/rconfig/v2"
|
||||||
|
@ -25,16 +24,19 @@ var (
|
||||||
GiteaURL string `flag:"gitea-url" default:"" description:"URL of the 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"`
|
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)"`
|
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
||||||
|
MappingFile string `flag:"mapping-file" default:"" description:"File containing several mappings to execute in one run"`
|
||||||
MigrateArchived bool `flag:"migrate-archived" default:"false" description:"Create migrations for archived repos"`
|
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"`
|
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!)"`
|
MigratePrivate bool `flag:"migrate-private" default:"true" description:"Migrate private repos (the given Github Token will be entered as sync credential!)"`
|
||||||
NoMirror bool `flag:"no-mirror" default:"false" description:"Do not enable mirroring but instad do a one-time clone"`
|
NoMirror bool `flag:"no-mirror" default:"false" description:"Do not enable mirroring but instad do a one-time clone"`
|
||||||
SourceExpression string `flag:"source-expression" default:"" description:"Regular expression to match the full name of the source repo (i.e. '^Luzifer/.*$')" validate:"nonzero"`
|
SourceExpression string `flag:"source-expression" default:"" description:"Regular expression to match the full name of the source repo (i.e. '^Luzifer/.*$')"`
|
||||||
TargetUser int64 `flag:"target-user" default:"0" description:"ID of the User / Organization in Gitea to assign the repo to" validate:"nonzero"`
|
TargetUser int64 `flag:"target-user" default:"0" description:"ID of the User / Organization in Gitea to assign the repo to"`
|
||||||
TargetUserName string `flag:"target-user-name" default:"" description:"Username of the given ID (to check whether repo already exists)" validate:"nonzero"`
|
TargetUserName string `flag:"target-user-name" default:"" description:"Username of the given ID (to check whether repo already exists)"`
|
||||||
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
|
mappings *mapFile
|
||||||
|
|
||||||
version = "dev"
|
version = "dev"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,11 +59,33 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
mappings = newMapFile()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
|
||||||
|
case cfg.MappingFile != "":
|
||||||
|
m, err := loadMapFile(cfg.MappingFile)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("Unable to load mappings file")
|
||||||
|
}
|
||||||
|
mappings = m
|
||||||
|
|
||||||
|
case cfg.SourceExpression != "" && cfg.TargetUserName != "" && cfg.TargetUser > 0:
|
||||||
|
mappings.Mappings = []mapping{{
|
||||||
|
SourceExpression: cfg.SourceExpression,
|
||||||
|
TargetUser: cfg.TargetUser,
|
||||||
|
TargetUserName: cfg.TargetUserName,
|
||||||
|
}}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Fatal("No mappings are defined")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"dry-run": cfg.DryRun,
|
"dry-run": cfg.DryRun,
|
||||||
"source": cfg.SourceExpression,
|
"map_definitions": len(mappings.Mappings),
|
||||||
"target-user": cfg.TargetUserName,
|
"version": version,
|
||||||
"version": version,
|
|
||||||
}).Info("create-gitea-migration started")
|
}).Info("create-gitea-migration started")
|
||||||
|
|
||||||
log.Info("Collecting source repos...")
|
log.Info("Collecting source repos...")
|
||||||
|
@ -92,8 +116,6 @@ func fetchGithubRepos() ([]*github.Repository, error) {
|
||||||
ListOptions: github.ListOptions{PerPage: 100},
|
ListOptions: github.ListOptions{PerPage: 100},
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceExpr := regexp.MustCompile(cfg.SourceExpression)
|
|
||||||
|
|
||||||
// get all pages of results
|
// get all pages of results
|
||||||
var allRepos []*github.Repository
|
var allRepos []*github.Repository
|
||||||
for {
|
for {
|
||||||
|
@ -103,8 +125,8 @@ func fetchGithubRepos() ([]*github.Repository, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range repos {
|
for _, r := range repos {
|
||||||
if !sourceExpr.MatchString(*r.FullName) {
|
if !mappings.MappingAvailable(*r.FullName) {
|
||||||
log.WithField("repo", *r.FullName).Debug("Skip: Name does not match")
|
log.WithField("repo", *r.FullName).Debug("Skip: No mapping matches repo name")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,12 +154,15 @@ func fetchGithubRepos() ([]*github.Repository, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func giteaCreateMigration(gr *github.Repository) error {
|
func giteaCreateMigration(gr *github.Repository) error {
|
||||||
|
repoMapping := mappings.GetMapping(*gr.FullName)
|
||||||
|
|
||||||
logger := log.WithFields(log.Fields{
|
logger := log.WithFields(log.Fields{
|
||||||
"repo": strFromPtr(gr.Name),
|
"private": boolFromPtr(gr.Private),
|
||||||
"private": boolFromPtr(gr.Private),
|
"source_repo": strFromPtr(gr.FullName),
|
||||||
|
"target_repo": strings.Join([]string{repoMapping.TargetUserName, strFromPtr(gr.Name)}, "/"),
|
||||||
})
|
})
|
||||||
|
|
||||||
req, _ := http.NewRequest(http.MethodGet, giteaURL(strings.Join([]string{"api/v1/repos", cfg.TargetUserName, strFromPtr(gr.Name)}, "/")), nil)
|
req, _ := http.NewRequest(http.MethodGet, giteaURL(strings.Join([]string{"api/v1/repos", repoMapping.TargetUserName, strFromPtr(gr.Name)}, "/")), nil)
|
||||||
req.Header.Set("Authorization", "token "+cfg.GiteaToken)
|
req.Header.Set("Authorization", "token "+cfg.GiteaToken)
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
@ -151,7 +176,7 @@ func giteaCreateMigration(gr *github.Repository) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmr := createMigrationRequestFromGithubRepo(gr)
|
cmr := createMigrationRequestFromGithubRepo(gr, repoMapping)
|
||||||
|
|
||||||
body := new(bytes.Buffer)
|
body := new(bytes.Buffer)
|
||||||
if err := json.NewEncoder(body).Encode(cmr); err != nil {
|
if err := json.NewEncoder(body).Encode(cmr); err != nil {
|
||||||
|
|
58
mapfile.go
Normal file
58
mapfile.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mapFile struct {
|
||||||
|
Mappings []mapping `yaml:"mappings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapping struct {
|
||||||
|
SourceExpression string `yaml:"source_expression"`
|
||||||
|
TargetUser int64 `yaml:"target_user"`
|
||||||
|
TargetUserName string `yaml:"target_user_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMapFile(fileName string) (*mapFile, error) {
|
||||||
|
if _, err := os.Stat(fileName); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Mapping file not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Unable to open mapping file")
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var out = &mapFile{}
|
||||||
|
return out, errors.Wrap(yaml.NewDecoder(f).Decode(out), "Unable to decode mapping file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMapFile() *mapFile {
|
||||||
|
return &mapFile{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mapFile) GetMapping(repoName string) *mapping {
|
||||||
|
for _, me := range m.Mappings {
|
||||||
|
if regexp.MustCompile(me.SourceExpression).MatchString(repoName) {
|
||||||
|
return &me
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mapFile) MappingAvailable(repoName string) bool {
|
||||||
|
for _, me := range m.Mappings {
|
||||||
|
if regexp.MustCompile(me.SourceExpression).MatchString(repoName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
16
mapping_sample.yaml
Normal file
16
mapping_sample.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
mappings:
|
||||||
|
- source_expression: '^Luzifer/'
|
||||||
|
target_user: 1
|
||||||
|
target_user_name: luzifer
|
||||||
|
|
||||||
|
- source_expression: '^luzifer-ansible/'
|
||||||
|
target_user: 3
|
||||||
|
target_user_name: luzifer-ansible
|
||||||
|
|
||||||
|
- source_expression: '^luzifer-docker/'
|
||||||
|
target_user: 2
|
||||||
|
target_user_name: luzifer-docker
|
||||||
|
|
||||||
|
...
|
Loading…
Reference in a new issue