diff options
Diffstat (limited to 'routers/web/repo/repo.go')
-rw-r--r-- | routers/web/repo/repo.go | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go new file mode 100644 index 0000000000..f149e92a8b --- /dev/null +++ b/routers/web/repo/repo.go @@ -0,0 +1,388 @@ +// 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 ( + "errors" + "fmt" + "net/http" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/web" + archiver_service "code.gitea.io/gitea/services/archiver" + "code.gitea.io/gitea/services/forms" + repo_service "code.gitea.io/gitea/services/repository" +) + +const ( + tplCreate base.TplName = "repo/create" + tplAlertDetails base.TplName = "base/alert_details" +) + +// MustBeNotEmpty render when a repo is a empty git dir +func MustBeNotEmpty(ctx *context.Context) { + if ctx.Repo.Repository.IsEmpty { + ctx.NotFound("MustBeNotEmpty", nil) + } +} + +// MustBeEditable check that repo can be edited +func MustBeEditable(ctx *context.Context) { + if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { + ctx.NotFound("", nil) + return + } +} + +// MustBeAbleToUpload check that repo can be uploaded to +func MustBeAbleToUpload(ctx *context.Context) { + if !setting.Repository.Upload.Enabled { + ctx.NotFound("", nil) + } +} + +func checkContextUser(ctx *context.Context, uid int64) *models.User { + orgs, err := models.GetOrgsCanCreateRepoByUserID(ctx.User.ID) + if err != nil { + ctx.ServerError("GetOrgsCanCreateRepoByUserID", err) + return nil + } + + if !ctx.User.IsAdmin { + orgsAvailable := []*models.User{} + for i := 0; i < len(orgs); i++ { + if orgs[i].CanCreateRepo() { + orgsAvailable = append(orgsAvailable, orgs[i]) + } + } + ctx.Data["Orgs"] = orgsAvailable + } else { + ctx.Data["Orgs"] = orgs + } + + // Not equal means current user is an organization. + if uid == ctx.User.ID || uid == 0 { + return ctx.User + } + + org, err := models.GetUserByID(uid) + if models.IsErrUserNotExist(err) { + return ctx.User + } + + if err != nil { + ctx.ServerError("GetUserByID", fmt.Errorf("[%d]: %v", uid, err)) + return nil + } + + // Check ownership of organization. + if !org.IsOrganization() { + ctx.Error(http.StatusForbidden) + return nil + } + if !ctx.User.IsAdmin { + canCreate, err := org.CanCreateOrgRepo(ctx.User.ID) + if err != nil { + ctx.ServerError("CanCreateOrgRepo", err) + return nil + } else if !canCreate { + ctx.Error(http.StatusForbidden) + return nil + } + } else { + ctx.Data["Orgs"] = orgs + } + return org +} + +func getRepoPrivate(ctx *context.Context) bool { + switch strings.ToLower(setting.Repository.DefaultPrivate) { + case setting.RepoCreatingLastUserVisibility: + return ctx.User.LastRepoVisibility + case setting.RepoCreatingPrivate: + return true + case setting.RepoCreatingPublic: + return false + default: + return ctx.User.LastRepoVisibility + } +} + +// Create render creating repository page +func Create(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("new_repo") + + // Give default value for template to render. + ctx.Data["Gitignores"] = models.Gitignores + ctx.Data["LabelTemplates"] = models.LabelTemplates + ctx.Data["Licenses"] = models.Licenses + ctx.Data["Readmes"] = models.Readmes + ctx.Data["readme"] = "Default" + ctx.Data["private"] = getRepoPrivate(ctx) + ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate + ctx.Data["default_branch"] = setting.Repository.DefaultBranch + + ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + ctx.Data["repo_template_name"] = ctx.Tr("repo.template_select") + templateID := ctx.QueryInt64("template_id") + if templateID > 0 { + templateRepo, err := models.GetRepositoryByID(templateID) + if err == nil && templateRepo.CheckUnitUser(ctxUser, models.UnitTypeCode) { + ctx.Data["repo_template"] = templateID + ctx.Data["repo_template_name"] = templateRepo.Name + } + } + + ctx.Data["CanCreateRepo"] = ctx.User.CanCreateRepo() + ctx.Data["MaxCreationLimit"] = ctx.User.MaxCreationLimit() + + ctx.HTML(http.StatusOK, tplCreate) +} + +func handleCreateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) { + switch { + 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.IsErrRepoFilesAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + switch { + case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form) + case setting.Repository.AllowAdoptionOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form) + case setting.Repository.AllowDeleteOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form) + default: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), 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: + ctx.ServerError(name, err) + } +} + +// CreatePost response for creating repository +func CreatePost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.CreateRepoForm) + ctx.Data["Title"] = ctx.Tr("new_repo") + + ctx.Data["Gitignores"] = models.Gitignores + ctx.Data["LabelTemplates"] = models.LabelTemplates + ctx.Data["Licenses"] = models.Licenses + ctx.Data["Readmes"] = models.Readmes + + ctx.Data["CanCreateRepo"] = ctx.User.CanCreateRepo() + ctx.Data["MaxCreationLimit"] = ctx.User.MaxCreationLimit() + + ctxUser := checkContextUser(ctx, form.UID) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + if ctx.HasError() { + ctx.HTML(http.StatusOK, tplCreate) + return + } + + var repo *models.Repository + var err error + if form.RepoTemplate > 0 { + opts := models.GenerateRepoOptions{ + Name: form.RepoName, + Description: form.Description, + Private: form.Private, + GitContent: form.GitContent, + Topics: form.Topics, + GitHooks: form.GitHooks, + Webhooks: form.Webhooks, + Avatar: form.Avatar, + IssueLabels: form.Labels, + } + + if !opts.IsValid() { + ctx.RenderWithErr(ctx.Tr("repo.template.one_item"), tplCreate, form) + return + } + + templateRepo := getRepository(ctx, form.RepoTemplate) + if ctx.Written() { + return + } + + if !templateRepo.IsTemplate { + ctx.RenderWithErr(ctx.Tr("repo.template.invalid"), tplCreate, form) + return + } + + repo, err = repo_service.GenerateRepository(ctx.User, ctxUser, templateRepo, opts) + if err == nil { + log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) + ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) + return + } + } else { + repo, err = repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + Name: form.RepoName, + Description: form.Description, + Gitignores: form.Gitignores, + IssueLabels: form.IssueLabels, + License: form.License, + Readme: form.Readme, + IsPrivate: form.Private || setting.Repository.ForcePrivate, + DefaultBranch: form.DefaultBranch, + AutoInit: form.AutoInit, + IsTemplate: form.Template, + TrustModel: models.ToTrustModel(form.TrustModel), + }) + if err == nil { + log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) + ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) + return + } + } + + handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) +} + +// Action response for actions to a repository +func Action(ctx *context.Context) { + var err error + switch ctx.Params(":action") { + case "watch": + err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) + case "unwatch": + err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) + case "star": + err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) + case "unstar": + err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) + case "accept_transfer": + err = acceptOrRejectRepoTransfer(ctx, true) + case "reject_transfer": + err = acceptOrRejectRepoTransfer(ctx, false) + case "desc": // FIXME: this is not used + if !ctx.Repo.IsOwner() { + ctx.Error(http.StatusNotFound) + return + } + + ctx.Repo.Repository.Description = ctx.Query("desc") + ctx.Repo.Repository.Website = ctx.Query("site") + err = models.UpdateRepository(ctx.Repo.Repository, false) + } + + if err != nil { + ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) + return + } + + ctx.RedirectToFirst(ctx.Query("redirect_to"), ctx.Repo.RepoLink) +} + +func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { + repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository) + if err != nil { + return err + } + + if err := repoTransfer.LoadAttributes(); err != nil { + return err + } + + if !repoTransfer.CanUserAcceptTransfer(ctx.User) { + return errors.New("user does not have enough permissions") + } + + if accept { + if err := repo_service.TransferOwnership(repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams); err != nil { + return err + } + ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success")) + } else { + if err := models.CancelRepositoryTransfer(ctx.Repo.Repository); err != nil { + return err + } + ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) + } + + ctx.Redirect(ctx.Repo.Repository.HTMLURL()) + return nil +} + +// RedirectDownload return a file based on the following infos: +func RedirectDownload(ctx *context.Context) { + var ( + vTag = ctx.Params("vTag") + fileName = ctx.Params("fileName") + ) + tagNames := []string{vTag} + curRepo := ctx.Repo.Repository + releases, err := models.GetReleasesByRepoIDAndNames(models.DefaultDBContext(), curRepo.ID, tagNames) + if err != nil { + if models.IsErrAttachmentNotExist(err) { + ctx.Error(http.StatusNotFound) + return + } + ctx.ServerError("RedirectDownload", err) + return + } + if len(releases) == 1 { + release := releases[0] + att, err := models.GetAttachmentByReleaseIDFileName(release.ID, fileName) + if err != nil { + ctx.Error(http.StatusNotFound) + return + } + if att != nil { + ctx.Redirect(att.DownloadURL()) + return + } + } + ctx.Error(http.StatusNotFound) +} + +// InitiateDownload will enqueue an archival request, as needed. It may submit +// a request that's already in-progress, but the archiver service will just +// kind of drop it on the floor if this is the case. +func InitiateDownload(ctx *context.Context) { + uri := ctx.Params("*") + aReq := archiver_service.DeriveRequestFrom(ctx, uri) + + if aReq == nil { + ctx.Error(http.StatusNotFound) + return + } + + complete := aReq.IsComplete() + if !complete { + aReq = archiver_service.ArchiveRepository(aReq) + complete, _ = aReq.TimedWaitForCompletion(ctx, 2*time.Second) + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "complete": complete, + }) +} |