Add tag protection (#15629)

* Added tag protection in hook.

* Prevent UI tag creation if protected.

* Added settings page.

* Added tests.

* Added suggestions.

* Moved tests.

* Use individual errors.

* Removed unneeded methods.

* Switched delete selector.

* Changed method names.

* No reason to be unique.

* Allow editing of protected tags.

* Removed unique key from migration.

* Added docs page.

* Changed date.

* Respond with 404 to not found tags.

* Replaced glob with regex pattern.

* Added support for glob and regex pattern.

* Updated documentation.

* Changed white* to allow*.

* Fixed edit button link.

* Added cancel button.

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
KN4CK3R 2021-06-25 16:28:55 +02:00 committed by GitHub
parent 7a0ed9a046
commit 44b8b07631
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1227 additions and 189 deletions

View file

@ -155,125 +155,202 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
private.GitQuarantinePath+"="+opts.GitQuarantinePath)
}
protectedTags, err := repo.GetProtectedTags()
if err != nil {
log.Error("Unable to get protected tags for %-v Error: %v", repo, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err.Error(),
})
return
}
// Iterate across the provided old commit IDs
for i := range opts.OldCommitIDs {
oldCommitID := opts.OldCommitIDs[i]
newCommitID := opts.NewCommitIDs[i]
refFullName := opts.RefFullNames[i]
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA {
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName),
})
return
}
if strings.HasPrefix(refFullName, git.BranchPrefix) {
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA {
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName),
})
return
}
protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
if err != nil {
log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err.Error(),
})
return
}
// Allow pushes to non-protected branches
if protectBranch == nil || !protectBranch.IsProtected() {
continue
}
// This ref is a protected branch.
//
// First of all we need to enforce absolutely:
//
// 1. Detect and prevent deletion of the branch
if newCommitID == git.EmptySHA {
log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is protected from deletion", branchName),
})
return
}
// 2. Disallow force pushes to protected branches
if git.EmptySHA != oldCommitID {
output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
if err != nil {
log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Fail to detect force push: %v", err),
Err: err.Error(),
})
return
} else if len(output) > 0 {
log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is protected from force push", branchName),
})
return
}
}
// 3. Enforce require signed commits
if protectBranch.RequireSignedCommits {
err := verifyCommits(oldCommitID, newCommitID, gitRepo, env)
if err != nil {
if !isErrUnverifiedCommit(err) {
log.Error("Unable to check commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
// Allow pushes to non-protected branches
if protectBranch == nil || !protectBranch.IsProtected() {
continue
}
// This ref is a protected branch.
//
// First of all we need to enforce absolutely:
//
// 1. Detect and prevent deletion of the branch
if newCommitID == git.EmptySHA {
log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is protected from deletion", branchName),
})
return
}
// 2. Disallow force pushes to protected branches
if git.EmptySHA != oldCommitID {
output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
if err != nil {
log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to check commits from %s to %s: %v", oldCommitID, newCommitID, err),
Err: fmt.Sprintf("Fail to detect force push: %v", err),
})
return
} else if len(output) > 0 {
log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is protected from force push", branchName),
})
return
}
}
// 3. Enforce require signed commits
if protectBranch.RequireSignedCommits {
err := verifyCommits(oldCommitID, newCommitID, gitRepo, env)
if err != nil {
if !isErrUnverifiedCommit(err) {
log.Error("Unable to check commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to check commits from %s to %s: %v", oldCommitID, newCommitID, err),
})
return
}
unverifiedCommit := err.(*errUnverifiedCommit).sha
log.Warn("Forbidden: Branch: %s in %-v is protected from unverified commit %s", branchName, repo, unverifiedCommit)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is protected from unverified commit %s", branchName, unverifiedCommit),
})
return
}
unverifiedCommit := err.(*errUnverifiedCommit).sha
log.Warn("Forbidden: Branch: %s in %-v is protected from unverified commit %s", branchName, repo, unverifiedCommit)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is protected from unverified commit %s", branchName, unverifiedCommit),
})
return
}
}
// Now there are several tests which can be overridden:
//
// 4. Check protected file patterns - this is overridable from the UI
changedProtectedfiles := false
protectedFilePath := ""
// Now there are several tests which can be overridden:
//
// 4. Check protected file patterns - this is overridable from the UI
changedProtectedfiles := false
protectedFilePath := ""
globs := protectBranch.GetProtectedFilePatterns()
if len(globs) > 0 {
_, err := pull_service.CheckFileProtection(oldCommitID, newCommitID, globs, 1, env, gitRepo)
if err != nil {
if !models.IsErrFilePathProtected(err) {
log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
globs := protectBranch.GetProtectedFilePatterns()
if len(globs) > 0 {
_, err := pull_service.CheckFileProtection(oldCommitID, newCommitID, globs, 1, env, gitRepo)
if err != nil {
if !models.IsErrFilePathProtected(err) {
log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err),
})
return
}
changedProtectedfiles = true
protectedFilePath = err.(models.ErrFilePathProtected).Path
}
}
// 5. Check if the doer is allowed to push
canPush := false
if opts.IsDeployKey {
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
} else {
canPush = !changedProtectedfiles && protectBranch.CanUserPush(opts.UserID)
}
// 6. If we're not allowed to push directly
if !canPush {
// Is this is a merge from the UI/API?
if opts.PullRequestID == 0 {
// 6a. If we're not merging from the UI/API then there are two ways we got here:
//
// We are changing a protected file and we're not allowed to do that
if changedProtectedfiles {
log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath),
})
return
}
// Or we're simply not able to push to this protected branch
log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", opts.UserID, branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
})
return
}
// 6b. Merge (from UI or API)
// Get the PR, user and permissions for the user in the repository
pr, err := models.GetPullRequestByID(opts.PullRequestID)
if err != nil {
log.Error("Unable to get PullRequest %d Error: %v", opts.PullRequestID, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err),
Err: fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.PullRequestID, err),
})
return
}
user, err := models.GetUserByID(opts.UserID)
if err != nil {
log.Error("Unable to get User id %d Error: %v", opts.UserID, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get User id %d Error: %v", opts.UserID, err),
})
return
}
perm, err := models.GetUserRepoPermission(repo, user)
if err != nil {
log.Error("Unable to get Repo permission of repo %s/%s of User %s", repo.OwnerName, repo.Name, user.Name, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", repo.OwnerName, repo.Name, user.Name, err),
})
return
}
changedProtectedfiles = true
protectedFilePath = err.(models.ErrFilePathProtected).Path
}
}
// Now check if the user is allowed to merge PRs for this repository
allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, user)
if err != nil {
log.Error("Error calculating if allowed to merge: %v", err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Error calculating if allowed to merge: %v", err),
})
return
}
// 5. Check if the doer is allowed to push
canPush := false
if opts.IsDeployKey {
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
} else {
canPush = !changedProtectedfiles && protectBranch.CanUserPush(opts.UserID)
}
if !allowedMerge {
log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", opts.UserID, branchName, repo, pr.Index)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
})
return
}
// 6. If we're not allowed to push directly
if !canPush {
// Is this is a merge from the UI/API?
if opts.PullRequestID == 0 {
// 6a. If we're not merging from the UI/API then there are two ways we got here:
//
// We are changing a protected file and we're not allowed to do that
// If we're an admin for the repository we can ignore status checks, reviews and override protected files
if perm.IsAdmin() {
continue
}
// Now if we're not an admin - we can't overwrite protected files so fail now
if changedProtectedfiles {
log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
ctx.JSON(http.StatusForbidden, private.Response{
@ -282,88 +359,44 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
return
}
// Or we're simply not able to push to this protected branch
log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", opts.UserID, branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
})
return
}
// 6b. Merge (from UI or API)
// Get the PR, user and permissions for the user in the repository
pr, err := models.GetPullRequestByID(opts.PullRequestID)
if err != nil {
log.Error("Unable to get PullRequest %d Error: %v", opts.PullRequestID, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.PullRequestID, err),
})
return
}
user, err := models.GetUserByID(opts.UserID)
if err != nil {
log.Error("Unable to get User id %d Error: %v", opts.UserID, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get User id %d Error: %v", opts.UserID, err),
})
return
}
perm, err := models.GetUserRepoPermission(repo, user)
if err != nil {
log.Error("Unable to get Repo permission of repo %s/%s of User %s", repo.OwnerName, repo.Name, user.Name, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", repo.OwnerName, repo.Name, user.Name, err),
})
return
}
// Now check if the user is allowed to merge PRs for this repository
allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, user)
if err != nil {
log.Error("Error calculating if allowed to merge: %v", err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Error calculating if allowed to merge: %v", err),
})
return
}
if !allowedMerge {
log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", opts.UserID, branchName, repo, pr.Index)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
})
return
}
// If we're an admin for the repository we can ignore status checks, reviews and override protected files
if perm.IsAdmin() {
continue
}
// Now if we're not an admin - we can't overwrite protected files so fail now
if changedProtectedfiles {
log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath),
})
return
}
// Check all status checks and reviews are ok
if err := pull_service.CheckPRReadyToMerge(pr, true); err != nil {
if models.IsErrNotAllowedToMerge(err) {
log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", opts.UserID, branchName, repo, pr.Index, err.Error())
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, opts.PullRequestID, err.Error()),
// Check all status checks and reviews are ok
if err := pull_service.CheckPRReadyToMerge(pr, true); err != nil {
if models.IsErrNotAllowedToMerge(err) {
log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", opts.UserID, branchName, repo, pr.Index, err.Error())
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, opts.PullRequestID, err.Error()),
})
return
}
log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", opts.UserID, branchName, repo, pr.Index, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get status of pull request %d. Error: %v", opts.PullRequestID, err),
})
return
}
log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", opts.UserID, branchName, repo, pr.Index, err)
}
} else if strings.HasPrefix(refFullName, git.TagPrefix) {
tagName := strings.TrimPrefix(refFullName, git.TagPrefix)
isAllowed, err := models.IsUserAllowedToControlTag(protectedTags, tagName, opts.UserID)
if err != nil {
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get status of pull request %d. Error: %v", opts.PullRequestID, err),
Err: err.Error(),
})
return
}
if !isAllowed {
log.Warn("Forbidden: Tag %s in %-v is protected", tagName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
Err: fmt.Sprintf("Tag %s is protected", tagName),
})
return
}
} else {
log.Error("Unexpected ref: %s", refFullName)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unexpected ref: %s", refFullName),
})
}
}

