mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-27 09:11:53 -05:00
72e956b79a
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>
370 lines
13 KiB
Go
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))
|
|
}
|