| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- // Copyright 2024 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package repo
-
- import (
- "errors"
- "net/http"
- "net/url"
-
- "code.gitea.io/gitea/models/db"
- git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/models/organization"
- repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/optional"
- "code.gitea.io/gitea/modules/repository"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/templates"
- "code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/services/context"
- "code.gitea.io/gitea/services/forms"
- repo_service "code.gitea.io/gitea/services/repository"
- )
-
- const (
- tplFork templates.TplName = "repo/pulls/fork"
- )
-
- func getForkRepository(ctx *context.Context) *repo_model.Repository {
- forkRepo := ctx.Repo.Repository
- if ctx.Written() {
- return nil
- }
-
- if forkRepo.IsEmpty {
- log.Trace("Empty repository %-v", forkRepo)
- ctx.NotFound(nil)
- return nil
- }
-
- if err := forkRepo.LoadOwner(ctx); err != nil {
- ctx.ServerError("LoadOwner", err)
- return nil
- }
-
- ctx.Data["repo_name"] = forkRepo.Name
- ctx.Data["description"] = forkRepo.Description
- ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate
- canForkToUser := repository.CanUserForkBetweenOwners(forkRepo.OwnerID, ctx.Doer.ID) && !repo_model.HasForkedRepo(ctx, ctx.Doer.ID, forkRepo.ID)
-
- ctx.Data["ForkRepo"] = forkRepo
-
- ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, ctx.Doer.ID)
- if err != nil {
- ctx.ServerError("GetOrgsCanCreateRepoByUserID", err)
- return nil
- }
- var orgs []*organization.Organization
- for _, org := range ownedOrgs {
- if forkRepo.OwnerID != org.ID && !repo_model.HasForkedRepo(ctx, org.ID, forkRepo.ID) {
- orgs = append(orgs, org)
- }
- }
-
- traverseParentRepo := forkRepo
- for {
- if !repository.CanUserForkBetweenOwners(ctx.Doer.ID, traverseParentRepo.OwnerID) {
- canForkToUser = false
- } else {
- for i, org := range orgs {
- if org.ID == traverseParentRepo.OwnerID {
- orgs = append(orgs[:i], orgs[i+1:]...)
- break
- }
- }
- }
-
- if !traverseParentRepo.IsFork {
- break
- }
- traverseParentRepo, err = repo_model.GetRepositoryByID(ctx, traverseParentRepo.ForkID)
- if err != nil {
- ctx.ServerError("GetRepositoryByID", err)
- return nil
- }
- }
-
- ctx.Data["CanForkToUser"] = canForkToUser
- ctx.Data["Orgs"] = orgs
-
- // TODO: this message should only be shown for the "current doer" when it is selected, just like the "new repo" page.
- // msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", ctx.Doer.MaxCreationLimit())
-
- if canForkToUser {
- ctx.Data["ContextUser"] = ctx.Doer
- ctx.Data["CanForkRepoInNewOwner"] = true
- } else if len(orgs) > 0 {
- ctx.Data["ContextUser"] = orgs[0]
- ctx.Data["CanForkRepoInNewOwner"] = true
- } else {
- ctx.Data["CanForkRepoInNewOwner"] = false
- ctx.Flash.Error(ctx.Tr("repo.fork_no_valid_owners"), true)
- return nil
- }
-
- branches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
- RepoID: ctx.Repo.Repository.ID,
- ListOptions: db.ListOptionsAll,
- IsDeletedBranch: optional.Some(false),
- // Add it as the first option
- ExcludeBranchNames: []string{ctx.Repo.Repository.DefaultBranch},
- })
- if err != nil {
- ctx.ServerError("FindBranchNames", err)
- return nil
- }
- ctx.Data["Branches"] = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
-
- return forkRepo
- }
-
- // Fork render repository fork page
- func Fork(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("new_fork")
- getForkRepository(ctx)
- if ctx.Written() {
- return
- }
-
- ctx.HTML(http.StatusOK, tplFork)
- }
-
- // ForkPost response for forking a repository
- func ForkPost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.CreateRepoForm)
- ctx.Data["Title"] = ctx.Tr("new_fork")
-
- ctxUser := checkContextUser(ctx, form.UID)
- if ctx.Written() {
- return
- }
-
- forkRepo := getForkRepository(ctx)
- if ctx.Written() {
- return
- }
-
- ctx.Data["ContextUser"] = ctxUser
-
- if ctx.HasError() {
- ctx.JSONError(ctx.GetErrMsg())
- return
- }
-
- var err error
- traverseParentRepo := forkRepo
- for {
- if !repository.CanUserForkBetweenOwners(ctxUser.ID, traverseParentRepo.OwnerID) {
- ctx.JSONError(ctx.Tr("repo.settings.new_owner_has_same_repo"))
- return
- }
- repo := repo_model.GetForkedRepo(ctx, ctxUser.ID, traverseParentRepo.ID)
- if repo != nil {
- ctx.JSONRedirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
- return
- }
- if !traverseParentRepo.IsFork {
- break
- }
- traverseParentRepo, err = repo_model.GetRepositoryByID(ctx, traverseParentRepo.ForkID)
- if err != nil {
- ctx.ServerError("GetRepositoryByID", err)
- return
- }
- }
-
- // Check if user is allowed to create repo's on the organization.
- if ctxUser.IsOrganization() {
- isAllowedToFork, err := organization.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx, ctx.Doer.ID)
- if err != nil {
- ctx.ServerError("CanCreateOrgRepo", err)
- return
- } else if !isAllowedToFork {
- ctx.HTTPError(http.StatusForbidden)
- return
- }
- }
-
- repo := ForkRepoTo(ctx, ctxUser, repo_service.ForkRepoOptions{
- BaseRepo: forkRepo,
- Name: form.RepoName,
- Description: form.Description,
- SingleBranch: form.ForkSingleBranch,
- })
- if ctx.Written() {
- return
- }
- ctx.JSONRedirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
- }
-
- func ForkRepoTo(ctx *context.Context, owner *user_model.User, forkOpts repo_service.ForkRepoOptions) *repo_model.Repository {
- repo, err := repo_service.ForkRepository(ctx, ctx.Doer, owner, forkOpts)
- if err != nil {
- ctx.Data["Err_RepoName"] = true
- switch {
- case repo_model.IsErrReachLimitOfRepo(err):
- maxCreationLimit := owner.MaxCreationLimit()
- msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
- ctx.JSONError(msg)
- case repo_model.IsErrRepoAlreadyExist(err):
- ctx.JSONError(ctx.Tr("repo.settings.new_owner_has_same_repo"))
- case repo_model.IsErrRepoFilesAlreadyExist(err):
- switch {
- case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
- ctx.JSONError(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"))
- case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
- ctx.JSONError(ctx.Tr("form.repository_files_already_exist.adopt"))
- case setting.Repository.AllowDeleteOfUnadoptedRepositories:
- ctx.JSONError(ctx.Tr("form.repository_files_already_exist.delete"))
- default:
- ctx.JSONError(ctx.Tr("form.repository_files_already_exist"))
- }
- case db.IsErrNameReserved(err):
- ctx.JSONError(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name))
- case db.IsErrNamePatternNotAllowed(err):
- ctx.JSONError(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern))
- case errors.Is(err, user_model.ErrBlockedUser):
- ctx.JSONError(ctx.Tr("repo.fork.blocked_user"))
- default:
- ctx.ServerError("ForkPost", err)
- }
- return nil
- }
- return repo
- }
|