Skip to content

Add a migrate service type switch page #12697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Sep 9, 2020
8 changes: 5 additions & 3 deletions integrations/repo_migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
package integrations

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"

"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)

func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName string) *httptest.ResponseRecorder {
req := NewRequest(t, "GET", "/repo/migrate")
req := NewRequest(t, "GET", fmt.Sprintf("/repo/migrate?service_type=%d", structs.PlainGitService)) // render plain git migration page
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)

Expand All @@ -28,8 +30,8 @@ func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName str
"clone_addr": cloneAddr,
"uid": uid,
"repo_name": repoName,
},
)
"service": fmt.Sprintf("%d", structs.PlainGitService),
})
resp = session.MakeRequest(t, req, http.StatusFound)

return resp
Expand Down
13 changes: 2 additions & 11 deletions modules/structs/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package structs

import (
"strings"
"time"
)

Expand Down Expand Up @@ -205,17 +206,7 @@ const (
// Name represents the service type's name
// WARNNING: the name have to be equal to that on goth's library
func (gt GitServiceType) Name() string {
switch gt {
case GithubService:
return "github"
case GiteaService:
return "gitea"
case GitlabService:
return "gitlab"
case GogsService:
return "gogs"
}
return ""
return strings.ToLower(gt.Title())
}

// Title represents the service type's proper title
Expand Down
7 changes: 6 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,7 @@ migrate_items_milestones = Milestones
migrate_items_labels = Labels
migrate_items_issues = Issues
migrate_items_pullrequests = Pull Requests
migrate_items_merge_requests = Merge Requests
migrate_items_releases = Releases
migrate_repo = Migrate Repository
migrate.clone_address = Migrate / Clone From URL
Expand All @@ -729,11 +730,15 @@ 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.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.migrate_items_options = Authentication is needed to migrate items from a service that supports them.
migrate.migrate_items_options = Access Token is required to migrate additional items
migrated_from = Migrated from <a href="%[1]s">%[2]s</a>
migrated_from_fake = Migrated From %[1]s
migrate.migrate = Migrate From %s
migrate.migrating = Migrating from <b>%s</b> ...
migrate.migrating_failed = Migrating from <b>%s</b> failed.
migrate.github.description = Migrating data from Github.com or Github Enterprise.
migrate.git.description = Migrating or Mirroring git data from Git services
migrate.gitlab.description = Migrating data from GitLab.com or Self-Hosted gitlab server.

mirror_from = mirror of
forked_from = forked from
Expand Down
1 change: 1 addition & 0 deletions public/img/svg/gitea-git.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/img/svg/gitea-github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/img/svg/gitea-gitlab.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
173 changes: 173 additions & 0 deletions routers/repo/migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package repo

import (
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/task"
"code.gitea.io/gitea/modules/util"
)

const (
tplMigrate base.TplName = "repo/migrate/migrate"
)

// Migrate render migration of repository page
func Migrate(ctx *context.Context) {
ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)
serviceType := ctx.QueryInt("service_type")
if serviceType == 0 {
ctx.HTML(200, tplMigrate)
return
}

ctx.Data["Title"] = ctx.Tr("new_migrate")
ctx.Data["private"] = getRepoPrivate(ctx)
ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
ctx.Data["DisableMirrors"] = setting.Repository.DisableMirrors
ctx.Data["mirror"] = ctx.Query("mirror") == "1"
ctx.Data["wiki"] = ctx.Query("wiki") == "1"
ctx.Data["milestones"] = ctx.Query("milestones") == "1"
ctx.Data["labels"] = ctx.Query("labels") == "1"
ctx.Data["issues"] = ctx.Query("issues") == "1"
ctx.Data["pull_requests"] = ctx.Query("pull_requests") == "1"
ctx.Data["releases"] = ctx.Query("releases") == "1"
ctx.Data["LFSActive"] = setting.LFS.StartServer
// Plain git should be first
ctx.Data["service"] = structs.GitServiceType(serviceType)

ctxUser := checkContextUser(ctx, ctx.QueryInt64("org"))
if ctx.Written() {
return
}
ctx.Data["ContextUser"] = ctxUser

ctx.HTML(200, base.TplName("repo/migrate/"+structs.GitServiceType(serviceType).Name()))
}

func handleMigrateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form *auth.MigrateRepoForm) {
switch {
case migrations.IsRateLimitError(err):
ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tpl, form)
case migrations.IsTwoFactorAuthError(err):
ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form)
case models.IsErrReachLimitOfRepo(err):
ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form)
case models.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
case models.IsErrNameReserved(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form)
case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form)
default:
remoteAddr, _ := form.ParseRemoteAddr(owner)
err = util.URLSanitizedError(err, remoteAddr)
if strings.Contains(err.Error(), "Authentication failed") ||
strings.Contains(err.Error(), "Bad credentials") ||
strings.Contains(err.Error(), "could not read Username") {
ctx.Data["Err_Auth"] = true
ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tpl, form)
} else if strings.Contains(err.Error(), "fatal:") {
ctx.Data["Err_CloneAddr"] = true
ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tpl, form)
} else {
ctx.ServerError(name, err)
}
}
}

// MigratePost response for migrating from external git repository
func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
ctx.Data["Title"] = ctx.Tr("new_migrate")
// Plain git should be first
ctx.Data["service"] = form.Service
ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)

ctxUser := checkContextUser(ctx, form.UID)
if ctx.Written() {
return
}
ctx.Data["ContextUser"] = ctxUser

if ctx.HasError() {
ctx.HTML(200, tplMigrate)
return
}

remoteAddr, err := form.ParseRemoteAddr(ctx.User)
if err != nil {
if models.IsErrInvalidCloneAddr(err) {
ctx.Data["Err_CloneAddr"] = true
addrErr := err.(models.ErrInvalidCloneAddr)
switch {
case addrErr.IsURLError:
ctx.RenderWithErr(ctx.Tr("form.url_error"), tplMigrate, &form)
case addrErr.IsPermissionDenied:
ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplMigrate, &form)
case addrErr.IsInvalidPath:
ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplMigrate, &form)
default:
ctx.ServerError("Unknown error", err)
}
} else {
ctx.ServerError("ParseRemoteAddr", err)
}
return
}

var opts = migrations.MigrateOptions{
OriginalURL: form.CloneAddr,
GitServiceType: structs.GitServiceType(form.Service),
CloneAddr: remoteAddr,
RepoName: form.RepoName,
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror && !setting.Repository.DisableMirrors,
AuthUsername: form.AuthUsername,
AuthPassword: form.AuthPassword,
AuthToken: form.AuthToken,
Wiki: form.Wiki,
Issues: form.Issues,
Milestones: form.Milestones,
Labels: form.Labels,
Comments: form.Issues || form.PullRequests,
PullRequests: form.PullRequests,
Releases: form.Releases,
}
if opts.Mirror {
opts.Issues = false
opts.Milestones = false
opts.Labels = false
opts.Comments = false
opts.PullRequests = false
opts.Releases = false
}

err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName)
if err != nil {
handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form)
return
}

err = task.MigrateRepository(ctx.User, ctxUser, opts)
if err == nil {
ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + opts.RepoName)
return
}

handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form)
}
Loading