loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Supports wildcard protected branch (#20825)

This PR introduce glob match for protected branch name. The separator is
`/` and you can use `*` matching non-separator chars and use `**` across
separator.

It also supports input an exist or non-exist branch name as matching
condition and branch name condition has high priority than glob rule.

Should fix #2529 and #15705

screenshots

<img width="1160" alt="image"
src="https://user-images.githubusercontent.com/81045/205651179-ebb5492a-4ade-4bb4-a13c-965e8c927063.png">

Co-authored-by: zeripath <art27@cantab.net>

authored by

Lunny Xiao
zeripath
and committed by
GitHub
2782c143 cc1f8cbe

+1216 -813
+15 -415
models/git/branches.go
··· 6 6 import ( 7 7 "context" 8 8 "fmt" 9 - "strings" 10 9 "time" 11 10 12 11 "code.gitea.io/gitea/models/db" 13 - "code.gitea.io/gitea/models/organization" 14 - "code.gitea.io/gitea/models/perm" 15 - access_model "code.gitea.io/gitea/models/perm/access" 16 12 repo_model "code.gitea.io/gitea/models/repo" 17 - "code.gitea.io/gitea/models/unit" 18 13 user_model "code.gitea.io/gitea/models/user" 19 - "code.gitea.io/gitea/modules/base" 20 14 "code.gitea.io/gitea/modules/log" 21 15 "code.gitea.io/gitea/modules/timeutil" 22 - "code.gitea.io/gitea/modules/util" 23 - 24 - "github.com/gobwas/glob" 25 16 ) 26 17 27 - // ProtectedBranch struct 28 - type ProtectedBranch struct { 29 - ID int64 `xorm:"pk autoincr"` 30 - RepoID int64 `xorm:"UNIQUE(s)"` 31 - BranchName string `xorm:"UNIQUE(s)"` 32 - CanPush bool `xorm:"NOT NULL DEFAULT false"` 33 - EnableWhitelist bool 34 - WhitelistUserIDs []int64 `xorm:"JSON TEXT"` 35 - WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 36 - EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` 37 - WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"` 38 - MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` 39 - MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 40 - EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` 41 - StatusCheckContexts []string `xorm:"JSON TEXT"` 42 - EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"` 43 - ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` 44 - ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 45 - RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"` 46 - BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"` 47 - BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"` 48 - BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"` 49 - DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` 50 - RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"` 51 - ProtectedFilePatterns string `xorm:"TEXT"` 52 - UnprotectedFilePatterns string `xorm:"TEXT"` 53 - 54 - CreatedUnix timeutil.TimeStamp `xorm:"created"` 55 - UpdatedUnix timeutil.TimeStamp `xorm:"updated"` 56 - } 57 - 58 - func init() { 59 - db.RegisterModel(new(ProtectedBranch)) 60 - db.RegisterModel(new(DeletedBranch)) 61 - db.RegisterModel(new(RenamedBranch)) 62 - } 63 - 64 - // IsProtected returns if the branch is protected 65 - func (protectBranch *ProtectedBranch) IsProtected() bool { 66 - return protectBranch.ID > 0 67 - } 68 - 69 - // CanUserPush returns if some user could push to this protected branch 70 - func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, userID int64) bool { 71 - if !protectBranch.CanPush { 72 - return false 73 - } 74 - 75 - if !protectBranch.EnableWhitelist { 76 - if user, err := user_model.GetUserByID(ctx, userID); err != nil { 77 - log.Error("GetUserByID: %v", err) 78 - return false 79 - } else if repo, err := repo_model.GetRepositoryByID(ctx, protectBranch.RepoID); err != nil { 80 - log.Error("repo_model.GetRepositoryByID: %v", err) 81 - return false 82 - } else if writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite); err != nil { 83 - log.Error("HasAccessUnit: %v", err) 84 - return false 85 - } else { 86 - return writeAccess 87 - } 88 - } 89 - 90 - if base.Int64sContains(protectBranch.WhitelistUserIDs, userID) { 91 - return true 92 - } 93 - 94 - if len(protectBranch.WhitelistTeamIDs) == 0 { 95 - return false 96 - } 97 - 98 - in, err := organization.IsUserInTeams(ctx, userID, protectBranch.WhitelistTeamIDs) 99 - if err != nil { 100 - log.Error("IsUserInTeams: %v", err) 101 - return false 102 - } 103 - return in 104 - } 105 - 106 - // IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch 107 - func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool { 108 - if !protectBranch.EnableMergeWhitelist { 109 - // Then we need to fall back on whether the user has write permission 110 - return permissionInRepo.CanWrite(unit.TypeCode) 111 - } 112 - 113 - if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) { 114 - return true 115 - } 116 - 117 - if len(protectBranch.MergeWhitelistTeamIDs) == 0 { 118 - return false 119 - } 120 - 121 - in, err := organization.IsUserInTeams(ctx, userID, protectBranch.MergeWhitelistTeamIDs) 122 - if err != nil { 123 - log.Error("IsUserInTeams: %v", err) 124 - return false 125 - } 126 - return in 127 - } 128 - 129 - // IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals) 130 - func IsUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) { 131 - repo, err := repo_model.GetRepositoryByID(ctx, protectBranch.RepoID) 132 - if err != nil { 133 - return false, err 134 - } 135 - 136 - if !protectBranch.EnableApprovalsWhitelist { 137 - // Anyone with write access is considered official reviewer 138 - writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite) 139 - if err != nil { 140 - return false, err 141 - } 142 - return writeAccess, nil 143 - } 144 - 145 - if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) { 146 - return true, nil 147 - } 148 - 149 - inTeam, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ApprovalsWhitelistTeamIDs) 150 - if err != nil { 151 - return false, err 152 - } 153 - 154 - return inTeam, nil 155 - } 156 - 157 - // GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice 158 - func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob { 159 - return getFilePatterns(protectBranch.ProtectedFilePatterns) 160 - } 161 - 162 - // GetUnprotectedFilePatterns parses a semicolon separated list of unprotected file patterns and returns a glob.Glob slice 163 - func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob { 164 - return getFilePatterns(protectBranch.UnprotectedFilePatterns) 165 - } 166 - 167 - func getFilePatterns(filePatterns string) []glob.Glob { 168 - extarr := make([]glob.Glob, 0, 10) 169 - for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") { 170 - expr = strings.TrimSpace(expr) 171 - if expr != "" { 172 - if g, err := glob.Compile(expr, '.', '/'); err != nil { 173 - log.Info("Invalid glob expression '%s' (skipped): %v", expr, err) 174 - } else { 175 - extarr = append(extarr, g) 176 - } 177 - } 178 - } 179 - return extarr 180 - } 181 - 182 - // MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change 183 - func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(changedProtectedFiles []string) bool { 184 - glob := protectBranch.GetProtectedFilePatterns() 185 - if len(glob) == 0 { 186 - return false 187 - } 188 - 189 - return len(changedProtectedFiles) > 0 190 - } 191 - 192 - // IsProtectedFile return if path is protected 193 - func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path string) bool { 194 - if len(patterns) == 0 { 195 - patterns = protectBranch.GetProtectedFilePatterns() 196 - if len(patterns) == 0 { 197 - return false 198 - } 199 - } 200 - 201 - lpath := strings.ToLower(strings.TrimSpace(path)) 202 - 203 - r := false 204 - for _, pat := range patterns { 205 - if pat.Match(lpath) { 206 - r = true 207 - break 208 - } 209 - } 210 - 211 - return r 212 - } 213 - 214 - // IsUnprotectedFile return if path is unprotected 215 - func (protectBranch *ProtectedBranch) IsUnprotectedFile(patterns []glob.Glob, path string) bool { 216 - if len(patterns) == 0 { 217 - patterns = protectBranch.GetUnprotectedFilePatterns() 218 - if len(patterns) == 0 { 219 - return false 220 - } 221 - } 222 - 223 - lpath := strings.ToLower(strings.TrimSpace(path)) 224 - 225 - r := false 226 - for _, pat := range patterns { 227 - if pat.Match(lpath) { 228 - r = true 229 - break 230 - } 231 - } 232 - 233 - return r 234 - } 235 - 236 - // GetProtectedBranchBy getting protected branch by ID/Name 237 - func GetProtectedBranchBy(ctx context.Context, repoID int64, branchName string) (*ProtectedBranch, error) { 238 - rel := &ProtectedBranch{RepoID: repoID, BranchName: branchName} 239 - has, err := db.GetByBean(ctx, rel) 240 - if err != nil { 241 - return nil, err 242 - } 243 - if !has { 244 - return nil, nil 245 - } 246 - return rel, nil 247 - } 248 - 249 - // WhitelistOptions represent all sorts of whitelists used for protected branches 250 - type WhitelistOptions struct { 251 - UserIDs []int64 252 - TeamIDs []int64 253 - 254 - MergeUserIDs []int64 255 - MergeTeamIDs []int64 256 - 257 - ApprovalsUserIDs []int64 258 - ApprovalsTeamIDs []int64 259 - } 260 - 261 - // UpdateProtectBranch saves branch protection options of repository. 262 - // If ID is 0, it creates a new record. Otherwise, updates existing record. 263 - // This function also performs check if whitelist user and team's IDs have been changed 264 - // to avoid unnecessary whitelist delete and regenerate. 265 - func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) { 266 - if err = repo.GetOwner(ctx); err != nil { 267 - return fmt.Errorf("GetOwner: %w", err) 268 - } 269 - 270 - whitelist, err := updateUserWhitelist(ctx, repo, protectBranch.WhitelistUserIDs, opts.UserIDs) 271 - if err != nil { 272 - return err 273 - } 274 - protectBranch.WhitelistUserIDs = whitelist 275 - 276 - whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs) 277 - if err != nil { 278 - return err 279 - } 280 - protectBranch.MergeWhitelistUserIDs = whitelist 281 - 282 - whitelist, err = updateApprovalWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs) 283 - if err != nil { 284 - return err 285 - } 286 - protectBranch.ApprovalsWhitelistUserIDs = whitelist 287 - 288 - // if the repo is in an organization 289 - whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs) 290 - if err != nil { 291 - return err 292 - } 293 - protectBranch.WhitelistTeamIDs = whitelist 294 - 295 - whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs) 296 - if err != nil { 297 - return err 298 - } 299 - protectBranch.MergeWhitelistTeamIDs = whitelist 300 - 301 - whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs) 302 - if err != nil { 303 - return err 304 - } 305 - protectBranch.ApprovalsWhitelistTeamIDs = whitelist 306 - 307 - // Make sure protectBranch.ID is not 0 for whitelists 308 - if protectBranch.ID == 0 { 309 - if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil { 310 - return fmt.Errorf("Insert: %w", err) 311 - } 312 - return nil 313 - } 314 - 315 - if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { 316 - return fmt.Errorf("Update: %w", err) 317 - } 318 - 319 - return nil 320 - } 321 - 322 - // GetProtectedBranches get all protected branches 323 - func GetProtectedBranches(ctx context.Context, repoID int64) ([]*ProtectedBranch, error) { 324 - protectedBranches := make([]*ProtectedBranch, 0) 325 - return protectedBranches, db.GetEngine(ctx).Find(&protectedBranches, &ProtectedBranch{RepoID: repoID}) 326 - } 327 - 328 - // IsProtectedBranch checks if branch is protected 329 - func IsProtectedBranch(ctx context.Context, repoID int64, branchName string) (bool, error) { 330 - protectedBranch := &ProtectedBranch{ 331 - RepoID: repoID, 332 - BranchName: branchName, 333 - } 334 - 335 - has, err := db.GetEngine(ctx).Exist(protectedBranch) 336 - if err != nil { 337 - return true, err 338 - } 339 - return has, nil 340 - } 341 - 342 - // updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with 343 - // the users from newWhitelist which have explicit read or write access to the repo. 344 - func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 345 - hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) 346 - if !hasUsersChanged { 347 - return currentWhitelist, nil 348 - } 349 - 350 - whitelist = make([]int64, 0, len(newWhitelist)) 351 - for _, userID := range newWhitelist { 352 - if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil { 353 - return nil, err 354 - } else if !reader { 355 - continue 356 - } 357 - whitelist = append(whitelist, userID) 358 - } 359 - 360 - return whitelist, err 361 - } 362 - 363 - // updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with 364 - // the users from newWhitelist which have write access to the repo. 365 - func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 366 - hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) 367 - if !hasUsersChanged { 368 - return currentWhitelist, nil 369 - } 370 - 371 - whitelist = make([]int64, 0, len(newWhitelist)) 372 - for _, userID := range newWhitelist { 373 - user, err := user_model.GetUserByID(ctx, userID) 374 - if err != nil { 375 - return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err) 376 - } 377 - perm, err := access_model.GetUserRepoPermission(ctx, repo, user) 378 - if err != nil { 379 - return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err) 380 - } 381 - 382 - if !perm.CanWrite(unit.TypeCode) { 383 - continue // Drop invalid user ID 384 - } 385 - 386 - whitelist = append(whitelist, userID) 387 - } 388 - 389 - return whitelist, err 390 - } 391 - 392 - // updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with 393 - // the teams from newWhitelist which have write access to the repo. 394 - func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 395 - hasTeamsChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) 396 - if !hasTeamsChanged { 397 - return currentWhitelist, nil 398 - } 399 - 400 - teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead) 401 - if err != nil { 402 - return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %w", repo.OwnerID, repo.ID, err) 403 - } 404 - 405 - whitelist = make([]int64, 0, len(teams)) 406 - for i := range teams { 407 - if util.SliceContains(newWhitelist, teams[i].ID) { 408 - whitelist = append(whitelist, teams[i].ID) 409 - } 410 - } 411 - 412 - return whitelist, err 413 - } 414 - 415 - // DeleteProtectedBranch removes ProtectedBranch relation between the user and repository. 416 - func DeleteProtectedBranch(ctx context.Context, repoID, id int64) (err error) { 417 - protectedBranch := &ProtectedBranch{ 418 - RepoID: repoID, 419 - ID: id, 420 - } 421 - 422 - if affected, err := db.GetEngine(ctx).Delete(protectedBranch); err != nil { 423 - return err 424 - } else if affected != 1 { 425 - return fmt.Errorf("delete protected branch ID(%v) failed", id) 426 - } 427 - 428 - return nil 429 - } 430 - 431 18 // DeletedBranch struct 432 19 type DeletedBranch struct { 433 20 ID int64 `xorm:"pk autoincr"` ··· 437 24 DeletedByID int64 `xorm:"INDEX"` 438 25 DeletedBy *user_model.User `xorm:"-"` 439 26 DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"` 27 + } 28 + 29 + func init() { 30 + db.RegisterModel(new(DeletedBranch)) 31 + db.RegisterModel(new(RenamedBranch)) 440 32 } 441 33 442 34 // AddDeletedBranch adds a deleted branch to the database ··· 556 148 } 557 149 558 150 // 2. Update protected branch if needed 559 - protectedBranch, err := GetProtectedBranchBy(ctx, repo.ID, from) 151 + protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from) 560 152 if err != nil { 561 153 return err 562 154 } 563 155 564 156 if protectedBranch != nil { 565 - protectedBranch.BranchName = to 157 + protectedBranch.RuleName = to 566 158 _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) 567 159 if err != nil { 568 160 return err 161 + } 162 + } else { 163 + protected, err := IsBranchProtected(ctx, repo.ID, from) 164 + if err != nil { 165 + return err 166 + } 167 + if protected { 168 + return ErrBranchIsProtected 569 169 } 570 170 } 571 171
+4 -4
models/git/branches_test.go
··· 105 105 defer committer.Close() 106 106 assert.NoError(t, err) 107 107 assert.NoError(t, git_model.UpdateProtectBranch(ctx, repo1, &git_model.ProtectedBranch{ 108 - RepoID: repo1.ID, 109 - BranchName: "master", 108 + RepoID: repo1.ID, 109 + RuleName: "master", 110 110 }, git_model.WhitelistOptions{})) 111 111 assert.NoError(t, committer.Commit()) 112 112 ··· 131 131 assert.Equal(t, int64(1), renamedBranch.RepoID) 132 132 133 133 unittest.AssertExistsAndLoadBean(t, &git_model.ProtectedBranch{ 134 - RepoID: repo1.ID, 135 - BranchName: "main", 134 + RepoID: repo1.ID, 135 + RuleName: "main", 136 136 }) 137 137 } 138 138
+501
models/git/protected_branch.go
··· 1 + // Copyright 2022 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package git 5 + 6 + import ( 7 + "context" 8 + "errors" 9 + "fmt" 10 + "strings" 11 + 12 + "code.gitea.io/gitea/models/db" 13 + "code.gitea.io/gitea/models/organization" 14 + "code.gitea.io/gitea/models/perm" 15 + access_model "code.gitea.io/gitea/models/perm/access" 16 + repo_model "code.gitea.io/gitea/models/repo" 17 + "code.gitea.io/gitea/models/unit" 18 + user_model "code.gitea.io/gitea/models/user" 19 + "code.gitea.io/gitea/modules/base" 20 + "code.gitea.io/gitea/modules/log" 21 + "code.gitea.io/gitea/modules/timeutil" 22 + "code.gitea.io/gitea/modules/util" 23 + 24 + "github.com/gobwas/glob" 25 + "github.com/gobwas/glob/syntax" 26 + ) 27 + 28 + var ErrBranchIsProtected = errors.New("branch is protected") 29 + 30 + // ProtectedBranch struct 31 + type ProtectedBranch struct { 32 + ID int64 `xorm:"pk autoincr"` 33 + RepoID int64 `xorm:"UNIQUE(s)"` 34 + Repo *repo_model.Repository `xorm:"-"` 35 + RuleName string `xorm:"'branch_name' UNIQUE(s)"` // a branch name or a glob match to branch name 36 + globRule glob.Glob `xorm:"-"` 37 + isPlainName bool `xorm:"-"` 38 + CanPush bool `xorm:"NOT NULL DEFAULT false"` 39 + EnableWhitelist bool 40 + WhitelistUserIDs []int64 `xorm:"JSON TEXT"` 41 + WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 42 + EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` 43 + WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"` 44 + MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` 45 + MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 46 + EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` 47 + StatusCheckContexts []string `xorm:"JSON TEXT"` 48 + EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"` 49 + ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` 50 + ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 51 + RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"` 52 + BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"` 53 + BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"` 54 + BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"` 55 + DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` 56 + RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"` 57 + ProtectedFilePatterns string `xorm:"TEXT"` 58 + UnprotectedFilePatterns string `xorm:"TEXT"` 59 + 60 + CreatedUnix timeutil.TimeStamp `xorm:"created"` 61 + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` 62 + } 63 + 64 + func init() { 65 + db.RegisterModel(new(ProtectedBranch)) 66 + } 67 + 68 + // IsRuleNameSpecial return true if it contains special character 69 + func IsRuleNameSpecial(ruleName string) bool { 70 + for i := 0; i < len(ruleName); i++ { 71 + if syntax.Special(ruleName[i]) { 72 + return true 73 + } 74 + } 75 + return false 76 + } 77 + 78 + func (protectBranch *ProtectedBranch) loadGlob() { 79 + if protectBranch.globRule == nil { 80 + var err error 81 + protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/') 82 + if err != nil { 83 + log.Warn("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err) 84 + protectBranch.globRule = glob.MustCompile(glob.QuoteMeta(protectBranch.RuleName), '/') 85 + } 86 + protectBranch.isPlainName = !IsRuleNameSpecial(protectBranch.RuleName) 87 + } 88 + } 89 + 90 + // Match tests if branchName matches the rule 91 + func (protectBranch *ProtectedBranch) Match(branchName string) bool { 92 + protectBranch.loadGlob() 93 + if protectBranch.isPlainName { 94 + return strings.EqualFold(protectBranch.RuleName, branchName) 95 + } 96 + 97 + return protectBranch.globRule.Match(branchName) 98 + } 99 + 100 + func (protectBranch *ProtectedBranch) LoadRepo(ctx context.Context) (err error) { 101 + if protectBranch.Repo != nil { 102 + return nil 103 + } 104 + protectBranch.Repo, err = repo_model.GetRepositoryByID(ctx, protectBranch.RepoID) 105 + return err 106 + } 107 + 108 + // CanUserPush returns if some user could push to this protected branch 109 + func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, user *user_model.User) bool { 110 + if !protectBranch.CanPush { 111 + return false 112 + } 113 + 114 + if !protectBranch.EnableWhitelist { 115 + if err := protectBranch.LoadRepo(ctx); err != nil { 116 + log.Error("LoadRepo: %v", err) 117 + return false 118 + } 119 + 120 + writeAccess, err := access_model.HasAccessUnit(ctx, user, protectBranch.Repo, unit.TypeCode, perm.AccessModeWrite) 121 + if err != nil { 122 + log.Error("HasAccessUnit: %v", err) 123 + return false 124 + } 125 + return writeAccess 126 + } 127 + 128 + if base.Int64sContains(protectBranch.WhitelistUserIDs, user.ID) { 129 + return true 130 + } 131 + 132 + if len(protectBranch.WhitelistTeamIDs) == 0 { 133 + return false 134 + } 135 + 136 + in, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.WhitelistTeamIDs) 137 + if err != nil { 138 + log.Error("IsUserInTeams: %v", err) 139 + return false 140 + } 141 + return in 142 + } 143 + 144 + // IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch 145 + func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool { 146 + if !protectBranch.EnableMergeWhitelist { 147 + // Then we need to fall back on whether the user has write permission 148 + return permissionInRepo.CanWrite(unit.TypeCode) 149 + } 150 + 151 + if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) { 152 + return true 153 + } 154 + 155 + if len(protectBranch.MergeWhitelistTeamIDs) == 0 { 156 + return false 157 + } 158 + 159 + in, err := organization.IsUserInTeams(ctx, userID, protectBranch.MergeWhitelistTeamIDs) 160 + if err != nil { 161 + log.Error("IsUserInTeams: %v", err) 162 + return false 163 + } 164 + return in 165 + } 166 + 167 + // IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals) 168 + func IsUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) { 169 + repo, err := repo_model.GetRepositoryByID(ctx, protectBranch.RepoID) 170 + if err != nil { 171 + return false, err 172 + } 173 + 174 + if !protectBranch.EnableApprovalsWhitelist { 175 + // Anyone with write access is considered official reviewer 176 + writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite) 177 + if err != nil { 178 + return false, err 179 + } 180 + return writeAccess, nil 181 + } 182 + 183 + if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) { 184 + return true, nil 185 + } 186 + 187 + inTeam, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ApprovalsWhitelistTeamIDs) 188 + if err != nil { 189 + return false, err 190 + } 191 + 192 + return inTeam, nil 193 + } 194 + 195 + // GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice 196 + func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob { 197 + return getFilePatterns(protectBranch.ProtectedFilePatterns) 198 + } 199 + 200 + // GetUnprotectedFilePatterns parses a semicolon separated list of unprotected file patterns and returns a glob.Glob slice 201 + func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob { 202 + return getFilePatterns(protectBranch.UnprotectedFilePatterns) 203 + } 204 + 205 + func getFilePatterns(filePatterns string) []glob.Glob { 206 + extarr := make([]glob.Glob, 0, 10) 207 + for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") { 208 + expr = strings.TrimSpace(expr) 209 + if expr != "" { 210 + if g, err := glob.Compile(expr, '.', '/'); err != nil { 211 + log.Info("Invalid glob expression '%s' (skipped): %v", expr, err) 212 + } else { 213 + extarr = append(extarr, g) 214 + } 215 + } 216 + } 217 + return extarr 218 + } 219 + 220 + // MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change 221 + func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(changedProtectedFiles []string) bool { 222 + glob := protectBranch.GetProtectedFilePatterns() 223 + if len(glob) == 0 { 224 + return false 225 + } 226 + 227 + return len(changedProtectedFiles) > 0 228 + } 229 + 230 + // IsProtectedFile return if path is protected 231 + func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path string) bool { 232 + if len(patterns) == 0 { 233 + patterns = protectBranch.GetProtectedFilePatterns() 234 + if len(patterns) == 0 { 235 + return false 236 + } 237 + } 238 + 239 + lpath := strings.ToLower(strings.TrimSpace(path)) 240 + 241 + r := false 242 + for _, pat := range patterns { 243 + if pat.Match(lpath) { 244 + r = true 245 + break 246 + } 247 + } 248 + 249 + return r 250 + } 251 + 252 + // IsUnprotectedFile return if path is unprotected 253 + func (protectBranch *ProtectedBranch) IsUnprotectedFile(patterns []glob.Glob, path string) bool { 254 + if len(patterns) == 0 { 255 + patterns = protectBranch.GetUnprotectedFilePatterns() 256 + if len(patterns) == 0 { 257 + return false 258 + } 259 + } 260 + 261 + lpath := strings.ToLower(strings.TrimSpace(path)) 262 + 263 + r := false 264 + for _, pat := range patterns { 265 + if pat.Match(lpath) { 266 + r = true 267 + break 268 + } 269 + } 270 + 271 + return r 272 + } 273 + 274 + // GetProtectedBranchRuleByName getting protected branch rule by name 275 + func GetProtectedBranchRuleByName(ctx context.Context, repoID int64, ruleName string) (*ProtectedBranch, error) { 276 + rel := &ProtectedBranch{RepoID: repoID, RuleName: ruleName} 277 + has, err := db.GetByBean(ctx, rel) 278 + if err != nil { 279 + return nil, err 280 + } 281 + if !has { 282 + return nil, nil 283 + } 284 + return rel, nil 285 + } 286 + 287 + // GetProtectedBranchRuleByID getting protected branch rule by rule ID 288 + func GetProtectedBranchRuleByID(ctx context.Context, repoID, ruleID int64) (*ProtectedBranch, error) { 289 + rel := &ProtectedBranch{ID: ruleID, RepoID: repoID} 290 + has, err := db.GetByBean(ctx, rel) 291 + if err != nil { 292 + return nil, err 293 + } 294 + if !has { 295 + return nil, nil 296 + } 297 + return rel, nil 298 + } 299 + 300 + // WhitelistOptions represent all sorts of whitelists used for protected branches 301 + type WhitelistOptions struct { 302 + UserIDs []int64 303 + TeamIDs []int64 304 + 305 + MergeUserIDs []int64 306 + MergeTeamIDs []int64 307 + 308 + ApprovalsUserIDs []int64 309 + ApprovalsTeamIDs []int64 310 + } 311 + 312 + // UpdateProtectBranch saves branch protection options of repository. 313 + // If ID is 0, it creates a new record. Otherwise, updates existing record. 314 + // This function also performs check if whitelist user and team's IDs have been changed 315 + // to avoid unnecessary whitelist delete and regenerate. 316 + func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) { 317 + if err = repo.GetOwner(ctx); err != nil { 318 + return fmt.Errorf("GetOwner: %v", err) 319 + } 320 + 321 + whitelist, err := updateUserWhitelist(ctx, repo, protectBranch.WhitelistUserIDs, opts.UserIDs) 322 + if err != nil { 323 + return err 324 + } 325 + protectBranch.WhitelistUserIDs = whitelist 326 + 327 + whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs) 328 + if err != nil { 329 + return err 330 + } 331 + protectBranch.MergeWhitelistUserIDs = whitelist 332 + 333 + whitelist, err = updateApprovalWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs) 334 + if err != nil { 335 + return err 336 + } 337 + protectBranch.ApprovalsWhitelistUserIDs = whitelist 338 + 339 + // if the repo is in an organization 340 + whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs) 341 + if err != nil { 342 + return err 343 + } 344 + protectBranch.WhitelistTeamIDs = whitelist 345 + 346 + whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs) 347 + if err != nil { 348 + return err 349 + } 350 + protectBranch.MergeWhitelistTeamIDs = whitelist 351 + 352 + whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs) 353 + if err != nil { 354 + return err 355 + } 356 + protectBranch.ApprovalsWhitelistTeamIDs = whitelist 357 + 358 + // Make sure protectBranch.ID is not 0 for whitelists 359 + if protectBranch.ID == 0 { 360 + if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil { 361 + return fmt.Errorf("Insert: %v", err) 362 + } 363 + return nil 364 + } 365 + 366 + if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { 367 + return fmt.Errorf("Update: %v", err) 368 + } 369 + 370 + return nil 371 + } 372 + 373 + // updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with 374 + // the users from newWhitelist which have explicit read or write access to the repo. 375 + func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 376 + hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) 377 + if !hasUsersChanged { 378 + return currentWhitelist, nil 379 + } 380 + 381 + whitelist = make([]int64, 0, len(newWhitelist)) 382 + for _, userID := range newWhitelist { 383 + if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil { 384 + return nil, err 385 + } else if !reader { 386 + continue 387 + } 388 + whitelist = append(whitelist, userID) 389 + } 390 + 391 + return whitelist, err 392 + } 393 + 394 + // updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with 395 + // the users from newWhitelist which have write access to the repo. 396 + func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 397 + hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) 398 + if !hasUsersChanged { 399 + return currentWhitelist, nil 400 + } 401 + 402 + whitelist = make([]int64, 0, len(newWhitelist)) 403 + for _, userID := range newWhitelist { 404 + user, err := user_model.GetUserByID(ctx, userID) 405 + if err != nil { 406 + return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) 407 + } 408 + perm, err := access_model.GetUserRepoPermission(ctx, repo, user) 409 + if err != nil { 410 + return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) 411 + } 412 + 413 + if !perm.CanWrite(unit.TypeCode) { 414 + continue // Drop invalid user ID 415 + } 416 + 417 + whitelist = append(whitelist, userID) 418 + } 419 + 420 + return whitelist, err 421 + } 422 + 423 + // updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with 424 + // the teams from newWhitelist which have write access to the repo. 425 + func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 426 + hasTeamsChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) 427 + if !hasTeamsChanged { 428 + return currentWhitelist, nil 429 + } 430 + 431 + teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead) 432 + if err != nil { 433 + return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err) 434 + } 435 + 436 + whitelist = make([]int64, 0, len(teams)) 437 + for i := range teams { 438 + if util.SliceContains(newWhitelist, teams[i].ID) { 439 + whitelist = append(whitelist, teams[i].ID) 440 + } 441 + } 442 + 443 + return whitelist, err 444 + } 445 + 446 + // DeleteProtectedBranch removes ProtectedBranch relation between the user and repository. 447 + func DeleteProtectedBranch(ctx context.Context, repoID, id int64) (err error) { 448 + protectedBranch := &ProtectedBranch{ 449 + RepoID: repoID, 450 + ID: id, 451 + } 452 + 453 + if affected, err := db.GetEngine(ctx).Delete(protectedBranch); err != nil { 454 + return err 455 + } else if affected != 1 { 456 + return fmt.Errorf("delete protected branch ID(%v) failed", id) 457 + } 458 + 459 + return nil 460 + } 461 + 462 + // RemoveUserIDFromProtectedBranch remove all user ids from protected branch options 463 + func RemoveUserIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID int64) error { 464 + lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs) 465 + p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, userID) 466 + p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, userID) 467 + p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, userID) 468 + 469 + if lenIDs != len(p.WhitelistUserIDs) || lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) || 470 + lenMergeIDs != len(p.MergeWhitelistUserIDs) { 471 + if _, err := db.GetEngine(ctx).ID(p.ID).Cols( 472 + "whitelist_user_i_ds", 473 + "merge_whitelist_user_i_ds", 474 + "approvals_whitelist_user_i_ds", 475 + ).Update(p); err != nil { 476 + return fmt.Errorf("updateProtectedBranches: %v", err) 477 + } 478 + } 479 + return nil 480 + } 481 + 482 + // RemoveTeamIDFromProtectedBranch remove all team ids from protected branch options 483 + func RemoveTeamIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, teamID int64) error { 484 + lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs) 485 + p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, teamID) 486 + p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, teamID) 487 + p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, teamID) 488 + 489 + if lenIDs != len(p.WhitelistTeamIDs) || 490 + lenApprovalIDs != len(p.ApprovalsWhitelistTeamIDs) || 491 + lenMergeIDs != len(p.MergeWhitelistTeamIDs) { 492 + if _, err := db.GetEngine(ctx).ID(p.ID).Cols( 493 + "whitelist_team_i_ds", 494 + "merge_whitelist_team_i_ds", 495 + "approvals_whitelist_team_i_ds", 496 + ).Update(p); err != nil { 497 + return fmt.Errorf("updateProtectedBranches: %v", err) 498 + } 499 + } 500 + return nil 501 + }
+86
models/git/protected_branch_list.go
··· 1 + // Copyright 2022 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package git 5 + 6 + import ( 7 + "context" 8 + "sort" 9 + 10 + "code.gitea.io/gitea/models/db" 11 + "code.gitea.io/gitea/modules/git" 12 + 13 + "github.com/gobwas/glob" 14 + ) 15 + 16 + type ProtectedBranchRules []*ProtectedBranch 17 + 18 + func (rules ProtectedBranchRules) GetFirstMatched(branchName string) *ProtectedBranch { 19 + for _, rule := range rules { 20 + if rule.Match(branchName) { 21 + return rule 22 + } 23 + } 24 + return nil 25 + } 26 + 27 + func (rules ProtectedBranchRules) sort() { 28 + sort.Slice(rules, func(i, j int) bool { 29 + rules[i].loadGlob() 30 + rules[j].loadGlob() 31 + if rules[i].isPlainName { 32 + if !rules[j].isPlainName { 33 + return true 34 + } 35 + } else if rules[j].isPlainName { 36 + return true 37 + } 38 + return rules[i].CreatedUnix < rules[j].CreatedUnix 39 + }) 40 + } 41 + 42 + // FindRepoProtectedBranchRules load all repository's protected rules 43 + func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedBranchRules, error) { 44 + var rules ProtectedBranchRules 45 + err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Asc("created_unix").Find(&rules) 46 + if err != nil { 47 + return nil, err 48 + } 49 + rules.sort() 50 + return rules, nil 51 + } 52 + 53 + // FindAllMatchedBranches find all matched branches 54 + func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) { 55 + // FIXME: how many should we get? 56 + branches, _, err := gitRepo.GetBranchNames(0, 9999999) 57 + if err != nil { 58 + return nil, err 59 + } 60 + rule := glob.MustCompile(ruleName) 61 + results := make([]string, 0, len(branches)) 62 + for _, branch := range branches { 63 + if rule.Match(branch) { 64 + results = append(results, branch) 65 + } 66 + } 67 + return results, nil 68 + } 69 + 70 + // GetFirstMatchProtectedBranchRule returns the first matched rules 71 + func GetFirstMatchProtectedBranchRule(ctx context.Context, repoID int64, branchName string) (*ProtectedBranch, error) { 72 + rules, err := FindRepoProtectedBranchRules(ctx, repoID) 73 + if err != nil { 74 + return nil, err 75 + } 76 + return rules.GetFirstMatched(branchName), nil 77 + } 78 + 79 + // IsBranchProtected checks if branch is protected 80 + func IsBranchProtected(ctx context.Context, repoID int64, branchName string) (bool, error) { 81 + rule, err := GetFirstMatchProtectedBranchRule(ctx, repoID, branchName) 82 + if err != nil { 83 + return false, err 84 + } 85 + return rule != nil, nil 86 + }
+78
models/git/protected_branch_test.go
··· 1 + // Copyright 2022 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package git 5 + 6 + import ( 7 + "fmt" 8 + "testing" 9 + 10 + "github.com/stretchr/testify/assert" 11 + ) 12 + 13 + func TestBranchRuleMatch(t *testing.T) { 14 + kases := []struct { 15 + Rule string 16 + BranchName string 17 + ExpectedMatch bool 18 + }{ 19 + { 20 + Rule: "release/*", 21 + BranchName: "release/v1.17", 22 + ExpectedMatch: true, 23 + }, 24 + { 25 + Rule: "release/**/v1.17", 26 + BranchName: "release/test/v1.17", 27 + ExpectedMatch: true, 28 + }, 29 + { 30 + Rule: "release/**/v1.17", 31 + BranchName: "release/test/1/v1.17", 32 + ExpectedMatch: true, 33 + }, 34 + { 35 + Rule: "release/*/v1.17", 36 + BranchName: "release/test/1/v1.17", 37 + ExpectedMatch: false, 38 + }, 39 + { 40 + Rule: "release/v*", 41 + BranchName: "release/v1.16", 42 + ExpectedMatch: true, 43 + }, 44 + { 45 + Rule: "*", 46 + BranchName: "release/v1.16", 47 + ExpectedMatch: false, 48 + }, 49 + { 50 + Rule: "**", 51 + BranchName: "release/v1.16", 52 + ExpectedMatch: true, 53 + }, 54 + { 55 + Rule: "main", 56 + BranchName: "main", 57 + ExpectedMatch: true, 58 + }, 59 + { 60 + Rule: "master", 61 + BranchName: "main", 62 + ExpectedMatch: false, 63 + }, 64 + } 65 + 66 + for _, kase := range kases { 67 + pb := ProtectedBranch{RuleName: kase.Rule} 68 + var should, infact string 69 + if !kase.ExpectedMatch { 70 + should = " not" 71 + } else { 72 + infact = " not" 73 + } 74 + assert.EqualValues(t, kase.ExpectedMatch, pb.Match(kase.BranchName), 75 + fmt.Sprintf("%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact), 76 + ) 77 + } 78 + }
+2 -20
models/issues/pull.go
··· 164 164 HeadBranch string 165 165 HeadCommitID string `xorm:"-"` 166 166 BaseBranch string 167 - ProtectedBranch *git_model.ProtectedBranch `xorm:"-"` 168 - MergeBase string `xorm:"VARCHAR(40)"` 169 - AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"` 167 + MergeBase string `xorm:"VARCHAR(40)"` 168 + AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"` 170 169 171 170 HasMerged bool `xorm:"INDEX"` 172 171 MergedCommitID string `xorm:"VARCHAR(40)"` ··· 289 288 pr.Issue, err = GetIssueByID(ctx, pr.IssueID) 290 289 if err == nil { 291 290 pr.Issue.PullRequest = pr 292 - } 293 - return err 294 - } 295 - 296 - // LoadProtectedBranch loads the protected branch of the base branch 297 - func (pr *PullRequest) LoadProtectedBranch(ctx context.Context) (err error) { 298 - if pr.ProtectedBranch == nil { 299 - if pr.BaseRepo == nil { 300 - if pr.BaseRepoID == 0 { 301 - return nil 302 - } 303 - pr.BaseRepo, err = repo_model.GetRepositoryByID(ctx, pr.BaseRepoID) 304 - if err != nil { 305 - return 306 - } 307 - } 308 - pr.ProtectedBranch, err = git_model.GetProtectedBranchBy(ctx, pr.BaseRepo.ID, pr.BaseBranch) 309 291 } 310 292 return err 311 293 }
+10 -7
models/issues/review.go
··· 263 263 if err != nil { 264 264 return false, err 265 265 } 266 - if err = pr.LoadProtectedBranch(ctx); err != nil { 266 + 267 + rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) 268 + if err != nil { 267 269 return false, err 268 270 } 269 - if pr.ProtectedBranch == nil { 271 + if rule == nil { 270 272 return false, nil 271 273 } 272 274 273 275 for _, reviewer := range reviewers { 274 - official, err := git_model.IsUserOfficialReviewer(ctx, pr.ProtectedBranch, reviewer) 276 + official, err := git_model.IsUserOfficialReviewer(ctx, rule, reviewer) 275 277 if official || err != nil { 276 278 return official, err 277 279 } ··· 286 288 if err != nil { 287 289 return false, err 288 290 } 289 - if err = pr.LoadProtectedBranch(ctx); err != nil { 291 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) 292 + if err != nil { 290 293 return false, err 291 294 } 292 - if pr.ProtectedBranch == nil { 295 + if pb == nil { 293 296 return false, nil 294 297 } 295 298 296 - if !pr.ProtectedBranch.EnableApprovalsWhitelist { 299 + if !pb.EnableApprovalsWhitelist { 297 300 return team.UnitAccessMode(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil 298 301 } 299 302 300 - return base.Int64sContains(pr.ProtectedBranch.ApprovalsWhitelistTeamIDs, team.ID), nil 303 + return base.Int64sContains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil 301 304 } 302 305 303 306 // CreateReview creates a new review based on opts
+4 -17
models/org_team.go
··· 378 378 return err 379 379 } 380 380 defer committer.Close() 381 - sess := db.GetEngine(ctx) 382 381 383 382 if err := t.LoadRepositories(ctx); err != nil { 384 383 return err ··· 391 390 // update branch protections 392 391 { 393 392 protections := make([]*git_model.ProtectedBranch, 0, 10) 394 - err := sess.In("repo_id", 393 + err := db.GetEngine(ctx).In("repo_id", 395 394 builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})). 396 395 Find(&protections) 397 396 if err != nil { 398 397 return fmt.Errorf("findProtectedBranches: %w", err) 399 398 } 400 399 for _, p := range protections { 401 - lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs) 402 - p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, t.ID) 403 - p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, t.ID) 404 - p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, t.ID) 405 - if lenIDs != len(p.WhitelistTeamIDs) || 406 - lenApprovalIDs != len(p.ApprovalsWhitelistTeamIDs) || 407 - lenMergeIDs != len(p.MergeWhitelistTeamIDs) { 408 - if _, err = sess.ID(p.ID).Cols( 409 - "whitelist_team_i_ds", 410 - "merge_whitelist_team_i_ds", 411 - "approvals_whitelist_team_i_ds", 412 - ).Update(p); err != nil { 413 - return fmt.Errorf("updateProtectedBranches: %w", err) 414 - } 400 + if err := git_model.RemoveTeamIDFromProtectedBranch(ctx, p, t.ID); err != nil { 401 + return err 415 402 } 416 403 } 417 404 } ··· 432 419 } 433 420 434 421 // Update organization number of teams. 435 - if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil { 422 + if _, err := db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil { 436 423 return err 437 424 } 438 425
+2 -15
models/user.go
··· 23 23 repo_model "code.gitea.io/gitea/models/repo" 24 24 user_model "code.gitea.io/gitea/models/user" 25 25 "code.gitea.io/gitea/modules/setting" 26 - "code.gitea.io/gitea/modules/util" 27 26 ) 28 27 29 28 // DeleteUser deletes models associated to an user. ··· 141 140 break 142 141 } 143 142 for _, p := range protections { 144 - lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs) 145 - p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, u.ID) 146 - p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, u.ID) 147 - p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, u.ID) 148 - if lenIDs != len(p.WhitelistUserIDs) || 149 - lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) || 150 - lenMergeIDs != len(p.MergeWhitelistUserIDs) { 151 - if _, err = e.ID(p.ID).Cols( 152 - "whitelist_user_i_ds", 153 - "merge_whitelist_user_i_ds", 154 - "approvals_whitelist_user_i_ds", 155 - ).Update(p); err != nil { 156 - return fmt.Errorf("updateProtectedBranches: %w", err) 157 - } 143 + if err := git_model.RemoveUserIDFromProtectedBranch(ctx, p, u.ID); err != nil { 144 + return err 158 145 } 159 146 } 160 147 }
+3 -2
modules/context/repo.go
··· 119 119 // 120 120 // and branch is not protected for push 121 121 func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) { 122 - protectedBranch, err := git_model.GetProtectedBranchBy(ctx, r.Repository.ID, r.BranchName) 122 + protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, r.Repository.ID, r.BranchName) 123 123 if err != nil { 124 124 return CanCommitToBranchResults{}, err 125 125 } 126 126 userCanPush := true 127 127 requireSigned := false 128 128 if protectedBranch != nil { 129 - userCanPush = protectedBranch.CanUserPush(ctx, doer.ID) 129 + protectedBranch.Repo = r.Repository 130 + userCanPush = protectedBranch.CanUserPush(ctx, doer) 130 131 requireSigned = protectedBranch.RequireSignedCommits 131 132 } 132 133
+4
modules/structs/repo_branch.go
··· 22 22 23 23 // BranchProtection represents a branch protection for a repository 24 24 type BranchProtection struct { 25 + // Deprecated: true 25 26 BranchName string `json:"branch_name"` 27 + RuleName string `json:"rule_name"` 26 28 EnablePush bool `json:"enable_push"` 27 29 EnablePushWhitelist bool `json:"enable_push_whitelist"` 28 30 PushWhitelistUsernames []string `json:"push_whitelist_usernames"` ··· 52 54 53 55 // CreateBranchProtectionOption options for creating a branch protection 54 56 type CreateBranchProtectionOption struct { 57 + // Deprecated: true 55 58 BranchName string `json:"branch_name"` 59 + RuleName string `json:"rule_name"` 56 60 EnablePush bool `json:"enable_push"` 57 61 EnablePushWhitelist bool `json:"enable_push_whitelist"` 58 62 PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
+9 -3
options/locale/locale_en-US.ini
··· 1824 1824 settings.site = Website 1825 1825 settings.update_settings = Update Settings 1826 1826 settings.branches.update_default_branch = Update Default Branch 1827 + settings.branches.add_new_rule = Add New Rule 1827 1828 settings.advanced_settings = Advanced Settings 1828 1829 settings.wiki_desc = Enable Repository Wiki 1829 1830 settings.use_internal_wiki = Use Built-In Wiki ··· 2069 2070 settings.deploy_key_deletion_success = The deploy key has been removed. 2070 2071 settings.branches = Branches 2071 2072 settings.protected_branch = Branch Protection 2073 + settings.protected_branch.save_rule = Save Rule 2074 + settings.protected_branch.delete_rule = Delete Rule 2072 2075 settings.protected_branch_can_push = Allow push? 2073 2076 settings.protected_branch_can_push_yes = You can push 2074 2077 settings.protected_branch_can_push_no = You cannot push ··· 2103 2106 settings.dismiss_stale_approvals_desc = When new commits that change the content of the pull request are pushed to the branch, old approvals will be dismissed. 2104 2107 settings.require_signed_commits = Require Signed Commits 2105 2108 settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable. 2109 + settings.protect_branch_name_pattern = Protected Branch Name Pattern 2106 2110 settings.protect_protected_file_patterns = Protected file patterns (separated using semicolon '\;'): 2107 2111 settings.protect_protected_file_patterns_desc = Protected files that are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. Multiple patterns can be separated using semicolon ('\;'). See <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentation for pattern syntax. Examples: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. 2108 2112 settings.protect_unprotected_file_patterns = Unprotected file patterns (separated using semicolon '\;'): 2109 2113 settings.protect_unprotected_file_patterns_desc = Unprotected files that are allowed to be changed directly if user has write access, bypassing push restriction. Multiple patterns can be separated using semicolon ('\;'). See <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentation for pattern syntax. Examples: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. 2110 2114 settings.add_protected_branch = Enable protection 2111 2115 settings.delete_protected_branch = Disable protection 2112 - settings.update_protect_branch_success = Branch protection for branch '%s' has been updated. 2113 - settings.remove_protected_branch_success = Branch protection for branch '%s' has been disabled. 2114 - settings.protected_branch_deletion = Disable Branch Protection 2116 + settings.update_protect_branch_success = Branch protection for rule '%s' has been updated. 2117 + settings.remove_protected_branch_success = Branch protection for rule '%s' has been removed. 2118 + settings.remove_protected_branch_failed = Removing branch protection rule '%s' failed. 2119 + settings.protected_branch_deletion = Delete Branch Protection 2115 2120 settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? 2116 2121 settings.block_rejected_reviews = Block merge on rejected reviews 2117 2122 settings.block_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals. ··· 2124 2129 settings.choose_branch = Choose a branch… 2125 2130 settings.no_protected_branch = There are no protected branches. 2126 2131 settings.edit_protected_branch = Edit 2132 + settings.protected_branch_required_rule_name = Required rule name 2127 2133 settings.protected_branch_required_approvals_min = Required approvals cannot be negative. 2128 2134 settings.tags = Tags 2129 2135 settings.tags.protection = Tag Protection
+99 -26
routers/api/v1/repo/branch.go
··· 70 70 return 71 71 } 72 72 73 - branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branchName) 73 + branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName) 74 74 if err != nil { 75 75 ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) 76 76 return ··· 124 124 ctx.NotFound(err) 125 125 case errors.Is(err, repo_service.ErrBranchIsDefault): 126 126 ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch")) 127 - case errors.Is(err, repo_service.ErrBranchIsProtected): 127 + case errors.Is(err, git_model.ErrBranchIsProtected): 128 128 ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected")) 129 129 default: 130 130 ctx.Error(http.StatusInternalServerError, "DeleteBranch", err) ··· 206 206 return 207 207 } 208 208 209 - branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch.Name) 209 + branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branch.Name) 210 210 if err != nil { 211 211 ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) 212 212 return ··· 257 257 listOptions := utils.GetListOptions(ctx) 258 258 259 259 if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil { 260 + rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) 261 + if err != nil { 262 + ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err) 263 + return 264 + } 265 + 260 266 skip, _ := listOptions.GetStartEnd() 261 267 branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize) 262 268 if err != nil { ··· 276 282 ctx.Error(http.StatusInternalServerError, "GetCommit", err) 277 283 return 278 284 } 279 - branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name) 280 - if err != nil { 281 - ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err) 282 - return 283 - } 285 + 286 + branchProtection := rules.GetFirstMatched(branches[i].Name) 284 287 apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 285 288 if err != nil { 286 289 ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) ··· 328 331 329 332 repo := ctx.Repo.Repository 330 333 bpName := ctx.Params(":name") 331 - bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName) 334 + bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) 332 335 if err != nil { 333 336 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) 334 337 return ··· 364 367 // "$ref": "#/responses/BranchProtectionList" 365 368 366 369 repo := ctx.Repo.Repository 367 - bps, err := git_model.GetProtectedBranches(ctx, repo.ID) 370 + bps, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID) 368 371 if err != nil { 369 372 ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err) 370 373 return ··· 414 417 form := web.GetForm(ctx).(*api.CreateBranchProtectionOption) 415 418 repo := ctx.Repo.Repository 416 419 417 - // Currently protection must match an actual branch 418 - if !git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), form.BranchName) { 419 - ctx.NotFound() 420 - return 420 + ruleName := form.RuleName 421 + if ruleName == "" { 422 + ruleName = form.BranchName //nolint 423 + } 424 + 425 + isPlainRule := !git_model.IsRuleNameSpecial(ruleName) 426 + var isBranchExist bool 427 + if isPlainRule { 428 + isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), ruleName) 421 429 } 422 430 423 - protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, form.BranchName) 431 + protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, ruleName) 424 432 if err != nil { 425 433 ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err) 426 434 return ··· 494 502 495 503 protectBranch = &git_model.ProtectedBranch{ 496 504 RepoID: ctx.Repo.Repository.ID, 497 - BranchName: form.BranchName, 505 + RuleName: form.RuleName, 498 506 CanPush: form.EnablePush, 499 507 EnableWhitelist: form.EnablePush && form.EnablePushWhitelist, 500 508 EnableMergeWhitelist: form.EnableMergeWhitelist, ··· 525 533 return 526 534 } 527 535 528 - if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil { 529 - ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err) 530 - return 536 + if isBranchExist { 537 + if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, form.RuleName); err != nil { 538 + ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err) 539 + return 540 + } 541 + } else { 542 + if !isPlainRule { 543 + if ctx.Repo.GitRepo == nil { 544 + ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath()) 545 + if err != nil { 546 + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) 547 + return 548 + } 549 + defer func() { 550 + ctx.Repo.GitRepo.Close() 551 + ctx.Repo.GitRepo = nil 552 + }() 553 + } 554 + // FIXME: since we only need to recheck files protected rules, we could improve this 555 + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, form.RuleName) 556 + if err != nil { 557 + ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) 558 + return 559 + } 560 + 561 + for _, branchName := range matchedBranches { 562 + if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil { 563 + ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err) 564 + return 565 + } 566 + } 567 + } 531 568 } 532 569 533 570 // Reload from db to get all whitelists 534 - bp, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, form.BranchName) 571 + bp, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, form.RuleName) 535 572 if err != nil { 536 573 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) 537 574 return ··· 583 620 form := web.GetForm(ctx).(*api.EditBranchProtectionOption) 584 621 repo := ctx.Repo.Repository 585 622 bpName := ctx.Params(":name") 586 - protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName) 623 + protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) 587 624 if err != nil { 588 625 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) 589 626 return ··· 760 797 return 761 798 } 762 799 763 - if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil { 764 - ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err) 765 - return 800 + isPlainRule := !git_model.IsRuleNameSpecial(bpName) 801 + var isBranchExist bool 802 + if isPlainRule { 803 + isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), bpName) 804 + } 805 + 806 + if isBranchExist { 807 + if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, bpName); err != nil { 808 + ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err) 809 + return 810 + } 811 + } else { 812 + if !isPlainRule { 813 + if ctx.Repo.GitRepo == nil { 814 + ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath()) 815 + if err != nil { 816 + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) 817 + return 818 + } 819 + defer func() { 820 + ctx.Repo.GitRepo.Close() 821 + ctx.Repo.GitRepo = nil 822 + }() 823 + } 824 + 825 + // FIXME: since we only need to recheck files protected rules, we could improve this 826 + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) 827 + if err != nil { 828 + ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) 829 + return 830 + } 831 + 832 + for _, branchName := range matchedBranches { 833 + if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil { 834 + ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err) 835 + return 836 + } 837 + } 838 + } 766 839 } 767 840 768 841 // Reload from db to ensure get all whitelists 769 - bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName) 842 + bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) 770 843 if err != nil { 771 844 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err) 772 845 return ··· 810 883 811 884 repo := ctx.Repo.Repository 812 885 bpName := ctx.Params(":name") 813 - bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName) 886 + bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) 814 887 if err != nil { 815 888 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) 816 889 return
+2 -1
routers/api/v1/repo/pull.go
··· 14 14 15 15 "code.gitea.io/gitea/models" 16 16 activities_model "code.gitea.io/gitea/models/activities" 17 + git_model "code.gitea.io/gitea/models/git" 17 18 issues_model "code.gitea.io/gitea/models/issues" 18 19 access_model "code.gitea.io/gitea/models/perm/access" 19 20 pull_model "code.gitea.io/gitea/models/pull" ··· 902 903 ctx.NotFound(err) 903 904 case errors.Is(err, repo_service.ErrBranchIsDefault): 904 905 ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch")) 905 - case errors.Is(err, repo_service.ErrBranchIsProtected): 906 + case errors.Is(err, git_model.ErrBranchIsProtected): 906 907 ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected")) 907 908 default: 908 909 ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
+12 -4
routers/private/hook_pre_receive.go
··· 156 156 return 157 157 } 158 158 159 - protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, branchName) 159 + protectBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName) 160 160 if err != nil { 161 161 log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err) 162 162 ctx.JSON(http.StatusInternalServerError, private.Response{ ··· 166 166 } 167 167 168 168 // Allow pushes to non-protected branches 169 - if protectBranch == nil || !protectBranch.IsProtected() { 169 + if protectBranch == nil { 170 170 return 171 171 } 172 + protectBranch.Repo = repo 172 173 173 174 // This ref is a protected branch. 174 175 // ··· 238 239 Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err), 239 240 }) 240 241 return 241 - 242 242 } 243 243 244 244 changedProtectedfiles = true ··· 251 251 if ctx.opts.DeployKeyID != 0 { 252 252 canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys) 253 253 } else { 254 - canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, ctx.opts.UserID) 254 + user, err := user_model.GetUserByID(ctx, ctx.opts.UserID) 255 + if err != nil { 256 + log.Error("Unable to GetUserByID for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) 257 + ctx.JSON(http.StatusInternalServerError, private.Response{ 258 + Err: fmt.Sprintf("Unable to GetUserByID for commits from %s to %s: %v", oldCommitID, newCommitID, err), 259 + }) 260 + return 261 + } 262 + canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, user) 255 263 } 256 264 257 265 // 6. If we're not allowed to push directly
+8 -13
routers/web/repo/branch.go
··· 99 99 case errors.Is(err, repo_service.ErrBranchIsDefault): 100 100 log.Debug("DeleteBranch: Can't delete default branch '%s'", branchName) 101 101 ctx.Flash.Error(ctx.Tr("repo.branch.default_deletion_failed", branchName)) 102 - case errors.Is(err, repo_service.ErrBranchIsProtected): 102 + case errors.Is(err, git_model.ErrBranchIsProtected): 103 103 log.Debug("DeleteBranch: Can't delete protected branch '%s'", branchName) 104 104 ctx.Flash.Error(ctx.Tr("repo.branch.protected_deletion_failed", branchName)) 105 105 default: ··· 189 189 return nil, nil, 0 190 190 } 191 191 192 - protectedBranches, err := git_model.GetProtectedBranches(ctx, ctx.Repo.Repository.ID) 192 + rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) 193 193 if err != nil { 194 - ctx.ServerError("GetProtectedBranches", err) 194 + ctx.ServerError("FindRepoProtectedBranchRules", err) 195 195 return nil, nil, 0 196 196 } 197 197 ··· 208 208 continue 209 209 } 210 210 211 - branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, protectedBranches, repoIDToRepo, repoIDToGitRepo) 211 + branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo) 212 212 if branch == nil { 213 213 return nil, nil, 0 214 214 } ··· 220 220 if defaultBranch != nil { 221 221 // Always add the default branch 222 222 log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name) 223 - defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, protectedBranches, repoIDToRepo, repoIDToGitRepo) 223 + defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo) 224 224 branches = append(branches, defaultBranchBranch) 225 225 } 226 226 ··· 236 236 return defaultBranchBranch, branches, totalNumOfBranches 237 237 } 238 238 239 - func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches []*git_model.ProtectedBranch, 239 + func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules, 240 240 repoIDToRepo map[int64]*repo_model.Repository, 241 241 repoIDToGitRepo map[int64]*git.Repository, 242 242 ) *Branch { ··· 249 249 } 250 250 251 251 branchName := rawBranch.Name 252 - var isProtected bool 253 - for _, b := range protectedBranches { 254 - if b.BranchName == branchName { 255 - isProtected = true 256 - break 257 - } 258 - } 252 + p := protectedBranches.GetFirstMatched(branchName) 253 + isProtected := p != nil 259 254 260 255 divergence := &git.DivergeObject{ 261 256 Ahead: -1,
+13 -10
routers/web/repo/issue.go
··· 1604 1604 if perm.CanWrite(unit.TypeCode) { 1605 1605 // Check if branch is not protected 1606 1606 if pull.HeadBranch != pull.HeadRepo.DefaultBranch { 1607 - if protected, err := git_model.IsProtectedBranch(ctx, pull.HeadRepo.ID, pull.HeadBranch); err != nil { 1607 + if protected, err := git_model.IsBranchProtected(ctx, pull.HeadRepo.ID, pull.HeadBranch); err != nil { 1608 1608 log.Error("IsProtectedBranch: %v", err) 1609 1609 } else if !protected { 1610 1610 canDelete = true ··· 1680 1680 ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage 1681 1681 ctx.Data["DefaultSquashMergeBody"] = defaultSquashMergeBody 1682 1682 1683 - if err = pull.LoadProtectedBranch(ctx); err != nil { 1683 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch) 1684 + if err != nil { 1684 1685 ctx.ServerError("LoadProtectedBranch", err) 1685 1686 return 1686 1687 } 1687 1688 ctx.Data["ShowMergeInstructions"] = true 1688 - if pull.ProtectedBranch != nil { 1689 + if pb != nil { 1690 + pb.Repo = pull.BaseRepo 1689 1691 var showMergeInstructions bool 1690 1692 if ctx.Doer != nil { 1691 - showMergeInstructions = pull.ProtectedBranch.CanUserPush(ctx, ctx.Doer.ID) 1693 + showMergeInstructions = pb.CanUserPush(ctx, ctx.Doer) 1692 1694 } 1693 - ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pull.ProtectedBranch, pull) 1694 - ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pull.ProtectedBranch, pull) 1695 - ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pull.ProtectedBranch, pull) 1696 - ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pull.ProtectedBranch, pull) 1697 - ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pull.ProtectedBranch, pull) 1698 - ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits 1695 + ctx.Data["ProtectedBranch"] = pb 1696 + ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull) 1697 + ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull) 1698 + ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull) 1699 + ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pb, pull) 1700 + ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull) 1701 + ctx.Data["RequireSigned"] = pb.RequireSignedCommits 1699 1702 ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles 1700 1703 ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0 1701 1704 ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
+12 -10
routers/web/repo/pull.go
··· 440 440 441 441 setMergeTarget(ctx, pull) 442 442 443 - if err := pull.LoadProtectedBranch(ctx); err != nil { 443 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pull.BaseBranch) 444 + if err != nil { 444 445 ctx.ServerError("LoadProtectedBranch", err) 445 446 return nil 446 447 } 447 - ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck 448 + ctx.Data["EnableStatusCheck"] = pb != nil && pb.EnableStatusCheck 448 449 449 450 var baseGitRepo *git.Repository 450 451 if pull.BaseRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil { ··· 570 571 ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses) 571 572 } 572 573 573 - if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck { 574 + if pb != nil && pb.EnableStatusCheck { 574 575 ctx.Data["is_context_required"] = func(context string) bool { 575 - for _, c := range pull.ProtectedBranch.StatusCheckContexts { 576 + for _, c := range pb.StatusCheckContexts { 576 577 if c == context { 577 578 return true 578 579 } 579 580 } 580 581 return false 581 582 } 582 - ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pull.ProtectedBranch.StatusCheckContexts) 583 + ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pb.StatusCheckContexts) 583 584 } 584 585 585 586 ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha ··· 752 753 return 753 754 } 754 755 755 - if err = pull.LoadProtectedBranch(ctx); err != nil { 756 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch) 757 + if err != nil { 756 758 ctx.ServerError("LoadProtectedBranch", err) 757 759 return 758 760 } 759 761 760 - if pull.ProtectedBranch != nil { 761 - glob := pull.ProtectedBranch.GetProtectedFilePatterns() 762 + if pb != nil { 763 + glob := pb.GetProtectedFilePatterns() 762 764 if len(glob) != 0 { 763 765 for _, file := range diff.Files { 764 - file.IsProtected = pull.ProtectedBranch.IsProtectedFile(glob, file.Name) 766 + file.IsProtected = pb.IsProtectedFile(glob, file.Name) 765 767 } 766 768 } 767 769 } ··· 1400 1402 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1401 1403 case errors.Is(err, repo_service.ErrBranchIsDefault): 1402 1404 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1403 - case errors.Is(err, repo_service.ErrBranchIsProtected): 1405 + case errors.Is(err, git_model.ErrBranchIsProtected): 1404 1406 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1405 1407 default: 1406 1408 log.Error("DeleteBranch: %v", err)
-1
routers/web/repo/setting.go
··· 56 56 tplGithooks base.TplName = "repo/settings/githooks" 57 57 tplGithookEdit base.TplName = "repo/settings/githook_edit" 58 58 tplDeployKeys base.TplName = "repo/settings/deploy_keys" 59 - tplProtectedBranch base.TplName = "repo/settings/protected_branch" 60 59 ) 61 60 62 61 // SettingsCtxData is a middleware that sets all the general context data for the
+166 -142
routers/web/repo/setting_protected_branch.go
··· 19 19 "code.gitea.io/gitea/modules/git" 20 20 "code.gitea.io/gitea/modules/log" 21 21 "code.gitea.io/gitea/modules/setting" 22 - "code.gitea.io/gitea/modules/util" 23 22 "code.gitea.io/gitea/modules/web" 24 23 "code.gitea.io/gitea/services/forms" 25 24 pull_service "code.gitea.io/gitea/services/pull" 26 25 "code.gitea.io/gitea/services/repository" 27 26 ) 28 27 29 - // ProtectedBranch render the page to protect the repository 30 - func ProtectedBranch(ctx *context.Context) { 28 + const ( 29 + tplProtectedBranch base.TplName = "repo/settings/protected_branch" 30 + ) 31 + 32 + // ProtectedBranchRules render the page to protect the repository 33 + func ProtectedBranchRules(ctx *context.Context) { 31 34 ctx.Data["Title"] = ctx.Tr("repo.settings") 32 35 ctx.Data["PageIsSettingsBranches"] = true 33 36 34 - protectedBranches, err := git_model.GetProtectedBranches(ctx, ctx.Repo.Repository.ID) 37 + rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) 35 38 if err != nil { 36 39 ctx.ServerError("GetProtectedBranches", err) 37 40 return 38 41 } 39 - ctx.Data["ProtectedBranches"] = protectedBranches 40 - 41 - branches := ctx.Data["Branches"].([]string) 42 - leftBranches := make([]string, 0, len(branches)-len(protectedBranches)) 43 - for _, b := range branches { 44 - var protected bool 45 - for _, pb := range protectedBranches { 46 - if b == pb.BranchName { 47 - protected = true 48 - break 49 - } 50 - } 51 - if !protected { 52 - leftBranches = append(leftBranches, b) 53 - } 54 - } 55 - 56 - ctx.Data["LeftBranches"] = leftBranches 42 + ctx.Data["ProtectedBranches"] = rules 57 43 58 44 ctx.HTML(http.StatusOK, tplBranches) 59 45 } 60 46 61 - // ProtectedBranchPost response for protect for a branch of a repository 62 - func ProtectedBranchPost(ctx *context.Context) { 47 + // SetDefaultBranchPost set default branch 48 + func SetDefaultBranchPost(ctx *context.Context) { 63 49 ctx.Data["Title"] = ctx.Tr("repo.settings") 64 50 ctx.Data["PageIsSettingsBranches"] = true 65 51 ··· 101 87 102 88 // SettingsProtectedBranch renders the protected branch setting page 103 89 func SettingsProtectedBranch(c *context.Context) { 104 - branch := c.Params("*") 105 - if !c.Repo.GitRepo.IsBranchExist(branch) { 106 - c.NotFound("IsBranchExist", nil) 107 - return 108 - } 109 - 110 - c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + branch 111 - c.Data["PageIsSettingsBranches"] = true 112 - 113 - protectBranch, err := git_model.GetProtectedBranchBy(c, c.Repo.Repository.ID, branch) 114 - if err != nil { 115 - if !git.IsErrBranchNotExist(err) { 90 + ruleName := c.FormString("rule_name") 91 + var rule *git_model.ProtectedBranch 92 + if ruleName != "" { 93 + var err error 94 + rule, err = git_model.GetProtectedBranchRuleByName(c, c.Repo.Repository.ID, ruleName) 95 + if err != nil { 116 96 c.ServerError("GetProtectBranchOfRepoByName", err) 117 97 return 118 98 } 119 99 } 120 100 121 - if protectBranch == nil { 101 + if rule == nil { 122 102 // No options found, create defaults. 123 - protectBranch = &git_model.ProtectedBranch{ 124 - BranchName: branch, 125 - } 103 + rule = &git_model.ProtectedBranch{} 126 104 } 127 105 106 + c.Data["PageIsSettingsBranches"] = true 107 + c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + rule.RuleName 108 + 128 109 users, err := access_model.GetRepoReaders(c.Repo.Repository) 129 110 if err != nil { 130 111 c.ServerError("Repo.Repository.GetReaders", err) 131 112 return 132 113 } 133 114 c.Data["Users"] = users 134 - c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistUserIDs), ",") 135 - c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistUserIDs), ",") 136 - c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistUserIDs), ",") 115 + c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",") 116 + c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",") 117 + c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",") 137 118 contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c, c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts 138 - for _, ctx := range protectBranch.StatusCheckContexts { 119 + for _, ctx := range rule.StatusCheckContexts { 139 120 var found bool 140 121 for i := range contexts { 141 122 if contexts[i] == ctx { ··· 150 131 151 132 c.Data["branch_status_check_contexts"] = contexts 152 133 c.Data["is_context_required"] = func(context string) bool { 153 - for _, c := range protectBranch.StatusCheckContexts { 134 + for _, c := range rule.StatusCheckContexts { 154 135 if c == context { 155 136 return true 156 137 } ··· 165 146 return 166 147 } 167 148 c.Data["Teams"] = teams 168 - c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistTeamIDs), ",") 169 - c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistTeamIDs), ",") 170 - c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistTeamIDs), ",") 149 + c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",") 150 + c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",") 151 + c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",") 171 152 } 172 153 173 - c.Data["Branch"] = protectBranch 154 + c.Data["Rule"] = rule 174 155 c.HTML(http.StatusOK, tplProtectedBranch) 175 156 } 176 157 177 158 // SettingsProtectedBranchPost updates the protected branch settings 178 159 func SettingsProtectedBranchPost(ctx *context.Context) { 179 160 f := web.GetForm(ctx).(*forms.ProtectBranchForm) 180 - branch := ctx.Params("*") 181 - if !ctx.Repo.GitRepo.IsBranchExist(branch) { 182 - ctx.NotFound("IsBranchExist", nil) 161 + var protectBranch *git_model.ProtectedBranch 162 + if f.RuleName == "" { 163 + ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_rule_name")) 164 + ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit", ctx.Repo.RepoLink)) 183 165 return 184 166 } 185 167 186 - protectBranch, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch) 168 + var err error 169 + protectBranch, err = git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName) 187 170 if err != nil { 188 - if !git.IsErrBranchNotExist(err) { 189 - ctx.ServerError("GetProtectBranchOfRepoByName", err) 190 - return 171 + ctx.ServerError("GetProtectBranchOfRepoByName", err) 172 + return 173 + } 174 + if protectBranch == nil { 175 + // No options found, create defaults. 176 + protectBranch = &git_model.ProtectedBranch{ 177 + RepoID: ctx.Repo.Repository.ID, 178 + RuleName: f.RuleName, 191 179 } 192 180 } 193 181 194 - if f.Protected { 195 - if protectBranch == nil { 196 - // No options found, create defaults. 197 - protectBranch = &git_model.ProtectedBranch{ 198 - RepoID: ctx.Repo.Repository.ID, 199 - BranchName: branch, 200 - } 182 + var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64 183 + protectBranch.RuleName = f.RuleName 184 + if f.RequiredApprovals < 0 { 185 + ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min")) 186 + ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, f.RuleName)) 187 + return 188 + } 189 + 190 + switch f.EnablePush { 191 + case "all": 192 + protectBranch.CanPush = true 193 + protectBranch.EnableWhitelist = false 194 + protectBranch.WhitelistDeployKeys = false 195 + case "whitelist": 196 + protectBranch.CanPush = true 197 + protectBranch.EnableWhitelist = true 198 + protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys 199 + if strings.TrimSpace(f.WhitelistUsers) != "" { 200 + whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ",")) 201 201 } 202 - if f.RequiredApprovals < 0 { 203 - ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min")) 204 - ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch))) 202 + if strings.TrimSpace(f.WhitelistTeams) != "" { 203 + whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ",")) 205 204 } 205 + default: 206 + protectBranch.CanPush = false 207 + protectBranch.EnableWhitelist = false 208 + protectBranch.WhitelistDeployKeys = false 209 + } 206 210 207 - var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64 208 - switch f.EnablePush { 209 - case "all": 210 - protectBranch.CanPush = true 211 - protectBranch.EnableWhitelist = false 212 - protectBranch.WhitelistDeployKeys = false 213 - case "whitelist": 214 - protectBranch.CanPush = true 215 - protectBranch.EnableWhitelist = true 216 - protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys 217 - if strings.TrimSpace(f.WhitelistUsers) != "" { 218 - whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ",")) 219 - } 220 - if strings.TrimSpace(f.WhitelistTeams) != "" { 221 - whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ",")) 222 - } 223 - default: 224 - protectBranch.CanPush = false 225 - protectBranch.EnableWhitelist = false 226 - protectBranch.WhitelistDeployKeys = false 211 + protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist 212 + if f.EnableMergeWhitelist { 213 + if strings.TrimSpace(f.MergeWhitelistUsers) != "" { 214 + mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ",")) 227 215 } 216 + if strings.TrimSpace(f.MergeWhitelistTeams) != "" { 217 + mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ",")) 218 + } 219 + } 228 220 229 - protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist 230 - if f.EnableMergeWhitelist { 231 - if strings.TrimSpace(f.MergeWhitelistUsers) != "" { 232 - mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ",")) 233 - } 234 - if strings.TrimSpace(f.MergeWhitelistTeams) != "" { 235 - mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ",")) 236 - } 237 - } 221 + protectBranch.EnableStatusCheck = f.EnableStatusCheck 222 + if f.EnableStatusCheck { 223 + protectBranch.StatusCheckContexts = f.StatusCheckContexts 224 + } else { 225 + protectBranch.StatusCheckContexts = nil 226 + } 238 227 239 - protectBranch.EnableStatusCheck = f.EnableStatusCheck 240 - if f.EnableStatusCheck { 241 - protectBranch.StatusCheckContexts = f.StatusCheckContexts 242 - } else { 243 - protectBranch.StatusCheckContexts = nil 228 + protectBranch.RequiredApprovals = f.RequiredApprovals 229 + protectBranch.EnableApprovalsWhitelist = f.EnableApprovalsWhitelist 230 + if f.EnableApprovalsWhitelist { 231 + if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" { 232 + approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ",")) 244 233 } 245 - 246 - protectBranch.RequiredApprovals = f.RequiredApprovals 247 - protectBranch.EnableApprovalsWhitelist = f.EnableApprovalsWhitelist 248 - if f.EnableApprovalsWhitelist { 249 - if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" { 250 - approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ",")) 251 - } 252 - if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" { 253 - approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ",")) 254 - } 234 + if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" { 235 + approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ",")) 255 236 } 256 - protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews 257 - protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests 258 - protectBranch.DismissStaleApprovals = f.DismissStaleApprovals 259 - protectBranch.RequireSignedCommits = f.RequireSignedCommits 260 - protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns 261 - protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns 262 - protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch 237 + } 238 + protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews 239 + protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests 240 + protectBranch.DismissStaleApprovals = f.DismissStaleApprovals 241 + protectBranch.RequireSignedCommits = f.RequireSignedCommits 242 + protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns 243 + protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns 244 + protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch 263 245 264 - err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{ 265 - UserIDs: whitelistUsers, 266 - TeamIDs: whitelistTeams, 267 - MergeUserIDs: mergeWhitelistUsers, 268 - MergeTeamIDs: mergeWhitelistTeams, 269 - ApprovalsUserIDs: approvalsWhitelistUsers, 270 - ApprovalsTeamIDs: approvalsWhitelistTeams, 271 - }) 272 - if err != nil { 273 - ctx.ServerError("UpdateProtectBranch", err) 274 - return 275 - } 276 - if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil { 277 - ctx.ServerError("CheckPrsForBaseBranch", err) 246 + err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{ 247 + UserIDs: whitelistUsers, 248 + TeamIDs: whitelistTeams, 249 + MergeUserIDs: mergeWhitelistUsers, 250 + MergeTeamIDs: mergeWhitelistTeams, 251 + ApprovalsUserIDs: approvalsWhitelistUsers, 252 + ApprovalsTeamIDs: approvalsWhitelistTeams, 253 + }) 254 + if err != nil { 255 + ctx.ServerError("UpdateProtectBranch", err) 256 + return 257 + } 258 + 259 + // FIXME: since we only need to recheck files protected rules, we could improve this 260 + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) 261 + if err != nil { 262 + ctx.ServerError("FindAllMatchedBranches", err) 263 + return 264 + } 265 + for _, branchName := range matchedBranches { 266 + if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil { 267 + ctx.ServerError("CheckPRsForBaseBranch", err) 278 268 return 279 269 } 280 - ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", branch)) 281 - ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch))) 282 - } else { 283 - if protectBranch != nil { 284 - if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, protectBranch.ID); err != nil { 285 - ctx.ServerError("DeleteProtectedBranch", err) 286 - return 287 - } 288 - } 289 - ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", branch)) 290 - ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) 270 + } 271 + 272 + ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", protectBranch.RuleName)) 273 + ctx.Redirect(fmt.Sprintf("%s/settings/branches?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName)) 274 + } 275 + 276 + // DeleteProtectedBranchRulePost delete protected branch rule by id 277 + func DeleteProtectedBranchRulePost(ctx *context.Context) { 278 + ruleID := ctx.ParamsInt64("id") 279 + if ruleID <= 0 { 280 + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) 281 + ctx.JSON(http.StatusOK, map[string]interface{}{ 282 + "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), 283 + }) 284 + return 285 + } 286 + 287 + rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID) 288 + if err != nil { 289 + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) 290 + ctx.JSON(http.StatusOK, map[string]interface{}{ 291 + "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), 292 + }) 293 + return 291 294 } 295 + 296 + if rule == nil { 297 + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) 298 + ctx.JSON(http.StatusOK, map[string]interface{}{ 299 + "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), 300 + }) 301 + return 302 + } 303 + 304 + if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, ruleID); err != nil { 305 + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName)) 306 + ctx.JSON(http.StatusOK, map[string]interface{}{ 307 + "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), 308 + }) 309 + return 310 + } 311 + 312 + ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName)) 313 + ctx.JSON(http.StatusOK, map[string]interface{}{ 314 + "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), 315 + }) 292 316 } 293 317 294 318 // RenameBranchPost responses for rename a branch
+8 -2
routers/web/web.go
··· 861 861 }) 862 862 863 863 m.Group("/branches", func() { 864 - m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost) 865 - m.Combo("/*").Get(repo.SettingsProtectedBranch). 864 + m.Post("/", repo.SetDefaultBranchPost) 865 + }, repo.MustBeNotEmpty) 866 + 867 + m.Group("/branches", func() { 868 + m.Get("/", repo.ProtectedBranchRules) 869 + m.Combo("/edit").Get(repo.SettingsProtectedBranch). 866 870 Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost) 871 + m.Post("/{id}/delete", repo.DeleteProtectedBranchRulePost) 867 872 }, repo.MustBeNotEmpty) 873 + 868 874 m.Post("/rename_branch", web.Bind(forms.RenameBranchForm{}), context.RepoMustNotBeArchived(), repo.RenameBranchPost) 869 875 870 876 m.Group("/tags", func() {
+1 -1
services/asymkey/sign.go
··· 310 310 return false, "", nil, &ErrWontSign{twofa} 311 311 } 312 312 case approved: 313 - protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, pr.BaseBranch) 313 + protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pr.BaseBranch) 314 314 if err != nil { 315 315 return false, "", nil, err 316 316 }
+10 -3
services/convert/convert.go
··· 79 79 } 80 80 81 81 if isRepoAdmin { 82 - branch.EffectiveBranchProtectionName = bp.BranchName 82 + branch.EffectiveBranchProtectionName = bp.RuleName 83 83 } 84 84 85 85 if user != nil { ··· 87 87 if err != nil { 88 88 return nil, err 89 89 } 90 - branch.UserCanPush = bp.CanUserPush(db.DefaultContext, user.ID) 90 + bp.Repo = repo 91 + branch.UserCanPush = bp.CanUserPush(db.DefaultContext, user) 91 92 branch.UserCanMerge = git_model.IsUserMergeWhitelisted(db.DefaultContext, bp, user.ID, permission) 92 93 } 93 94 ··· 121 122 log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err) 122 123 } 123 124 125 + branchName := "" 126 + if !git_model.IsRuleNameSpecial(bp.RuleName) { 127 + branchName = bp.RuleName 128 + } 129 + 124 130 return &api.BranchProtection{ 125 - BranchName: bp.BranchName, 131 + BranchName: branchName, 132 + RuleName: bp.RuleName, 126 133 EnablePush: bp.CanPush, 127 134 EnablePushWhitelist: bp.EnableWhitelist, 128 135 PushWhitelistUsernames: pushWhitelistUsernames,
+1 -1
services/forms/repo_form.go
··· 186 186 187 187 // ProtectBranchForm form for changing protected branch settings 188 188 type ProtectBranchForm struct { 189 - Protected bool 189 + RuleName string `binding:"Required"` 190 190 EnablePush string 191 191 WhitelistUsers string 192 192 WhitelistTeams string
+6 -4
services/pull/check.go
··· 14 14 15 15 "code.gitea.io/gitea/models" 16 16 "code.gitea.io/gitea/models/db" 17 + git_model "code.gitea.io/gitea/models/git" 17 18 issues_model "code.gitea.io/gitea/models/issues" 18 19 access_model "code.gitea.io/gitea/models/perm/access" 19 20 repo_model "code.gitea.io/gitea/models/repo" ··· 126 127 127 128 // isSignedIfRequired check if merge will be signed if required 128 129 func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) (bool, error) { 129 - if err := pr.LoadProtectedBranch(ctx); err != nil { 130 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) 131 + if err != nil { 130 132 return false, err 131 133 } 132 134 133 - if pr.ProtectedBranch == nil || !pr.ProtectedBranch.RequireSignedCommits { 135 + if pb == nil || !pb.RequireSignedCommits { 134 136 return true, nil 135 137 } 136 138 ··· 348 350 checkAndUpdateStatus(ctx, pr) 349 351 } 350 352 351 - // CheckPrsForBaseBranch check all pulls with bseBrannch 352 - func CheckPrsForBaseBranch(baseRepo *repo_model.Repository, baseBranchName string) error { 353 + // CheckPRsForBaseBranch check all pulls with baseBrannch 354 + func CheckPRsForBaseBranch(baseRepo *repo_model.Repository, baseBranchName string) error { 353 355 prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(baseRepo.ID, baseBranchName) 354 356 if err != nil { 355 357 return err
+7 -5
services/pull/commit_status.go
··· 83 83 84 84 // IsPullCommitStatusPass returns if all required status checks PASS 85 85 func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) { 86 - if err := pr.LoadProtectedBranch(ctx); err != nil { 86 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) 87 + if err != nil { 87 88 return false, errors.Wrap(err, "GetLatestCommitStatus") 88 89 } 89 - if pr.ProtectedBranch == nil || !pr.ProtectedBranch.EnableStatusCheck { 90 + if pb == nil || !pb.EnableStatusCheck { 90 91 return true, nil 91 92 } 92 93 ··· 137 138 return "", errors.Wrap(err, "GetLatestCommitStatus") 138 139 } 139 140 140 - if err := pr.LoadProtectedBranch(ctx); err != nil { 141 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) 142 + if err != nil { 141 143 return "", errors.Wrap(err, "LoadProtectedBranch") 142 144 } 143 145 var requiredContexts []string 144 - if pr.ProtectedBranch != nil { 145 - requiredContexts = pr.ProtectedBranch.StatusCheckContexts 146 + if pb != nil { 147 + requiredContexts = pb.StatusCheckContexts 146 148 } 147 149 148 150 return MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts), nil
+14 -10
services/pull/merge.go
··· 760 760 return false, nil 761 761 } 762 762 763 - err := pr.LoadProtectedBranch(ctx) 763 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) 764 764 if err != nil { 765 765 return false, err 766 766 } 767 767 768 - if (p.CanWrite(unit.TypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && git_model.IsUserMergeWhitelisted(ctx, pr.ProtectedBranch, user.ID, p)) { 768 + if (p.CanWrite(unit.TypeCode) && pb == nil) || (pb != nil && git_model.IsUserMergeWhitelisted(ctx, pb, user.ID, p)) { 769 769 return true, nil 770 770 } 771 771 ··· 778 778 return fmt.Errorf("LoadBaseRepo: %w", err) 779 779 } 780 780 781 - if err = pr.LoadProtectedBranch(ctx); err != nil { 782 - return fmt.Errorf("LoadProtectedBranch: %w", err) 781 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) 782 + if err != nil { 783 + return fmt.Errorf("LoadProtectedBranch: %v", err) 783 784 } 784 - if pr.ProtectedBranch == nil { 785 + if pb == nil { 785 786 return nil 786 787 } 787 788 ··· 795 796 } 796 797 } 797 798 798 - if !issues_model.HasEnoughApprovals(ctx, pr.ProtectedBranch, pr) { 799 + if !issues_model.HasEnoughApprovals(ctx, pb, pr) { 799 800 return models.ErrDisallowedToMerge{ 800 801 Reason: "Does not have enough approvals", 801 802 } 802 803 } 803 - if issues_model.MergeBlockedByRejectedReview(ctx, pr.ProtectedBranch, pr) { 804 + if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) { 804 805 return models.ErrDisallowedToMerge{ 805 806 Reason: "There are requested changes", 806 807 } 807 808 } 808 - if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pr.ProtectedBranch, pr) { 809 + if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) { 809 810 return models.ErrDisallowedToMerge{ 810 811 Reason: "There are official review requests", 811 812 } 812 813 } 813 814 814 - if issues_model.MergeBlockedByOutdatedBranch(pr.ProtectedBranch, pr) { 815 + if issues_model.MergeBlockedByOutdatedBranch(pb, pr) { 815 816 return models.ErrDisallowedToMerge{ 816 817 Reason: "The head branch is behind the base branch", 817 818 } ··· 821 822 return nil 822 823 } 823 824 824 - if pr.ProtectedBranch.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) { 825 + if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) { 825 826 return models.ErrDisallowedToMerge{ 826 827 Reason: "Changed protected files", 827 828 } ··· 836 837 defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID)) 837 838 838 839 if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { 840 + if err := pr.LoadBaseRepo(ctx); err != nil { 841 + return err 842 + } 839 843 prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests) 840 844 if err != nil { 841 845 return err
+8 -8
services/pull/patch.go
··· 14 14 "strings" 15 15 16 16 "code.gitea.io/gitea/models" 17 - "code.gitea.io/gitea/models/db" 17 + git_model "code.gitea.io/gitea/models/git" 18 18 issues_model "code.gitea.io/gitea/models/issues" 19 19 "code.gitea.io/gitea/models/unit" 20 20 "code.gitea.io/gitea/modules/container" ··· 106 106 } 107 107 108 108 // 3. Check for protected files changes 109 - if err = checkPullFilesProtection(pr, gitRepo); err != nil { 110 - return fmt.Errorf("pr.CheckPullFilesProtection(): %w", err) 109 + if err = checkPullFilesProtection(ctx, pr, gitRepo); err != nil { 110 + return fmt.Errorf("pr.CheckPullFilesProtection(): %v", err) 111 111 } 112 112 113 113 if len(pr.ChangedProtectedFiles) > 0 { ··· 544 544 } 545 545 546 546 // checkPullFilesProtection check if pr changed protected files and save results 547 - func checkPullFilesProtection(pr *issues_model.PullRequest, gitRepo *git.Repository) error { 547 + func checkPullFilesProtection(ctx context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) error { 548 548 if pr.Status == issues_model.PullRequestStatusEmpty { 549 549 pr.ChangedProtectedFiles = nil 550 550 return nil 551 551 } 552 552 553 - if err := pr.LoadProtectedBranch(db.DefaultContext); err != nil { 553 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) 554 + if err != nil { 554 555 return err 555 556 } 556 557 557 - if pr.ProtectedBranch == nil { 558 + if pb == nil { 558 559 pr.ChangedProtectedFiles = nil 559 560 return nil 560 561 } 561 562 562 - var err error 563 - pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.MergeBase, "tracking", pr.ProtectedBranch.GetProtectedFilePatterns(), 10, os.Environ()) 563 + pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.MergeBase, "tracking", pb.GetProtectedFilePatterns(), 10, os.Environ()) 564 564 if err != nil && !models.IsErrFilePathProtected(err) { 565 565 return err 566 566 }
+17 -4
services/pull/update.go
··· 8 8 "fmt" 9 9 10 10 "code.gitea.io/gitea/models" 11 + git_model "code.gitea.io/gitea/models/git" 11 12 issues_model "code.gitea.io/gitea/models/issues" 12 13 access_model "code.gitea.io/gitea/models/perm/access" 13 14 repo_model "code.gitea.io/gitea/models/repo" ··· 92 93 return false, false, err 93 94 } 94 95 96 + if err := pull.LoadBaseRepo(ctx); err != nil { 97 + return false, false, err 98 + } 99 + 95 100 pr := &issues_model.PullRequest{ 96 101 HeadRepoID: pull.BaseRepoID, 102 + HeadRepo: pull.BaseRepo, 97 103 BaseRepoID: pull.HeadRepoID, 104 + BaseRepo: pull.HeadRepo, 98 105 HeadBranch: pull.BaseBranch, 99 106 BaseBranch: pull.HeadBranch, 100 107 } 101 108 102 - err = pr.LoadProtectedBranch(ctx) 109 + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch) 103 110 if err != nil { 104 111 return false, false, err 105 112 } 106 113 107 114 // can't do rebase on protected branch because need force push 108 - if pr.ProtectedBranch == nil { 115 + if pb == nil { 116 + if err := pr.LoadBaseRepo(ctx); err != nil { 117 + return false, false, err 118 + } 109 119 prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests) 110 120 if err != nil { 111 121 log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err) ··· 115 125 } 116 126 117 127 // Update function need push permission 118 - if pr.ProtectedBranch != nil && !pr.ProtectedBranch.CanUserPush(ctx, user.ID) { 119 - return false, false, nil 128 + if pb != nil { 129 + pb.Repo = pull.BaseRepo 130 + if !pb.CanUserPush(ctx, user) { 131 + return false, false, nil 132 + } 120 133 } 121 134 122 135 baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user)
+3 -5
services/repository/branch.go
··· 149 149 150 150 // enmuerates all branch related errors 151 151 var ( 152 - ErrBranchIsDefault = errors.New("branch is default") 153 - ErrBranchIsProtected = errors.New("branch is protected") 152 + ErrBranchIsDefault = errors.New("branch is default") 154 153 ) 155 154 156 155 // DeleteBranch delete branch ··· 159 158 return ErrBranchIsDefault 160 159 } 161 160 162 - isProtected, err := git_model.IsProtectedBranch(db.DefaultContext, repo.ID, branchName) 161 + isProtected, err := git_model.IsBranchProtected(db.DefaultContext, repo.ID, branchName) 163 162 if err != nil { 164 163 return err 165 164 } 166 - 167 165 if isProtected { 168 - return ErrBranchIsProtected 166 + return git_model.ErrBranchIsProtected 169 167 } 170 168 171 169 commit, err := gitRepo.GetBranchCommit(branchName)
+7 -4
services/repository/files/patch.go
··· 66 66 return err 67 67 } 68 68 } else { 69 - protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, opts.OldBranch) 69 + protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, opts.OldBranch) 70 70 if err != nil { 71 71 return err 72 72 } 73 - if protectedBranch != nil && !protectedBranch.CanUserPush(ctx, doer.ID) { 74 - return models.ErrUserCannotCommit{ 75 - UserName: doer.LowerName, 73 + if protectedBranch != nil { 74 + protectedBranch.Repo = repo 75 + if !protectedBranch.CanUserPush(ctx, doer) { 76 + return models.ErrUserCannotCommit{ 77 + UserName: doer.LowerName, 78 + } 76 79 } 77 80 } 78 81 if protectedBranch != nil && protectedBranch.RequireSignedCommits {
+3 -2
services/repository/files/update.go
··· 463 463 464 464 // VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch 465 465 func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName, treePath string) error { 466 - protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, branchName) 466 + protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName) 467 467 if err != nil { 468 468 return err 469 469 } 470 470 if protectedBranch != nil { 471 + protectedBranch.Repo = repo 471 472 isUnprotectedFile := false 472 473 glob := protectedBranch.GetUnprotectedFilePatterns() 473 474 if len(glob) != 0 { 474 475 isUnprotectedFile = protectedBranch.IsUnprotectedFile(glob, treePath) 475 476 } 476 - if !protectedBranch.CanUserPush(ctx, doer.ID) && !isUnprotectedFile { 477 + if !protectedBranch.CanUserPush(ctx, doer) && !isUnprotectedFile { 477 478 return models.ErrUserCannotCommit{ 478 479 UserName: doer.LowerName, 479 480 }
+2 -2
templates/repo/issue/view_content/pull.tmpl
··· 204 204 {{if .IsBlockedByApprovals}} 205 205 <div class="item"> 206 206 <i class="icon icon-octicon">{{svg "octicon-x"}}</i> 207 - {{$.locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .Issue.PullRequest.ProtectedBranch.RequiredApprovals}} 207 + {{$.locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}} 208 208 </div> 209 209 {{else if .IsBlockedByRejection}} 210 210 <div class="item"> ··· 444 444 {{if .IsBlockedByApprovals}} 445 445 <div class="item text red"> 446 446 {{svg "octicon-x"}} 447 - {{$.locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .Issue.PullRequest.ProtectedBranch.RequiredApprovals}} 447 + {{$.locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}} 448 448 </div> 449 449 {{else if .IsBlockedByRejection}} 450 450 <div class="item text red">
+21 -16
templates/repo/settings/branches.tmpl
··· 43 43 44 44 <h4 class="ui top attached header"> 45 45 {{.locale.Tr "repo.settings.protected_branch"}} 46 + <div class="ui right"> 47 + <a class="ui primary tiny button" href="{{$.Repository.Link}}/settings/branches/edit">{{$.locale.Tr "repo.settings.branches.add_new_rule"}}</a> 48 + </div> 46 49 </h4> 47 50 48 51 <div class="ui attached table segment"> 49 52 <div class="ui grid padded"> 50 - <div class="eight wide column"> 51 - <div class="ui fluid dropdown selection" tabindex="0"> 52 - {{svg "octicon-triangle-down" 14 "dropdown icon"}} 53 - <div class="default text">{{.locale.Tr "repo.settings.choose_branch"}}</div> 54 - <div class="menu transition hidden" tabindex="-1" style="display: block !important;"> 55 - {{range .LeftBranches}} 56 - <a class="item" href="{{$.Repository.Link}}/settings/branches/{{. | PathEscapeSegments}}">{{.}}</a> 57 - {{end}} 58 - </div> 59 - </div> 60 - </div> 61 - </div> 62 - 63 - <div class="ui grid padded"> 64 53 <div class="sixteen wide column"> 65 54 <table class="ui single line table padded"> 66 55 <tbody> 67 56 {{range .ProtectedBranches}} 68 57 <tr> 69 - <td><div class="ui basic primary label">{{.BranchName}}</div></td> 70 - <td class="right aligned"><a class="rm ui button" href="{{$.Repository.Link}}/settings/branches/{{.BranchName | PathEscapeSegments}}">{{$.locale.Tr "repo.settings.edit_protected_branch"}}</a></td> 58 + <td><div class="ui basic primary label">{{.RuleName}}</div></td> 59 + <td class="right aligned"> 60 + <a class="rm ui button" href="{{$.Repository.Link}}/settings/branches/edit?rule_name={{.RuleName}}">{{$.locale.Tr "repo.settings.edit_protected_branch"}}</a> 61 + <button class="ui red tiny button delete-button" data-url="{{$.Repository.Link}}/settings/branches/{{.ID}}/delete" data-id="{{.ID}}"> 62 + {{$.locale.Tr "repo.settings.protected_branch.delete_rule"}}</button> 63 + </td> 71 64 </tr> 72 65 {{else}} 73 66 <tr class="center aligned"><td>{{.locale.Tr "repo.settings.no_protected_branch"}}</td></tr> ··· 102 95 {{end}} 103 96 </div> 104 97 </div> 98 + 99 + <div class="ui small basic delete modal"> 100 + <div class="ui header"> 101 + {{svg "octicon-trash" 16 "mr-2"}} 102 + {{.locale.Tr "repo.settings.protected_branch_deletion"}} 103 + </div> 104 + <div class="content"> 105 + <p>{{.locale.Tr "repo.settings.protected_branch_deletion_desc"}}</p> 106 + </div> 107 + {{template "base/delete_modal_actions" .}} 108 + </div> 109 + 105 110 {{template "base/footer" .}}
+39 -36
templates/repo/settings/protected_branch.tmpl
··· 4 4 {{template "repo/settings/navbar" .}} 5 5 <div class="ui container"> 6 6 {{template "base/alert" .}} 7 - <h4 class="ui top attached header"> 8 - {{.locale.Tr "repo.settings.branch_protection" (.Branch.BranchName|Escape) | Str2html}} 9 - </h4> 10 - <div class="ui attached segment branch-protection"> 11 - <form class="ui form" action="{{.Link}}" method="post"> 12 - {{.CsrfTokenHtml}} 13 - <div class="inline field"> 14 - <div class="ui checkbox"> 15 - <input class="enable-protection" name="protected" type="checkbox" data-target="#protection_box" {{if .Branch.IsProtected}}checked{{end}}> 16 - <label>{{.locale.Tr "repo.settings.protect_this_branch"}}</label> 17 - <p class="help">{{.locale.Tr "repo.settings.protect_this_branch_desc"}}</p> 18 - </div> 7 + <form class="ui form" action="{{.Link}}" method="post"> 8 + <h4 class="ui top attached header"> 9 + {{.locale.Tr "repo.settings.branch_protection" (.Rule.RuleName|Escape) | Str2html}} 10 + </h4> 11 + <div class="ui attached segment branch-protection"> 12 + <div class="field"> 13 + <label for="protected_file_patterns">{{.locale.Tr "repo.settings.protect_branch_name_pattern"}}</label> 14 + <input name="rule_name" type="text" value="{{.Rule.RuleName}}"> 15 + <input name="rule_id" type="hidden" value="{{.Rule.ID}}"> 19 16 </div> 20 - <div id="protection_box" class="fields {{if not .Branch.IsProtected}}disabled{{end}}"> 17 + 18 + <div class="ui divider"></div> 19 + 20 + {{.CsrfTokenHtml}} 21 + <div id="protection_box" class="fields"> 21 22 <div class="field"> 22 23 <div class="ui radio checkbox"> 23 - <input name="enable_push" type="radio" value="none" class="disable-whitelist" data-target="#whitelist_box" {{if not .Branch.CanPush}}checked{{end}}> 24 + <input name="enable_push" type="radio" value="none" class="disable-whitelist" data-target="#whitelist_box" {{if not .Rule.CanPush}}checked{{end}}> 24 25 <label>{{.locale.Tr "repo.settings.protect_disable_push"}}</label> 25 26 <p class="help">{{.locale.Tr "repo.settings.protect_disable_push_desc"}}</p> 26 27 </div> 27 28 </div> 28 29 <div class="field"> 29 30 <div class="ui radio checkbox"> 30 - <input name="enable_push" type="radio" value="all" class="disable-whitelist" data-target="#whitelist_box" {{if and (.Branch.CanPush) (not .Branch.EnableWhitelist)}}checked{{end}}> 31 + <input name="enable_push" type="radio" value="all" class="disable-whitelist" data-target="#whitelist_box" {{if and (.Rule.CanPush) (not .Rule.EnableWhitelist)}}checked{{end}}> 31 32 <label>{{.locale.Tr "repo.settings.protect_enable_push"}}</label> 32 33 <p class="help">{{.locale.Tr "repo.settings.protect_enable_push_desc"}}</p> 33 34 </div> 34 35 </div> 35 36 <div class="field"> 36 37 <div class="ui radio checkbox"> 37 - <input name="enable_push" type="radio" value="whitelist" class="enable-whitelist" data-target="#whitelist_box" {{if and (.Branch.CanPush) (.Branch.EnableWhitelist)}}checked{{end}}> 38 + <input name="enable_push" type="radio" value="whitelist" class="enable-whitelist" data-target="#whitelist_box" {{if and (.Rule.CanPush) (.Rule.EnableWhitelist)}}checked{{end}}> 38 39 <label>{{.locale.Tr "repo.settings.protect_whitelist_committers"}}</label> 39 40 <p class="help">{{.locale.Tr "repo.settings.protect_whitelist_committers_desc"}}</p> 40 41 </div> 41 42 </div> 42 - <div id="whitelist_box" class="fields {{if not .Branch.EnableWhitelist}}disabled{{end}}"> 43 + <div id="whitelist_box" class="fields {{if not .Rule.EnableWhitelist}}disabled{{end}}"> 43 44 <div class="whitelist field"> 44 45 <label>{{.locale.Tr "repo.settings.protect_whitelist_users"}}</label> 45 46 <div class="ui multiple search selection dropdown"> ··· 76 77 <br> 77 78 <div class="whitelist field"> 78 79 <div class="ui checkbox"> 79 - <input type="checkbox" name="whitelist_deploy_keys" {{if .Branch.WhitelistDeployKeys}}checked{{end}}> 80 + <input type="checkbox" name="whitelist_deploy_keys" {{if .Rule.WhitelistDeployKeys}}checked{{end}}> 80 81 <label for="whitelist_deploy_keys">{{.locale.Tr "repo.settings.protect_whitelist_deploy_keys"}}</label> 81 82 </div> 82 83 </div> 83 84 </div> 84 85 86 + <div class="ui divider"></div> 87 + 85 88 <div class="field"> 86 89 <div class="ui checkbox"> 87 - <input class="enable-whitelist" name="enable_merge_whitelist" type="checkbox" data-target="#merge_whitelist_box" {{if .Branch.EnableMergeWhitelist}}checked{{end}}> 90 + <input class="enable-whitelist" name="enable_merge_whitelist" type="checkbox" data-target="#merge_whitelist_box" {{if .Rule.EnableMergeWhitelist}}checked{{end}}> 88 91 <label>{{.locale.Tr "repo.settings.protect_merge_whitelist_committers"}}</label> 89 92 <p class="help">{{.locale.Tr "repo.settings.protect_merge_whitelist_committers_desc"}}</p> 90 93 </div> 91 94 </div> 92 - <div id="merge_whitelist_box" class="fields {{if not .Branch.EnableMergeWhitelist}}disabled{{end}}"> 95 + <div id="merge_whitelist_box" class="fields {{if not .Rule.EnableMergeWhitelist}}disabled{{end}}"> 93 96 <div class="whitelist field"> 94 97 <label>{{.locale.Tr "repo.settings.protect_merge_whitelist_users"}}</label> 95 98 <div class="ui multiple search selection dropdown"> ··· 127 130 128 131 <div class="field"> 129 132 <div class="ui checkbox"> 130 - <input class="enable-statuscheck" name="enable_status_check" type="checkbox" data-target="#statuscheck_contexts_box" {{if eq (len .branch_status_check_contexts) 0}}disabled{{end}} {{if .Branch.EnableStatusCheck}}checked{{end}}> 133 + <input class="enable-statuscheck" name="enable_status_check" type="checkbox" data-target="#statuscheck_contexts_box" {{if eq (len .branch_status_check_contexts) 0}}disabled{{end}} {{if .Rule.EnableStatusCheck}}checked{{end}}> 131 134 <label>{{.locale.Tr "repo.settings.protect_check_status_contexts"}}</label> 132 135 <p class="help">{{.locale.Tr "repo.settings.protect_check_status_contexts_desc"}}</p> 133 136 </div> 134 137 </div> 135 138 136 - <div id="statuscheck_contexts_box" class="fields {{if not .Branch.EnableStatusCheck}}disabled{{end}}"> 139 + <div id="statuscheck_contexts_box" class="fields {{if not .Rule.EnableStatusCheck}}disabled{{end}}"> 137 140 <div class="field"> 138 141 <table class="ui celled table six column"> 139 142 <thead> ··· 159 162 160 163 <div class="field"> 161 164 <label for="required-approvals">{{.locale.Tr "repo.settings.protect_required_approvals"}}</label> 162 - <input name="required_approvals" id="required-approvals" type="number" value="{{.Branch.RequiredApprovals}}"> 165 + <input name="required_approvals" id="required-approvals" type="number" value="{{.Rule.RequiredApprovals}}"> 163 166 <p class="help">{{.locale.Tr "repo.settings.protect_required_approvals_desc"}}</p> 164 167 </div> 165 168 <div class="field"> 166 169 <div class="ui checkbox"> 167 - <input class="enable-whitelist" name="enable_approvals_whitelist" type="checkbox" data-target="#approvals_whitelist_box" {{if .Branch.EnableApprovalsWhitelist}}checked{{end}}> 170 + <input class="enable-whitelist" name="enable_approvals_whitelist" type="checkbox" data-target="#approvals_whitelist_box" {{if .Rule.EnableApprovalsWhitelist}}checked{{end}}> 168 171 <label>{{.locale.Tr "repo.settings.protect_approvals_whitelist_enabled"}}</label> 169 172 <p class="help">{{.locale.Tr "repo.settings.protect_approvals_whitelist_enabled_desc"}}</p> 170 173 </div> 171 174 </div> 172 - <div id="approvals_whitelist_box" class="fields {{if not .Branch.EnableApprovalsWhitelist}}disabled{{end}}"> 175 + <div id="approvals_whitelist_box" class="fields {{if not .Rule.EnableApprovalsWhitelist}}disabled{{end}}"> 173 176 <div class="whitelist field"> 174 177 <label>{{.locale.Tr "repo.settings.protect_approvals_whitelist_users"}}</label> 175 178 <div class="ui multiple search selection dropdown"> ··· 206 209 </div> 207 210 <div class="field"> 208 211 <div class="ui checkbox"> 209 - <input name="block_on_rejected_reviews" type="checkbox" {{if .Branch.BlockOnRejectedReviews}}checked{{end}}> 212 + <input name="block_on_rejected_reviews" type="checkbox" {{if .Rule.BlockOnRejectedReviews}}checked{{end}}> 210 213 <label for="block_on_rejected_reviews">{{.locale.Tr "repo.settings.block_rejected_reviews"}}</label> 211 214 <p class="help">{{.locale.Tr "repo.settings.block_rejected_reviews_desc"}}</p> 212 215 </div> 213 216 </div> 214 217 <div class="field"> 215 218 <div class="ui checkbox"> 216 - <input name="block_on_official_review_requests" type="checkbox" {{if .Branch.BlockOnOfficialReviewRequests}}checked{{end}}> 219 + <input name="block_on_official_review_requests" type="checkbox" {{if .Rule.BlockOnOfficialReviewRequests}}checked{{end}}> 217 220 <label for="block_on_official_review_requests">{{.locale.Tr "repo.settings.block_on_official_review_requests"}}</label> 218 221 <p class="help">{{.locale.Tr "repo.settings.block_on_official_review_requests_desc"}}</p> 219 222 </div> 220 223 </div> 221 224 <div class="field"> 222 225 <div class="ui checkbox"> 223 - <input name="dismiss_stale_approvals" type="checkbox" {{if .Branch.DismissStaleApprovals}}checked{{end}}> 226 + <input name="dismiss_stale_approvals" type="checkbox" {{if .Rule.DismissStaleApprovals}}checked{{end}}> 224 227 <label for="dismiss_stale_approvals">{{.locale.Tr "repo.settings.dismiss_stale_approvals"}}</label> 225 228 <p class="help">{{.locale.Tr "repo.settings.dismiss_stale_approvals_desc"}}</p> 226 229 </div> 227 230 </div> 228 231 <div class="field"> 229 232 <div class="ui checkbox"> 230 - <input name="require_signed_commits" type="checkbox" {{if .Branch.RequireSignedCommits}}checked{{end}}> 233 + <input name="require_signed_commits" type="checkbox" {{if .Rule.RequireSignedCommits}}checked{{end}}> 231 234 <label for="require_signed_commits">{{.locale.Tr "repo.settings.require_signed_commits"}}</label> 232 235 <p class="help">{{.locale.Tr "repo.settings.require_signed_commits_desc"}}</p> 233 236 </div> 234 237 </div> 235 238 <div class="field"> 236 239 <div class="ui checkbox"> 237 - <input name="block_on_outdated_branch" type="checkbox" {{if .Branch.BlockOnOutdatedBranch}}checked{{end}}> 240 + <input name="block_on_outdated_branch" type="checkbox" {{if .Rule.BlockOnOutdatedBranch}}checked{{end}}> 238 241 <label for="block_on_outdated_branch">{{.locale.Tr "repo.settings.block_outdated_branch"}}</label> 239 242 <p class="help">{{.locale.Tr "repo.settings.block_outdated_branch_desc"}}</p> 240 243 </div> 241 244 </div> 242 245 <div class="field"> 243 246 <label for="protected_file_patterns">{{.locale.Tr "repo.settings.protect_protected_file_patterns"}}</label> 244 - <input name="protected_file_patterns" id="protected_file_patterns" type="text" value="{{.Branch.ProtectedFilePatterns}}"> 247 + <input name="protected_file_patterns" id="protected_file_patterns" type="text" value="{{.Rule.ProtectedFilePatterns}}"> 245 248 <p class="help">{{.locale.Tr "repo.settings.protect_protected_file_patterns_desc" | Safe}}</p> 246 249 </div> 247 250 <div class="field"> 248 251 <label for="unprotected_file_patterns">{{.locale.Tr "repo.settings.protect_unprotected_file_patterns"}}</label> 249 - <input name="unprotected_file_patterns" id="unprotected_file_patterns" type="text" value="{{.Branch.UnprotectedFilePatterns}}"> 252 + <input name="unprotected_file_patterns" id="unprotected_file_patterns" type="text" value="{{.Rule.UnprotectedFilePatterns}}"> 250 253 <p class="help">{{.locale.Tr "repo.settings.protect_unprotected_file_patterns_desc" | Safe}}</p> 251 254 </div> 252 - 253 255 </div> 254 256 255 257 <div class="ui divider"></div> 256 258 257 259 <div class="field"> 258 - <button class="ui green button">{{$.locale.Tr "repo.settings.update_settings"}}</button> 260 + <button class="ui green button">{{$.locale.Tr "repo.settings.protected_branch.save_rule"}}</button> 261 + <button class="ui gray button">{{$.locale.Tr "cancel"}}</button> 259 262 </div> 260 - </form> 261 - </div> 263 + </div> 264 + </form> 262 265 </div> 263 266 </div> 264 267 {{template "base/footer" .}}
+10
templates/swagger/v1_json.tmpl
··· 14233 14233 "x-go-name": "BlockOnRejectedReviews" 14234 14234 }, 14235 14235 "branch_name": { 14236 + "description": "Deprecated: true", 14236 14237 "type": "string", 14237 14238 "x-go-name": "BranchName" 14238 14239 }, ··· 14309 14310 "type": "integer", 14310 14311 "format": "int64", 14311 14312 "x-go-name": "RequiredApprovals" 14313 + }, 14314 + "rule_name": { 14315 + "type": "string", 14316 + "x-go-name": "RuleName" 14312 14317 }, 14313 14318 "status_check_contexts": { 14314 14319 "type": "array", ··· 14772 14777 "x-go-name": "BlockOnRejectedReviews" 14773 14778 }, 14774 14779 "branch_name": { 14780 + "description": "Deprecated: true", 14775 14781 "type": "string", 14776 14782 "x-go-name": "BranchName" 14777 14783 }, ··· 14843 14849 "type": "integer", 14844 14850 "format": "int64", 14845 14851 "x-go-name": "RequiredApprovals" 14852 + }, 14853 + "rule_name": { 14854 + "type": "string", 14855 + "x-go-name": "RuleName" 14846 14856 }, 14847 14857 "status_check_contexts": { 14848 14858 "type": "array",
+6 -6
tests/integration/api_branch_test.go
··· 38 38 if resp.Code == http.StatusOK { 39 39 var branchProtection api.BranchProtection 40 40 DecodeJSON(t, resp, &branchProtection) 41 - assert.EqualValues(t, branchName, branchProtection.BranchName) 41 + assert.EqualValues(t, branchName, branchProtection.RuleName) 42 42 } 43 43 } 44 44 45 45 func testAPICreateBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { 46 46 token := getUserToken(t, "user2") 47 47 req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections?token="+token, &api.BranchProtection{ 48 - BranchName: branchName, 48 + RuleName: branchName, 49 49 }) 50 50 resp := MakeRequest(t, req, expectedHTTPStatus) 51 51 52 52 if resp.Code == http.StatusCreated { 53 53 var branchProtection api.BranchProtection 54 54 DecodeJSON(t, resp, &branchProtection) 55 - assert.EqualValues(t, branchName, branchProtection.BranchName) 55 + assert.EqualValues(t, branchName, branchProtection.RuleName) 56 56 } 57 57 } 58 58 ··· 64 64 if resp.Code == http.StatusOK { 65 65 var branchProtection api.BranchProtection 66 66 DecodeJSON(t, resp, &branchProtection) 67 - assert.EqualValues(t, branchName, branchProtection.BranchName) 67 + assert.EqualValues(t, branchName, branchProtection.RuleName) 68 68 } 69 69 } 70 70 ··· 169 169 func TestAPIBranchProtection(t *testing.T) { 170 170 defer tests.PrepareTestEnv(t)() 171 171 172 - // Branch protection only on branch that exist 173 - testAPICreateBranchProtection(t, "master/doesnotexist", http.StatusNotFound) 172 + // Branch protection on branch that not exist 173 + testAPICreateBranchProtection(t, "master/doesnotexist", http.StatusCreated) 174 174 // Get branch protection on branch that exist but not branch protection 175 175 testAPIGetBranchProtection(t, "master", http.StatusNotFound) 176 176
+18 -9
tests/integration/editor_test.go
··· 10 10 "path" 11 11 "testing" 12 12 13 + "code.gitea.io/gitea/modules/json" 14 + 13 15 "github.com/stretchr/testify/assert" 14 16 ) 15 17 ··· 43 45 44 46 csrf := GetCSRF(t, session, "/user2/repo1/settings/branches") 45 47 // Change master branch to protected 46 - req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ 47 - "_csrf": csrf, 48 - "protected": "on", 48 + req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{ 49 + "_csrf": csrf, 50 + "rule_name": "master", 51 + "enable_push": "true", 49 52 }) 50 53 session.MakeRequest(t, req, http.StatusSeeOther) 51 54 // Check if master branch has been locked successfully 52 55 flashCookie := session.GetCookie("macaron_flash") 53 56 assert.NotNil(t, flashCookie) 54 - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) 57 + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) 55 58 56 59 // Request editor page 57 60 req = NewRequest(t, "GET", "/user2/repo1/_new/master/") ··· 76 79 77 80 // remove the protected branch 78 81 csrf = GetCSRF(t, session, "/user2/repo1/settings/branches") 82 + 79 83 // Change master branch to protected 80 - req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ 81 - "_csrf": csrf, 82 - "protected": "off", 84 + req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/1/delete", map[string]string{ 85 + "_csrf": csrf, 83 86 }) 84 - session.MakeRequest(t, req, http.StatusSeeOther) 87 + 88 + resp = session.MakeRequest(t, req, http.StatusOK) 89 + 90 + res := make(map[string]string) 91 + assert.NoError(t, json.NewDecoder(resp.Body).Decode(&res)) 92 + assert.EqualValues(t, "/user2/repo1/settings/branches", res["redirect"]) 93 + 85 94 // Check if master branch has been locked successfully 86 95 flashCookie = session.GetCookie("macaron_flash") 87 96 assert.NotNil(t, flashCookie) 88 - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bdisabled.", flashCookie.Value) 97 + assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25271%2527%2Bfailed.", flashCookie.Value) 89 98 }) 90 99 } 91 100
+5 -5
tests/integration/git_test.go
··· 414 414 415 415 if userToWhitelist == "" { 416 416 // Change branch to protected 417 - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{ 417 + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{ 418 418 "_csrf": csrf, 419 - "protected": "on", 419 + "rule_name": branch, 420 420 "unprotected_file_patterns": unprotectedFilePatterns, 421 421 }) 422 422 ctx.Session.MakeRequest(t, req, http.StatusSeeOther) ··· 424 424 user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelist) 425 425 assert.NoError(t, err) 426 426 // Change branch to protected 427 - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{ 427 + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{ 428 428 "_csrf": csrf, 429 - "protected": "on", 429 + "rule_name": branch, 430 430 "enable_push": "whitelist", 431 431 "enable_whitelist": "on", 432 432 "whitelist_users": strconv.FormatInt(user.ID, 10), ··· 437 437 // Check if master branch has been locked successfully 438 438 flashCookie := ctx.Session.GetCookie("macaron_flash") 439 439 assert.NotNil(t, flashCookie) 440 - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) 440 + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) 441 441 } 442 442 } 443 443