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