mirror of
https://github.com/go-gitea/gitea
synced 2025-07-31 22:58:35 +00:00
Move some files under repo/setting (#25585)
There are too many files under `routers/web/repo` and the file `routers/web/repo/setting.go` is too big. This PR move all setting related routers' body functions under `routers/web/repo/setting` and also split `routers/web/repo/setting.go`
This commit is contained in:
76
routers/web/repo/setting/avatar.go
Normal file
76
routers/web/repo/setting/avatar.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
|
||||
// UpdateAvatarSetting update repo's avatar
|
||||
func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error {
|
||||
ctxRepo := ctx.Repo.Repository
|
||||
|
||||
if form.Avatar == nil {
|
||||
// No avatar is uploaded and we not removing it here.
|
||||
// No random avatar generated here.
|
||||
// Just exit, no action.
|
||||
if ctxRepo.CustomAvatarRelativePath() == "" {
|
||||
log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
r, err := form.Avatar.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Avatar.Open: %w", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
if form.Avatar.Size > setting.Avatar.MaxFileSize {
|
||||
return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big"))
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("io.ReadAll: %w", err)
|
||||
}
|
||||
st := typesniffer.DetectContentType(data)
|
||||
if !(st.IsImage() && !st.IsSvgImage()) {
|
||||
return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image"))
|
||||
}
|
||||
if err = repo_service.UploadAvatar(ctx, ctxRepo, data); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SettingsAvatar save new POSTed repository avatar
|
||||
func SettingsAvatar(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.AvatarForm)
|
||||
form.Source = forms.AvatarLocal
|
||||
if err := UpdateAvatarSetting(ctx, *form); err != nil {
|
||||
ctx.Flash.Error(err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_avatar_success"))
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
}
|
||||
|
||||
// SettingsDeleteAvatar delete repository avatar
|
||||
func SettingsDeleteAvatar(ctx *context.Context) {
|
||||
if err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository); err != nil {
|
||||
ctx.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err))
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
}
|
210
routers/web/repo/setting/collaboration.go
Normal file
210
routers/web/repo/setting/collaboration.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
)
|
||||
|
||||
// Collaboration render a repository's collaboration page
|
||||
func Collaboration(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.collaboration")
|
||||
ctx.Data["PageIsSettingsCollaboration"] = true
|
||||
|
||||
users, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCollaborators", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Collaborators"] = users
|
||||
|
||||
teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepoTeams", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Teams"] = teams
|
||||
ctx.Data["Repo"] = ctx.Repo.Repository
|
||||
ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID
|
||||
ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName
|
||||
ctx.Data["Org"] = ctx.Repo.Repository.Owner
|
||||
ctx.Data["Units"] = unit_model.Units
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCollaboration)
|
||||
}
|
||||
|
||||
// CollaborationPost response for actions for a collaboration of a repository
|
||||
func CollaborationPost(ctx *context.Context) {
|
||||
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator")))
|
||||
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
|
||||
u, err := user_model.GetUserByName(ctx, name)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
} else {
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !u.IsActive {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_inactive_user"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
|
||||
// Organization is not allowed to be added as a collaborator.
|
||||
if u.IsOrganization() {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
|
||||
if got, err := repo_model.IsCollaborator(ctx, ctx.Repo.Repository.ID, u.ID); err == nil && got {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_duplicate"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
// find the owner team of the organization the repo belongs too and
|
||||
// check if the user we're trying to add is an owner.
|
||||
if ctx.Repo.Repository.Owner.IsOrganization() {
|
||||
if isOwner, err := organization.IsOrganizationOwner(ctx, ctx.Repo.Repository.Owner.ID, u.ID); err != nil {
|
||||
ctx.ServerError("IsOrganizationOwner", err)
|
||||
return
|
||||
} else if isOwner {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_owner"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = repo_module.AddCollaborator(ctx, ctx.Repo.Repository, u); err != nil {
|
||||
ctx.ServerError("AddCollaborator", err)
|
||||
return
|
||||
}
|
||||
|
||||
if setting.Service.EnableNotifyMail {
|
||||
mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository)
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
}
|
||||
|
||||
// ChangeCollaborationAccessMode response for changing access of a collaboration
|
||||
func ChangeCollaborationAccessMode(ctx *context.Context) {
|
||||
if err := repo_model.ChangeCollaborationAccessMode(
|
||||
ctx,
|
||||
ctx.Repo.Repository,
|
||||
ctx.FormInt64("uid"),
|
||||
perm.AccessMode(ctx.FormInt("mode"))); err != nil {
|
||||
log.Error("ChangeCollaborationAccessMode: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCollaboration delete a collaboration for a repository
|
||||
func DeleteCollaboration(ctx *context.Context) {
|
||||
if err := models.DeleteCollaboration(ctx.Repo.Repository, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteCollaboration: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": ctx.Repo.RepoLink + "/settings/collaboration",
|
||||
})
|
||||
}
|
||||
|
||||
// AddTeamPost response for adding a team to a repository
|
||||
func AddTeamPost(ctx *context.Context) {
|
||||
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("team")))
|
||||
if len(name) == 0 {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
team, err := organization.OrgFromUser(ctx.Repo.Owner).GetTeam(ctx, name)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.team_not_exist"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
} else {
|
||||
ctx.ServerError("GetTeam", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if team.OrgID != ctx.Repo.Repository.OwnerID {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
if organization.HasTeamRepo(ctx, ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
if err = org_service.TeamAddRepository(team, ctx.Repo.Repository); err != nil {
|
||||
ctx.ServerError("TeamAddRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
}
|
||||
|
||||
// DeleteTeam response for deleting a team from a repository
|
||||
func DeleteTeam(ctx *context.Context) {
|
||||
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
team, err := organization.GetTeamByID(ctx, ctx.FormInt64("id"))
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTeamByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = models.RemoveRepository(team, ctx.Repo.Repository.ID); err != nil {
|
||||
ctx.ServerError("team.RemoveRepositorys", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success"))
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": ctx.Repo.RepoLink + "/settings/collaboration",
|
||||
})
|
||||
}
|
111
routers/web/repo/setting/deploy_key.go
Normal file
111
routers/web/repo/setting/deploy_key.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
||||
// DeployKeys render the deploy keys list of a repository page
|
||||
func DeployKeys(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") + " / " + ctx.Tr("secrets.secrets")
|
||||
ctx.Data["PageIsSettingsKeys"] = true
|
||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
|
||||
keys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID})
|
||||
if err != nil {
|
||||
ctx.ServerError("ListDeployKeys", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Deploykeys"] = keys
|
||||
|
||||
ctx.HTML(http.StatusOK, tplDeployKeys)
|
||||
}
|
||||
|
||||
// DeployKeysPost response for adding a deploy key of a repository
|
||||
func DeployKeysPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.AddKeyForm)
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
|
||||
ctx.Data["PageIsSettingsKeys"] = true
|
||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
|
||||
keys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID})
|
||||
if err != nil {
|
||||
ctx.ServerError("ListDeployKeys", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Deploykeys"] = keys
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplDeployKeys)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := asymkey_model.CheckPublicKeyString(form.Content)
|
||||
if err != nil {
|
||||
if db.IsErrSSHDisabled(err) {
|
||||
ctx.Flash.Info(ctx.Tr("settings.ssh_disabled"))
|
||||
} else if asymkey_model.IsErrKeyUnableVerify(err) {
|
||||
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
||||
} else if err == asymkey_model.ErrKeyIsPrivate {
|
||||
ctx.Data["HasError"] = true
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.Flash.Error(ctx.Tr("form.must_use_public_key"))
|
||||
} else {
|
||||
ctx.Data["HasError"] = true
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
|
||||
return
|
||||
}
|
||||
|
||||
key, err := asymkey_model.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, !form.IsWritable)
|
||||
if err != nil {
|
||||
ctx.Data["HasError"] = true
|
||||
switch {
|
||||
case asymkey_model.IsErrDeployKeyAlreadyExist(err):
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form)
|
||||
case asymkey_model.IsErrKeyAlreadyExist(err):
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form)
|
||||
case asymkey_model.IsErrKeyNameAlreadyUsed(err):
|
||||
ctx.Data["Err_Title"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form)
|
||||
case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err):
|
||||
ctx.Data["Err_Title"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form)
|
||||
default:
|
||||
ctx.ServerError("AddDeployKey", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID)
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
|
||||
}
|
||||
|
||||
// DeleteDeployKey response for deleting a deploy key
|
||||
func DeleteDeployKey(ctx *context.Context) {
|
||||
if err := asymkey_service.DeleteDeployKey(ctx.Doer, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteDeployKey: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success"))
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": ctx.Repo.RepoLink + "/settings/keys",
|
||||
})
|
||||
}
|
65
routers/web/repo/setting/git_hooks.go
Normal file
65
routers/web/repo/setting/git_hooks.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
)
|
||||
|
||||
// GitHooks hooks of a repository
|
||||
func GitHooks(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
|
||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||
|
||||
hooks, err := ctx.Repo.GitRepo.Hooks()
|
||||
if err != nil {
|
||||
ctx.ServerError("Hooks", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Hooks"] = hooks
|
||||
|
||||
ctx.HTML(http.StatusOK, tplGithooks)
|
||||
}
|
||||
|
||||
// GitHooksEdit render for editing a hook of repository page
|
||||
func GitHooksEdit(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
|
||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||
|
||||
name := ctx.Params(":name")
|
||||
hook, err := ctx.Repo.GitRepo.GetHook(name)
|
||||
if err != nil {
|
||||
if err == git.ErrNotValidHook {
|
||||
ctx.NotFound("GetHook", err)
|
||||
} else {
|
||||
ctx.ServerError("GetHook", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.Data["Hook"] = hook
|
||||
ctx.HTML(http.StatusOK, tplGithookEdit)
|
||||
}
|
||||
|
||||
// GitHooksEditPost response for editing a git hook of a repository
|
||||
func GitHooksEditPost(ctx *context.Context) {
|
||||
name := ctx.Params(":name")
|
||||
hook, err := ctx.Repo.GitRepo.GetHook(name)
|
||||
if err != nil {
|
||||
if err == git.ErrNotValidHook {
|
||||
ctx.NotFound("GetHook", err)
|
||||
} else {
|
||||
ctx.ServerError("GetHook", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
hook.Content = ctx.FormString("content")
|
||||
if err = hook.Update(); err != nil {
|
||||
ctx.ServerError("hook.Update", err)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git")
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
package setting
|
||||
|
||||
import (
|
||||
"bytes"
|
17
routers/web/repo/setting/main_test.go
Normal file
17
routers/web/repo/setting/main_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
GiteaRootPath: filepath.Join("..", "..", "..", ".."),
|
||||
})
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -19,8 +19,12 @@ import (
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
||||
const (
|
||||
tplTags base.TplName = "repo/settings/tags"
|
||||
)
|
||||
|
||||
// Tags render the page to protect tags
|
||||
func Tags(ctx *context.Context) {
|
||||
func ProtectedTags(ctx *context.Context) {
|
||||
if setTagsContext(ctx) != nil {
|
||||
return
|
||||
}
|
@@ -2,22 +2,18 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -32,17 +28,13 @@ import (
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
wiki_service "code.gitea.io/gitea/services/wiki"
|
||||
)
|
||||
@@ -51,7 +43,6 @@ const (
|
||||
tplSettingsOptions base.TplName = "repo/settings/options"
|
||||
tplCollaboration base.TplName = "repo/settings/collaboration"
|
||||
tplBranches base.TplName = "repo/settings/branches"
|
||||
tplTags base.TplName = "repo/settings/tags"
|
||||
tplGithooks base.TplName = "repo/settings/githooks"
|
||||
tplGithookEdit base.TplName = "repo/settings/githook_edit"
|
||||
tplDeployKeys base.TplName = "repo/settings/deploy_keys"
|
||||
@@ -895,398 +886,6 @@ func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.R
|
||||
ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form)
|
||||
}
|
||||
|
||||
// Collaboration render a repository's collaboration page
|
||||
func Collaboration(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.collaboration")
|
||||
ctx.Data["PageIsSettingsCollaboration"] = true
|
||||
|
||||
users, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCollaborators", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Collaborators"] = users
|
||||
|
||||
teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepoTeams", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Teams"] = teams
|
||||
ctx.Data["Repo"] = ctx.Repo.Repository
|
||||
ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID
|
||||
ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName
|
||||
ctx.Data["Org"] = ctx.Repo.Repository.Owner
|
||||
ctx.Data["Units"] = unit_model.Units
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCollaboration)
|
||||
}
|
||||
|
||||
// CollaborationPost response for actions for a collaboration of a repository
|
||||
func CollaborationPost(ctx *context.Context) {
|
||||
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator")))
|
||||
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
|
||||
u, err := user_model.GetUserByName(ctx, name)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
} else {
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !u.IsActive {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_inactive_user"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
|
||||
// Organization is not allowed to be added as a collaborator.
|
||||
if u.IsOrganization() {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
|
||||
if got, err := repo_model.IsCollaborator(ctx, ctx.Repo.Repository.ID, u.ID); err == nil && got {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_duplicate"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
// find the owner team of the organization the repo belongs too and
|
||||
// check if the user we're trying to add is an owner.
|
||||
if ctx.Repo.Repository.Owner.IsOrganization() {
|
||||
if isOwner, err := organization.IsOrganizationOwner(ctx, ctx.Repo.Repository.Owner.ID, u.ID); err != nil {
|
||||
ctx.ServerError("IsOrganizationOwner", err)
|
||||
return
|
||||
} else if isOwner {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_owner"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = repo_module.AddCollaborator(ctx, ctx.Repo.Repository, u); err != nil {
|
||||
ctx.ServerError("AddCollaborator", err)
|
||||
return
|
||||
}
|
||||
|
||||
if setting.Service.EnableNotifyMail {
|
||||
mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository)
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success"))
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
}
|
||||
|
||||
// ChangeCollaborationAccessMode response for changing access of a collaboration
|
||||
func ChangeCollaborationAccessMode(ctx *context.Context) {
|
||||
if err := repo_model.ChangeCollaborationAccessMode(
|
||||
ctx,
|
||||
ctx.Repo.Repository,
|
||||
ctx.FormInt64("uid"),
|
||||
perm.AccessMode(ctx.FormInt("mode"))); err != nil {
|
||||
log.Error("ChangeCollaborationAccessMode: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCollaboration delete a collaboration for a repository
|
||||
func DeleteCollaboration(ctx *context.Context) {
|
||||
if err := models.DeleteCollaboration(ctx.Repo.Repository, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteCollaboration: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": ctx.Repo.RepoLink + "/settings/collaboration",
|
||||
})
|
||||
}
|
||||
|
||||
// AddTeamPost response for adding a team to a repository
|
||||
func AddTeamPost(ctx *context.Context) {
|
||||
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("team")))
|
||||
if len(name) == 0 {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
team, err := organization.OrgFromUser(ctx.Repo.Owner).GetTeam(ctx, name)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.team_not_exist"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
} else {
|
||||
ctx.ServerError("GetTeam", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if team.OrgID != ctx.Repo.Repository.OwnerID {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
if organization.HasTeamRepo(ctx, ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
if err = org_service.TeamAddRepository(team, ctx.Repo.Repository); err != nil {
|
||||
ctx.ServerError("TeamAddRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
}
|
||||
|
||||
// DeleteTeam response for deleting a team from a repository
|
||||
func DeleteTeam(ctx *context.Context) {
|
||||
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
team, err := organization.GetTeamByID(ctx, ctx.FormInt64("id"))
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTeamByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = models.RemoveRepository(team, ctx.Repo.Repository.ID); err != nil {
|
||||
ctx.ServerError("team.RemoveRepositorys", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success"))
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": ctx.Repo.RepoLink + "/settings/collaboration",
|
||||
})
|
||||
}
|
||||
|
||||
// GitHooks hooks of a repository
|
||||
func GitHooks(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
|
||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||
|
||||
hooks, err := ctx.Repo.GitRepo.Hooks()
|
||||
if err != nil {
|
||||
ctx.ServerError("Hooks", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Hooks"] = hooks
|
||||
|
||||
ctx.HTML(http.StatusOK, tplGithooks)
|
||||
}
|
||||
|
||||
// GitHooksEdit render for editing a hook of repository page
|
||||
func GitHooksEdit(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
|
||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||
|
||||
name := ctx.Params(":name")
|
||||
hook, err := ctx.Repo.GitRepo.GetHook(name)
|
||||
if err != nil {
|
||||
if err == git.ErrNotValidHook {
|
||||
ctx.NotFound("GetHook", err)
|
||||
} else {
|
||||
ctx.ServerError("GetHook", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.Data["Hook"] = hook
|
||||
ctx.HTML(http.StatusOK, tplGithookEdit)
|
||||
}
|
||||
|
||||
// GitHooksEditPost response for editing a git hook of a repository
|
||||
func GitHooksEditPost(ctx *context.Context) {
|
||||
name := ctx.Params(":name")
|
||||
hook, err := ctx.Repo.GitRepo.GetHook(name)
|
||||
if err != nil {
|
||||
if err == git.ErrNotValidHook {
|
||||
ctx.NotFound("GetHook", err)
|
||||
} else {
|
||||
ctx.ServerError("GetHook", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
hook.Content = ctx.FormString("content")
|
||||
if err = hook.Update(); err != nil {
|
||||
ctx.ServerError("hook.Update", err)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git")
|
||||
}
|
||||
|
||||
// DeployKeys render the deploy keys list of a repository page
|
||||
func DeployKeys(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") + " / " + ctx.Tr("secrets.secrets")
|
||||
ctx.Data["PageIsSettingsKeys"] = true
|
||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
|
||||
keys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID})
|
||||
if err != nil {
|
||||
ctx.ServerError("ListDeployKeys", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Deploykeys"] = keys
|
||||
|
||||
ctx.HTML(http.StatusOK, tplDeployKeys)
|
||||
}
|
||||
|
||||
// DeployKeysPost response for adding a deploy key of a repository
|
||||
func DeployKeysPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.AddKeyForm)
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
|
||||
ctx.Data["PageIsSettingsKeys"] = true
|
||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
|
||||
keys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID})
|
||||
if err != nil {
|
||||
ctx.ServerError("ListDeployKeys", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Deploykeys"] = keys
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplDeployKeys)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := asymkey_model.CheckPublicKeyString(form.Content)
|
||||
if err != nil {
|
||||
if db.IsErrSSHDisabled(err) {
|
||||
ctx.Flash.Info(ctx.Tr("settings.ssh_disabled"))
|
||||
} else if asymkey_model.IsErrKeyUnableVerify(err) {
|
||||
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
||||
} else if err == asymkey_model.ErrKeyIsPrivate {
|
||||
ctx.Data["HasError"] = true
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.Flash.Error(ctx.Tr("form.must_use_public_key"))
|
||||
} else {
|
||||
ctx.Data["HasError"] = true
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
|
||||
return
|
||||
}
|
||||
|
||||
key, err := asymkey_model.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, !form.IsWritable)
|
||||
if err != nil {
|
||||
ctx.Data["HasError"] = true
|
||||
switch {
|
||||
case asymkey_model.IsErrDeployKeyAlreadyExist(err):
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form)
|
||||
case asymkey_model.IsErrKeyAlreadyExist(err):
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form)
|
||||
case asymkey_model.IsErrKeyNameAlreadyUsed(err):
|
||||
ctx.Data["Err_Title"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form)
|
||||
case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err):
|
||||
ctx.Data["Err_Title"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form)
|
||||
default:
|
||||
ctx.ServerError("AddDeployKey", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID)
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
|
||||
}
|
||||
|
||||
// DeleteDeployKey response for deleting a deploy key
|
||||
func DeleteDeployKey(ctx *context.Context) {
|
||||
if err := asymkey_service.DeleteDeployKey(ctx.Doer, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteDeployKey: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success"))
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": ctx.Repo.RepoLink + "/settings/keys",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAvatarSetting update repo's avatar
|
||||
func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error {
|
||||
ctxRepo := ctx.Repo.Repository
|
||||
|
||||
if form.Avatar == nil {
|
||||
// No avatar is uploaded and we not removing it here.
|
||||
// No random avatar generated here.
|
||||
// Just exit, no action.
|
||||
if ctxRepo.CustomAvatarRelativePath() == "" {
|
||||
log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
r, err := form.Avatar.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Avatar.Open: %w", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
if form.Avatar.Size > setting.Avatar.MaxFileSize {
|
||||
return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big"))
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("io.ReadAll: %w", err)
|
||||
}
|
||||
st := typesniffer.DetectContentType(data)
|
||||
if !(st.IsImage() && !st.IsSvgImage()) {
|
||||
return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image"))
|
||||
}
|
||||
if err = repo_service.UploadAvatar(ctx, ctxRepo, data); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SettingsAvatar save new POSTed repository avatar
|
||||
func SettingsAvatar(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.AvatarForm)
|
||||
form.Source = forms.AvatarLocal
|
||||
if err := UpdateAvatarSetting(ctx, *form); err != nil {
|
||||
ctx.Flash.Error(err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_avatar_success"))
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
}
|
||||
|
||||
// SettingsDeleteAvatar delete repository avatar
|
||||
func SettingsDeleteAvatar(ctx *context.Context) {
|
||||
if err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository); err != nil {
|
||||
ctx.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err))
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
}
|
||||
|
||||
func selectPushMirrorByForm(ctx *context.Context, form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) {
|
||||
id, err := strconv.ParseInt(form.PushMirrorID, 10, 64)
|
||||
if err != nil {
|
@@ -1,7 +1,7 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
package setting
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -2,7 +2,7 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
Reference in New Issue
Block a user