View file

@ -322,6 +322,18 @@ func NewReleasePost(ctx *context.Context) {
return
}
if models.IsErrInvalidTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_invalid"))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
if models.IsErrProtectedTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
ctx.ServerError("releaseservice.CreateNewTag", err)
return
}
@ -333,7 +345,9 @@ func NewReleasePost(ctx *context.Context) {
rel = &models.Release{
RepoID: ctx.Repo.Repository.ID,
Repo: ctx.Repo.Repository,
PublisherID: ctx.User.ID,
Publisher: ctx.User,
Title: form.Title,
TagName: form.TagName,
Target: form.Target,
@ -350,6 +364,8 @@ func NewReleasePost(ctx *context.Context) {
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
case models.IsErrInvalidTagName(err):
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
case models.IsErrProtectedTagName(err):
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form)
default:
ctx.ServerError("CreateRelease", err)
}

View file

@ -40,6 +40,7 @@ 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"

182
routers/web/repo/tag.go Normal file
View file

@ -0,0 +1,182 @@
// Copyright 2021 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 (
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
)
// Tags render the page to protect tags
func Tags(ctx *context.Context) {
if setTagsContext(ctx) != nil {
return
}
ctx.HTML(http.StatusOK, tplTags)
}
// NewProtectedTagPost handles creation of a protect tag
func NewProtectedTagPost(ctx *context.Context) {
if setTagsContext(ctx) != nil {
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplTags)
return
}
repo := ctx.Repo.Repository
form := web.GetForm(ctx).(*forms.ProtectTagForm)
pt := &models.ProtectedTag{
RepoID: repo.ID,
NamePattern: strings.TrimSpace(form.NamePattern),
}
if strings.TrimSpace(form.AllowlistUsers) != "" {
pt.AllowlistUserIDs, _ = base.StringsToInt64s(strings.Split(form.AllowlistUsers, ","))
}
if strings.TrimSpace(form.AllowlistTeams) != "" {
pt.AllowlistTeamIDs, _ = base.StringsToInt64s(strings.Split(form.AllowlistTeams, ","))
}
if err := models.InsertProtectedTag(pt); err != nil {
ctx.ServerError("InsertProtectedTag", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
}
// EditProtectedTag render the page to edit a protect tag
func EditProtectedTag(ctx *context.Context) {
if setTagsContext(ctx) != nil {
return
}
ctx.Data["PageIsEditProtectedTag"] = true
pt := selectProtectedTagByContext(ctx)
if pt == nil {
return
}
ctx.Data["name_pattern"] = pt.NamePattern
ctx.Data["allowlist_users"] = strings.Join(base.Int64sToStrings(pt.AllowlistUserIDs), ",")
ctx.Data["allowlist_teams"] = strings.Join(base.Int64sToStrings(pt.AllowlistTeamIDs), ",")
ctx.HTML(http.StatusOK, tplTags)
}
// EditProtectedTagPost handles creation of a protect tag
func EditProtectedTagPost(ctx *context.Context) {
if setTagsContext(ctx) != nil {
return
}
ctx.Data["PageIsEditProtectedTag"] = true
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplTags)
return
}
pt := selectProtectedTagByContext(ctx)
if pt == nil {
return
}
form := web.GetForm(ctx).(*forms.ProtectTagForm)
pt.NamePattern = strings.TrimSpace(form.NamePattern)
pt.AllowlistUserIDs, _ = base.StringsToInt64s(strings.Split(form.AllowlistUsers, ","))
pt.AllowlistTeamIDs, _ = base.StringsToInt64s(strings.Split(form.AllowlistTeams, ","))
if err := models.UpdateProtectedTag(pt); err != nil {
ctx.ServerError("UpdateProtectedTag", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags")
}
// DeleteProtectedTagPost handles deletion of a protected tag
func DeleteProtectedTagPost(ctx *context.Context) {
pt := selectProtectedTagByContext(ctx)
if pt == nil {
return
}
if err := models.DeleteProtectedTag(pt); err != nil {
ctx.ServerError("DeleteProtectedTag", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags")
}
func setTagsContext(ctx *context.Context) error {
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsTags"] = true
protectedTags, err := ctx.Repo.Repository.GetProtectedTags()
if err != nil {
ctx.ServerError("GetProtectedTags", err)
return err
}
ctx.Data["ProtectedTags"] = protectedTags
users, err := ctx.Repo.Repository.GetReaders()
if err != nil {
ctx.ServerError("Repo.Repository.GetReaders", err)
return err
}
ctx.Data["Users"] = users
if ctx.Repo.Owner.IsOrganization() {
teams, err := ctx.Repo.Owner.TeamsWithAccessToRepo(ctx.Repo.Repository.ID, models.AccessModeRead)
if err != nil {
ctx.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
return err
}
ctx.Data["Teams"] = teams
}
return nil
}
func selectProtectedTagByContext(ctx *context.Context) *models.ProtectedTag {
id := ctx.QueryInt64("id")
if id == 0 {
id = ctx.ParamsInt64(":id")
}
tag, err := models.GetProtectedTagByID(id)
if err != nil {
ctx.ServerError("GetProtectedTagByID", err)
return nil
}
if tag != nil && tag.RepoID == ctx.Repo.Repository.ID {
return tag
}
ctx.NotFound("", fmt.Errorf("ProtectedTag[%v] not associated to repository %v", id, ctx.Repo.Repository))
return nil
}

View file

@ -594,12 +594,21 @@ func RegisterRoutes(m *web.Route) {
m.Post("/delete", repo.DeleteTeam)
})
})
m.Group("/branches", func() {
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
m.Combo("/*").Get(repo.SettingsProtectedBranch).
Post(bindIgnErr(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost)
}, repo.MustBeNotEmpty)
m.Group("/tags", func() {
m.Get("", repo.Tags)
m.Post("", bindIgnErr(forms.ProtectTagForm{}), context.RepoMustNotBeArchived(), repo.NewProtectedTagPost)
m.Post("/delete", context.RepoMustNotBeArchived(), repo.DeleteProtectedTagPost)
m.Get("/{id}", repo.EditProtectedTag)
m.Post("/{id}", bindIgnErr(forms.ProtectTagForm{}), context.RepoMustNotBeArchived(), repo.EditProtectedTagPost)
})
m.Group("/hooks/git", func() {
m.Get("", repo.GitHooks)
m.Combo("/{name}").Get(repo.GitHooksEdit).