summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2020-09-10 02:29:10 +0800
committerGitHub <noreply@github.com>2020-09-09 14:29:10 -0400
commite153cf07c34b1497906348df84a556851693c77f (patch)
tree79c642c2d04a907b0700c369108825d0b53085b9
parent6483dbc8ec780dda22e21b605d1dc409826baff1 (diff)
downloadgitea-e153cf07c34b1497906348df84a556851693c77f.tar.gz
gitea-e153cf07c34b1497906348df84a556851693c77f.zip
Add a migrate service type switch page (#12697)
* Add a migrat service type switch page * Improve translations * remove images * Fix images * remove extra create repo button on dashboard * Follow reviewers' opinions * Fix frontend lint * Remove wrong submit file * Fix tests * Adjust the size of image * Apply suggestions from code review Co-authored-by: 赵智超 <1012112796@qq.com> * Remove username and password from migration of github/gitlab * Improve docs * Improve interface docs Co-authored-by: 赵智超 <1012112796@qq.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
-rw-r--r--integrations/repo_migrate_test.go8
-rw-r--r--modules/structs/repo.go13
-rw-r--r--options/locale/locale_en-US.ini7
-rw-r--r--public/img/svg/gitea-git.svg1
-rw-r--r--public/img/svg/gitea-github.svg1
-rw-r--r--public/img/svg/gitea-gitlab.svg1
-rw-r--r--routers/repo/migrate.go173
-rw-r--r--routers/repo/repo.go150
-rw-r--r--routers/repo/view.go2
-rw-r--r--templates/repo/migrate/git.tmpl103
-rw-r--r--templates/repo/migrate/github.tmpl (renamed from templates/repo/migrate.tmpl)26
-rw-r--r--templates/repo/migrate/gitlab.tmpl137
-rw-r--r--templates/repo/migrate/migrate.tmpl23
-rw-r--r--templates/repo/migrate/migrating.tmpl (renamed from templates/repo/migrating.tmpl)0
-rw-r--r--templates/user/dashboard/repolist.tmpl8
-rw-r--r--web_src/js/features/migration.js14
-rw-r--r--web_src/less/_repository.less4
-rw-r--r--web_src/less/themes/theme-arc-green.less16
-rw-r--r--web_src/svg/gitea-git.svg1
-rw-r--r--web_src/svg/gitea-github.svg1
-rw-r--r--web_src/svg/gitea-gitlab.svg1
21 files changed, 481 insertions, 209 deletions
diff --git a/integrations/repo_migrate_test.go b/integrations/repo_migrate_test.go
index a9970655ef..5a02b4ba03 100644
--- a/integrations/repo_migrate_test.go
+++ b/integrations/repo_migrate_test.go
@@ -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)
@@ -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
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index 808d2ffbc8..f751c00789 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -5,6 +5,7 @@
package structs
import (
+ "strings"
"time"
)
@@ -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
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index ce35375052..80308214dd 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -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
@@ -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
diff --git a/public/img/svg/gitea-git.svg b/public/img/svg/gitea-git.svg
new file mode 100644
index 0000000000..c716b1b283
--- /dev/null
+++ b/public/img/svg/gitea-git.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 48 48" class="svg gitea-git" width="16" height="16" aria-hidden="true"><path fill="#F4511E" d="M42.2 22.1L25.9 5.8c-.5-.5-1.2-.8-1.9-.8s-1.4.3-1.9.8l-3.5 3.5 4.1 4.1c.4-.2.8-.3 1.3-.3 1.7 0 3 1.3 3 3 0 .5-.1.9-.3 1.3l4 4c.4-.2.8-.3 1.3-.3 1.7 0 3 1.3 3 3s-1.3 3-3 3-3-1.3-3-3c0-.5.1-.9.3-1.3l-4-4c-.1 0-.2.1-.3.1v10.4c1.2.4 2 1.5 2 2.8 0 1.7-1.3 3-3 3s-3-1.3-3-3c0-1.3.8-2.4 2-2.8V18.8c-1.2-.4-2-1.5-2-2.8 0-.5.1-.9.3-1.3l-4.1-4.1L5.8 22.1c-.5.5-.8 1.2-.8 1.9s.3 1.4.8 1.9l16.3 16.3c.5.5 1.2.8 1.9.8s1.4-.3 1.9-.8l16.3-16.3c.5-.5.8-1.2.8-1.9s-.3-1.4-.8-1.9z"/></svg> \ No newline at end of file
diff --git a/public/img/svg/gitea-github.svg b/public/img/svg/gitea-github.svg
new file mode 100644
index 0000000000..0ed1d44b55
--- /dev/null
+++ b/public/img/svg/gitea-github.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 64 64" class="svg gitea-github" width="16" height="16" aria-hidden="true"><linearGradient id="a" x1="30.999" x2="30.999" y1="16" y2="55.342" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#6dc7ff"/><stop offset="1" stop-color="#e6abff"/></linearGradient><path fill="url(#a)" d="M25.008 56.007c-.003-.368-.006-1.962-.009-3.454l-.003-1.55c-6.729.915-8.358-3.78-8.376-3.83-.934-2.368-2.211-3.045-2.266-3.073l-.124-.072c-.463-.316-1.691-1.157-1.342-2.263.315-.997 1.536-1.1 2.091-1.082 3.074.215 4.63 2.978 4.694 3.095 1.569 2.689 3.964 2.411 5.509 1.844.144-.688.367-1.32.659-1.878-4.956-.879-10.571-3.515-10.571-13.104 0-2.633.82-4.96 2.441-6.929-.362-1.206-.774-3.666.446-6.765l.174-.442.452-.144c.416-.137 2.688-.624 7.359 2.433a24.959 24.959 0 016.074-.759c2.115.01 4.158.265 6.09.759 4.667-3.058 6.934-2.565 7.351-2.433l.451.145.174.44c1.225 3.098.813 5.559.451 6.766 1.618 1.963 2.438 4.291 2.438 6.929 0 9.591-5.621 12.219-10.588 13.087.563 1.065.868 2.402.868 3.878 0 1.683-.007 7.204-.015 8.402l-2-.014c.008-1.196.015-6.708.015-8.389 0-2.442-.943-3.522-1.35-3.874l-1.73-1.497 2.274-.253c5.205-.578 10.525-2.379 10.525-11.341 0-2.33-.777-4.361-2.31-6.036l-.43-.469.242-.587c.166-.401.894-2.442-.043-5.291-.758.045-2.568.402-5.584 2.447l-.384.259-.445-.123c-1.863-.518-3.938-.796-6.001-.806-2.052.01-4.124.288-5.984.806l-.445.123-.383-.259c-3.019-2.044-4.833-2.404-5.594-2.449-.935 2.851-.206 4.892-.04 5.293l.242.587-.429.469c-1.536 1.681-2.314 3.712-2.314 6.036 0 8.958 5.31 10.77 10.504 11.361l2.252.256-1.708 1.49c-.372.325-1.03 1.112-1.254 2.727l-.075.549-.506.227c-1.321.592-5.839 2.162-8.548-2.485-.015-.025-.544-.945-1.502-1.557.646.639 1.433 1.673 2.068 3.287.066.19 1.357 3.622 7.28 2.339l1.206-.262.012 3.978c.003 1.487.006 3.076.009 3.444l-1.998.014z"/><linearGradient id="b" x1="32" x2="32" y1="5" y2="59.167" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#1a6dff"/><stop offset="1" stop-color="#c822ff"/></linearGradient><path fill="url(#b)" d="M32 58C17.663 58 6 46.337 6 32S17.663 6 32 6s26 11.663 26 26-11.663 26-26 26zm0-50C18.767 8 8 18.767 8 32s10.767 24 24 24 24-10.767 24-24S45.233 8 32 8z"/></svg> \ No newline at end of file
diff --git a/public/img/svg/gitea-gitlab.svg b/public/img/svg/gitea-gitlab.svg
new file mode 100644
index 0000000000..a72a378234
--- /dev/null
+++ b/public/img/svg/gitea-gitlab.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 48 48" class="svg gitea-gitlab" width="16" height="16" aria-hidden="true"><path fill="#e53935" d="M24 43l-8-23h16z"/><path fill="#ff7043" d="M24 43l18-23H32z"/><path fill="#e53935" d="M37 5l5 15H32z"/><path fill="#ffa726" d="M24 43l18-23 3 8z"/><path fill="#ff7043" d="M24 43L6 20h10z"/><path fill="#e53935" d="M11 5L6 20h10z"/><path fill="#ffa726" d="M24 43L6 20l-3 8z"/></svg> \ No newline at end of file
diff --git a/routers/repo/migrate.go b/routers/repo/migrate.go
new file mode 100644
index 0000000000..497f2ce36f
--- /dev/null
+++ b/routers/repo/migrate.go
@@ -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)
+}
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index 5fc081a6f6..9a4fbfa130 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -17,19 +17,14 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
- "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"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/unknwon/com"
)
const (
- tplCreate base.TplName = "repo/create"
- tplMigrate base.TplName = "repo/migrate"
+ tplCreate base.TplName = "repo/create"
)
// MustBeNotEmpty render when a repo is a empty git dir
@@ -254,149 +249,6 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form)
}
-// Migrate render migration of repository page
-func Migrate(ctx *context.Context) {
- 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.PlainGitService
- ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)
-
- ctxUser := checkContextUser(ctx, ctx.QueryInt64("org"))
- if ctx.Written() {
- return
- }
- ctx.Data["ContextUser"] = ctxUser
-
- ctx.HTML(200, tplMigrate)
-}
-
-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"] = structs.PlainGitService
- 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: true,
- 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)
-}
-
// Action response for actions to a repository
func Action(ctx *context.Context) {
var err error
diff --git a/routers/repo/view.go b/routers/repo/view.go
index a05c0b1366..3e2a57415d 100644
--- a/routers/repo/view.go
+++ b/routers/repo/view.go
@@ -34,7 +34,7 @@ const (
tplRepoHome base.TplName = "repo/home"
tplWatchers base.TplName = "repo/watchers"
tplForks base.TplName = "repo/forks"
- tplMigrating base.TplName = "repo/migrating"
+ tplMigrating base.TplName = "repo/migrate/migrating"
)
type namedBlob struct {
diff --git a/templates/repo/migrate/git.tmpl b/templates/repo/migrate/git.tmpl
new file mode 100644
index 0000000000..34a1c7bd0d
--- /dev/null
+++ b/templates/repo/migrate/git.tmpl
@@ -0,0 +1,103 @@
+{{template "base/head" .}}
+<div class="repository new migrate">
+ <div class="ui middle very relaxed page grid">
+ <div class="column">
+ <form class="ui form" action="{{.Link}}" method="post">
+ {{.CsrfTokenHtml}}
+ <h3 class="ui top attached header">
+ {{.i18n.Tr "repo.migrate.migrate" .service.Title}}
+ <input id="service_type" type="hidden" name="service" value="{{.service}}">
+ </h3>
+ <div class="ui attached segment">
+ {{template "base/alert" .}}
+ <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
+ <label for="clone_addr">{{.i18n.Tr "repo.migrate.clone_address"}}</label>
+ <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
+ <span class="help">
+ {{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}}
+ {{if .LFSActive}}<br/>{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}}
+ </span>
+ </div>
+ <div class="inline field {{if .Err_Auth}}error{{end}}">
+ <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">
+ <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>
+
+ <div class="ui divider"></div>
+
+ <div class="inline required field {{if .Err_Owner}}error{{end}}">
+ <label>{{.i18n.Tr "repo.owner"}}</label>
+ <div class="ui selection owner dropdown">
+ <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required>
+ <span class="text" title="{{.ContextUser.Name}}">
+ <img class="ui mini image" src="{{.ContextUser.RelAvatarLink}}">
+ {{.ContextUser.ShortName 20}}
+ </span>
+ <i class="dropdown icon"></i>
+ <div class="menu" title="{{.SignedUser.Name}}">
+ <div class="item" data-value="{{.SignedUser.ID}}">
+ <img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}">
+ {{.SignedUser.ShortName 20}}
+ </div>
+ {{range .Orgs}}
+ <div class="item" data-value="{{.ID}}" title="{{.Name}}">
+ <img class="ui mini image" src="{{.RelAvatarLink}}">
+ {{.ShortName 20}}
+ </div>
+ {{end}}
+ </div>
+ </div>
+ </div>
+
+ <div class="inline required field {{if .Err_RepoName}}error{{end}}">
+ <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
+ <input id="repo_name" name="repo_name" value="{{.repo_name}}" required>
+ </div>
+ <div class="inline field">
+ <label>{{.i18n.Tr "repo.visibility"}}</label>
+ <div class="ui checkbox">
+ {{if .IsForcedPrivate}}
+ <input name="private" type="checkbox" checked readonly>
+ <label>{{.i18n.Tr "repo.visibility_helper_forced" | Safe}}</label>
+ {{else}}
+ <input name="private" type="checkbox" {{if .private}}checked{{end}}>
+ <label>{{.i18n.Tr "repo.visibility_helper" | Safe}}</label>
+ {{end}}
+ </div>
+ </div>
+ <div class="inline field {{if .Err_Description}}error{{end}}">
+ <label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
+ <textarea id="description" name="description">{{.description}}</textarea>
+ </div>
+
+ <div class="inline field">
+ <label></label>
+ <button class="ui green button">
+ {{.i18n.Tr "repo.migrate_repo"}}
+ </button>
+ <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+{{template "base/footer" .}}
diff --git a/templates/repo/migrate.tmpl b/templates/repo/migrate/github.tmpl
index d5a31a6800..cf84ad39e0 100644
--- a/templates/repo/migrate.tmpl
+++ b/templates/repo/migrate/github.tmpl
@@ -5,7 +5,8 @@
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
- {{.i18n.Tr "new_migrate"}}
+ {{.i18n.Tr "repo.migrate.migrate" .service.Title}}
+ <input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
@@ -18,31 +19,10 @@
</span>
</div>
- <div class="inline field">
- <label>{{.i18n.Tr "repo.migrate_service"}}</label>
- <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 class="inline field {{if .Err_Auth}}error{{end}}">
- <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}}>
+ <a target=”_blank” href="https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token">{{svg "octicon-question" 16}}</a>
</div>
<div class="inline field">
diff --git a/templates/repo/migrate/gitlab.tmpl b/templates/repo/migrate/gitlab.tmpl
new file mode 100644
index 0000000000..427a1e05df
--- /dev/null
+++ b/templates/repo/migrate/gitlab.tmpl
@@ -0,0 +1,137 @@
+{{template "base/head" .}}
+<div class="repository new migrate">
+ <div class="ui middle very relaxed page grid">
+ <div class="column">
+ <form class="ui form" action="{{.Link}}" method="post">
+ {{.CsrfTokenHtml}}
+ <h3 class="ui top attached header">
+ {{.i18n.Tr "repo.migrate.migrate" .service.Title}}
+ <input id="service_type" type="hidden" name="service" value="{{.service}}">
+ </h3>
+ <div class="ui attached segment">
+ {{template "base/alert" .}}
+ <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
+ <label for="clone_addr">{{.i18n.Tr "repo.migrate.clone_address"}}</label>
+ <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
+ <span class="help">
+ {{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}}
+ {{if .LFSActive}}<br/>{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}}
+ </span>
+ </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}}>
+ <a target=”_blank” href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">{{svg "octicon-question" 16}}</a>
+ </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_merge_requests" | 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="ui divider"></div>
+
+ <div class="inline required field {{if .Err_Owner}}error{{end}}">
+ <label>{{.i18n.Tr "repo.owner"}}</label>
+ <div class="ui selection owner dropdown">
+ <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required>
+ <span class="text" title="{{.ContextUser.Name}}">
+ <img class="ui mini image" src="{{.ContextUser.RelAvatarLink}}">
+ {{.ContextUser.ShortName 20}}
+ </span>
+ <i class="dropdown icon"></i>
+ <div class="menu" title="{{.SignedUser.Name}}">
+ <div class="item" data-value="{{.SignedUser.ID}}">
+ <img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}">
+ {{.SignedUser.ShortName 20}}
+ </div>
+ {{range .Orgs}}
+ <div class="item" data-value="{{.ID}}" title="{{.Name}}">
+ <img class="ui mini image" src="{{.RelAvatarLink}}">
+ {{.ShortName 20}}
+ </div>
+ {{end}}
+ </div>
+ </div>
+ </div>
+
+ <div class="inline required field {{if .Err_RepoName}}error{{end}}">
+ <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
+ <input id="repo_name" name="repo_name" value="{{.repo_name}}" required>
+ </div>
+ <div class="inline field">
+ <label>{{.i18n.Tr "repo.visibility"}}</label>
+ <div class="ui checkbox">
+ {{if .IsForcedPrivate}}
+ <input name="private" type="checkbox" checked readonly>
+ <label>{{.i18n.Tr "repo.visibility_helper_forced" | Safe}}</label>
+ {{else}}
+ <input name="private" type="checkbox" {{if .private}}checked{{end}}>
+ <label>{{.i18n.Tr "repo.visibility_helper" | Safe}}</label>
+ {{end}}
+ </div>
+ </div>
+ <div class="inline field {{if .Err_Description}}error{{end}}">
+ <label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
+ <textarea id="description" name="description">{{.description}}</textarea>
+ </div>
+
+ <div class="inline field">
+ <label></label>
+ <button class="ui green button">
+ {{.i18n.Tr "repo.migrate_repo"}}
+ </button>
+ <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+{{template "base/footer" .}}
diff --git a/templates/repo/migrate/migrate.tmpl b/templates/repo/migrate/migrate.tmpl
new file mode 100644
index 0000000000..1521620b0e
--- /dev/null
+++ b/templates/repo/migrate/migrate.tmpl
@@ -0,0 +1,23 @@
+{{template "base/head" .}}
+<div class="repository new migrate">
+ <div class="ui middle very relaxed page grid">
+ <div class="column">
+ <div class="ui three stackable cards">
+ {{range .Services}}
+ <div class="ui card">
+ <a class="image" href="{{AppSubUrl}}/repo/migrate?service_type={{.}}">
+ {{svg (Printf "gitea-%s" .Name) 184}}
+ </a>
+ <div class="content">
+ <a class="header" href="{{AppSubUrl}}/repo/migrate?service_type={{.}}">{{.Title}}</a>
+ <div class="description">
+ {{(Printf "repo.migrate.%s.description" .Name) | $.i18n.Tr }}
+ </div>
+ </div>
+ </div>
+ {{end}}
+ </div>
+ </div>
+ </div>
+</div>
+{{template "base/footer" .}}
diff --git a/templates/repo/migrating.tmpl b/templates/repo/migrate/migrating.tmpl
index 0057325e91..0057325e91 100644
--- a/templates/repo/migrating.tmpl
+++ b/templates/repo/migrate/migrating.tmpl
diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl
index ca055e9d87..ce4a97a36f 100644
--- a/templates/user/dashboard/repolist.tmpl
+++ b/templates/user/dashboard/repolist.tmpl
@@ -25,14 +25,6 @@
<div v-show="tab === 'repos'" class="ui tab active list dashboard-repos">
<h4 class="ui top attached header">
{{.i18n.Tr "home.my_repos"}} <span class="ui grey label">${reposTotalCount}</span>
- {{if or (not .ContextUser.IsOrganization) .IsOrganizationOwner}}
- <div class="ui right">
- <a class="poping up" :href="suburl + '/repo/create{{if .ContextUser.IsOrganization}}?org={{.ContextUser.ID}}{{end}}'" data-content="{{.i18n.Tr "new_repo"}}" data-variation="tiny inverted" data-position="left center">
- <i class="plus icon"></i>
- <span class="sr-only">{{.i18n.Tr "new_repo"}}</span>
- </a>
- </div>
- {{end}}
</h4>
<div class="ui attached secondary segment repos-search">
<div class="ui fluid right action left icon input" :class="{loading: isLoading}">
diff --git a/web_src/js/features/migration.js b/web_src/js/features/migration.js
index e4c306307f..7a406616fd 100644
--- a/web_src/js/features/migration.js
+++ b/web_src/js/features/migration.js
@@ -7,7 +7,6 @@ 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)});
@@ -23,19 +22,8 @@ export default function initMigration() {
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);
+ checkItems(serviceType !== 1);
}
function checkItems(tokenAuth) {
diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less
index 2a8d3b5693..b3a5f60ed1 100644
--- a/web_src/less/_repository.less
+++ b/web_src/less/_repository.less
@@ -3200,3 +3200,7 @@ td.blob-excerpt {
.select-project .item .svg {
margin-right: .5rem;
}
+
+.migrate .cards .card {
+ text-align: center;
+}
diff --git a/web_src/less/themes/theme-arc-green.less b/web_src/less/themes/theme-arc-green.less
index 9b78d2e67b..0de6e1c710 100644
--- a/web_src/less/themes/theme-arc-green.less
+++ b/web_src/less/themes/theme-arc-green.less
@@ -1994,3 +1994,19 @@ footer .container .links > * {
border: 1px solid rgba(121, 71, 66, .5) !important;
border-bottom: none !important;
}
+
+.migrate .cards .card {
+ text-align: center;
+}
+
+.migrate .cards .card .content a {
+ color: rgb(158, 158, 158) !important;
+}
+
+.migrate .cards .card .content a:hover {
+ color: rgb(255, 255, 255) !important;
+}
+
+.migrate .cards .card .content .description {
+ color: rgb(158, 158, 158);
+}
diff --git a/web_src/svg/gitea-git.svg b/web_src/svg/gitea-git.svg
new file mode 100644
index 0000000000..fee8870daf
--- /dev/null
+++ b/web_src/svg/gitea-git.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="64px" height="64px"><path fill="#F4511E" d="M42.2,22.1L25.9,5.8C25.4,5.3,24.7,5,24,5c0,0,0,0,0,0c-0.7,0-1.4,0.3-1.9,0.8l-3.5,3.5l4.1,4.1c0.4-0.2,0.8-0.3,1.3-0.3c1.7,0,3,1.3,3,3c0,0.5-0.1,0.9-0.3,1.3l4,4c0.4-0.2,0.8-0.3,1.3-0.3c1.7,0,3,1.3,3,3s-1.3,3-3,3c-1.7,0-3-1.3-3-3c0-0.5,0.1-0.9,0.3-1.3l-4-4c-0.1,0-0.2,0.1-0.3,0.1v10.4c1.2,0.4,2,1.5,2,2.8c0,1.7-1.3,3-3,3s-3-1.3-3-3c0-1.3,0.8-2.4,2-2.8V18.8c-1.2-0.4-2-1.5-2-2.8c0-0.5,0.1-0.9,0.3-1.3l-4.1-4.1L5.8,22.1C5.3,22.6,5,23.3,5,24c0,0.7,0.3,1.4,0.8,1.9l16.3,16.3c0,0,0,0,0,0c0.5,0.5,1.2,0.8,1.9,0.8s1.4-0.3,1.9-0.8l16.3-16.3c0.5-0.5,0.8-1.2,0.8-1.9C43,23.3,42.7,22.6,42.2,22.1z"/></svg> \ No newline at end of file
diff --git a/web_src/svg/gitea-github.svg b/web_src/svg/gitea-github.svg
new file mode 100644
index 0000000000..918a35ecf6
--- /dev/null
+++ b/web_src/svg/gitea-github.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64px" height="64px"><linearGradient id="KpzH_ttTMIjq8dhx1zD2pa" x1="30.999" x2="30.999" y1="16" y2="55.342" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#6dc7ff"/><stop offset="1" stop-color="#e6abff"/></linearGradient><path fill="url(#KpzH_ttTMIjq8dhx1zD2pa)" d="M25.008,56.007c-0.003-0.368-0.006-1.962-0.009-3.454l-0.003-1.55 c-6.729,0.915-8.358-3.78-8.376-3.83c-0.934-2.368-2.211-3.045-2.266-3.073l-0.124-0.072c-0.463-0.316-1.691-1.157-1.342-2.263 c0.315-0.997,1.536-1.1,2.091-1.082c3.074,0.215,4.63,2.978,4.694,3.095c1.569,2.689,3.964,2.411,5.509,1.844 c0.144-0.688,0.367-1.32,0.659-1.878C20.885,42.865,15.27,40.229,15.27,30.64c0-2.633,0.82-4.96,2.441-6.929 c-0.362-1.206-0.774-3.666,0.446-6.765l0.174-0.442l0.452-0.144c0.416-0.137,2.688-0.624,7.359,2.433 c1.928-0.494,3.969-0.749,6.074-0.759c2.115,0.01,4.158,0.265,6.09,0.759c4.667-3.058,6.934-2.565,7.351-2.433l0.451,0.145 l0.174,0.44c1.225,3.098,0.813,5.559,0.451,6.766c1.618,1.963,2.438,4.291,2.438,6.929c0,9.591-5.621,12.219-10.588,13.087 c0.563,1.065,0.868,2.402,0.868,3.878c0,1.683-0.007,7.204-0.015,8.402l-2-0.014c0.008-1.196,0.015-6.708,0.015-8.389 c0-2.442-0.943-3.522-1.35-3.874l-1.73-1.497l2.274-0.253c5.205-0.578,10.525-2.379,10.525-11.341c0-2.33-0.777-4.361-2.31-6.036 l-0.43-0.469l0.242-0.587c0.166-0.401,0.894-2.442-0.043-5.291c-0.758,0.045-2.568,0.402-5.584,2.447l-0.384,0.259l-0.445-0.123 c-1.863-0.518-3.938-0.796-6.001-0.806c-2.052,0.01-4.124,0.288-5.984,0.806l-0.445,0.123l-0.383-0.259 c-3.019-2.044-4.833-2.404-5.594-2.449c-0.935,2.851-0.206,4.892-0.04,5.293l0.242,0.587l-0.429,0.469 c-1.536,1.681-2.314,3.712-2.314,6.036c0,8.958,5.31,10.77,10.504,11.361l2.252,0.256l-1.708,1.49 c-0.372,0.325-1.03,1.112-1.254,2.727l-0.075,0.549l-0.506,0.227c-1.321,0.592-5.839,2.162-8.548-2.485 c-0.015-0.025-0.544-0.945-1.502-1.557c0.646,0.639,1.433,1.673,2.068,3.287c0.066,0.19,1.357,3.622,7.28,2.339l1.206-0.262 l0.012,3.978c0.003,1.487,0.006,3.076,0.009,3.444L25.008,56.007z"/><linearGradient id="KpzH_ttTMIjq8dhx1zD2pb" x1="32" x2="32" y1="5" y2="59.167" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#1a6dff"/><stop offset="1" stop-color="#c822ff"/></linearGradient><path fill="url(#KpzH_ttTMIjq8dhx1zD2pb)" d="M32,58C17.663,58,6,46.337,6,32S17.663,6,32,6s26,11.663,26,26S46.337,58,32,58z M32,8 C18.767,8,8,18.767,8,32s10.767,24,24,24s24-10.767,24-24S45.233,8,32,8z"/></svg> \ No newline at end of file
diff --git a/web_src/svg/gitea-gitlab.svg b/web_src/svg/gitea-gitlab.svg
new file mode 100644
index 0000000000..6a9c8a80f4
--- /dev/null
+++ b/web_src/svg/gitea-gitlab.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="64px" height="64px"><path fill="#e53935" d="M24 43L16 20 32 20z"/><path fill="#ff7043" d="M24 43L42 20 32 20z"/><path fill="#e53935" d="M37 5L42 20 32 20z"/><path fill="#ffa726" d="M24 43L42 20 45 28z"/><path fill="#ff7043" d="M24 43L6 20 16 20z"/><path fill="#e53935" d="M11 5L6 20 16 20z"/><path fill="#ffa726" d="M24 43L6 20 3 28z"/></svg> \ No newline at end of file