summaryrefslogtreecommitdiffstats
path: root/modules/migrations
diff options
context:
space:
mode:
author6543 <6543@obermui.de>2021-01-21 20:33:58 +0100
committerGitHub <noreply@github.com>2021-01-21 20:33:58 +0100
commit81c833d92d04e0a5579e7168aba548dad7e17451 (patch)
treec928f1b43fc6e2f27603193f0eed657f0760c96d /modules/migrations
parentb5570d3e680570343c1552bfc972b19b161209cd (diff)
downloadgitea-81c833d92d04e0a5579e7168aba548dad7e17451.tar.gz
gitea-81c833d92d04e0a5579e7168aba548dad7e17451.zip
Add support to migrate from gogs (#14342)
Add support to migrate gogs: * issues * comments * labels * milestones * wiki Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Andrew Thornton <art27@cantab.net>
Diffstat (limited to 'modules/migrations')
-rw-r--r--modules/migrations/base/downloader.go212
-rw-r--r--modules/migrations/base/error.go26
-rw-r--r--modules/migrations/base/milestone.go2
-rw-r--r--modules/migrations/base/null_downloader.go82
-rw-r--r--modules/migrations/base/retry_downloader.go247
-rw-r--r--modules/migrations/error.go3
-rw-r--r--modules/migrations/git.go40
-rw-r--r--modules/migrations/gitea_downloader.go3
-rw-r--r--modules/migrations/gitea_uploader.go23
-rw-r--r--modules/migrations/github.go1
-rw-r--r--modules/migrations/gitlab.go1
-rw-r--r--modules/migrations/gogs.go312
-rw-r--r--modules/migrations/gogs_test.go122
-rw-r--r--modules/migrations/migrate.go83
-rw-r--r--modules/migrations/restore.go1
15 files changed, 860 insertions, 298 deletions
diff --git a/modules/migrations/base/downloader.go b/modules/migrations/base/downloader.go
index afa99105c9..919f4b52a0 100644
--- a/modules/migrations/base/downloader.go
+++ b/modules/migrations/base/downloader.go
@@ -7,7 +7,6 @@ package base
import (
"context"
- "time"
"code.gitea.io/gitea/modules/structs"
)
@@ -24,6 +23,7 @@ type Downloader interface {
GetComments(issueNumber int64) ([]*Comment, error)
GetPullRequests(page, perPage int) ([]*PullRequest, bool, error)
GetReviews(pullRequestNumber int64) ([]*Review, error)
+ FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error)
}
// DownloaderFactory defines an interface to match a downloader implementation and create a downloader
@@ -31,213 +31,3 @@ type DownloaderFactory interface {
New(ctx context.Context, opts MigrateOptions) (Downloader, error)
GitServiceType() structs.GitServiceType
}
-
-var (
- _ Downloader = &RetryDownloader{}
-)
-
-// RetryDownloader retry the downloads
-type RetryDownloader struct {
- Downloader
- ctx context.Context
- RetryTimes int // the total execute times
- RetryDelay int // time to delay seconds
-}
-
-// NewRetryDownloader creates a retry downloader
-func NewRetryDownloader(ctx context.Context, downloader Downloader, retryTimes, retryDelay int) *RetryDownloader {
- return &RetryDownloader{
- Downloader: downloader,
- ctx: ctx,
- RetryTimes: retryTimes,
- RetryDelay: retryDelay,
- }
-}
-
-// SetContext set context
-func (d *RetryDownloader) SetContext(ctx context.Context) {
- d.ctx = ctx
- d.Downloader.SetContext(ctx)
-}
-
-// GetRepoInfo returns a repository information with retry
-func (d *RetryDownloader) GetRepoInfo() (*Repository, error) {
- var (
- times = d.RetryTimes
- repo *Repository
- err error
- )
- for ; times > 0; times-- {
- if repo, err = d.Downloader.GetRepoInfo(); err == nil {
- return repo, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, err
-}
-
-// GetTopics returns a repository's topics with retry
-func (d *RetryDownloader) GetTopics() ([]string, error) {
- var (
- times = d.RetryTimes
- topics []string
- err error
- )
- for ; times > 0; times-- {
- if topics, err = d.Downloader.GetTopics(); err == nil {
- return topics, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, err
-}
-
-// GetMilestones returns a repository's milestones with retry
-func (d *RetryDownloader) GetMilestones() ([]*Milestone, error) {
- var (
- times = d.RetryTimes
- milestones []*Milestone
- err error
- )
- for ; times > 0; times-- {
- if milestones, err = d.Downloader.GetMilestones(); err == nil {
- return milestones, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, err
-}
-
-// GetReleases returns a repository's releases with retry
-func (d *RetryDownloader) GetReleases() ([]*Release, error) {
- var (
- times = d.RetryTimes
- releases []*Release
- err error
- )
- for ; times > 0; times-- {
- if releases, err = d.Downloader.GetReleases(); err == nil {
- return releases, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, err
-}
-
-// GetLabels returns a repository's labels with retry
-func (d *RetryDownloader) GetLabels() ([]*Label, error) {
- var (
- times = d.RetryTimes
- labels []*Label
- err error
- )
- for ; times > 0; times-- {
- if labels, err = d.Downloader.GetLabels(); err == nil {
- return labels, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, err
-}
-
-// GetIssues returns a repository's issues with retry
-func (d *RetryDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) {
- var (
- times = d.RetryTimes
- issues []*Issue
- isEnd bool
- err error
- )
- for ; times > 0; times-- {
- if issues, isEnd, err = d.Downloader.GetIssues(page, perPage); err == nil {
- return issues, isEnd, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, false, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, false, err
-}
-
-// GetComments returns a repository's comments with retry
-func (d *RetryDownloader) GetComments(issueNumber int64) ([]*Comment, error) {
- var (
- times = d.RetryTimes
- comments []*Comment
- err error
- )
- for ; times > 0; times-- {
- if comments, err = d.Downloader.GetComments(issueNumber); err == nil {
- return comments, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, err
-}
-
-// GetPullRequests returns a repository's pull requests with retry
-func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) {
- var (
- times = d.RetryTimes
- prs []*PullRequest
- err error
- isEnd bool
- )
- for ; times > 0; times-- {
- if prs, isEnd, err = d.Downloader.GetPullRequests(page, perPage); err == nil {
- return prs, isEnd, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, false, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, false, err
-}
-
-// GetReviews returns pull requests reviews
-func (d *RetryDownloader) GetReviews(pullRequestNumber int64) ([]*Review, error) {
- var (
- times = d.RetryTimes
- reviews []*Review
- err error
- )
- for ; times > 0; times-- {
- if reviews, err = d.Downloader.GetReviews(pullRequestNumber); err == nil {
- return reviews, nil
- }
- select {
- case <-d.ctx.Done():
- return nil, d.ctx.Err()
- case <-time.After(time.Second * time.Duration(d.RetryDelay)):
- }
- }
- return nil, err
-}
diff --git a/modules/migrations/base/error.go b/modules/migrations/base/error.go
new file mode 100644
index 0000000000..40ddcf4b75
--- /dev/null
+++ b/modules/migrations/base/error.go
@@ -0,0 +1,26 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import "fmt"
+
+// ErrNotSupported represents status if a downloader do not supported something.
+type ErrNotSupported struct {
+ Entity string
+}
+
+// IsErrNotSupported checks if an error is an ErrNotSupported
+func IsErrNotSupported(err error) bool {
+ _, ok := err.(ErrNotSupported)
+ return ok
+}
+
+// Error return error message
+func (err ErrNotSupported) Error() string {
+ if len(err.Entity) != 0 {
+ return fmt.Sprintf("'%s' not supported", err.Entity)
+ }
+ return "not supported"
+}
diff --git a/modules/migrations/base/milestone.go b/modules/migrations/base/milestone.go
index 8736aa6cfd..921968fcb5 100644
--- a/modules/migrations/base/milestone.go
+++ b/modules/migrations/base/milestone.go
@@ -15,5 +15,5 @@ type Milestone struct {
Created time.Time
Updated *time.Time
Closed *time.Time
- State string
+ State string // open, closed
}
diff --git a/modules/migrations/base/null_downloader.go b/modules/migrations/base/null_downloader.go
new file mode 100644
index 0000000000..a93c20339b
--- /dev/null
+++ b/modules/migrations/base/null_downloader.go
@@ -0,0 +1,82 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "context"
+ "net/url"
+)
+
+// NullDownloader implements a blank downloader
+type NullDownloader struct {
+}
+
+var (
+ _ Downloader = &NullDownloader{}
+)
+
+// SetContext set context
+func (n NullDownloader) SetContext(_ context.Context) {}
+
+// GetRepoInfo returns a repository information
+func (n NullDownloader) GetRepoInfo() (*Repository, error) {
+ return nil, &ErrNotSupported{Entity: "RepoInfo"}
+}
+
+// GetTopics return repository topics
+func (n NullDownloader) GetTopics() ([]string, error) {
+ return nil, &ErrNotSupported{Entity: "Topics"}
+}
+
+// GetMilestones returns milestones
+func (n NullDownloader) GetMilestones() ([]*Milestone, error) {
+ return nil, &ErrNotSupported{Entity: "Milestones"}
+}
+
+// GetReleases returns releases
+func (n NullDownloader) GetReleases() ([]*Release, error) {
+ return nil, &ErrNotSupported{Entity: "Releases"}
+}
+
+// GetLabels returns labels
+func (n NullDownloader) GetLabels() ([]*Label, error) {
+ return nil, &ErrNotSupported{Entity: "Labels"}
+}
+
+// GetIssues returns issues according start and limit
+func (n NullDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) {
+ return nil, false, &ErrNotSupported{Entity: "Issues"}
+}
+
+// GetComments returns comments according issueNumber
+func (n NullDownloader) GetComments(issueNumber int64) ([]*Comment, error) {
+ return nil, &ErrNotSupported{Entity: "Comments"}
+}
+
+// GetPullRequests returns pull requests according page and perPage
+func (n NullDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) {
+ return nil, false, &ErrNotSupported{Entity: "PullRequests"}
+}
+
+// GetReviews returns pull requests review
+func (n NullDownloader) GetReviews(pullRequestNumber int64) ([]*Review, error) {
+ return nil, &ErrNotSupported{Entity: "Reviews"}
+}
+
+// FormatCloneURL add authentification into remote URLs
+func (n NullDownloader) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
+ if len(opts.AuthToken) > 0 || len(opts.AuthUsername) > 0 {
+ u, err := url.Parse(remoteAddr)
+ if err != nil {
+ return "", err
+ }
+ u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
+ if len(opts.AuthToken) > 0 {
+ u.User = url.UserPassword("oauth2", opts.AuthToken)
+ }
+ return u.String(), nil
+ }
+ return remoteAddr, nil
+}
diff --git a/modules/migrations/base/retry_downloader.go b/modules/migrations/base/retry_downloader.go
new file mode 100644
index 0000000000..eeb3cabbc1
--- /dev/null
+++ b/modules/migrations/base/retry_downloader.go
@@ -0,0 +1,247 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "context"
+ "time"
+)
+
+var (
+ _ Downloader = &RetryDownloader{}
+)
+
+// RetryDownloader retry the downloads
+type RetryDownloader struct {
+ Downloader
+ ctx context.Context
+ RetryTimes int // the total execute times
+ RetryDelay int // time to delay seconds
+}
+
+// NewRetryDownloader creates a retry downloader
+func NewRetryDownloader(ctx context.Context, downloader Downloader, retryTimes, retryDelay int) *RetryDownloader {
+ return &RetryDownloader{
+ Downloader: downloader,
+ ctx: ctx,
+ RetryTimes: retryTimes,
+ RetryDelay: retryDelay,
+ }
+}
+
+// SetContext set context
+func (d *RetryDownloader) SetContext(ctx context.Context) {
+ d.ctx = ctx
+ d.Downloader.SetContext(ctx)
+}
+
+// GetRepoInfo returns a repository information with retry
+func (d *RetryDownloader) GetRepoInfo() (*Repository, error) {
+ var (
+ times = d.RetryTimes
+ repo *Repository
+ err error
+ )
+ for ; times > 0; times-- {
+ if repo, err = d.Downloader.GetRepoInfo(); err == nil {
+ return repo, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, err
+}
+
+// GetTopics returns a repository's topics with retry
+func (d *RetryDownloader) GetTopics() ([]string, error) {
+ var (
+ times = d.RetryTimes
+ topics []string
+ err error
+ )
+ for ; times > 0; times-- {
+ if topics, err = d.Downloader.GetTopics(); err == nil {
+ return topics, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, err
+}
+
+// GetMilestones returns a repository's milestones with retry
+func (d *RetryDownloader) GetMilestones() ([]*Milestone, error) {
+ var (
+ times = d.RetryTimes
+ milestones []*Milestone
+ err error
+ )
+ for ; times > 0; times-- {
+ if milestones, err = d.Downloader.GetMilestones(); err == nil {
+ return milestones, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, err
+}
+
+// GetReleases returns a repository's releases with retry
+func (d *RetryDownloader) GetReleases() ([]*Release, error) {
+ var (
+ times = d.RetryTimes
+ releases []*Release
+ err error
+ )
+ for ; times > 0; times-- {
+ if releases, err = d.Downloader.GetReleases(); err == nil {
+ return releases, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, err
+}
+
+// GetLabels returns a repository's labels with retry
+func (d *RetryDownloader) GetLabels() ([]*Label, error) {
+ var (
+ times = d.RetryTimes
+ labels []*Label
+ err error
+ )
+ for ; times > 0; times-- {
+ if labels, err = d.Downloader.GetLabels(); err == nil {
+ return labels, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, err
+}
+
+// GetIssues returns a repository's issues with retry
+func (d *RetryDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) {
+ var (
+ times = d.RetryTimes
+ issues []*Issue
+ isEnd bool
+ err error
+ )
+ for ; times > 0; times-- {
+ if issues, isEnd, err = d.Downloader.GetIssues(page, perPage); err == nil {
+ return issues, isEnd, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, false, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, false, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, false, err
+}
+
+// GetComments returns a repository's comments with retry
+func (d *RetryDownloader) GetComments(issueNumber int64) ([]*Comment, error) {
+ var (
+ times = d.RetryTimes
+ comments []*Comment
+ err error
+ )
+ for ; times > 0; times-- {
+ if comments, err = d.Downloader.GetComments(issueNumber); err == nil {
+ return comments, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, err
+}
+
+// GetPullRequests returns a repository's pull requests with retry
+func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) {
+ var (
+ times = d.RetryTimes
+ prs []*PullRequest
+ err error
+ isEnd bool
+ )
+ for ; times > 0; times-- {
+ if prs, isEnd, err = d.Downloader.GetPullRequests(page, perPage); err == nil {
+ return prs, isEnd, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, false, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, false, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, false, err
+}
+
+// GetReviews returns pull requests reviews
+func (d *RetryDownloader) GetReviews(pullRequestNumber int64) ([]*Review, error) {
+ var (
+ times = d.RetryTimes
+ reviews []*Review
+ err error
+ )
+ for ; times > 0; times-- {
+ if reviews, err = d.Downloader.GetReviews(pullRequestNumber); err == nil {
+ return reviews, nil
+ }
+ if IsErrNotSupported(err) {
+ return nil, err
+ }
+ select {
+ case <-d.ctx.Done():
+ return nil, d.ctx.Err()
+ case <-time.After(time.Second * time.Duration(d.RetryDelay)):
+ }
+ }
+ return nil, err
+}
diff --git a/modules/migrations/error.go b/modules/migrations/error.go
index 462ba29026..1c77fa9f2f 100644
--- a/modules/migrations/error.go
+++ b/modules/migrations/error.go
@@ -12,9 +12,6 @@ import (
)
var (
- // ErrNotSupported returns the error not supported
- ErrNotSupported = errors.New("not supported")
-
// ErrRepoNotCreated returns the error that repository not created
ErrRepoNotCreated = errors.New("repository is not created yet")
)
diff --git a/modules/migrations/git.go b/modules/migrations/git.go
index 88222086e4..7e41945474 100644
--- a/modules/migrations/git.go
+++ b/modules/migrations/git.go
@@ -16,6 +16,7 @@ var (
// PlainGitDownloader implements a Downloader interface to clone git from a http/https URL
type PlainGitDownloader struct {
+ base.NullDownloader
ownerName string
repoName string
remoteURL string
@@ -44,42 +45,7 @@ func (g *PlainGitDownloader) GetRepoInfo() (*base.Repository, error) {
}, nil
}
-// GetTopics returns empty list for plain git repo
-func (g *PlainGitDownloader) GetTopics() ([]string, error) {
+// GetTopics return empty string slice
+func (g PlainGitDownloader) GetTopics() ([]string, error) {
return []string{}, nil
}
-
-// GetMilestones returns milestones
-func (g *PlainGitDownloader) GetMilestones() ([]*base.Milestone, error) {
- return nil, ErrNotSupported
-}
-
-// GetLabels returns labels
-func (g *PlainGitDownloader) GetLabels() ([]*base.Label, error) {
- return nil, ErrNotSupported
-}
-
-// GetReleases returns releases
-func (g *PlainGitDownloader) GetReleases() ([]*base.Release, error) {
- return nil, ErrNotSupported
-}
-
-// GetIssues returns issues according page and perPage
-func (g *PlainGitDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
- return nil, false, ErrNotSupported
-}
-
-// GetComments returns comments according issueNumber
-func (g *PlainGitDownloader) GetComments(issueNumber int64) ([]*base.Comment, error) {
- return nil, ErrNotSupported
-}
-
-// GetPullRequests returns pull requests according page and perPage
-func (g *PlainGitDownloader) GetPullRequests(start, limit int) ([]*base.PullRequest, bool, error) {
- return nil, false, ErrNotSupported
-}
-
-// GetReviews returns reviews according issue number
-func (g *PlainGitDownloader) GetReviews(issueNumber int64) ([]*base.Review, error) {
- return nil, ErrNotSupported
-}
diff --git a/modules/migrations/gitea_downloader.go b/modules/migrations/gitea_downloader.go
index 0c690464fa..70daf8d5a3 100644
--- a/modules/migrations/gitea_downloader.go
+++ b/modules/migrations/gitea_downloader.go
@@ -69,6 +69,7 @@ func (f *GiteaDownloaderFactory) GitServiceType() structs.GitServiceType {
// GiteaDownloader implements a Downloader interface to get repository information's
type GiteaDownloader struct {
+ base.NullDownloader
ctx context.Context
client *gitea_sdk.Client
repoOwner string
@@ -95,7 +96,7 @@ func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, passwo
path := strings.Split(repoPath, "/")
paginationSupport := true
- if err := giteaClient.CheckServerVersionConstraint(">=1.12"); err != nil {
+ if err = giteaClient.CheckServerVersionConstraint(">=1.12"); err != nil {
paginationSupport = false
}
diff --git a/modules/migrations/gitea_uploader.go b/modules/migrations/gitea_uploader.go
index 2c79bd4b0f..3be49b5c6c 100644
--- a/modules/migrations/gitea_uploader.go
+++ b/modules/migrations/gitea_uploader.go
@@ -10,7 +10,6 @@ import (
"context"
"fmt"
"io"
- "net/url"
"os"
"path/filepath"
"strings"
@@ -86,22 +85,6 @@ func (g *GiteaLocalUploader) MaxBatchInsertSize(tp string) int {
return 10
}
-func fullURL(opts base.MigrateOptions, remoteAddr string) (string, error) {
- var fullRemoteAddr = remoteAddr
- if len(opts.AuthToken) > 0 || len(opts.AuthUsername) > 0 {
- u, err := url.Parse(remoteAddr)
- if err != nil {
- return "", err
- }
- u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
- if len(opts.AuthToken) > 0 {
- u.User = url.UserPassword("oauth2", opts.AuthToken)
- }
- fullRemoteAddr = u.String()
- }
- return fullRemoteAddr, nil
-}
-
// CreateRepo creates a repository
func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.MigrateOptions) error {
owner, err := models.GetUserByName(g.repoOwner)
@@ -109,10 +92,6 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
return err
}
- remoteAddr, err := fullURL(opts, repo.CloneURL)
- if err != nil {
- return err
- }
var r *models.Repository
if opts.MigrateToRepoID <= 0 {
r, err = repo_module.CreateRepository(g.doer, owner, models.CreateRepoOptions{
@@ -138,7 +117,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
OriginalURL: repo.OriginalURL,
GitServiceType: opts.GitServiceType,
Mirror: repo.IsMirror,
- CloneAddr: remoteAddr,
+ CloneAddr: repo.CloneURL,
Private: repo.IsPrivate,
Wiki: opts.Wiki,
Releases: opts.Releases, // if didn't get releases, then sync them from tags
diff --git a/modules/migrations/github.go b/modules/migrations/github.go
index 178517ba42..4d832387ba 100644
--- a/modules/migrations/github.go
+++ b/modules/migrations/github.go
@@ -65,6 +65,7 @@ func (f *GithubDownloaderV3Factory) GitServiceType() structs.GitServiceType {
// GithubDownloaderV3 implements a Downloader interface to get repository informations
// from github via APIv3
type GithubDownloaderV3 struct {
+ base.NullDownloader
ctx context.Context
client *github.Client
repoOwner string
diff --git a/modules/migrations/gitlab.go b/modules/migrations/gitlab.go
index e3fa956758..a697075ff8 100644
--- a/modules/migrations/gitlab.go
+++ b/modules/migrations/gitlab.go
@@ -63,6 +63,7 @@ func (f *GitlabDownloaderFactory) GitServiceType() structs.GitServiceType {
// - issueSeen, working alongside issueCount, is checked in GetComments() to see whether we
// need to fetch the Issue or PR comments, as Gitlab stores them separately.
type GitlabDownloader struct {
+ base.NullDownloader
ctx context.Context
client *gitlab.Client
repoID int
diff --git a/modules/migrations/gogs.go b/modules/migrations/gogs.go
new file mode 100644
index 0000000000..b616907938
--- /dev/null
+++ b/modules/migrations/gogs.go
@@ -0,0 +1,312 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/migrations/base"
+ "code.gitea.io/gitea/modules/structs"
+
+ "github.com/gogs/go-gogs-client"
+)
+
+var (
+ _ base.Downloader = &GogsDownloader{}
+ _ base.DownloaderFactory = &GogsDownloaderFactory{}
+)
+
+func init() {
+ RegisterDownloaderFactory(&GogsDownloaderFactory{})
+}
+
+// GogsDownloaderFactory defines a gogs downloader factory
+type GogsDownloaderFactory struct {
+}
+
+// New returns a Downloader related to this factory according MigrateOptions
+func (f *GogsDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
+ u, err := url.Parse(opts.CloneAddr)
+ if err != nil {
+ return nil, err
+ }
+
+ baseURL := u.Scheme + "://" + u.Host
+ repoNameSpace := strings.TrimSuffix(u.Path, ".git")
+ repoNameSpace = strings.Trim(repoNameSpace, "/")
+
+ fields := strings.Split(repoNameSpace, "/")
+ if len(fields) < 2 {
+ return nil, fmt.Errorf("invalid path: %s", repoNameSpace)
+ }
+
+ log.Trace("Create gogs downloader. BaseURL: %s RepoOwner: %s RepoName: %s", baseURL, fields[0], fields[1])
+ return NewGogsDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, fields[0], fields[1]), nil
+}
+
+// GitServiceType returns the type of git service
+func (f *GogsDownloaderFactory) GitServiceType() structs.GitServiceType {
+ return structs.GogsService
+}
+
+// GogsDownloader implements a Downloader interface to get repository informations
+// from gogs via API
+type GogsDownloader struct {
+ base.NullDownloader
+ ctx context.Context
+ client *gogs.Client
+ baseURL string
+ repoOwner string
+ repoName string
+ userName string
+ password string
+ openIssuesFinished bool
+ openIssuesPages int
+ transport http.RoundTripper
+}
+
+// SetContext set context
+func (g *GogsDownloader) SetContext(ctx context.Context) {
+ g.ctx = ctx
+}
+
+// NewGogsDownloader creates a gogs Downloader via gogs API
+func NewGogsDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GogsDownloader {
+ var downloader = GogsDownloader{
+ ctx: ctx,
+ baseURL: baseURL,
+ userName: userName,
+ password: password,
+ repoOwner: repoOwner,
+ repoName: repoName,
+ }
+
+ var client *gogs.Client
+ if len(token) != 0 {
+ client = gogs.NewClient(baseURL, token)
+ downloader.userName = token
+ } else {
+ downloader.transport = &http.Transport{
+ Proxy: func(req *http.Request) (*url.URL, error) {
+ req.SetBasicAuth(userName, password)
+ return nil, nil
+ },
+ }
+
+ client = gogs.NewClient(baseURL, "")
+ client.SetHTTPClient(&http.Client{
+ Transport: &downloader,
+ })
+ }
+
+ downloader.client = client
+ return &downloader
+}
+
+// RoundTrip wraps the provided request within this downloader's context and passes it to our internal http.Transport.
+// This implements http.RoundTripper and makes the gogs client requests cancellable even though it is not cancellable itself
+func (g *GogsDownloader) RoundTrip(req *http.Request) (*http.Response, error) {
+ return g.transport.RoundTrip(req.WithContext(g.ctx))
+}
+
+// GetRepoInfo returns a repository information
+func (g *GogsDownloader) GetRepoInfo() (*base.Repository, error) {
+ gr, err := g.client.GetRepo(g.repoOwner, g.repoName)
+ if err != nil {
+ return nil, err
+ }
+
+ // convert gogs repo to stand Repo
+ return &base.Repository{
+ Owner: g.repoOwner,
+ Name: g.repoName,
+ IsPrivate: gr.Private,
+ Description: gr.Description,
+ CloneURL: gr.CloneURL,
+ OriginalURL: gr.HTMLURL,
+ DefaultBranch: gr.DefaultBranch,
+ }, nil
+}
+
+// GetMilestones returns milestones
+func (g *GogsDownloader) GetMilestones() ([]*base.Milestone, error) {
+ var perPage = 100
+ var milestones = make([]*base.Milestone, 0, perPage)
+
+ ms, err := g.client.ListRepoMilestones(g.repoOwner, g.repoName)
+ if err != nil {
+ return nil, err
+ }
+
+ t := time.Now()
+
+ for _, m := range ms {
+ milestones = append(milestones, &base.Milestone{
+ Title: m.Title,
+ Description: m.Description,
+ Deadline: m.Deadline,
+ State: string(m.State),
+ Created: t,
+ Updated: &t,
+ Closed: m.Closed,
+ })
+ }
+
+ return milestones, nil
+}
+
+// GetLabels returns labels
+func (g *GogsDownloader) GetLabels() ([]*base.Label, error) {
+ var perPage = 100
+ var labels = make([]*base.Label, 0, perPage)
+ ls, err := g.client.ListRepoLabels(g.repoOwner, g.repoName)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, label := range ls {
+ labels = append(labels, convertGogsLabel(label))
+ }
+
+ return labels, nil
+}
+
+// GetIssues returns issues according start and limit, perPage is not supported
+func (g *GogsDownloader) GetIssues(page, _ int) ([]*base.Issue, bool, error) {
+ var state string
+ if g.openIssuesFinished {
+ state = string(gogs.STATE_CLOSED)
+ page -= g.openIssuesPages
+ } else {
+ state = string(gogs.STATE_OPEN)
+ g.openIssuesPages = page
+ }
+
+ issues, isEnd, err := g.getIssues(page, state)
+ if err != nil {
+ return nil, false, err
+ }
+
+ if isEnd {
+ if g.openIssuesFinished {
+ return issues, true, nil
+ }
+ g.openIssuesFinished = true
+ }
+
+ return issues, false, nil
+}
+
+func (g *GogsDownloader) getIssues(page int, state string) ([]*base.Issue, bool, error) {
+ var allIssues = make([]*base.Issue, 0, 10)
+
+ issues, err := g.client.ListRepoIssues(g.repoOwner, g.repoName, gogs.ListIssueOption{
+ Page: page,
+ State: state,
+ })
+ if err != nil {
+ return nil, false, fmt.Errorf("error while listing repos: %v", err)
+ }
+
+ for _, issue := range issues {
+ if issue.PullRequest != nil {
+ continue
+ }
+ allIssues = append(allIssues, convertGogsIssue(issue))
+ }
+
+ return allIssues, len(issues) == 0, nil
+}
+
+// GetComments returns comments according issueNumber
+func (g *GogsDownloader) GetComments(issueNumber int64) ([]*base.Comment, error) {
+ var allComments = make([]*base.Comment, 0, 100)
+
+ comments, err := g.client.ListIssueComments(g.repoOwner, g.repoName, issueNumber)
+ if err != nil {
+ return nil, fmt.Errorf("error while listing repos: %v", err)
+ }
+ for _, comment := range comments {
+ if len(comment.Body) == 0 || comment.Poster == nil {
+ continue
+ }
+ allComments = append(allComments, &base.Comment{
+ IssueIndex: issueNumber,
+ PosterID: comment.Poster.ID,
+ PosterName: comment.Poster.Login,
+ PosterEmail: comment.Poster.Email,
+ Content: comment.Body,
+ Created: comment.Created,
+ Updated: comment.Updated,
+ })
+ }
+
+ return allComments, nil
+}
+
+// GetTopics return repository topics
+func (g *GogsDownloader) GetTopics() ([]string, error) {
+ return []string{}, nil
+}
+
+// FormatCloneURL add authentification into remote URLs
+func (g *GogsDownloader) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
+ if len(opts.AuthToken) > 0 || len(opts.AuthUsername) > 0 {
+ u, err := url.Parse(remoteAddr)
+ if err != nil {
+ return "", err
+ }
+ if len(opts.AuthToken) != 0 {
+ u.User = url.UserPassword(opts.AuthToken, "")
+ } else {
+ u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
+ }
+ return u.String(), nil
+ }
+ return remoteAddr, nil
+}
+
+func convertGogsIssue(issue *gogs.Issue) *base.Issue {
+ var milestone string
+ if issue.Milestone != nil {
+ milestone = issue.Milestone.Title
+ }
+ var labels = make([]*base.Label, 0, len(issue.Labels))
+ for _, l := range issue.Labels {
+ labels = append(labels, convertGogsLabel(l))
+ }
+
+ var closed *time.Time
+ if issue.State == gogs.STATE_CLOSED {
+ // gogs client haven't provide closed, so we use updated instead
+ closed = &issue.Updated
+ }
+
+ return &base.Issue{
+ Title: issue.Title,
+ Number: issue.Index,
+ PosterName: issue.Poster.Login,
+ PosterEmail: issue.Poster.Email,
+ Content: issue.Body,
+ Milestone: milestone,
+ State: string(issue.State),
+ Created: issue.Created,
+ Labels: labels,
+ Closed: closed,
+ }
+}
+
+func convertGogsLabel(label *gogs.Label) *base.Label {
+ return &base.Label{
+ Name: label.Name,
+ Color: label.Color,
+ }
+}
diff --git a/modules/migrations/gogs_test.go b/modules/migrations/gogs_test.go
new file mode 100644
index 0000000000..c240ae6432
--- /dev/null
+++ b/modules/migrations/gogs_test.go
@@ -0,0 +1,122 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+ "context"
+ "net/http"
+ "os"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/modules/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGogsDownloadRepo(t *testing.T) {
+ // Skip tests if Gogs token is not found
+ gogsPersonalAccessToken := os.Getenv("GOGS_READ_TOKEN")
+ if len(gogsPersonalAccessToken) == 0 {
+ t.Skip("skipped test because GOGS_READ_TOKEN was not in the environment")
+ }
+
+ resp, err := http.Get("https://try.gogs.io/lunnytest/TESTREPO")
+ if err != nil || resp.StatusCode/100 != 2 {
+ // skip and don't run test
+ t.Skipf("visit test repo failed, ignored")
+ return
+ }
+
+ downloader := NewGogsDownloader(context.Background(), "https://try.gogs.io", "", "", gogsPersonalAccessToken, "lunnytest", "TESTREPO")
+ repo, err := downloader.GetRepoInfo()
+ assert.NoError(t, err)
+
+ assert.EqualValues(t, &base.Repository{
+ Name: "TESTREPO",
+ Owner: "lunnytest",
+ Description: "",
+ CloneURL: "https://try.gogs.io/lunnytest/TESTREPO.git",
+ }, repo)
+
+ milestones, err := downloader.GetMilestones()
+ assert.NoError(t, err)
+ assert.True(t, len(milestones) == 1)
+
+ for _, milestone := range milestones {
+ switch milestone.Title {
+ case "1.0":
+ assert.EqualValues(t, "open", milestone.State)
+ }
+ }
+
+ labels, err := downloader.GetLabels()
+ assert.NoError(t, err)
+ assert.Len(t, labels, 7)
+ for _, l := range labels {
+ switch l.Name {
+ case "bug":
+ assertLabelEqual(t, "bug", "ee0701", "", l)
+ case "duplicated":
+ assertLabelEqual(t, "duplicated", "cccccc", "", l)
+ case "enhancement":
+ assertLabelEqual(t, "enhancement", "84b6eb", "", l)
+ case "help wanted":
+ assertLabelEqual(t, "help wanted", "128a0c", "", l)
+ case "invalid":
+ assertLabelEqual(t, "invalid", "e6e6e6", "", l)
+ case "question":
+ assertLabelEqual(t, "question", "cc317c", "", l)
+ case "wontfix":
+ assertLabelEqual(t, "wontfix", "ffffff", "", l)
+ }
+ }
+
+ _, err = downloader.GetReleases()
+ assert.Error(t, err)
+
+ // downloader.GetIssues()
+ issues, isEnd, err := downloader.GetIssues(1, 8)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, len(issues))
+ assert.False(t, isEnd)
+
+ assert.EqualValues(t, []*base.Issue{
+ {
+ Number: 1,
+ Title: "test",
+ Content: "test",
+ Milestone: "",
+ PosterName: "lunny",
+ PosterEmail: "xiaolunwen@gmail.com",
+ State: "open",
+ Created: time.Date(2019, 06, 11, 8, 16, 44, 0, time.UTC),
+ Labels: []*base.Label{
+ {
+ Name: "bug",
+ Color: "ee0701",
+ },
+ },
+ },
+ }, issues)
+
+ // downloader.GetComments()
+ comments, err := downloader.GetComments(1)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, len(comments))
+ assert.EqualValues(t, []*base.Comment{
+ {
+ PosterName: "lunny",
+ PosterEmail: "xiaolunwen@gmail.com",
+ Created: time.Date(2019, 06, 11, 8, 19, 50, 0, time.UTC),
+ Updated: time.Date(2019, 06, 11, 8, 19, 50, 0, time.UTC),
+ Content: `1111`,
+ },
+ }, comments)
+
+ // downloader.GetPullRequests()
+ _, _, err = downloader.GetPullRequests(1, 3)
+ assert.Error(t, err)
+}
diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go
index 4c15626e57..b9c17478a9 100644
--- a/modules/migrations/migrate.go
+++ b/modules/migrations/migrate.go
@@ -133,15 +133,22 @@ func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptio
func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions) error {
repo, err := downloader.GetRepoInfo()
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Info("migrating repo infos is not supported, ignored")
}
repo.IsPrivate = opts.Private
repo.IsMirror = opts.Mirror
if opts.Description != "" {
repo.Description = opts.Description
}
+ if repo.CloneURL, err = downloader.FormatCloneURL(opts, repo.CloneURL); err != nil {
+ return err
+ }
+
log.Trace("migrating git data")
- if err := uploader.CreateRepo(repo, opts); err != nil {
+ if err = uploader.CreateRepo(repo, opts); err != nil {
return err
}
defer uploader.Close()
@@ -149,10 +156,13 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
log.Trace("migrating topics")
topics, err := downloader.GetTopics()
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating topics is not supported, ignored")
}
- if len(topics) > 0 {
- if err := uploader.CreateTopics(topics...); err != nil {
+ if len(topics) != 0 {
+ if err = uploader.CreateTopics(topics...); err != nil {
return err
}
}
@@ -161,7 +171,10 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
log.Trace("migrating milestones")
milestones, err := downloader.GetMilestones()
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating milestones is not supported, ignored")
}
msBatchSize := uploader.MaxBatchInsertSize("milestone")
@@ -181,7 +194,10 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
log.Trace("migrating labels")
labels, err := downloader.GetLabels()
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating labels is not supported, ignored")
}
lbBatchSize := uploader.MaxBatchInsertSize("label")
@@ -201,7 +217,10 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
log.Trace("migrating releases")
releases, err := downloader.GetReleases()
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating releases is not supported, ignored")
}
relBatchSize := uploader.MaxBatchInsertSize("release")
@@ -210,14 +229,14 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
relBatchSize = len(releases)
}
- if err := uploader.CreateReleases(releases[:relBatchSize]...); err != nil {
+ if err = uploader.CreateReleases(releases[:relBatchSize]...); err != nil {
return err
}
releases = releases[relBatchSize:]
}
// Once all releases (if any) are inserted, sync any remaining non-release tags
- if err := uploader.SyncTags(); err != nil {
+ if err = uploader.SyncTags(); err != nil {
return err
}
}
@@ -234,7 +253,11 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
for i := 1; ; i++ {
issues, isEnd, err := downloader.GetIssues(i, issueBatchSize)
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating issues is not supported, ignored")
+ break
}
if err := uploader.CreateIssues(issues...); err != nil {
@@ -247,13 +270,16 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
log.Trace("migrating issue %d's comments", issue.Number)
comments, err := downloader.GetComments(issue.Number)
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating comments is not supported, ignored")
}
allComments = append(allComments, comments...)
if len(allComments) >= commentBatchSize {
- if err := uploader.CreateComments(allComments[:commentBatchSize]...); err != nil {
+ if err = uploader.CreateComments(allComments[:commentBatchSize]...); err != nil {
return err
}
@@ -262,7 +288,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
}
if len(allComments) > 0 {
- if err := uploader.CreateComments(allComments...); err != nil {
+ if err = uploader.CreateComments(allComments...); err != nil {
return err
}
}
@@ -280,7 +306,11 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
for i := 1; ; i++ {
prs, isEnd, err := downloader.GetPullRequests(i, prBatchSize)
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating pull requests is not supported, ignored")
+ break
}
if err := uploader.CreatePullRequests(prs...); err != nil {
@@ -294,20 +324,23 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
log.Trace("migrating pull request %d's comments", pr.Number)
comments, err := downloader.GetComments(pr.Number)
if err != nil {
- return err
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating comments is not supported, ignored")
}
allComments = append(allComments, comments...)
if len(allComments) >= commentBatchSize {
- if err := uploader.CreateComments(allComments[:commentBatchSize]...); err != nil {
+ if err = uploader.CreateComments(allComments[:commentBatchSize]...); err != nil {
return err
}
allComments = allComments[commentBatchSize:]
}
}
if len(allComments) > 0 {
- if err := uploader.CreateComments(allComments...); err != nil {
+ if err = uploader.CreateComments(allComments...); err != nil {
return err
}
}
@@ -323,26 +356,30 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
}
reviews, err := downloader.GetReviews(number)
+ if err != nil {
+ if !base.IsErrNotSupported(err) {
+ return err
+ }
+ log.Warn("migrating reviews is not supported, ignored")
+ break
+ }
if pr.OriginalNumber > 0 {
for i := range reviews {
reviews[i].IssueIndex = pr.Number
}
}
- if err != nil {
- return err
- }
allReviews = append(allReviews, reviews...)
if len(allReviews) >= reviewBatchSize {
- if err := uploader.CreateReviews(allReviews[:reviewBatchSize]...); err != nil {
+ if err = uploader.CreateReviews(allReviews[:reviewBatchSize]...); err != nil {
return err
}
allReviews = allReviews[reviewBatchSize:]
}
}
if len(allReviews) > 0 {
- if err := uploader.CreateReviews(allReviews...); err != nil {
+ if err = uploader.CreateReviews(allReviews...); err != nil {
return err
}
}
diff --git a/modules/migrations/restore.go b/modules/migrations/restore.go
index 5550aaeb03..e1ab408e41 100644
--- a/modules/migrations/restore.go
+++ b/modules/migrations/restore.go
@@ -19,6 +19,7 @@ import (
// RepositoryRestorer implements an Downloader from the local directory
type RepositoryRestorer struct {
+ base.NullDownloader
ctx context.Context
baseDir string
repoOwner string