mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00: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,15 +83,7 @@ func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *Gith | |||||||
| 		repoName:  repoName, | 		repoName:  repoName, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var client *http.Client | 	client := &http.Client{ | ||||||
| 	if userName != "" { |  | ||||||
| 		if password == "" { |  | ||||||
| 			ts := oauth2.StaticTokenSource( |  | ||||||
| 				&oauth2.Token{AccessToken: userName}, |  | ||||||
| 			) |  | ||||||
| 			client = oauth2.NewClient(downloader.ctx, ts) |  | ||||||
| 		} else { |  | ||||||
| 			client = &http.Client{ |  | ||||||
| 		Transport: &http.Transport{ | 		Transport: &http.Transport{ | ||||||
| 			Proxy: func(req *http.Request) (*url.URL, error) { | 			Proxy: func(req *http.Request) (*url.URL, error) { | ||||||
| 				req.SetBasicAuth(userName, password) | 				req.SetBasicAuth(userName, password) | ||||||
| @@ -106,7 +91,11 @@ func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *Gith | |||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 		} | 	if token != "" { | ||||||
|  | 		ts := oauth2.StaticTokenSource( | ||||||
|  | 			&oauth2.Token{AccessToken: token}, | ||||||
|  | 		) | ||||||
|  | 		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,16 +73,14 @@ 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 { | ||||||
| 		log.Trace("Error logging into gitlab: %v", err) | 		log.Trace("Error logging into gitlab: %v", err) | ||||||
| @@ -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,16 +14,23 @@ | |||||||
| 						<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"> | ||||||
|  | 							<input id="service_type" type="hidden" name="service" value="{{.service}}"> | ||||||
|  | 							<div class="default text"></div> | ||||||
|  | 							<i class="dropdown icon"></i> | ||||||
|  | 							<div class="menu"> | ||||||
|  | 								{{range .Services}} | ||||||
|  | 									<div id="service-{{.}}" class="item" data-token="{{.TokenAuth}}" data-value="{{.}}">{{.Title}}</div> | ||||||
|  | 								{{end}} | ||||||
|  | 							</div> | ||||||
|  | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| 						<div class="content {{if .Err_Auth}}active{{end}}"> |  | ||||||
| 					<div class="inline field {{if .Err_Auth}}error{{end}}"> | 					<div class="inline field {{if .Err_Auth}}error{{end}}"> | ||||||
| 						<label for="auth_username">{{.i18n.Tr "username"}}</label> | 						<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}}> | 						<input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}> | ||||||
| @@ -33,6 +40,58 @@ | |||||||
| 						<label for="auth_password">{{.i18n.Tr "password"}}</label> | 						<label for="auth_password">{{.i18n.Tr "password"}}</label> | ||||||
| 						<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}"> | 						<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}"> | ||||||
| 					</div> | 					</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> | ||||||
|  |  | ||||||
| @@ -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
	 John Olheiser
					John Olheiser