mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Git migration UX (#12619)
* Initial work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Implementation Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix gitlab and token cloning Signed-off-by: jolheiser <john.olheiser@gmail.com> * Imports and JS Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Linting Signed-off-by: jolheiser <john.olheiser@gmail.com> * Generate swagger Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move mirror toggle and rename options Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		@@ -56,8 +56,10 @@ func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bin
 | 
				
			|||||||
type MigrateRepoForm struct {
 | 
					type MigrateRepoForm struct {
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	CloneAddr    string `json:"clone_addr" binding:"Required"`
 | 
						CloneAddr    string `json:"clone_addr" binding:"Required"`
 | 
				
			||||||
 | 
						Service      int    `json:"service"`
 | 
				
			||||||
	AuthUsername string `json:"auth_username"`
 | 
						AuthUsername string `json:"auth_username"`
 | 
				
			||||||
	AuthPassword string `json:"auth_password"`
 | 
						AuthPassword string `json:"auth_password"`
 | 
				
			||||||
 | 
						AuthToken    string `json:"auth_token"`
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	UID int64 `json:"uid" binding:"Required"`
 | 
						UID int64 `json:"uid" binding:"Required"`
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,13 +7,20 @@ package base
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/structs"
 | 
						"code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AssetDownloader downloads an asset (attachment) for a release
 | 
				
			||||||
 | 
					type AssetDownloader interface {
 | 
				
			||||||
 | 
						GetAsset(tag string, id int64) (io.ReadCloser, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Downloader downloads the site repo informations
 | 
					// Downloader downloads the site repo informations
 | 
				
			||||||
type Downloader interface {
 | 
					type Downloader interface {
 | 
				
			||||||
 | 
						AssetDownloader
 | 
				
			||||||
	SetContext(context.Context)
 | 
						SetContext(context.Context)
 | 
				
			||||||
	GetRepoInfo() (*Repository, error)
 | 
						GetRepoInfo() (*Repository, error)
 | 
				
			||||||
	GetTopics() ([]string, error)
 | 
						GetTopics() ([]string, error)
 | 
				
			||||||
@@ -28,7 +35,6 @@ type Downloader interface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// DownloaderFactory defines an interface to match a downloader implementation and create a downloader
 | 
					// DownloaderFactory defines an interface to match a downloader implementation and create a downloader
 | 
				
			||||||
type DownloaderFactory interface {
 | 
					type DownloaderFactory interface {
 | 
				
			||||||
	Match(opts MigrateOptions) (bool, error)
 | 
					 | 
				
			||||||
	New(opts MigrateOptions) (Downloader, error)
 | 
						New(opts MigrateOptions) (Downloader, error)
 | 
				
			||||||
	GitServiceType() structs.GitServiceType
 | 
						GitServiceType() structs.GitServiceType
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ import "time"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ReleaseAsset represents a release asset
 | 
					// ReleaseAsset represents a release asset
 | 
				
			||||||
type ReleaseAsset struct {
 | 
					type ReleaseAsset struct {
 | 
				
			||||||
	URL           string
 | 
						ID            int64
 | 
				
			||||||
	Name          string
 | 
						Name          string
 | 
				
			||||||
	ContentType   *string
 | 
						ContentType   *string
 | 
				
			||||||
	Size          *int
 | 
						Size          *int
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ type Uploader interface {
 | 
				
			|||||||
	CreateRepo(repo *Repository, opts MigrateOptions) error
 | 
						CreateRepo(repo *Repository, opts MigrateOptions) error
 | 
				
			||||||
	CreateTopics(topic ...string) error
 | 
						CreateTopics(topic ...string) error
 | 
				
			||||||
	CreateMilestones(milestones ...*Milestone) error
 | 
						CreateMilestones(milestones ...*Milestone) error
 | 
				
			||||||
	CreateReleases(releases ...*Release) error
 | 
						CreateReleases(downloader Downloader, releases ...*Release) error
 | 
				
			||||||
	SyncTags() error
 | 
						SyncTags() error
 | 
				
			||||||
	CreateLabels(labels ...*Label) error
 | 
						CreateLabels(labels ...*Label) error
 | 
				
			||||||
	CreateIssues(issues ...*Issue) error
 | 
						CreateIssues(issues ...*Issue) error
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ package migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/migrations/base"
 | 
						"code.gitea.io/gitea/modules/migrations/base"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -64,6 +65,11 @@ func (g *PlainGitDownloader) GetReleases() ([]*base.Release, error) {
 | 
				
			|||||||
	return nil, ErrNotSupported
 | 
						return nil, ErrNotSupported
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAsset returns an asset
 | 
				
			||||||
 | 
					func (g *PlainGitDownloader) GetAsset(_ string, _ int64) (io.ReadCloser, error) {
 | 
				
			||||||
 | 
						return nil, ErrNotSupported
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetIssues returns issues according page and perPage
 | 
					// GetIssues returns issues according page and perPage
 | 
				
			||||||
func (g *PlainGitDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
 | 
					func (g *PlainGitDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
 | 
				
			||||||
	return nil, false, ErrNotSupported
 | 
						return nil, false, ErrNotSupported
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,12 +93,15 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var remoteAddr = repo.CloneURL
 | 
						var remoteAddr = repo.CloneURL
 | 
				
			||||||
	if len(opts.AuthUsername) > 0 {
 | 
						if len(opts.AuthToken) > 0 || len(opts.AuthUsername) > 0 {
 | 
				
			||||||
		u, err := url.Parse(repo.CloneURL)
 | 
							u, err := url.Parse(repo.CloneURL)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
 | 
							u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
 | 
				
			||||||
 | 
							if len(opts.AuthToken) > 0 {
 | 
				
			||||||
 | 
								u.User = url.UserPassword("oauth2", opts.AuthToken)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		remoteAddr = u.String()
 | 
							remoteAddr = u.String()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -210,7 +213,7 @@ func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateReleases creates releases
 | 
					// CreateReleases creates releases
 | 
				
			||||||
func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
 | 
					func (g *GiteaLocalUploader) CreateReleases(downloader base.Downloader, releases ...*base.Release) error {
 | 
				
			||||||
	var rels = make([]*models.Release, 0, len(releases))
 | 
						var rels = make([]*models.Release, 0, len(releases))
 | 
				
			||||||
	for _, release := range releases {
 | 
						for _, release := range releases {
 | 
				
			||||||
		var rel = models.Release{
 | 
							var rel = models.Release{
 | 
				
			||||||
@@ -269,13 +272,11 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			// download attachment
 | 
								// download attachment
 | 
				
			||||||
			err = func() error {
 | 
								err = func() error {
 | 
				
			||||||
				resp, err := http.Get(asset.URL)
 | 
									rc, err := downloader.GetAsset(rel.TagName, asset.ID)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					return err
 | 
										return err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				defer resp.Body.Close()
 | 
									_, err = storage.Attachments.Save(attach.RelativePath(), rc)
 | 
				
			||||||
 | 
					 | 
				
			||||||
				_, err = storage.Attachments.Save(attach.RelativePath(), resp.Body)
 | 
					 | 
				
			||||||
				return err
 | 
									return err
 | 
				
			||||||
			}()
 | 
								}()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,7 @@ func TestGiteaUploadRepo(t *testing.T) {
 | 
				
			|||||||
	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
 | 
						user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		downloader = NewGithubDownloaderV3("", "", "go-xorm", "builder")
 | 
							downloader = NewGithubDownloaderV3("", "", "", "go-xorm", "builder")
 | 
				
			||||||
		repoName   = "builder-" + time.Now().Format("2006-01-02-15-04-05")
 | 
							repoName   = "builder-" + time.Now().Format("2006-01-02-15-04-05")
 | 
				
			||||||
		uploader   = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName)
 | 
							uploader   = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName)
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,11 @@
 | 
				
			|||||||
package migrations
 | 
					package migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
@@ -37,16 +40,6 @@ func init() {
 | 
				
			|||||||
type GithubDownloaderV3Factory struct {
 | 
					type GithubDownloaderV3Factory struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Match returns ture if the migration remote URL matched this downloader factory
 | 
					 | 
				
			||||||
func (f *GithubDownloaderV3Factory) Match(opts base.MigrateOptions) (bool, error) {
 | 
					 | 
				
			||||||
	u, err := url.Parse(opts.CloneAddr)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return false, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return strings.EqualFold(u.Host, "github.com") && opts.AuthUsername != "", nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// New returns a Downloader related to this factory according MigrateOptions
 | 
					// New returns a Downloader related to this factory according MigrateOptions
 | 
				
			||||||
func (f *GithubDownloaderV3Factory) New(opts base.MigrateOptions) (base.Downloader, error) {
 | 
					func (f *GithubDownloaderV3Factory) New(opts base.MigrateOptions) (base.Downloader, error) {
 | 
				
			||||||
	u, err := url.Parse(opts.CloneAddr)
 | 
						u, err := url.Parse(opts.CloneAddr)
 | 
				
			||||||
@@ -60,7 +53,7 @@ func (f *GithubDownloaderV3Factory) New(opts base.MigrateOptions) (base.Download
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	log.Trace("Create github downloader: %s/%s", oldOwner, oldName)
 | 
						log.Trace("Create github downloader: %s/%s", oldOwner, oldName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return NewGithubDownloaderV3(opts.AuthUsername, opts.AuthPassword, oldOwner, oldName), nil
 | 
						return NewGithubDownloaderV3(opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GitServiceType returns the type of git service
 | 
					// GitServiceType returns the type of git service
 | 
				
			||||||
@@ -81,7 +74,7 @@ type GithubDownloaderV3 struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewGithubDownloaderV3 creates a github Downloader via github v3 API
 | 
					// NewGithubDownloaderV3 creates a github Downloader via github v3 API
 | 
				
			||||||
func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *GithubDownloaderV3 {
 | 
					func NewGithubDownloaderV3(userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 {
 | 
				
			||||||
	var downloader = GithubDownloaderV3{
 | 
						var downloader = GithubDownloaderV3{
 | 
				
			||||||
		userName:  userName,
 | 
							userName:  userName,
 | 
				
			||||||
		password:  password,
 | 
							password:  password,
 | 
				
			||||||
@@ -90,23 +83,19 @@ func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *Gith
 | 
				
			|||||||
		repoName:  repoName,
 | 
							repoName:  repoName,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var client *http.Client
 | 
						client := &http.Client{
 | 
				
			||||||
	if userName != "" {
 | 
							Transport: &http.Transport{
 | 
				
			||||||
		if password == "" {
 | 
								Proxy: func(req *http.Request) (*url.URL, error) {
 | 
				
			||||||
			ts := oauth2.StaticTokenSource(
 | 
									req.SetBasicAuth(userName, password)
 | 
				
			||||||
				&oauth2.Token{AccessToken: userName},
 | 
									return nil, nil
 | 
				
			||||||
			)
 | 
								},
 | 
				
			||||||
			client = oauth2.NewClient(downloader.ctx, ts)
 | 
							},
 | 
				
			||||||
		} else {
 | 
						}
 | 
				
			||||||
			client = &http.Client{
 | 
						if token != "" {
 | 
				
			||||||
				Transport: &http.Transport{
 | 
							ts := oauth2.StaticTokenSource(
 | 
				
			||||||
					Proxy: func(req *http.Request) (*url.URL, error) {
 | 
								&oauth2.Token{AccessToken: token},
 | 
				
			||||||
						req.SetBasicAuth(userName, password)
 | 
							)
 | 
				
			||||||
						return nil, nil
 | 
							client = oauth2.NewClient(downloader.ctx, ts)
 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	downloader.client = github.NewClient(client)
 | 
						downloader.client = github.NewClient(client)
 | 
				
			||||||
	return &downloader
 | 
						return &downloader
 | 
				
			||||||
@@ -290,10 +279,8 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, asset := range rel.Assets {
 | 
						for _, asset := range rel.Assets {
 | 
				
			||||||
		u, _ := url.Parse(*asset.BrowserDownloadURL)
 | 
					 | 
				
			||||||
		u.User = url.UserPassword(g.userName, g.password)
 | 
					 | 
				
			||||||
		r.Assets = append(r.Assets, base.ReleaseAsset{
 | 
							r.Assets = append(r.Assets, base.ReleaseAsset{
 | 
				
			||||||
			URL:           u.String(),
 | 
								ID:            *asset.ID,
 | 
				
			||||||
			Name:          *asset.Name,
 | 
								Name:          *asset.Name,
 | 
				
			||||||
			ContentType:   asset.ContentType,
 | 
								ContentType:   asset.ContentType,
 | 
				
			||||||
			Size:          asset.Size,
 | 
								Size:          asset.Size,
 | 
				
			||||||
@@ -331,6 +318,18 @@ func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) {
 | 
				
			|||||||
	return releases, nil
 | 
						return releases, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAsset returns an asset
 | 
				
			||||||
 | 
					func (g *GithubDownloaderV3) GetAsset(_ string, id int64) (io.ReadCloser, error) {
 | 
				
			||||||
 | 
						asset, redir, err := g.client.Repositories.DownloadReleaseAsset(g.ctx, g.repoOwner, g.repoName, id, http.DefaultClient)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if asset == nil {
 | 
				
			||||||
 | 
							return ioutil.NopCloser(bytes.NewBufferString(redir)), nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return asset, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetIssues returns issues according start and limit
 | 
					// GetIssues returns issues according start and limit
 | 
				
			||||||
func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
 | 
					func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
 | 
				
			||||||
	opt := &github.IssueListByRepoOptions{
 | 
						opt := &github.IssueListByRepoOptions{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,7 +64,7 @@ func assertLabelEqual(t *testing.T, name, color, description string, label *base
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestGitHubDownloadRepo(t *testing.T) {
 | 
					func TestGitHubDownloadRepo(t *testing.T) {
 | 
				
			||||||
	GithubLimitRateRemaining = 3 //Wait at 3 remaining since we could have 3 CI in //
 | 
						GithubLimitRateRemaining = 3 //Wait at 3 remaining since we could have 3 CI in //
 | 
				
			||||||
	downloader := NewGithubDownloaderV3(os.Getenv("GITHUB_READ_TOKEN"), "", "go-gitea", "test_repo")
 | 
						downloader := NewGithubDownloaderV3("", "", os.Getenv("GITHUB_READ_TOKEN"), "go-gitea", "test_repo")
 | 
				
			||||||
	err := downloader.RefreshRate()
 | 
						err := downloader.RefreshRate()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,8 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -32,21 +34,6 @@ func init() {
 | 
				
			|||||||
type GitlabDownloaderFactory struct {
 | 
					type GitlabDownloaderFactory struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Match returns true if the migration remote URL matched this downloader factory
 | 
					 | 
				
			||||||
func (f *GitlabDownloaderFactory) Match(opts base.MigrateOptions) (bool, error) {
 | 
					 | 
				
			||||||
	var matched bool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	u, err := url.Parse(opts.CloneAddr)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return false, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if strings.EqualFold(u.Host, "gitlab.com") && opts.AuthUsername != "" {
 | 
					 | 
				
			||||||
		matched = true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return matched, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// New returns a Downloader related to this factory according MigrateOptions
 | 
					// New returns a Downloader related to this factory according MigrateOptions
 | 
				
			||||||
func (f *GitlabDownloaderFactory) New(opts base.MigrateOptions) (base.Downloader, error) {
 | 
					func (f *GitlabDownloaderFactory) New(opts base.MigrateOptions) (base.Downloader, error) {
 | 
				
			||||||
	u, err := url.Parse(opts.CloneAddr)
 | 
						u, err := url.Parse(opts.CloneAddr)
 | 
				
			||||||
@@ -56,10 +43,11 @@ func (f *GitlabDownloaderFactory) New(opts base.MigrateOptions) (base.Downloader
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	baseURL := u.Scheme + "://" + u.Host
 | 
						baseURL := u.Scheme + "://" + u.Host
 | 
				
			||||||
	repoNameSpace := strings.TrimPrefix(u.Path, "/")
 | 
						repoNameSpace := strings.TrimPrefix(u.Path, "/")
 | 
				
			||||||
 | 
						repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
 | 
						log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return NewGitlabDownloader(baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword), nil
 | 
						return NewGitlabDownloader(baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword, opts.AuthToken), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GitServiceType returns the type of git service
 | 
					// GitServiceType returns the type of git service
 | 
				
			||||||
@@ -85,15 +73,13 @@ type GitlabDownloader struct {
 | 
				
			|||||||
// NewGitlabDownloader creates a gitlab Downloader via gitlab API
 | 
					// NewGitlabDownloader creates a gitlab Downloader via gitlab API
 | 
				
			||||||
//   Use either a username/password, personal token entered into the username field, or anonymous/public access
 | 
					//   Use either a username/password, personal token entered into the username field, or anonymous/public access
 | 
				
			||||||
//   Note: Public access only allows very basic access
 | 
					//   Note: Public access only allows very basic access
 | 
				
			||||||
func NewGitlabDownloader(baseURL, repoPath, username, password string) *GitlabDownloader {
 | 
					func NewGitlabDownloader(baseURL, repoPath, username, password, token string) *GitlabDownloader {
 | 
				
			||||||
	var gitlabClient *gitlab.Client
 | 
						var gitlabClient *gitlab.Client
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
	if username != "" {
 | 
						if token != "" {
 | 
				
			||||||
		if password == "" {
 | 
							gitlabClient, err = gitlab.NewClient(token, gitlab.WithBaseURL(baseURL))
 | 
				
			||||||
			gitlabClient, err = gitlab.NewClient(username, gitlab.WithBaseURL(baseURL))
 | 
						} else {
 | 
				
			||||||
		} else {
 | 
							gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL))
 | 
				
			||||||
			gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL))
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -271,7 +257,7 @@ func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Release {
 | 
					func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Release {
 | 
				
			||||||
 | 
						var zero int
 | 
				
			||||||
	r := &base.Release{
 | 
						r := &base.Release{
 | 
				
			||||||
		TagName:         rel.TagName,
 | 
							TagName:         rel.TagName,
 | 
				
			||||||
		TargetCommitish: rel.Commit.ID,
 | 
							TargetCommitish: rel.Commit.ID,
 | 
				
			||||||
@@ -284,9 +270,11 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for k, asset := range rel.Assets.Links {
 | 
						for k, asset := range rel.Assets.Links {
 | 
				
			||||||
		r.Assets = append(r.Assets, base.ReleaseAsset{
 | 
							r.Assets = append(r.Assets, base.ReleaseAsset{
 | 
				
			||||||
			URL:         asset.URL,
 | 
								ID:            int64(asset.ID),
 | 
				
			||||||
			Name:        asset.Name,
 | 
								Name:          asset.Name,
 | 
				
			||||||
			ContentType: &rel.Assets.Sources[k].Format,
 | 
								ContentType:   &rel.Assets.Sources[k].Format,
 | 
				
			||||||
 | 
								Size:          &zero,
 | 
				
			||||||
 | 
								DownloadCount: &zero,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return r
 | 
						return r
 | 
				
			||||||
@@ -315,6 +303,21 @@ func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
 | 
				
			|||||||
	return releases, nil
 | 
						return releases, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAsset returns an asset
 | 
				
			||||||
 | 
					func (g *GitlabDownloader) GetAsset(tag string, id int64) (io.ReadCloser, error) {
 | 
				
			||||||
 | 
						link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, tag, int(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						resp, err := http.Get(link.URL)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// resp.Body is closed by the uploader
 | 
				
			||||||
 | 
						return resp.Body, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetIssues returns issues according start and limit
 | 
					// GetIssues returns issues according start and limit
 | 
				
			||||||
//   Note: issue label description and colors are not supported by the go-gitlab library at this time
 | 
					//   Note: issue label description and colors are not supported by the go-gitlab library at this time
 | 
				
			||||||
//   TODO: figure out how to transfer issue reactions
 | 
					//   TODO: figure out how to transfer issue reactions
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
 | 
				
			|||||||
		t.Skipf("Can't access test repo, skipping %s", t.Name())
 | 
							t.Skipf("Can't access test repo, skipping %s", t.Name())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	downloader := NewGitlabDownloader("https://gitlab.com", "gitea/test_repo", gitlabPersonalAccessToken, "")
 | 
						downloader := NewGitlabDownloader("https://gitlab.com", "gitea/test_repo", "", "", gitlabPersonalAccessToken)
 | 
				
			||||||
	if downloader == nil {
 | 
						if downloader == nil {
 | 
				
			||||||
		t.Fatal("NewGitlabDownloader is nil")
 | 
							t.Fatal("NewGitlabDownloader is nil")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,6 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/migrations/base"
 | 
						"code.gitea.io/gitea/modules/migrations/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/structs"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MigrateOptions is equal to base.MigrateOptions
 | 
					// MigrateOptions is equal to base.MigrateOptions
 | 
				
			||||||
@@ -33,18 +32,15 @@ func MigrateRepository(ctx context.Context, doer *models.User, ownerName string,
 | 
				
			|||||||
	var (
 | 
						var (
 | 
				
			||||||
		downloader base.Downloader
 | 
							downloader base.Downloader
 | 
				
			||||||
		uploader   = NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName)
 | 
							uploader   = NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName)
 | 
				
			||||||
		theFactory base.DownloaderFactory
 | 
							err        error
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, factory := range factories {
 | 
						for _, factory := range factories {
 | 
				
			||||||
		if match, err := factory.Match(opts); err != nil {
 | 
							if factory.GitServiceType() == opts.GitServiceType {
 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		} else if match {
 | 
					 | 
				
			||||||
			downloader, err = factory.New(opts)
 | 
								downloader, err = factory.New(opts)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return nil, err
 | 
									return nil, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			theFactory = factory
 | 
					 | 
				
			||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -57,11 +53,8 @@ func MigrateRepository(ctx context.Context, doer *models.User, ownerName string,
 | 
				
			|||||||
		opts.Comments = false
 | 
							opts.Comments = false
 | 
				
			||||||
		opts.Issues = false
 | 
							opts.Issues = false
 | 
				
			||||||
		opts.PullRequests = false
 | 
							opts.PullRequests = false
 | 
				
			||||||
		opts.GitServiceType = structs.PlainGitService
 | 
					 | 
				
			||||||
		downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr)
 | 
							downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr)
 | 
				
			||||||
		log.Trace("Will migrate from git: %s", opts.OriginalURL)
 | 
							log.Trace("Will migrate from git: %s", opts.OriginalURL)
 | 
				
			||||||
	} else if opts.GitServiceType == structs.NotMigrated {
 | 
					 | 
				
			||||||
		opts.GitServiceType = theFactory.GitServiceType()
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	uploader.gitServiceType = opts.GitServiceType
 | 
						uploader.gitServiceType = opts.GitServiceType
 | 
				
			||||||
@@ -169,7 +162,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
 | 
				
			|||||||
				relBatchSize = len(releases)
 | 
									relBatchSize = len(releases)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := uploader.CreateReleases(releases[:relBatchSize]...); err != nil {
 | 
								if err := uploader.CreateReleases(downloader, releases[:relBatchSize]...); err != nil {
 | 
				
			||||||
				return err
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			releases = releases[relBatchSize:]
 | 
								releases = releases[relBatchSize:]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -218,6 +218,32 @@ func (gt GitServiceType) Name() string {
 | 
				
			|||||||
	return ""
 | 
						return ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Title represents the service type's proper title
 | 
				
			||||||
 | 
					func (gt GitServiceType) Title() string {
 | 
				
			||||||
 | 
						switch gt {
 | 
				
			||||||
 | 
						case GithubService:
 | 
				
			||||||
 | 
							return "GitHub"
 | 
				
			||||||
 | 
						case GiteaService:
 | 
				
			||||||
 | 
							return "Gitea"
 | 
				
			||||||
 | 
						case GitlabService:
 | 
				
			||||||
 | 
							return "GitLab"
 | 
				
			||||||
 | 
						case GogsService:
 | 
				
			||||||
 | 
							return "Gogs"
 | 
				
			||||||
 | 
						case PlainGitService:
 | 
				
			||||||
 | 
							return "Git"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TokenAuth represents whether a service type supports token-based auth
 | 
				
			||||||
 | 
					func (gt GitServiceType) TokenAuth() bool {
 | 
				
			||||||
 | 
						switch gt {
 | 
				
			||||||
 | 
						case GithubService, GiteaService, GitlabService:
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	// SupportedFullGitService represents all git services supported to migrate issues/labels/prs and etc.
 | 
						// SupportedFullGitService represents all git services supported to migrate issues/labels/prs and etc.
 | 
				
			||||||
	// TODO: add to this list after new git service added
 | 
						// TODO: add to this list after new git service added
 | 
				
			||||||
@@ -233,6 +259,7 @@ type MigrateRepoOption struct {
 | 
				
			|||||||
	CloneAddr    string `json:"clone_addr" binding:"Required"`
 | 
						CloneAddr    string `json:"clone_addr" binding:"Required"`
 | 
				
			||||||
	AuthUsername string `json:"auth_username"`
 | 
						AuthUsername string `json:"auth_username"`
 | 
				
			||||||
	AuthPassword string `json:"auth_password"`
 | 
						AuthPassword string `json:"auth_password"`
 | 
				
			||||||
 | 
						AuthToken    string `json:"auth_token"`
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	UID int `json:"uid" binding:"Required"`
 | 
						UID int `json:"uid" binding:"Required"`
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,7 @@ return_to_gitea = Return to Gitea
 | 
				
			|||||||
username = Username
 | 
					username = Username
 | 
				
			||||||
email = Email Address
 | 
					email = Email Address
 | 
				
			||||||
password = Password
 | 
					password = Password
 | 
				
			||||||
 | 
					access_token = Access Token
 | 
				
			||||||
re_type = Re-Type Password
 | 
					re_type = Re-Type Password
 | 
				
			||||||
captcha = CAPTCHA
 | 
					captcha = CAPTCHA
 | 
				
			||||||
twofa = Two-Factor Authentication
 | 
					twofa = Two-Factor Authentication
 | 
				
			||||||
@@ -707,9 +708,10 @@ form.name_reserved = The repository name '%s' is reserved.
 | 
				
			|||||||
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
 | 
					form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
need_auth = Clone Authorization
 | 
					need_auth = Clone Authorization
 | 
				
			||||||
migrate_type = Migration Type
 | 
					migrate_options = Migration Options
 | 
				
			||||||
migrate_type_helper = This repository will be a <span class="text blue">mirror</span>
 | 
					migrate_service = Migration Service
 | 
				
			||||||
migrate_type_helper_disabled = Your site administrator has disabled new mirrors.
 | 
					migrate_options_mirror_helper = This repository will be a <span class="text blue">mirror</span>
 | 
				
			||||||
 | 
					migrate_options_mirror_disabled = Your site administrator has disabled new mirrors.
 | 
				
			||||||
migrate_items = Migration Items
 | 
					migrate_items = Migration Items
 | 
				
			||||||
migrate_items_wiki = Wiki
 | 
					migrate_items_wiki = Wiki
 | 
				
			||||||
migrate_items_milestones = Milestones
 | 
					migrate_items_milestones = Milestones
 | 
				
			||||||
@@ -725,7 +727,7 @@ migrate.permission_denied = You are not allowed to import local repositories.
 | 
				
			|||||||
migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory."
 | 
					migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory."
 | 
				
			||||||
migrate.failed = Migration failed: %v
 | 
					migrate.failed = Migration failed: %v
 | 
				
			||||||
migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead.
 | 
					migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead.
 | 
				
			||||||
migrate.migrate_items_options = When migrating from github, input a username and migration options will be displayed.
 | 
					migrate.migrate_items_options = Authentication is needed to migrate items from a service that supports them.
 | 
				
			||||||
migrated_from = Migrated from <a href="%[1]s">%[2]s</a>
 | 
					migrated_from = Migrated from <a href="%[1]s">%[2]s</a>
 | 
				
			||||||
migrated_from_fake = Migrated From %[1]s
 | 
					migrated_from_fake = Migrated From %[1]s
 | 
				
			||||||
migrate.migrating = Migrating from <b>%s</b> ...
 | 
					migrate.migrating = Migrating from <b>%s</b> ...
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,6 @@ package repo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/url"
 | 
					 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
@@ -269,6 +268,9 @@ func Migrate(ctx *context.Context) {
 | 
				
			|||||||
	ctx.Data["pull_requests"] = ctx.Query("pull_requests") == "1"
 | 
						ctx.Data["pull_requests"] = ctx.Query("pull_requests") == "1"
 | 
				
			||||||
	ctx.Data["releases"] = ctx.Query("releases") == "1"
 | 
						ctx.Data["releases"] = ctx.Query("releases") == "1"
 | 
				
			||||||
	ctx.Data["LFSActive"] = setting.LFS.StartServer
 | 
						ctx.Data["LFSActive"] = setting.LFS.StartServer
 | 
				
			||||||
 | 
						// Plain git should be first
 | 
				
			||||||
 | 
						ctx.Data["service"] = structs.PlainGitService
 | 
				
			||||||
 | 
						ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctxUser := checkContextUser(ctx, ctx.QueryInt64("org"))
 | 
						ctxUser := checkContextUser(ctx, ctx.QueryInt64("org"))
 | 
				
			||||||
	if ctx.Written() {
 | 
						if ctx.Written() {
 | 
				
			||||||
@@ -316,6 +318,9 @@ func handleMigrateError(ctx *context.Context, owner *models.User, err error, nam
 | 
				
			|||||||
// MigratePost response for migrating from external git repository
 | 
					// MigratePost response for migrating from external git repository
 | 
				
			||||||
func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
 | 
					func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("new_migrate")
 | 
						ctx.Data["Title"] = ctx.Tr("new_migrate")
 | 
				
			||||||
 | 
						// Plain git should be first
 | 
				
			||||||
 | 
						ctx.Data["service"] = structs.PlainGitService
 | 
				
			||||||
 | 
						ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctxUser := checkContextUser(ctx, form.UID)
 | 
						ctxUser := checkContextUser(ctx, form.UID)
 | 
				
			||||||
	if ctx.Written() {
 | 
						if ctx.Written() {
 | 
				
			||||||
@@ -349,15 +354,9 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var gitServiceType = structs.PlainGitService
 | 
					 | 
				
			||||||
	u, err := url.Parse(form.CloneAddr)
 | 
					 | 
				
			||||||
	if err == nil && strings.EqualFold(u.Host, "github.com") {
 | 
					 | 
				
			||||||
		gitServiceType = structs.GithubService
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var opts = migrations.MigrateOptions{
 | 
						var opts = migrations.MigrateOptions{
 | 
				
			||||||
		OriginalURL:    form.CloneAddr,
 | 
							OriginalURL:    form.CloneAddr,
 | 
				
			||||||
		GitServiceType: gitServiceType,
 | 
							GitServiceType: structs.GitServiceType(form.Service),
 | 
				
			||||||
		CloneAddr:      remoteAddr,
 | 
							CloneAddr:      remoteAddr,
 | 
				
			||||||
		RepoName:       form.RepoName,
 | 
							RepoName:       form.RepoName,
 | 
				
			||||||
		Description:    form.Description,
 | 
							Description:    form.Description,
 | 
				
			||||||
@@ -365,6 +364,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
 | 
				
			|||||||
		Mirror:         form.Mirror && !setting.Repository.DisableMirrors,
 | 
							Mirror:         form.Mirror && !setting.Repository.DisableMirrors,
 | 
				
			||||||
		AuthUsername:   form.AuthUsername,
 | 
							AuthUsername:   form.AuthUsername,
 | 
				
			||||||
		AuthPassword:   form.AuthPassword,
 | 
							AuthPassword:   form.AuthPassword,
 | 
				
			||||||
 | 
							AuthToken:      form.AuthToken,
 | 
				
			||||||
		Wiki:           form.Wiki,
 | 
							Wiki:           form.Wiki,
 | 
				
			||||||
		Issues:         form.Issues,
 | 
							Issues:         form.Issues,
 | 
				
			||||||
		Milestones:     form.Milestones,
 | 
							Milestones:     form.Milestones,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,24 +14,83 @@
 | 
				
			|||||||
						<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
 | 
											<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
 | 
				
			||||||
						<span class="help">
 | 
											<span class="help">
 | 
				
			||||||
						{{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}}
 | 
											{{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}}
 | 
				
			||||||
						<br/>{{.i18n.Tr "repo.migrate.migrate_items_options"}}
 | 
					 | 
				
			||||||
						{{if .LFSActive}}<br/>{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}}
 | 
											{{if .LFSActive}}<br/>{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}}
 | 
				
			||||||
						</span>
 | 
											</span>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
					<div class="ui accordion optional field">
 | 
					
 | 
				
			||||||
						<div class="title {{if .Err_Auth}}text red active{{end}}">
 | 
										<div class="inline field">
 | 
				
			||||||
							<i class="icon dropdown"></i>
 | 
											<label>{{.i18n.Tr "repo.migrate_service"}}</label>
 | 
				
			||||||
							{{.i18n.Tr "repo.need_auth"}}
 | 
											<div class="ui selection dropdown">
 | 
				
			||||||
						</div>
 | 
												<input id="service_type" type="hidden" name="service" value="{{.service}}">
 | 
				
			||||||
						<div class="content {{if .Err_Auth}}active{{end}}">
 | 
												<div class="default text"></div>
 | 
				
			||||||
							<div class="inline field {{if .Err_Auth}}error{{end}}">
 | 
												<i class="dropdown icon"></i>
 | 
				
			||||||
								<label for="auth_username">{{.i18n.Tr "username"}}</label>
 | 
												<div class="menu">
 | 
				
			||||||
								<input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}>
 | 
													{{range .Services}}
 | 
				
			||||||
 | 
														<div id="service-{{.}}" class="item" data-token="{{.TokenAuth}}" data-value="{{.}}">{{.Title}}</div>
 | 
				
			||||||
 | 
													{{end}}
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
							<input class="fake" type="password">
 | 
											</div>
 | 
				
			||||||
							<div class="inline field {{if .Err_Auth}}error{{end}}">
 | 
										</div>
 | 
				
			||||||
								<label for="auth_password">{{.i18n.Tr "password"}}</label>
 | 
										<div class="inline field {{if .Err_Auth}}error{{end}}">
 | 
				
			||||||
								<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}">
 | 
											<label for="auth_username">{{.i18n.Tr "username"}}</label>
 | 
				
			||||||
 | 
											<input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<input class="fake" type="password">
 | 
				
			||||||
 | 
										<div class="inline field {{if .Err_Auth}}error{{end}}">
 | 
				
			||||||
 | 
											<label for="auth_password">{{.i18n.Tr "password"}}</label>
 | 
				
			||||||
 | 
											<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}">
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="inline field {{if .Err_Auth}}error{{end}}">
 | 
				
			||||||
 | 
											<label for="auth_token">{{.i18n.Tr "access_token"}}</label>
 | 
				
			||||||
 | 
											<input id="auth_token" name="auth_token" value="{{.auth_token}}" {{if not .auth_token}}data-need-clear="true"{{end}}>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										<div class="inline field">
 | 
				
			||||||
 | 
											<label>{{.i18n.Tr "repo.migrate_options"}}</label>
 | 
				
			||||||
 | 
											<div class="ui checkbox">
 | 
				
			||||||
 | 
												{{if .DisableMirrors}}
 | 
				
			||||||
 | 
													<input id="mirror" name="mirror" type="checkbox" readonly>
 | 
				
			||||||
 | 
													<label>{{.i18n.Tr "repo.migrate_options_mirror_disabled"}}</label>
 | 
				
			||||||
 | 
												{{else}}
 | 
				
			||||||
 | 
													<input id="mirror" name="mirror" type="checkbox" {{if .mirror}}checked{{end}}>
 | 
				
			||||||
 | 
													<label>{{.i18n.Tr "repo.migrate_options_mirror_helper" | Safe}}</label>
 | 
				
			||||||
 | 
												{{end}}
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										<span class="help">{{.i18n.Tr "repo.migrate.migrate_items_options"}}</span>
 | 
				
			||||||
 | 
										<div id="migrate_items">
 | 
				
			||||||
 | 
											<div class="inline field">
 | 
				
			||||||
 | 
												<label>{{.i18n.Tr "repo.migrate_items"}}</label>
 | 
				
			||||||
 | 
												<div class="ui checkbox">
 | 
				
			||||||
 | 
													<input name="wiki" type="checkbox" {{if .wiki}}checked{{end}}>
 | 
				
			||||||
 | 
													<label>{{.i18n.Tr "repo.migrate_items_wiki" | Safe}}</label>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
												<div class="ui checkbox">
 | 
				
			||||||
 | 
													<input name="milestones" type="checkbox" {{if .milestones}}checked{{end}}>
 | 
				
			||||||
 | 
													<label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
											<div class="inline field">
 | 
				
			||||||
 | 
												<label></label>
 | 
				
			||||||
 | 
												<div class="ui checkbox">
 | 
				
			||||||
 | 
													<input name="labels" type="checkbox" {{if .labels}}checked{{end}}>
 | 
				
			||||||
 | 
													<label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
												<div class="ui checkbox">
 | 
				
			||||||
 | 
													<input name="issues" type="checkbox" {{if .issues}}checked{{end}}>
 | 
				
			||||||
 | 
													<label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
											<div class="inline field">
 | 
				
			||||||
 | 
												<label></label>
 | 
				
			||||||
 | 
												<div class="ui checkbox">
 | 
				
			||||||
 | 
													<input name="pull_requests" type="checkbox" {{if .pull_requests}}checked{{end}}>
 | 
				
			||||||
 | 
													<label>{{.i18n.Tr "repo.migrate_items_pullrequests" | Safe}}</label>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
												<div class="ui checkbox">
 | 
				
			||||||
 | 
													<input name="releases" type="checkbox" {{if .releases}}checked{{end}}>
 | 
				
			||||||
 | 
													<label>{{.i18n.Tr "repo.migrate_items_releases" | Safe}}</label>
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
@@ -78,53 +137,6 @@
 | 
				
			|||||||
							{{end}}
 | 
												{{end}}
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
					<div class="inline field">
 | 
					 | 
				
			||||||
						<label>{{.i18n.Tr "repo.migrate_type"}}</label>
 | 
					 | 
				
			||||||
						<div class="ui checkbox">
 | 
					 | 
				
			||||||
							{{if .DisableMirrors}}
 | 
					 | 
				
			||||||
								<input id="mirror" name="mirror" type="checkbox" readonly>
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.migrate_type_helper_disabled"}}</label>
 | 
					 | 
				
			||||||
							{{else}}
 | 
					 | 
				
			||||||
								<input id="mirror" name="mirror" type="checkbox" {{if .mirror}}checked{{end}}>
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.migrate_type_helper" | Safe}}</label>
 | 
					 | 
				
			||||||
							{{end}}
 | 
					 | 
				
			||||||
						</div>
 | 
					 | 
				
			||||||
					</div>
 | 
					 | 
				
			||||||
					<div id="migrate_items" class="ui field">
 | 
					 | 
				
			||||||
						<div class="inline field">
 | 
					 | 
				
			||||||
							<label>{{.i18n.Tr "repo.migrate_items"}}</label>
 | 
					 | 
				
			||||||
							<div class="ui checkbox">
 | 
					 | 
				
			||||||
								<input name="wiki" type="checkbox" {{if .wiki}}checked{{end}}>
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.migrate_items_wiki" | Safe}}</label>
 | 
					 | 
				
			||||||
							</div>
 | 
					 | 
				
			||||||
							<div class="ui checkbox">
 | 
					 | 
				
			||||||
								<input name="milestones" type="checkbox" {{if .milestones}}checked{{end}}>
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label>
 | 
					 | 
				
			||||||
							</div>
 | 
					 | 
				
			||||||
						</div>
 | 
					 | 
				
			||||||
						<div class="inline field">
 | 
					 | 
				
			||||||
							<label></label>
 | 
					 | 
				
			||||||
							<div class="ui checkbox">
 | 
					 | 
				
			||||||
								<input name="labels" type="checkbox" {{if .labels}}checked{{end}}>
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label>
 | 
					 | 
				
			||||||
							</div>
 | 
					 | 
				
			||||||
							<div class="ui checkbox">
 | 
					 | 
				
			||||||
								<input name="issues" type="checkbox" {{if .issues}}checked{{end}}>
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label>
 | 
					 | 
				
			||||||
							</div>
 | 
					 | 
				
			||||||
						</div>
 | 
					 | 
				
			||||||
						<div class="inline field">
 | 
					 | 
				
			||||||
							<label></label>
 | 
					 | 
				
			||||||
							<div class="ui checkbox">
 | 
					 | 
				
			||||||
								<input name="pull_requests" type="checkbox" {{if .pull_requests}}checked{{end}}>
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.migrate_items_pullrequests" | Safe}}</label>
 | 
					 | 
				
			||||||
							</div>
 | 
					 | 
				
			||||||
							<div class="ui checkbox">
 | 
					 | 
				
			||||||
								<input name="releases" type="checkbox" {{if .releases}}checked{{end}}>
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.migrate_items_releases" | Safe}}</label>
 | 
					 | 
				
			||||||
							</div>
 | 
					 | 
				
			||||||
						</div>
 | 
					 | 
				
			||||||
					</div>
 | 
					 | 
				
			||||||
					<div class="inline field {{if .Err_Description}}error{{end}}">
 | 
										<div class="inline field {{if .Err_Description}}error{{end}}">
 | 
				
			||||||
						<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
 | 
											<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
 | 
				
			||||||
						<textarea id="description" name="description">{{.description}}</textarea>
 | 
											<textarea id="description" name="description">{{.description}}</textarea>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13446,6 +13446,10 @@
 | 
				
			|||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "AuthPassword"
 | 
					          "x-go-name": "AuthPassword"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "auth_token": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "AuthToken"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "auth_username": {
 | 
					        "auth_username": {
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "AuthUsername"
 | 
					          "x-go-name": "AuthUsername"
 | 
				
			||||||
@@ -13490,6 +13494,11 @@
 | 
				
			|||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "RepoName"
 | 
					          "x-go-name": "RepoName"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "service": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64",
 | 
				
			||||||
 | 
					          "x-go-name": "Service"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "uid": {
 | 
					        "uid": {
 | 
				
			||||||
          "type": "integer",
 | 
					          "type": "integer",
 | 
				
			||||||
          "format": "int64",
 | 
					          "format": "int64",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										53
									
								
								web_src/js/features/migration.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								web_src/js/features/migration.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					const $service = $('#service_type');
 | 
				
			||||||
 | 
					const $user = $('#auth_username');
 | 
				
			||||||
 | 
					const $pass = $('#auth_password');
 | 
				
			||||||
 | 
					const $token = $('#auth_token');
 | 
				
			||||||
 | 
					const $items = $('#migrate_items').find('.field');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function initMigration() {
 | 
				
			||||||
 | 
					  checkAuth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $service.on('change', checkAuth);
 | 
				
			||||||
 | 
					  $user.on('keyup', () => {checkItems(false)});
 | 
				
			||||||
 | 
					  $pass.on('keyup', () => {checkItems(false)});
 | 
				
			||||||
 | 
					  $token.on('keyup', () => {checkItems(true)});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const $cloneAddr = $('#clone_addr');
 | 
				
			||||||
 | 
					  $cloneAddr.on('change', () => {
 | 
				
			||||||
 | 
					    const $repoName = $('#repo_name');
 | 
				
			||||||
 | 
					    if ($cloneAddr.val().length > 0 && $repoName.val().length === 0) { // Only modify if repo_name input is blank
 | 
				
			||||||
 | 
					      $repoName.val($cloneAddr.val().match(/^(.*\/)?((.+?)(\.git)?)$/)[3]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function checkAuth() {
 | 
				
			||||||
 | 
					  const serviceType = $service.val();
 | 
				
			||||||
 | 
					  const tokenAuth = $(`#service-${serviceType}`).data('token');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (tokenAuth) {
 | 
				
			||||||
 | 
					    $user.parent().addClass('disabled');
 | 
				
			||||||
 | 
					    $pass.parent().addClass('disabled');
 | 
				
			||||||
 | 
					    $token.parent().removeClass('disabled');
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    $user.parent().removeClass('disabled');
 | 
				
			||||||
 | 
					    $pass.parent().removeClass('disabled');
 | 
				
			||||||
 | 
					    $token.parent().addClass('disabled');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  checkItems(tokenAuth);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function checkItems(tokenAuth) {
 | 
				
			||||||
 | 
					  let enableItems;
 | 
				
			||||||
 | 
					  if (tokenAuth) {
 | 
				
			||||||
 | 
					    enableItems = $token.val() !== '';
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    enableItems = $user.val() !== '' || $pass.val() !== '';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (enableItems && $service.val() > 1) {
 | 
				
			||||||
 | 
					    $items.removeClass('disabled');
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    $items.addClass('disabled');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,6 +8,7 @@ import {htmlEscape} from 'escape-goat';
 | 
				
			|||||||
import 'jquery.are-you-sure';
 | 
					import 'jquery.are-you-sure';
 | 
				
			||||||
import './vendor/semanticdropdown.js';
 | 
					import './vendor/semanticdropdown.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import initMigration from './features/migration.js';
 | 
				
			||||||
import initContextPopups from './features/contextpopup.js';
 | 
					import initContextPopups from './features/contextpopup.js';
 | 
				
			||||||
import initGitGraph from './features/gitgraph.js';
 | 
					import initGitGraph from './features/gitgraph.js';
 | 
				
			||||||
import initClipboard from './features/clipboard.js';
 | 
					import initClipboard from './features/clipboard.js';
 | 
				
			||||||
@@ -1155,25 +1156,6 @@ async function initRepository() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function initMigration() {
 | 
					 | 
				
			||||||
  const toggleMigrations = function () {
 | 
					 | 
				
			||||||
    const authUserName = $('#auth_username').val();
 | 
					 | 
				
			||||||
    const cloneAddr = $('#clone_addr').val();
 | 
					 | 
				
			||||||
    if (!$('#mirror').is(':checked') && (authUserName && authUserName.length > 0) &&
 | 
					 | 
				
			||||||
        (cloneAddr !== undefined && (cloneAddr.startsWith('https://github.com') || cloneAddr.startsWith('http://github.com') || cloneAddr.startsWith('http://gitlab.com') || cloneAddr.startsWith('https://gitlab.com')))) {
 | 
					 | 
				
			||||||
      $('#migrate_items').show();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      $('#migrate_items').hide();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  toggleMigrations();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  $('#clone_addr').on('input', toggleMigrations);
 | 
					 | 
				
			||||||
  $('#auth_username').on('input', toggleMigrations);
 | 
					 | 
				
			||||||
  $('#mirror').on('change', toggleMigrations);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function initPullRequestReview() {
 | 
					function initPullRequestReview() {
 | 
				
			||||||
  $('.show-outdated').on('click', function (e) {
 | 
					  $('.show-outdated').on('click', function (e) {
 | 
				
			||||||
    e.preventDefault();
 | 
					    e.preventDefault();
 | 
				
			||||||
@@ -2477,14 +2459,6 @@ $(document).ready(async () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const $cloneAddr = $('#clone_addr');
 | 
					 | 
				
			||||||
  $cloneAddr.on('change', () => {
 | 
					 | 
				
			||||||
    const $repoName = $('#repo_name');
 | 
					 | 
				
			||||||
    if ($cloneAddr.val().length > 0 && $repoName.val().length === 0) { // Only modify if repo_name input is blank
 | 
					 | 
				
			||||||
      $repoName.val($cloneAddr.val().match(/^(.*\/)?((.+?)(\.git)?)$/)[3]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // parallel init of async loaded features
 | 
					  // parallel init of async loaded features
 | 
				
			||||||
  await Promise.all([
 | 
					  await Promise.all([
 | 
				
			||||||
    attachTribute(document.querySelectorAll('#content, .emoji-input')),
 | 
					    attachTribute(document.querySelectorAll('#content, .emoji-input')),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -180,6 +180,11 @@
 | 
				
			|||||||
        text-align: center;
 | 
					        text-align: center;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .selection.dropdown {
 | 
				
			||||||
 | 
					        vertical-align: middle;
 | 
				
			||||||
 | 
					        width: 50% !important;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      @media only screen and (max-width: 768px) {
 | 
					      @media only screen and (max-width: 768px) {
 | 
				
			||||||
        label,
 | 
					        label,
 | 
				
			||||||
        input,
 | 
					        input,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user