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:
parent
ed2f6e137b
commit
211321fb93
20 changed files with 273 additions and 181 deletions
|
@ -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 a new issue