code.gitea.io/gitea@v1.21.7/routers/api/v1/repo/branch.go (about) 1 // Copyright 2016 The Gogs Authors. All rights reserved. 2 // Copyright 2019 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package repo 6 7 import ( 8 "errors" 9 "fmt" 10 "net/http" 11 12 "code.gitea.io/gitea/models" 13 git_model "code.gitea.io/gitea/models/git" 14 "code.gitea.io/gitea/models/organization" 15 user_model "code.gitea.io/gitea/models/user" 16 "code.gitea.io/gitea/modules/context" 17 "code.gitea.io/gitea/modules/git" 18 repo_module "code.gitea.io/gitea/modules/repository" 19 api "code.gitea.io/gitea/modules/structs" 20 "code.gitea.io/gitea/modules/util" 21 "code.gitea.io/gitea/modules/web" 22 "code.gitea.io/gitea/routers/api/v1/utils" 23 "code.gitea.io/gitea/services/convert" 24 pull_service "code.gitea.io/gitea/services/pull" 25 repo_service "code.gitea.io/gitea/services/repository" 26 ) 27 28 // GetBranch get a branch of a repository 29 func GetBranch(ctx *context.APIContext) { 30 // swagger:operation GET /repos/{owner}/{repo}/branches/{branch} repository repoGetBranch 31 // --- 32 // summary: Retrieve a specific branch from a repository, including its effective branch protection 33 // produces: 34 // - application/json 35 // parameters: 36 // - name: owner 37 // in: path 38 // description: owner of the repo 39 // type: string 40 // required: true 41 // - name: repo 42 // in: path 43 // description: name of the repo 44 // type: string 45 // required: true 46 // - name: branch 47 // in: path 48 // description: branch to get 49 // type: string 50 // required: true 51 // responses: 52 // "200": 53 // "$ref": "#/responses/Branch" 54 // "404": 55 // "$ref": "#/responses/notFound" 56 57 branchName := ctx.Params("*") 58 59 branch, err := ctx.Repo.GitRepo.GetBranch(branchName) 60 if err != nil { 61 if git.IsErrBranchNotExist(err) { 62 ctx.NotFound(err) 63 } else { 64 ctx.Error(http.StatusInternalServerError, "GetBranch", err) 65 } 66 return 67 } 68 69 c, err := branch.GetCommit() 70 if err != nil { 71 ctx.Error(http.StatusInternalServerError, "GetCommit", err) 72 return 73 } 74 75 branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName) 76 if err != nil { 77 ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) 78 return 79 } 80 81 br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 82 if err != nil { 83 ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) 84 return 85 } 86 87 ctx.JSON(http.StatusOK, br) 88 } 89 90 // DeleteBranch get a branch of a repository 91 func DeleteBranch(ctx *context.APIContext) { 92 // swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch} repository repoDeleteBranch 93 // --- 94 // summary: Delete a specific branch from a repository 95 // produces: 96 // - application/json 97 // parameters: 98 // - name: owner 99 // in: path 100 // description: owner of the repo 101 // type: string 102 // required: true 103 // - name: repo 104 // in: path 105 // description: name of the repo 106 // type: string 107 // required: true 108 // - name: branch 109 // in: path 110 // description: branch to delete 111 // type: string 112 // required: true 113 // responses: 114 // "204": 115 // "$ref": "#/responses/empty" 116 // "403": 117 // "$ref": "#/responses/error" 118 // "404": 119 // "$ref": "#/responses/notFound" 120 121 if ctx.Repo.Repository.IsEmpty { 122 ctx.Error(http.StatusNotFound, "", "Git Repository is empty.") 123 return 124 } 125 126 if ctx.Repo.Repository.IsArchived { 127 ctx.Error(http.StatusForbidden, "", "Git Repository is archived.") 128 return 129 } 130 131 if ctx.Repo.Repository.IsMirror { 132 ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.") 133 return 134 } 135 136 branchName := ctx.Params("*") 137 138 if ctx.Repo.Repository.IsEmpty { 139 ctx.Error(http.StatusForbidden, "", "Git Repository is empty.") 140 return 141 } 142 143 // check whether branches of this repository has been synced 144 totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{ 145 RepoID: ctx.Repo.Repository.ID, 146 IsDeletedBranch: util.OptionalBoolFalse, 147 }) 148 if err != nil { 149 ctx.Error(http.StatusInternalServerError, "CountBranches", err) 150 return 151 } 152 if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch 153 _, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) 154 if err != nil { 155 ctx.ServerError("SyncRepoBranches", err) 156 return 157 } 158 } 159 160 if ctx.Repo.Repository.IsArchived { 161 ctx.Error(http.StatusForbidden, "IsArchived", fmt.Errorf("can not delete branch of an archived repository")) 162 return 163 } 164 if ctx.Repo.Repository.IsMirror { 165 ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository")) 166 return 167 } 168 169 if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { 170 switch { 171 case git.IsErrBranchNotExist(err): 172 ctx.NotFound(err) 173 case errors.Is(err, repo_service.ErrBranchIsDefault): 174 ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch")) 175 case errors.Is(err, git_model.ErrBranchIsProtected): 176 ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected")) 177 default: 178 ctx.Error(http.StatusInternalServerError, "DeleteBranch", err) 179 } 180 return 181 } 182 183 ctx.Status(http.StatusNoContent) 184 } 185 186 // CreateBranch creates a branch for a user's repository 187 func CreateBranch(ctx *context.APIContext) { 188 // swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch 189 // --- 190 // summary: Create a branch 191 // consumes: 192 // - application/json 193 // produces: 194 // - application/json 195 // parameters: 196 // - name: owner 197 // in: path 198 // description: owner of the repo 199 // type: string 200 // required: true 201 // - name: repo 202 // in: path 203 // description: name of the repo 204 // type: string 205 // required: true 206 // - name: body 207 // in: body 208 // schema: 209 // "$ref": "#/definitions/CreateBranchRepoOption" 210 // responses: 211 // "201": 212 // "$ref": "#/responses/Branch" 213 // "403": 214 // description: The branch is archived or a mirror. 215 // "404": 216 // description: The old branch does not exist. 217 // "409": 218 // description: The branch with the same name already exists. 219 220 if ctx.Repo.Repository.IsEmpty { 221 ctx.Error(http.StatusNotFound, "", "Git Repository is empty.") 222 return 223 } 224 225 if ctx.Repo.Repository.IsArchived { 226 ctx.Error(http.StatusForbidden, "", "Git Repository is archived.") 227 return 228 } 229 230 if ctx.Repo.Repository.IsMirror { 231 ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.") 232 return 233 } 234 235 opt := web.GetForm(ctx).(*api.CreateBranchRepoOption) 236 237 var oldCommit *git.Commit 238 var err error 239 240 if len(opt.OldRefName) > 0 { 241 oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName) 242 if err != nil { 243 ctx.Error(http.StatusInternalServerError, "GetCommit", err) 244 return 245 } 246 } else if len(opt.OldBranchName) > 0 { //nolint 247 if ctx.Repo.GitRepo.IsBranchExist(opt.OldBranchName) { //nolint 248 oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint 249 if err != nil { 250 ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err) 251 return 252 } 253 } else { 254 ctx.Error(http.StatusNotFound, "", "The old branch does not exist") 255 return 256 } 257 } else { 258 oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) 259 if err != nil { 260 ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err) 261 return 262 } 263 } 264 265 err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName) 266 if err != nil { 267 if git_model.IsErrBranchNotExist(err) { 268 ctx.Error(http.StatusNotFound, "", "The old branch does not exist") 269 } else if models.IsErrTagAlreadyExists(err) { 270 ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.") 271 } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { 272 ctx.Error(http.StatusConflict, "", "The branch already exists.") 273 } else if git_model.IsErrBranchNameConflict(err) { 274 ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.") 275 } else { 276 ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err) 277 } 278 return 279 } 280 281 branch, err := ctx.Repo.GitRepo.GetBranch(opt.BranchName) 282 if err != nil { 283 ctx.Error(http.StatusInternalServerError, "GetBranch", err) 284 return 285 } 286 287 commit, err := branch.GetCommit() 288 if err != nil { 289 ctx.Error(http.StatusInternalServerError, "GetCommit", err) 290 return 291 } 292 293 branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branch.Name) 294 if err != nil { 295 ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) 296 return 297 } 298 299 br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 300 if err != nil { 301 ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) 302 return 303 } 304 305 ctx.JSON(http.StatusCreated, br) 306 } 307 308 // ListBranches list all the branches of a repository 309 func ListBranches(ctx *context.APIContext) { 310 // swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches 311 // --- 312 // summary: List a repository's branches 313 // produces: 314 // - application/json 315 // parameters: 316 // - name: owner 317 // in: path 318 // description: owner of the repo 319 // type: string 320 // required: true 321 // - name: repo 322 // in: path 323 // description: name of the repo 324 // type: string 325 // required: true 326 // - name: page 327 // in: query 328 // description: page number of results to return (1-based) 329 // type: integer 330 // - name: limit 331 // in: query 332 // description: page size of results 333 // type: integer 334 // responses: 335 // "200": 336 // "$ref": "#/responses/BranchList" 337 338 var totalNumOfBranches int64 339 var apiBranches []*api.Branch 340 341 listOptions := utils.GetListOptions(ctx) 342 343 if !ctx.Repo.Repository.IsEmpty { 344 if ctx.Repo.GitRepo == nil { 345 ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil) 346 return 347 } 348 349 branchOpts := git_model.FindBranchOptions{ 350 ListOptions: listOptions, 351 RepoID: ctx.Repo.Repository.ID, 352 IsDeletedBranch: util.OptionalBoolFalse, 353 } 354 var err error 355 totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts) 356 if err != nil { 357 ctx.Error(http.StatusInternalServerError, "CountBranches", err) 358 return 359 } 360 if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch 361 totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) 362 if err != nil { 363 ctx.ServerError("SyncRepoBranches", err) 364 return 365 } 366 } 367 368 rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) 369 if err != nil { 370 ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err) 371 return 372 } 373 374 branches, err := git_model.FindBranches(ctx, branchOpts) 375 if err != nil { 376 ctx.Error(http.StatusInternalServerError, "GetBranches", err) 377 return 378 } 379 380 apiBranches = make([]*api.Branch, 0, len(branches)) 381 for i := range branches { 382 c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name) 383 if err != nil { 384 // Skip if this branch doesn't exist anymore. 385 if git.IsErrNotExist(err) { 386 totalNumOfBranches-- 387 continue 388 } 389 ctx.Error(http.StatusInternalServerError, "GetCommit", err) 390 return 391 } 392 393 branchProtection := rules.GetFirstMatched(branches[i].Name) 394 apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 395 if err != nil { 396 ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) 397 return 398 } 399 apiBranches = append(apiBranches, apiBranch) 400 } 401 } 402 403 ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize) 404 ctx.SetTotalCountHeader(totalNumOfBranches) 405 ctx.JSON(http.StatusOK, apiBranches) 406 } 407 408 // GetBranchProtection gets a branch protection 409 func GetBranchProtection(ctx *context.APIContext) { 410 // swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection 411 // --- 412 // summary: Get a specific branch protection for the repository 413 // produces: 414 // - application/json 415 // parameters: 416 // - name: owner 417 // in: path 418 // description: owner of the repo 419 // type: string 420 // required: true 421 // - name: repo 422 // in: path 423 // description: name of the repo 424 // type: string 425 // required: true 426 // - name: name 427 // in: path 428 // description: name of protected branch 429 // type: string 430 // required: true 431 // responses: 432 // "200": 433 // "$ref": "#/responses/BranchProtection" 434 // "404": 435 // "$ref": "#/responses/notFound" 436 437 repo := ctx.Repo.Repository 438 bpName := ctx.Params(":name") 439 bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) 440 if err != nil { 441 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) 442 return 443 } 444 if bp == nil || bp.RepoID != repo.ID { 445 ctx.NotFound() 446 return 447 } 448 449 ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp)) 450 } 451 452 // ListBranchProtections list branch protections for a repo 453 func ListBranchProtections(ctx *context.APIContext) { 454 // swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection 455 // --- 456 // summary: List branch protections for a repository 457 // produces: 458 // - application/json 459 // parameters: 460 // - name: owner 461 // in: path 462 // description: owner of the repo 463 // type: string 464 // required: true 465 // - name: repo 466 // in: path 467 // description: name of the repo 468 // type: string 469 // required: true 470 // responses: 471 // "200": 472 // "$ref": "#/responses/BranchProtectionList" 473 474 repo := ctx.Repo.Repository 475 bps, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID) 476 if err != nil { 477 ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err) 478 return 479 } 480 apiBps := make([]*api.BranchProtection, len(bps)) 481 for i := range bps { 482 apiBps[i] = convert.ToBranchProtection(ctx, bps[i]) 483 } 484 485 ctx.JSON(http.StatusOK, apiBps) 486 } 487 488 // CreateBranchProtection creates a branch protection for a repo 489 func CreateBranchProtection(ctx *context.APIContext) { 490 // swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection 491 // --- 492 // summary: Create a branch protections for a repository 493 // consumes: 494 // - application/json 495 // produces: 496 // - application/json 497 // parameters: 498 // - name: owner 499 // in: path 500 // description: owner of the repo 501 // type: string 502 // required: true 503 // - name: repo 504 // in: path 505 // description: name of the repo 506 // type: string 507 // required: true 508 // - name: body 509 // in: body 510 // schema: 511 // "$ref": "#/definitions/CreateBranchProtectionOption" 512 // responses: 513 // "201": 514 // "$ref": "#/responses/BranchProtection" 515 // "403": 516 // "$ref": "#/responses/forbidden" 517 // "404": 518 // "$ref": "#/responses/notFound" 519 // "422": 520 // "$ref": "#/responses/validationError" 521 522 form := web.GetForm(ctx).(*api.CreateBranchProtectionOption) 523 repo := ctx.Repo.Repository 524 525 ruleName := form.RuleName 526 if ruleName == "" { 527 ruleName = form.BranchName //nolint 528 } 529 if len(ruleName) == 0 { 530 ctx.Error(http.StatusBadRequest, "both rule_name and branch_name are empty", "both rule_name and branch_name are empty") 531 return 532 } 533 534 isPlainRule := !git_model.IsRuleNameSpecial(ruleName) 535 var isBranchExist bool 536 if isPlainRule { 537 isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), ruleName) 538 } 539 540 protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, ruleName) 541 if err != nil { 542 ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err) 543 return 544 } else if protectBranch != nil { 545 ctx.Error(http.StatusForbidden, "Create branch protection", "Branch protection already exist") 546 return 547 } 548 549 var requiredApprovals int64 550 if form.RequiredApprovals > 0 { 551 requiredApprovals = form.RequiredApprovals 552 } 553 554 whitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false) 555 if err != nil { 556 if user_model.IsErrUserNotExist(err) { 557 ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err) 558 return 559 } 560 ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err) 561 return 562 } 563 mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false) 564 if err != nil { 565 if user_model.IsErrUserNotExist(err) { 566 ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err) 567 return 568 } 569 ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err) 570 return 571 } 572 approvalsWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false) 573 if err != nil { 574 if user_model.IsErrUserNotExist(err) { 575 ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err) 576 return 577 } 578 ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err) 579 return 580 } 581 var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64 582 if repo.Owner.IsOrganization() { 583 whitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false) 584 if err != nil { 585 if organization.IsErrTeamNotExist(err) { 586 ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) 587 return 588 } 589 ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) 590 return 591 } 592 mergeWhitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false) 593 if err != nil { 594 if organization.IsErrTeamNotExist(err) { 595 ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) 596 return 597 } 598 ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) 599 return 600 } 601 approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false) 602 if err != nil { 603 if organization.IsErrTeamNotExist(err) { 604 ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) 605 return 606 } 607 ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) 608 return 609 } 610 } 611 612 protectBranch = &git_model.ProtectedBranch{ 613 RepoID: ctx.Repo.Repository.ID, 614 RuleName: ruleName, 615 CanPush: form.EnablePush, 616 EnableWhitelist: form.EnablePush && form.EnablePushWhitelist, 617 EnableMergeWhitelist: form.EnableMergeWhitelist, 618 WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys, 619 EnableStatusCheck: form.EnableStatusCheck, 620 StatusCheckContexts: form.StatusCheckContexts, 621 EnableApprovalsWhitelist: form.EnableApprovalsWhitelist, 622 RequiredApprovals: requiredApprovals, 623 BlockOnRejectedReviews: form.BlockOnRejectedReviews, 624 BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests, 625 DismissStaleApprovals: form.DismissStaleApprovals, 626 RequireSignedCommits: form.RequireSignedCommits, 627 ProtectedFilePatterns: form.ProtectedFilePatterns, 628 UnprotectedFilePatterns: form.UnprotectedFilePatterns, 629 BlockOnOutdatedBranch: form.BlockOnOutdatedBranch, 630 } 631 632 err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{ 633 UserIDs: whitelistUsers, 634 TeamIDs: whitelistTeams, 635 MergeUserIDs: mergeWhitelistUsers, 636 MergeTeamIDs: mergeWhitelistTeams, 637 ApprovalsUserIDs: approvalsWhitelistUsers, 638 ApprovalsTeamIDs: approvalsWhitelistTeams, 639 }) 640 if err != nil { 641 ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err) 642 return 643 } 644 645 if isBranchExist { 646 if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, ruleName); err != nil { 647 ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err) 648 return 649 } 650 } else { 651 if !isPlainRule { 652 if ctx.Repo.GitRepo == nil { 653 ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath()) 654 if err != nil { 655 ctx.Error(http.StatusInternalServerError, "OpenRepository", err) 656 return 657 } 658 defer func() { 659 ctx.Repo.GitRepo.Close() 660 ctx.Repo.GitRepo = nil 661 }() 662 } 663 // FIXME: since we only need to recheck files protected rules, we could improve this 664 matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName) 665 if err != nil { 666 ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) 667 return 668 } 669 670 for _, branchName := range matchedBranches { 671 if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil { 672 ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err) 673 return 674 } 675 } 676 } 677 } 678 679 // Reload from db to get all whitelists 680 bp, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, ruleName) 681 if err != nil { 682 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) 683 return 684 } 685 if bp == nil || bp.RepoID != ctx.Repo.Repository.ID { 686 ctx.Error(http.StatusInternalServerError, "New branch protection not found", err) 687 return 688 } 689 690 ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp)) 691 } 692 693 // EditBranchProtection edits a branch protection for a repo 694 func EditBranchProtection(ctx *context.APIContext) { 695 // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection 696 // --- 697 // summary: Edit a branch protections for a repository. Only fields that are set will be changed 698 // consumes: 699 // - application/json 700 // produces: 701 // - application/json 702 // parameters: 703 // - name: owner 704 // in: path 705 // description: owner of the repo 706 // type: string 707 // required: true 708 // - name: repo 709 // in: path 710 // description: name of the repo 711 // type: string 712 // required: true 713 // - name: name 714 // in: path 715 // description: name of protected branch 716 // type: string 717 // required: true 718 // - name: body 719 // in: body 720 // schema: 721 // "$ref": "#/definitions/EditBranchProtectionOption" 722 // responses: 723 // "200": 724 // "$ref": "#/responses/BranchProtection" 725 // "404": 726 // "$ref": "#/responses/notFound" 727 // "422": 728 // "$ref": "#/responses/validationError" 729 form := web.GetForm(ctx).(*api.EditBranchProtectionOption) 730 repo := ctx.Repo.Repository 731 bpName := ctx.Params(":name") 732 protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) 733 if err != nil { 734 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) 735 return 736 } 737 if protectBranch == nil || protectBranch.RepoID != repo.ID { 738 ctx.NotFound() 739 return 740 } 741 742 if form.EnablePush != nil { 743 if !*form.EnablePush { 744 protectBranch.CanPush = false 745 protectBranch.EnableWhitelist = false 746 protectBranch.WhitelistDeployKeys = false 747 } else { 748 protectBranch.CanPush = true 749 if form.EnablePushWhitelist != nil { 750 if !*form.EnablePushWhitelist { 751 protectBranch.EnableWhitelist = false 752 protectBranch.WhitelistDeployKeys = false 753 } else { 754 protectBranch.EnableWhitelist = true 755 if form.PushWhitelistDeployKeys != nil { 756 protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys 757 } 758 } 759 } 760 } 761 } 762 763 if form.EnableMergeWhitelist != nil { 764 protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist 765 } 766 767 if form.EnableStatusCheck != nil { 768 protectBranch.EnableStatusCheck = *form.EnableStatusCheck 769 } 770 771 if form.StatusCheckContexts != nil { 772 protectBranch.StatusCheckContexts = form.StatusCheckContexts 773 } 774 775 if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 { 776 protectBranch.RequiredApprovals = *form.RequiredApprovals 777 } 778 779 if form.EnableApprovalsWhitelist != nil { 780 protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist 781 } 782 783 if form.BlockOnRejectedReviews != nil { 784 protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews 785 } 786 787 if form.BlockOnOfficialReviewRequests != nil { 788 protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests 789 } 790 791 if form.DismissStaleApprovals != nil { 792 protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals 793 } 794 795 if form.RequireSignedCommits != nil { 796 protectBranch.RequireSignedCommits = *form.RequireSignedCommits 797 } 798 799 if form.ProtectedFilePatterns != nil { 800 protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns 801 } 802 803 if form.UnprotectedFilePatterns != nil { 804 protectBranch.UnprotectedFilePatterns = *form.UnprotectedFilePatterns 805 } 806 807 if form.BlockOnOutdatedBranch != nil { 808 protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch 809 } 810 811 var whitelistUsers []int64 812 if form.PushWhitelistUsernames != nil { 813 whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false) 814 if err != nil { 815 if user_model.IsErrUserNotExist(err) { 816 ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err) 817 return 818 } 819 ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err) 820 return 821 } 822 } else { 823 whitelistUsers = protectBranch.WhitelistUserIDs 824 } 825 var mergeWhitelistUsers []int64 826 if form.MergeWhitelistUsernames != nil { 827 mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false) 828 if err != nil { 829 if user_model.IsErrUserNotExist(err) { 830 ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err) 831 return 832 } 833 ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err) 834 return 835 } 836 } else { 837 mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs 838 } 839 var approvalsWhitelistUsers []int64 840 if form.ApprovalsWhitelistUsernames != nil { 841 approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false) 842 if err != nil { 843 if user_model.IsErrUserNotExist(err) { 844 ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err) 845 return 846 } 847 ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err) 848 return 849 } 850 } else { 851 approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs 852 } 853 854 var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64 855 if repo.Owner.IsOrganization() { 856 if form.PushWhitelistTeams != nil { 857 whitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false) 858 if err != nil { 859 if organization.IsErrTeamNotExist(err) { 860 ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) 861 return 862 } 863 ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) 864 return 865 } 866 } else { 867 whitelistTeams = protectBranch.WhitelistTeamIDs 868 } 869 if form.MergeWhitelistTeams != nil { 870 mergeWhitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false) 871 if err != nil { 872 if organization.IsErrTeamNotExist(err) { 873 ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) 874 return 875 } 876 ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) 877 return 878 } 879 } else { 880 mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs 881 } 882 if form.ApprovalsWhitelistTeams != nil { 883 approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false) 884 if err != nil { 885 if organization.IsErrTeamNotExist(err) { 886 ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) 887 return 888 } 889 ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) 890 return 891 } 892 } else { 893 approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs 894 } 895 } 896 897 err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{ 898 UserIDs: whitelistUsers, 899 TeamIDs: whitelistTeams, 900 MergeUserIDs: mergeWhitelistUsers, 901 MergeTeamIDs: mergeWhitelistTeams, 902 ApprovalsUserIDs: approvalsWhitelistUsers, 903 ApprovalsTeamIDs: approvalsWhitelistTeams, 904 }) 905 if err != nil { 906 ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err) 907 return 908 } 909 910 isPlainRule := !git_model.IsRuleNameSpecial(bpName) 911 var isBranchExist bool 912 if isPlainRule { 913 isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), bpName) 914 } 915 916 if isBranchExist { 917 if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, bpName); err != nil { 918 ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err) 919 return 920 } 921 } else { 922 if !isPlainRule { 923 if ctx.Repo.GitRepo == nil { 924 ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath()) 925 if err != nil { 926 ctx.Error(http.StatusInternalServerError, "OpenRepository", err) 927 return 928 } 929 defer func() { 930 ctx.Repo.GitRepo.Close() 931 ctx.Repo.GitRepo = nil 932 }() 933 } 934 935 // FIXME: since we only need to recheck files protected rules, we could improve this 936 matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName) 937 if err != nil { 938 ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) 939 return 940 } 941 942 for _, branchName := range matchedBranches { 943 if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil { 944 ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err) 945 return 946 } 947 } 948 } 949 } 950 951 // Reload from db to ensure get all whitelists 952 bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) 953 if err != nil { 954 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err) 955 return 956 } 957 if bp == nil || bp.RepoID != ctx.Repo.Repository.ID { 958 ctx.Error(http.StatusInternalServerError, "New branch protection not found", err) 959 return 960 } 961 962 ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp)) 963 } 964 965 // DeleteBranchProtection deletes a branch protection for a repo 966 func DeleteBranchProtection(ctx *context.APIContext) { 967 // swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection 968 // --- 969 // summary: Delete a specific branch protection for the repository 970 // produces: 971 // - application/json 972 // parameters: 973 // - name: owner 974 // in: path 975 // description: owner of the repo 976 // type: string 977 // required: true 978 // - name: repo 979 // in: path 980 // description: name of the repo 981 // type: string 982 // required: true 983 // - name: name 984 // in: path 985 // description: name of protected branch 986 // type: string 987 // required: true 988 // responses: 989 // "204": 990 // "$ref": "#/responses/empty" 991 // "404": 992 // "$ref": "#/responses/notFound" 993 994 repo := ctx.Repo.Repository 995 bpName := ctx.Params(":name") 996 bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) 997 if err != nil { 998 ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) 999 return 1000 } 1001 if bp == nil || bp.RepoID != repo.ID { 1002 ctx.NotFound() 1003 return 1004 } 1005 1006 if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, bp.ID); err != nil { 1007 ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err) 1008 return 1009 } 1010 1011 ctx.Status(http.StatusNoContent) 1012 }