[BRANDING] add Forgejo Git Service and migration UI

[FEAT] add Forgejo Git Service (squash) register a Forgejo factory

If the Forgejo factory for the Forgejo service is not registered,
newDownloader will fallback to a git service and not migrate issues
etc.

Refs: https://codeberg.org/forgejo/forgejo/issues/1678
(cherry picked from commit 51938cd161)

[FEAT] add Forgero Git Service

Signed-off-by: cassiozareck <cassiomilczareck@gmail.com>
(cherry picked from commit a878adfe62)

Adding description and Forgejo SVG

(cherry picked from commit 13738c0380)

Undo reordering and tmpl redirection

(cherry picked from commit 9ae51c46f4)
(cherry picked from commit 70fffdc61d)
(cherry picked from commit c0ebfa9da3)
(cherry picked from commit 9922c92787)
(cherry picked from commit 00c0effbc7)
(cherry picked from commit e4c9525b13)
(cherry picked from commit 09d7b83211)
(cherry picked from commit bbcd5975c9)
(cherry picked from commit 55c70a0e18)
(cherry picked from commit 76596410c0)
(cherry picked from commit 1308043931)
(cherry picked from commit 919d6aedfe)

[FEAT] add Forgero Git Service (squash) more tests

Previously only Gitea service was being tested under self-hosted migrations. Since Forgejo is also self-hosted and in fact use the same downloader/migrator we can add to this suite another test that will do the same, migrating the same repository under the same local instance but for the Forgejo service (represented by 9)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1709
Co-authored-by: zareck <cassiomilczareck@gmail.com>
Co-committed-by: zareck <cassiomilczareck@gmail.com>
(cherry picked from commit 40a4b8f1a8)
(cherry picked from commit 3198b4a642)
(cherry picked from commit 4edda1f389)
(cherry picked from commit 4d91b77d29)
(cherry picked from commit afe85c52e3)
This commit is contained in:
Earl Warren 2023-12-05 16:43:35 +01:00
parent 2993203093
commit 5ea7df79ad
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
11 changed files with 121 additions and 37 deletions

View file

