1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2024-11-24 08:57:03 -05:00
forgejo/routers/web/repo/setting_protected_branch.go
Hester Gong 72e956b79a
Improve protected branch setting page (#24379)
Main changes:

1. Change html structure of protected branch page, use [`grouped
fields`](https://fomantic-ui.com/collections/form.html#grouped-fields)
instead of `fields` for better margin, and wrap `grouped fields` around
related `field`s, remove unnecessary `<div id="protection_box"
class="fields">` outer div

2. Changed some order of field to make them more categorized, used `ui
dividing header` for categorization and fine tune css.

Before:

<img width="1907" alt="Screen Shot 2023-04-27 at 14 56 19"
src="https://user-images.githubusercontent.com/17645053/234783731-bce8a7ce-dfc9-4d47-a3a8-b962ebea9467.png">
<img width="1849" alt="Screen Shot 2023-04-27 at 14 56 30"
src="https://user-images.githubusercontent.com/17645053/234783740-c47d314e-5e2d-4854-98fd-c88f85ef3584.png">
<img width="1872" alt="Screen Shot 2023-04-27 at 14 56 36"
src="https://user-images.githubusercontent.com/17645053/234783745-18e35a75-07e8-451d-b001-f9bcf16fcab5.png">

After:


https://user-images.githubusercontent.com/17645053/235114568-da010aad-7654-4410-ab8c-5d0fce7edadb.mov



3. Changed "Enable Merge Whitelist" to radio checkbox, and added "Enable
Merge" radio checkbox, which are exclusive

Before:

<img width="926" alt="Screen Shot 2023-04-28 at 13 08 29"
src="https://user-images.githubusercontent.com/17645053/235059233-75790f7a-e5ea-4e1c-82c6-509fef8b84b3.png">

After:

<img width="942" alt="Screen Shot 2023-04-28 at 13 09 28"
src="https://user-images.githubusercontent.com/17645053/235059367-852d1f61-8407-4126-8c79-315b9c1ffada.png">


4. Add a link to set default branch on branch list page (with reference
to github)


https://user-images.githubusercontent.com/17645053/234787404-61c1c7b6-aabf-429f-a109-5b690e4e0b5a.mov

5. Removed dead codes.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-04-29 06:44:52 -04:00

370 lines
13 KiB
Go

// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"fmt"
"net/http"
"strings"
"time"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
pull_service "code.gitea.io/gitea/services/pull"
"code.gitea.io/gitea/services/repository"
)
const (
tplProtectedBranch base.TplName = "repo/settings/protected_branch"
)
// ProtectedBranchRules render the page to protect the repository
func ProtectedBranchRules(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.branches")
ctx.Data["PageIsSettingsBranches"] = true
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("GetProtectedBranches", err)
return
}
ctx.Data["ProtectedBranches"] = rules
ctx.HTML(http.StatusOK, tplBranches)
}
// SetDefaultBranchPost set default branch
func SetDefaultBranchPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.branches.update_default_branch")
ctx.Data["PageIsSettingsBranches"] = true
repo := ctx.Repo.Repository
switch ctx.FormString("action") {
case "default_branch":
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplBranches)
return
}
branch := ctx.FormString("branch")
if !ctx.Repo.GitRepo.IsBranchExist(branch) {
ctx.Status(http.StatusNotFound)
return
} else if repo.DefaultBranch != branch {
repo.DefaultBranch = branch
if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.ServerError("SetDefaultBranch", err)
return
}
}
if err := repo_model.UpdateDefaultBranch(repo); err != nil {
ctx.ServerError("SetDefaultBranch", err)
return
}
}
log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
default:
ctx.NotFound("", nil)
}
}
// SettingsProtectedBranch renders the protected branch setting page
func SettingsProtectedBranch(c *context.Context) {
ruleName := c.FormString("rule_name")
var rule *git_model.ProtectedBranch
if ruleName != "" {
var err error
rule, err = git_model.GetProtectedBranchRuleByName(c, c.Repo.Repository.ID, ruleName)
if err != nil {
c.ServerError("GetProtectBranchOfRepoByName", err)
return
}
}
if rule == nil {
// No options found, create defaults.
rule = &git_model.ProtectedBranch{}
}
c.Data["PageIsSettingsBranches"] = true
c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + rule.RuleName
users, err := access_model.GetRepoReaders(c.Repo.Repository)
if err != nil {
c.ServerError("Repo.Repository.GetReaders", err)
return
}
c.Data["Users"] = users
c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",")
c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",")
c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",")
contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c, c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
for _, ctx := range rule.StatusCheckContexts {
var found bool
for i := range contexts {
if contexts[i] == ctx {
found = true
break
}
}
if !found {
contexts = append(contexts, ctx)
}
}
c.Data["branch_status_check_contexts"] = contexts
if c.Repo.Owner.IsOrganization() {
teams, err := organization.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c.Repo.Repository.ID, perm.AccessModeRead)
if err != nil {
c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
return
}
c.Data["Teams"] = teams
c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",")
c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",")
c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",")
}
c.Data["Rule"] = rule
c.HTML(http.StatusOK, tplProtectedBranch)
}
// SettingsProtectedBranchPost updates the protected branch settings
func SettingsProtectedBranchPost(ctx *context.Context) {
f := web.GetForm(ctx).(*forms.ProtectBranchForm)
var protectBranch *git_model.ProtectedBranch
if f.RuleName == "" {
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_rule_name"))
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit", ctx.Repo.RepoLink))
return
}
var err error
if f.RuleID > 0 {
// If the RuleID isn't 0, it must be an edit operation. So we get rule by id.
protectBranch, err = git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, f.RuleID)
if err != nil {
ctx.ServerError("GetProtectBranchOfRepoByID", err)
return
}
if protectBranch != nil && protectBranch.RuleName != f.RuleName {
// RuleName changed. We need to check if there is a rule with the same name.
// If a rule with the same name exists, an error should be returned.
sameNameProtectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName)
if err != nil {
ctx.ServerError("GetProtectBranchOfRepoByName", err)
return
}
if sameNameProtectBranch != nil {
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_duplicate_rule_name"))
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName))
return
}
}
} else {
// FIXME: If a new ProtectBranch has a duplicate RuleName, an error should be returned.
// Currently, if a new ProtectBranch with a duplicate RuleName is created, the existing ProtectBranch will be updated.
// But we cannot modify this logic now because many unit tests rely on it.
protectBranch, err = git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName)
if err != nil {
ctx.ServerError("GetProtectBranchOfRepoByName", err)
return
}
}
if protectBranch == nil {
// No options found, create defaults.
protectBranch = &git_model.ProtectedBranch{
RepoID: ctx.Repo.Repository.ID,
RuleName: f.RuleName,
}
}
var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
protectBranch.RuleName = f.RuleName
if f.RequiredApprovals < 0 {
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, f.RuleName))
return
}
switch f.EnablePush {
case "all":
protectBranch.CanPush = true
protectBranch.EnableWhitelist = false
protectBranch.WhitelistDeployKeys = false
case "whitelist":
protectBranch.CanPush = true
protectBranch.EnableWhitelist = true
protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys
if strings.TrimSpace(f.WhitelistUsers) != "" {
whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ","))
}
if strings.TrimSpace(f.WhitelistTeams) != "" {
whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ","))
}
default:
protectBranch.CanPush = false
protectBranch.EnableWhitelist = false
protectBranch.WhitelistDeployKeys = false
}
protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
if f.EnableMergeWhitelist {
if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ","))
}
if strings.TrimSpace(f.MergeWhitelistTeams) != "" {
mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ","))
}
}
protectBranch.EnableStatusCheck = f.EnableStatusCheck
if f.EnableStatusCheck {
protectBranch.StatusCheckContexts = f.StatusCheckContexts
} else {
protectBranch.StatusCheckContexts = nil
}
protectBranch.RequiredApprovals = f.RequiredApprovals
protectBranch.EnableApprovalsWhitelist = f.EnableApprovalsWhitelist
if f.EnableApprovalsWhitelist {
if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ","))
}
if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" {
approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ","))
}
}
protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
protectBranch.RequireSignedCommits = f.RequireSignedCommits
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,
TeamIDs: whitelistTeams,
MergeUserIDs: mergeWhitelistUsers,
MergeTeamIDs: mergeWhitelistTeams,
ApprovalsUserIDs: approvalsWhitelistUsers,
ApprovalsTeamIDs: approvalsWhitelistTeams,
})
if err != nil {
ctx.ServerError("UpdateProtectBranch", err)
return
}
// FIXME: since we only need to recheck files protected rules, we could improve this
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
if err != nil {
ctx.ServerError("FindAllMatchedBranches", err)
return
}
for _, branchName := range matchedBranches {
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil {
ctx.ServerError("CheckPRsForBaseBranch", err)
return
}
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", protectBranch.RuleName))
ctx.Redirect(fmt.Sprintf("%s/settings/branches?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName))
}
// DeleteProtectedBranchRulePost delete protected branch rule by id
func DeleteProtectedBranchRulePost(ctx *context.Context) {
ruleID := ctx.ParamsInt64("id")
if ruleID <= 0 {
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
})
return
}
rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID)
if err != nil {
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
})
return
}
if rule == nil {
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
})
return
}
if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, ruleID); err != nil {
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName))
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
})
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName))
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
})
}
// RenameBranchPost responses for rename a branch
func RenameBranchPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RenameBranchForm)
if !ctx.Repo.CanCreateBranch() {
ctx.NotFound("RenameBranch", nil)
return
}
if ctx.HasError() {
ctx.Flash.Error(ctx.GetErrMsg())
ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
return
}
msg, err := repository.RenameBranch(ctx, ctx.Repo.Repository, ctx.Doer, ctx.Repo.GitRepo, form.From, form.To)
if err != nil {
ctx.ServerError("RenameBranch", err)
return
}
if msg == "target_exist" {
ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To))
ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
return
}
if msg == "from_not_exist" {
ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From))
ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To))
ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
}