diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 3974c4db3a..0e4081da8e 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -287,6 +287,7 @@ const ( OneDevService // 6 onedev service GitBucketService // 7 gitbucket service CodebaseService // 8 codebase service + ForgejoService // 9 forgejo service ) // Name represents the service type's name @@ -312,6 +313,8 @@ func (gt GitServiceType) Title() string { return "GitBucket" case CodebaseService: return "Codebase" + case ForgejoService: + return "Forgejo" case PlainGitService: return "Git" } @@ -353,7 +356,7 @@ type MigrateRepoOptions struct { // TokenAuth represents whether a service type supports token-based auth func (gt GitServiceType) TokenAuth() bool { switch gt { - case GithubService, GiteaService, GitlabService: + case GithubService, GiteaService, GitlabService, ForgejoService: return true } return false @@ -364,6 +367,7 @@ func (gt GitServiceType) TokenAuth() bool { var SupportedFullGitService = []GitServiceType{ GithubService, GitlabService, + ForgejoService, GiteaService, GogsService, OneDevService, diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b72297aa32..363e06e052 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1094,6 +1094,7 @@ migrate.migrating_failed_no_addr = Migration failed. migrate.github.description = Migrate data from github.com or other GitHub instances. migrate.git.description = Migrate a repository only from any Git service. 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.gogs.description = Migrate data from notabug.org or other Gogs instances. migrate.onedev.description = Migrate data from code.onedev.io or other OneDev instances. diff --git a/public/assets/img/svg/gitea-forgejo.svg b/public/assets/img/svg/gitea-forgejo.svg new file mode 100644 index 0000000000..ef617c00f3 --- /dev/null +++ b/public/assets/img/svg/gitea-forgejo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/services/convert/utils.go b/services/convert/utils.go index cdce60831c..fe35fd2dac 100644 --- a/services/convert/utils.go +++ b/services/convert/utils.go @@ -36,6 +36,8 @@ func ToGitServiceType(value string) structs.GitServiceType { return structs.OneDevService case "gitbucket": return structs.GitBucketService + case "forgejo": + return structs.ForgejoService default: return structs.PlainGitService } diff --git a/services/convert/utils_test.go b/services/convert/utils_test.go index 1ac03a3097..b464d8bb68 100644 --- a/services/convert/utils_test.go +++ b/services/convert/utils_test.go @@ -28,6 +28,8 @@ func TestToGitServiceType(t *testing.T) { typ: "gitlab", enum: 4, }, { typ: "gogs", enum: 5, + }, { + typ: "forgejo", enum: 9, }, { typ: "trash", enum: 1, }} diff --git a/services/migrations/forgejo_downloader.go b/services/migrations/forgejo_downloader.go new file mode 100644 index 0000000000..25dbb6ec51 --- /dev/null +++ b/services/migrations/forgejo_downloader.go @@ -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 +} diff --git a/services/migrations/forgejo_downloader_test.go b/services/migrations/forgejo_downloader_test.go new file mode 100644 index 0000000000..5bd37551cc --- /dev/null +++ b/services/migrations/forgejo_downloader_test.go @@ -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)) +} diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index 0b83f3b4a3..ae164a7add 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" base "code.gitea.io/gitea/modules/migration" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "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 } +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) { var ( downloader base.Downloader err error ) - for _, factory := range factories { - if factory.GitServiceType() == opts.GitServiceType { - downloader, err = factory.New(ctx, opts) - if err != nil { - return nil, err - } - break + if factory := getFactoryFromServiceType(opts.GitServiceType); factory != nil { + downloader, err = factory.New(ctx, opts) + if err != nil { + return nil, err } } diff --git a/templates/repo/migrate/forgejo.tmpl b/templates/repo/migrate/forgejo.tmpl new file mode 100644 index 0000000000..3caadbee15 --- /dev/null +++ b/templates/repo/migrate/forgejo.tmpl @@ -0,0 +1 @@ +{{template "repo/migrate/gitea" .}} diff --git a/tests/integration/migrate_test.go b/tests/integration/migrate_test.go index f25329f66b..c4dd431594 100644 --- a/tests/integration/migrate_test.go +++ b/tests/integration/migrate_test.go @@ -4,6 +4,7 @@ package integration import ( + "context" "fmt" "net/http" "net/url" @@ -18,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/migrations" + "code.gitea.io/gitea/services/repository" "github.com/stretchr/testify/assert" ) @@ -49,7 +51,7 @@ func TestMigrateLocalPath(t *testing.T) { setting.ImportLocalPaths = old } -func TestMigrateGiteaForm(t *testing.T) { +func TestMigrate(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { AllowLocalNetworks := setting.Migrations.AllowLocalNetworks setting.Migrations.AllowLocalNetworks = true @@ -69,33 +71,44 @@ func TestMigrateGiteaForm(t *testing.T) { session := loginUser(t, ownerName) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadMisc) - // Step 0: verify the repo is available - req := NewRequestf(t, "GET", fmt.Sprintf("/%s/%s", ownerName, repoName)) - _ = session.MakeRequest(t, req, http.StatusOK) - // Step 1: get the Gitea migration form - req = NewRequestf(t, "GET", "/repo/migrate/?service_type=%d", structs.GiteaService) - resp := session.MakeRequest(t, req, http.StatusOK) - // Step 2: load the form - htmlDoc := NewHTMLParser(t, resp.Body) - link, exists := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`).Attr("action") - assert.True(t, exists, "The template has changed") - // Step 4: submit the migration to only migrate issues - migratedRepoName := "otherrepo" - req = NewRequestWithValues(t, "POST", link, map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - "service": fmt.Sprintf("%d", structs.GiteaService), - "clone_addr": fmt.Sprintf("%s%s/%s", u, ownerName, repoName), - "auth_token": token, - "issues": "on", - "repo_name": migratedRepoName, - "description": "", - "uid": fmt.Sprintf("%d", repoOwner.ID), - }) - 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 - unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: migratedRepoName}) + for _, s := range []struct { + svc structs.GitServiceType + }{ + {svc: structs.GiteaService}, + {svc: structs.ForgejoService}, + } { + // Step 0: verify the repo is available + req := NewRequestf(t, "GET", fmt.Sprintf("/%s/%s", ownerName, repoName)) + _ = session.MakeRequest(t, req, http.StatusOK) + // Step 1: get the Gitea migration form + req = NewRequestf(t, "GET", "/repo/migrate/?service_type=%d", s.svc) + resp := session.MakeRequest(t, req, http.StatusOK) + // Step 2: load the form + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`).Attr("action") + assert.True(t, exists, "The template has changed") + // Step 4: submit the migration to only migrate issues + migratedRepoName := "otherrepo" + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "service": fmt.Sprintf("%d", s.svc), + "clone_addr": fmt.Sprintf("%s%s/%s", u, ownerName, repoName), + "auth_token": token, + "issues": "on", + "repo_name": migratedRepoName, + "description": "", + "uid": fmt.Sprintf("%d", repoOwner.ID), + }) + 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) + } }) } diff --git a/web_src/svg/gitea-forgejo.svg b/web_src/svg/gitea-forgejo.svg new file mode 100644 index 0000000000..e00e5963cf --- /dev/null +++ b/web_src/svg/gitea-forgejo.svg @@ -0,0 +1,9 @@ +