@ -287,6 +287,7 @@ const (
OneDevService // 6 onedev service OneDevService // 6 onedev service
GitBucketService // 7 gitbucket service GitBucketService // 7 gitbucket service
CodebaseService // 8 codebase service CodebaseService // 8 codebase service
ForgejoService // 9 forgejo service
) )
// Name represents the service type's name // Name represents the service type's name
@ -312,6 +313,8 @@ func (gt GitServiceType) Title() string {
return "GitBucket" return "GitBucket"
case CodebaseService: case CodebaseService:
return "Codebase" return "Codebase"
case ForgejoService:
return "Forgejo"
case PlainGitService: case PlainGitService:
return "Git" return "Git"
} }
@ -353,7 +356,7 @@ type MigrateRepoOptions struct {
// TokenAuth represents whether a service type supports token-based auth // TokenAuth represents whether a service type supports token-based auth
func (gt GitServiceType) TokenAuth() bool { func (gt GitServiceType) TokenAuth() bool {
switch gt { switch gt {
case GithubService, GiteaService, GitlabService: case GithubService, GiteaService, GitlabService, ForgejoService:
return true return true
} }
return false return false
@ -364,6 +367,7 @@ func (gt GitServiceType) TokenAuth() bool {
var SupportedFullGitService = []GitServiceType{ var SupportedFullGitService = []GitServiceType{
GithubService, GithubService,
GitlabService, GitlabService,
ForgejoService,
GiteaService, GiteaService,
GogsService, GogsService,
OneDevService, OneDevService,

View file

@ -1100,6 +1100,7 @@ migrate.migrating_failed_no_addr = Migration failed.
migrate.github.description = Migrate data from github.com or other GitHub instances. migrate.github.description = Migrate data from github.com or other GitHub instances.
migrate.git.description = Migrate a repository only from any Git service. migrate.git.description = Migrate a repository only from any Git service.
migrate.gitlab.description = Migrate data from gitlab.com or other GitLab instances. migrate.gitlab.description = Migrate data from gitlab.com or other GitLab instances.
migrate.forgejo.description = Migrate data from codeberg.org or other Forgejo instances.
migrate.gitea.description = Migrate data from gitea.com or other Gitea instances. migrate.gitea.description = Migrate data from gitea.com or other Gitea instances.
migrate.gogs.description = Migrate data from notabug.org or other Gogs instances. migrate.gogs.description = Migrate data from notabug.org or other Gogs instances.
migrate.onedev.description = Migrate data from code.onedev.io or other OneDev instances. migrate.onedev.description = Migrate data from code.onedev.io or other OneDev instances.

9
public/assets/img/svg/gitea-forgejo.svg generated Normal file
View file

@ -0,0 +1,9 @@
<svg width="32" height="32" viewBox="-15 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(28,28)">
<path d="M58 168 v-98 a50 50 0 0 1 50-50 h20" fill="none" stroke="#ff6600" stroke-width="25" />
<path d="M58 168 v-30 a50 50 0 0 1 50-50 h20" fill="none" stroke="#d40000" stroke-width="25" />
<circle cx="142" cy="20" r="18" fill="none" stroke="#ff6600" stroke-width="15" />
<circle cx="142" cy="88" r="18" fill="none" stroke="#d40000" stroke-width="15" />
<circle cx="58" cy="180" r="18" fill="none" stroke="#d40000" stroke-width="15" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 595 B

View file

@ -36,6 +36,8 @@ func ToGitServiceType(value string) structs.GitServiceType {
return structs.OneDevService return structs.OneDevService
case "gitbucket": case "gitbucket":
return structs.GitBucketService return structs.GitBucketService
case "forgejo":
return structs.ForgejoService
default: default:
return structs.PlainGitService return structs.PlainGitService
} }

View file

@ -28,6 +28,8 @@ func TestToGitServiceType(t *testing.T) {
typ: "gitlab", enum: 4, typ: "gitlab", enum: 4,
}, { }, {
typ: "gogs", enum: 5, typ: "gogs", enum: 5,
}, {
typ: "forgejo", enum: 9,
}, { }, {
typ: "trash", enum: 1, typ: "trash", enum: 1,
}} }}

View file

@ -0,0 +1,20 @@
// Copyright 2023 The Forgejo Authors
// SPDX-License-Identifier: MIT
package migrations
import (
"code.gitea.io/gitea/modules/structs"
)
func init() {
RegisterDownloaderFactory(&ForgejoDownloaderFactory{})
}
type ForgejoDownloaderFactory struct {
GiteaDownloaderFactory
}
func (f *ForgejoDownloaderFactory) GitServiceType() structs.GitServiceType {
return structs.ForgejoService
}

View file

@ -0,0 +1,16 @@
// Copyright 2023 The Forgejo Authors
// SPDX-License-Identifier: MIT
package migrations
import (
"testing"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/require"
)
func TestForgejoDownload(t *testing.T) {
require.NotNil(t, getFactoryFromServiceType(structs.ForgejoService))
}

View file

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -139,19 +140,25 @@ func MigrateRepository(ctx context.Context, doer *user_model.User, ownerName str
return uploader.repo, nil return uploader.repo, nil
} }
func getFactoryFromServiceType(serviceType structs.GitServiceType) base.DownloaderFactory {
for _, factory := range factories {
if factory.GitServiceType() == serviceType {
return factory
}
}
return nil
}
func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptions) (base.Downloader, error) { func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptions) (base.Downloader, error) {
var ( var (
downloader base.Downloader downloader base.Downloader
err error err error
) )
for _, factory := range factories { if factory := getFactoryFromServiceType(opts.GitServiceType); factory != nil {
if factory.GitServiceType() == opts.GitServiceType { downloader, err = factory.New(ctx, opts)
downloader, err = factory.New(ctx, opts) if err != nil {
if err != nil { return nil, err
return nil, err
}
break
} }
} }

View file

@ -0,0 +1 @@
{{template "repo/migrate/gitea" .}}

View file

@ -4,6 +4,7 @@
package integration package integration
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@ -18,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
"code.gitea.io/gitea/services/repository"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -49,7 +51,7 @@ func TestMigrateLocalPath(t *testing.T) {
setting.ImportLocalPaths = old setting.ImportLocalPaths = old
} }
func TestMigrateGiteaForm(t *testing.T) { func TestMigrate(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
setting.Migrations.AllowLocalNetworks = true setting.Migrations.AllowLocalNetworks = true
@ -69,33 +71,44 @@ func TestMigrateGiteaForm(t *testing.T) {
session := loginUser(t, ownerName) session := loginUser(t, ownerName)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadMisc) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadMisc)
// Step 0: verify the repo is available for _, s := range []struct {
req := NewRequestf(t, "GET", fmt.Sprintf("/%s/%s", ownerName, repoName)) svc structs.GitServiceType
_ = session.MakeRequest(t, req, http.StatusOK) }{
// Step 1: get the Gitea migration form {svc: structs.GiteaService},
req = NewRequestf(t, "GET", "/repo/migrate/?service_type=%d", structs.GiteaService) {svc: structs.ForgejoService},
resp := session.MakeRequest(t, req, http.StatusOK) } {
// Step 2: load the form // Step 0: verify the repo is available
htmlDoc := NewHTMLParser(t, resp.Body) req := NewRequestf(t, "GET", fmt.Sprintf("/%s/%s", ownerName, repoName))
link, exists := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`).Attr("action") _ = session.MakeRequest(t, req, http.StatusOK)
assert.True(t, exists, "The template has changed") // Step 1: get the Gitea migration form
// Step 4: submit the migration to only migrate issues req = NewRequestf(t, "GET", "/repo/migrate/?service_type=%d", s.svc)
migratedRepoName := "otherrepo" resp := session.MakeRequest(t, req, http.StatusOK)
req = NewRequestWithValues(t, "POST", link, map[string]string{ // Step 2: load the form
"_csrf": htmlDoc.GetCSRF(), htmlDoc := NewHTMLParser(t, resp.Body)
"service": fmt.Sprintf("%d", structs.GiteaService), link, exists := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`).Attr("action")
"clone_addr": fmt.Sprintf("%s%s/%s", u, ownerName, repoName), assert.True(t, exists, "The template has changed")
"auth_token": token, // Step 4: submit the migration to only migrate issues
"issues": "on", migratedRepoName := "otherrepo"
"repo_name": migratedRepoName, req = NewRequestWithValues(t, "POST", link, map[string]string{
"description": "", "_csrf": htmlDoc.GetCSRF(),
"uid": fmt.Sprintf("%d", repoOwner.ID), "service": fmt.Sprintf("%d", s.svc),
}) "clone_addr": fmt.Sprintf("%s%s/%s", u, ownerName, repoName),
resp = session.MakeRequest(t, req, http.StatusSeeOther) "auth_token": token,
// Step 5: a redirection displays the migrated repository "issues": "on",
loc := resp.Header().Get("Location") "repo_name": migratedRepoName,
assert.EqualValues(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc) "description": "",
// Step 6: check the repo was created "uid": fmt.Sprintf("%d", repoOwner.ID),
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: migratedRepoName}) })
resp = session.MakeRequest(t, req, http.StatusSeeOther)
// Step 5: a redirection displays the migrated repository
loc := resp.Header().Get("Location")
assert.EqualValues(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc)
// Step 6: check the repo was created
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: migratedRepoName})
// Step 7: delete the repository, so we can test with other services
err := repository.DeleteRepository(context.Background(), repoOwner, repo, false)
assert.NoError(t, err)
}
}) })
} }

View file

@ -0,0 +1,9 @@
<svg width="64" height="64" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" class="forgejo-logo" aria-hidden="true">
<g transform="translate(28,28)">
<path d="M58 168 v-98 a50 50 0 0 1 50-50 h20" fill="none" stroke="#ff6600" stroke-width="25" />
<path d="M58 168 v-30 a50 50 0 0 1 50-50 h20" fill="none" stroke="#d40000" stroke-width="25" />
<circle cx="142" cy="20" r="18" fill="none" stroke="#ff6600" stroke-width="15" />
<circle cx="142" cy="88" r="18" fill="none" stroke="#d40000" stroke-width="15" />
<circle cx="58" cy="180" r="18" fill="none" stroke="#d40000" stroke-width="15" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 633 B