1// Copyright 2016 The Gitea Authors. All rights reserved. 2// Use of this source code is governed by a MIT-style 3// license that can be found in the LICENSE file. 4 5package models 6 7import ( 8 "context" 9 "fmt" 10 "strings" 11 "time" 12 13 "code.gitea.io/gitea/models/db" 14 "code.gitea.io/gitea/models/perm" 15 repo_model "code.gitea.io/gitea/models/repo" 16 "code.gitea.io/gitea/models/unit" 17 user_model "code.gitea.io/gitea/models/user" 18 "code.gitea.io/gitea/modules/base" 19 "code.gitea.io/gitea/modules/log" 20 "code.gitea.io/gitea/modules/timeutil" 21 "code.gitea.io/gitea/modules/util" 22 23 "github.com/gobwas/glob" 24) 25 26// ProtectedBranch struct 27type ProtectedBranch struct { 28 ID int64 `xorm:"pk autoincr"` 29 RepoID int64 `xorm:"UNIQUE(s)"` 30 BranchName string `xorm:"UNIQUE(s)"` 31 CanPush bool `xorm:"NOT NULL DEFAULT false"` 32 EnableWhitelist bool 33 WhitelistUserIDs []int64 `xorm:"JSON TEXT"` 34 WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 35 EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` 36 WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"` 37 MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` 38 MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 39 EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` 40 StatusCheckContexts []string `xorm:"JSON TEXT"` 41 EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"` 42 ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` 43 ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 44 RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"` 45 BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"` 46 BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"` 47 BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"` 48 DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` 49 RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"` 50 ProtectedFilePatterns string `xorm:"TEXT"` 51 UnprotectedFilePatterns string `xorm:"TEXT"` 52 53 CreatedUnix timeutil.TimeStamp `xorm:"created"` 54 UpdatedUnix timeutil.TimeStamp `xorm:"updated"` 55} 56 57func init() { 58 db.RegisterModel(new(ProtectedBranch)) 59 db.RegisterModel(new(DeletedBranch)) 60 db.RegisterModel(new(RenamedBranch)) 61} 62 63// IsProtected returns if the branch is protected 64func (protectBranch *ProtectedBranch) IsProtected() bool { 65 return protectBranch.ID > 0 66} 67 68// CanUserPush returns if some user could push to this protected branch 69func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool { 70 if !protectBranch.CanPush { 71 return false 72 } 73 74 if !protectBranch.EnableWhitelist { 75 if user, err := user_model.GetUserByID(userID); err != nil { 76 log.Error("GetUserByID: %v", err) 77 return false 78 } else if repo, err := repo_model.GetRepositoryByID(protectBranch.RepoID); err != nil { 79 log.Error("repo_model.GetRepositoryByID: %v", err) 80 return false 81 } else if writeAccess, err := HasAccessUnit(user, repo, unit.TypeCode, perm.AccessModeWrite); err != nil { 82 log.Error("HasAccessUnit: %v", err) 83 return false 84 } else { 85 return writeAccess 86 } 87 } 88 89 if base.Int64sContains(protectBranch.WhitelistUserIDs, userID) { 90 return true 91 } 92 93 if len(protectBranch.WhitelistTeamIDs) == 0 { 94 return false 95 } 96 97 in, err := IsUserInTeams(userID, protectBranch.WhitelistTeamIDs) 98 if err != nil { 99 log.Error("IsUserInTeams: %v", err) 100 return false 101 } 102 return in 103} 104 105// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch 106func IsUserMergeWhitelisted(protectBranch *ProtectedBranch, userID int64, permissionInRepo Permission) bool { 107 if !protectBranch.EnableMergeWhitelist { 108 // Then we need to fall back on whether the user has write permission 109 return permissionInRepo.CanWrite(unit.TypeCode) 110 } 111 112 if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) { 113 return true 114 } 115 116 if len(protectBranch.MergeWhitelistTeamIDs) == 0 { 117 return false 118 } 119 120 in, err := IsUserInTeams(userID, protectBranch.MergeWhitelistTeamIDs) 121 if err != nil { 122 log.Error("IsUserInTeams: %v", err) 123 return false 124 } 125 return in 126} 127 128// IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals) 129func IsUserOfficialReviewer(protectBranch *ProtectedBranch, user *user_model.User) (bool, error) { 130 return isUserOfficialReviewer(db.DefaultContext, protectBranch, user) 131} 132 133func isUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) { 134 repo, err := repo_model.GetRepositoryByIDCtx(ctx, protectBranch.RepoID) 135 if err != nil { 136 return false, err 137 } 138 139 if !protectBranch.EnableApprovalsWhitelist { 140 // Anyone with write access is considered official reviewer 141 writeAccess, err := hasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite) 142 if err != nil { 143 return false, err 144 } 145 return writeAccess, nil 146 } 147 148 if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) { 149 return true, nil 150 } 151 152 inTeam, err := isUserInTeams(db.GetEngine(ctx), user.ID, protectBranch.ApprovalsWhitelistTeamIDs) 153 if err != nil { 154 return false, err 155 } 156 157 return inTeam, nil 158} 159 160// HasEnoughApprovals returns true if pr has enough granted approvals. 161func (protectBranch *ProtectedBranch) HasEnoughApprovals(pr *PullRequest) bool { 162 if protectBranch.RequiredApprovals == 0 { 163 return true 164 } 165 return protectBranch.GetGrantedApprovalsCount(pr) >= protectBranch.RequiredApprovals 166} 167 168// GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist. 169func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest) int64 { 170 sess := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). 171 And("type = ?", ReviewTypeApprove). 172 And("official = ?", true). 173 And("dismissed = ?", false) 174 if protectBranch.DismissStaleApprovals { 175 sess = sess.And("stale = ?", false) 176 } 177 approvals, err := sess.Count(new(Review)) 178 if err != nil { 179 log.Error("GetGrantedApprovalsCount: %v", err) 180 return 0 181 } 182 183 return approvals 184} 185 186// MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews 187func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullRequest) bool { 188 if !protectBranch.BlockOnRejectedReviews { 189 return false 190 } 191 rejectExist, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). 192 And("type = ?", ReviewTypeReject). 193 And("official = ?", true). 194 And("dismissed = ?", false). 195 Exist(new(Review)) 196 if err != nil { 197 log.Error("MergeBlockedByRejectedReview: %v", err) 198 return true 199 } 200 201 return rejectExist 202} 203 204// MergeBlockedByOfficialReviewRequests block merge because of some review request to official reviewer 205// of from official review 206func (protectBranch *ProtectedBranch) MergeBlockedByOfficialReviewRequests(pr *PullRequest) bool { 207 if !protectBranch.BlockOnOfficialReviewRequests { 208 return false 209 } 210 has, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). 211 And("type = ?", ReviewTypeRequest). 212 And("official = ?", true). 213 Exist(new(Review)) 214 if err != nil { 215 log.Error("MergeBlockedByOfficialReviewRequests: %v", err) 216 return true 217 } 218 219 return has 220} 221 222// MergeBlockedByOutdatedBranch returns true if merge is blocked by an outdated head branch 223func (protectBranch *ProtectedBranch) MergeBlockedByOutdatedBranch(pr *PullRequest) bool { 224 return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0 225} 226 227// GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice 228func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob { 229 return getFilePatterns(protectBranch.ProtectedFilePatterns) 230} 231 232// GetUnprotectedFilePatterns parses a semicolon separated list of unprotected file patterns and returns a glob.Glob slice 233func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob { 234 return getFilePatterns(protectBranch.UnprotectedFilePatterns) 235} 236 237func getFilePatterns(filePatterns string) []glob.Glob { 238 extarr := make([]glob.Glob, 0, 10) 239 for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") { 240 expr = strings.TrimSpace(expr) 241 if expr != "" { 242 if g, err := glob.Compile(expr, '.', '/'); err != nil { 243 log.Info("Invalid glob expression '%s' (skipped): %v", expr, err) 244 } else { 245 extarr = append(extarr, g) 246 } 247 } 248 } 249 return extarr 250} 251 252// MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change 253func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(pr *PullRequest) bool { 254 glob := protectBranch.GetProtectedFilePatterns() 255 if len(glob) == 0 { 256 return false 257 } 258 259 return len(pr.ChangedProtectedFiles) > 0 260} 261 262// IsProtectedFile return if path is protected 263func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path string) bool { 264 if len(patterns) == 0 { 265 patterns = protectBranch.GetProtectedFilePatterns() 266 if len(patterns) == 0 { 267 return false 268 } 269 } 270 271 lpath := strings.ToLower(strings.TrimSpace(path)) 272 273 r := false 274 for _, pat := range patterns { 275 if pat.Match(lpath) { 276 r = true 277 break 278 } 279 } 280 281 return r 282} 283 284// IsUnprotectedFile return if path is unprotected 285func (protectBranch *ProtectedBranch) IsUnprotectedFile(patterns []glob.Glob, path string) bool { 286 if len(patterns) == 0 { 287 patterns = protectBranch.GetUnprotectedFilePatterns() 288 if len(patterns) == 0 { 289 return false 290 } 291 } 292 293 lpath := strings.ToLower(strings.TrimSpace(path)) 294 295 r := false 296 for _, pat := range patterns { 297 if pat.Match(lpath) { 298 r = true 299 break 300 } 301 } 302 303 return r 304} 305 306// GetProtectedBranchBy getting protected branch by ID/Name 307func GetProtectedBranchBy(repoID int64, branchName string) (*ProtectedBranch, error) { 308 return getProtectedBranchBy(db.GetEngine(db.DefaultContext), repoID, branchName) 309} 310 311func getProtectedBranchBy(e db.Engine, repoID int64, branchName string) (*ProtectedBranch, error) { 312 rel := &ProtectedBranch{RepoID: repoID, BranchName: branchName} 313 has, err := e.Get(rel) 314 if err != nil { 315 return nil, err 316 } 317 if !has { 318 return nil, nil 319 } 320 return rel, nil 321} 322 323// WhitelistOptions represent all sorts of whitelists used for protected branches 324type WhitelistOptions struct { 325 UserIDs []int64 326 TeamIDs []int64 327 328 MergeUserIDs []int64 329 MergeTeamIDs []int64 330 331 ApprovalsUserIDs []int64 332 ApprovalsTeamIDs []int64 333} 334 335// UpdateProtectBranch saves branch protection options of repository. 336// If ID is 0, it creates a new record. Otherwise, updates existing record. 337// This function also performs check if whitelist user and team's IDs have been changed 338// to avoid unnecessary whitelist delete and regenerate. 339func UpdateProtectBranch(repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) { 340 if err = repo.GetOwner(db.DefaultContext); err != nil { 341 return fmt.Errorf("GetOwner: %v", err) 342 } 343 344 whitelist, err := updateUserWhitelist(repo, protectBranch.WhitelistUserIDs, opts.UserIDs) 345 if err != nil { 346 return err 347 } 348 protectBranch.WhitelistUserIDs = whitelist 349 350 whitelist, err = updateUserWhitelist(repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs) 351 if err != nil { 352 return err 353 } 354 protectBranch.MergeWhitelistUserIDs = whitelist 355 356 whitelist, err = updateApprovalWhitelist(repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs) 357 if err != nil { 358 return err 359 } 360 protectBranch.ApprovalsWhitelistUserIDs = whitelist 361 362 // if the repo is in an organization 363 whitelist, err = updateTeamWhitelist(repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs) 364 if err != nil { 365 return err 366 } 367 protectBranch.WhitelistTeamIDs = whitelist 368 369 whitelist, err = updateTeamWhitelist(repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs) 370 if err != nil { 371 return err 372 } 373 protectBranch.MergeWhitelistTeamIDs = whitelist 374 375 whitelist, err = updateTeamWhitelist(repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs) 376 if err != nil { 377 return err 378 } 379 protectBranch.ApprovalsWhitelistTeamIDs = whitelist 380 381 // Make sure protectBranch.ID is not 0 for whitelists 382 if protectBranch.ID == 0 { 383 if _, err = db.GetEngine(db.DefaultContext).Insert(protectBranch); err != nil { 384 return fmt.Errorf("Insert: %v", err) 385 } 386 return nil 387 } 388 389 if _, err = db.GetEngine(db.DefaultContext).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { 390 return fmt.Errorf("Update: %v", err) 391 } 392 393 return nil 394} 395 396// GetProtectedBranches get all protected branches 397func GetProtectedBranches(repoID int64) ([]*ProtectedBranch, error) { 398 protectedBranches := make([]*ProtectedBranch, 0) 399 return protectedBranches, db.GetEngine(db.DefaultContext).Find(&protectedBranches, &ProtectedBranch{RepoID: repoID}) 400} 401 402// IsProtectedBranch checks if branch is protected 403func IsProtectedBranch(repoID int64, branchName string) (bool, error) { 404 protectedBranch := &ProtectedBranch{ 405 RepoID: repoID, 406 BranchName: branchName, 407 } 408 409 has, err := db.GetEngine(db.DefaultContext).Exist(protectedBranch) 410 if err != nil { 411 return true, err 412 } 413 return has, nil 414} 415 416// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with 417// the users from newWhitelist which have explicit read or write access to the repo. 418func updateApprovalWhitelist(repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 419 hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) 420 if !hasUsersChanged { 421 return currentWhitelist, nil 422 } 423 424 whitelist = make([]int64, 0, len(newWhitelist)) 425 for _, userID := range newWhitelist { 426 if reader, err := IsRepoReader(repo, userID); err != nil { 427 return nil, err 428 } else if !reader { 429 continue 430 } 431 whitelist = append(whitelist, userID) 432 } 433 434 return 435} 436 437// updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with 438// the users from newWhitelist which have write access to the repo. 439func updateUserWhitelist(repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 440 hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) 441 if !hasUsersChanged { 442 return currentWhitelist, nil 443 } 444 445 whitelist = make([]int64, 0, len(newWhitelist)) 446 for _, userID := range newWhitelist { 447 user, err := user_model.GetUserByID(userID) 448 if err != nil { 449 return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) 450 } 451 perm, err := GetUserRepoPermission(repo, user) 452 if err != nil { 453 return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) 454 } 455 456 if !perm.CanWrite(unit.TypeCode) { 457 continue // Drop invalid user ID 458 } 459 460 whitelist = append(whitelist, userID) 461 } 462 463 return 464} 465 466// updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with 467// the teams from newWhitelist which have write access to the repo. 468func updateTeamWhitelist(repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { 469 hasTeamsChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) 470 if !hasTeamsChanged { 471 return currentWhitelist, nil 472 } 473 474 teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, perm.AccessModeRead) 475 if err != nil { 476 return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err) 477 } 478 479 whitelist = make([]int64, 0, len(teams)) 480 for i := range teams { 481 if util.IsInt64InSlice(teams[i].ID, newWhitelist) { 482 whitelist = append(whitelist, teams[i].ID) 483 } 484 } 485 486 return 487} 488 489// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository. 490func DeleteProtectedBranch(repoID, id int64) (err error) { 491 protectedBranch := &ProtectedBranch{ 492 RepoID: repoID, 493 ID: id, 494 } 495 496 if affected, err := db.GetEngine(db.DefaultContext).Delete(protectedBranch); err != nil { 497 return err 498 } else if affected != 1 { 499 return fmt.Errorf("delete protected branch ID(%v) failed", id) 500 } 501 502 return nil 503} 504 505// DeletedBranch struct 506type DeletedBranch struct { 507 ID int64 `xorm:"pk autoincr"` 508 RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` 509 Name string `xorm:"UNIQUE(s) NOT NULL"` 510 Commit string `xorm:"UNIQUE(s) NOT NULL"` 511 DeletedByID int64 `xorm:"INDEX"` 512 DeletedBy *user_model.User `xorm:"-"` 513 DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"` 514} 515 516// AddDeletedBranch adds a deleted branch to the database 517func AddDeletedBranch(repoID int64, branchName, commit string, deletedByID int64) error { 518 deletedBranch := &DeletedBranch{ 519 RepoID: repoID, 520 Name: branchName, 521 Commit: commit, 522 DeletedByID: deletedByID, 523 } 524 525 _, err := db.GetEngine(db.DefaultContext).Insert(deletedBranch) 526 return err 527} 528 529// GetDeletedBranches returns all the deleted branches 530func GetDeletedBranches(repoID int64) ([]*DeletedBranch, error) { 531 deletedBranches := make([]*DeletedBranch, 0) 532 return deletedBranches, db.GetEngine(db.DefaultContext).Where("repo_id = ?", repoID).Desc("deleted_unix").Find(&deletedBranches) 533} 534 535// GetDeletedBranchByID get a deleted branch by its ID 536func GetDeletedBranchByID(repoID, id int64) (*DeletedBranch, error) { 537 deletedBranch := &DeletedBranch{} 538 has, err := db.GetEngine(db.DefaultContext).Where("repo_id = ?", repoID).And("id = ?", id).Get(deletedBranch) 539 if err != nil { 540 return nil, err 541 } 542 if !has { 543 return nil, nil 544 } 545 return deletedBranch, nil 546} 547 548// RemoveDeletedBranchByID removes a deleted branch from the database 549func RemoveDeletedBranchByID(repoID, id int64) (err error) { 550 deletedBranch := &DeletedBranch{ 551 RepoID: repoID, 552 ID: id, 553 } 554 555 if affected, err := db.GetEngine(db.DefaultContext).Delete(deletedBranch); err != nil { 556 return err 557 } else if affected != 1 { 558 return fmt.Errorf("remove deleted branch ID(%v) failed", id) 559 } 560 561 return nil 562} 563 564// LoadUser loads the user that deleted the branch 565// When there's no user found it returns a user_model.NewGhostUser 566func (deletedBranch *DeletedBranch) LoadUser() { 567 user, err := user_model.GetUserByID(deletedBranch.DeletedByID) 568 if err != nil { 569 user = user_model.NewGhostUser() 570 } 571 deletedBranch.DeletedBy = user 572} 573 574// RemoveDeletedBranchByName removes all deleted branches 575func RemoveDeletedBranchByName(repoID int64, branch string) error { 576 _, err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch)) 577 return err 578} 579 580// RemoveOldDeletedBranches removes old deleted branches 581func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { 582 // Nothing to do for shutdown or terminate 583 log.Trace("Doing: DeletedBranchesCleanup") 584 585 deleteBefore := time.Now().Add(-olderThan) 586 _, err := db.GetEngine(db.DefaultContext).Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch)) 587 if err != nil { 588 log.Error("DeletedBranchesCleanup: %v", err) 589 } 590} 591 592// RenamedBranch provide renamed branch log 593// will check it when a branch can't be found 594type RenamedBranch struct { 595 ID int64 `xorm:"pk autoincr"` 596 RepoID int64 `xorm:"INDEX NOT NULL"` 597 From string 598 To string 599 CreatedUnix timeutil.TimeStamp `xorm:"created"` 600} 601 602// FindRenamedBranch check if a branch was renamed 603func FindRenamedBranch(repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { 604 branch = &RenamedBranch{ 605 RepoID: repoID, 606 From: from, 607 } 608 exist, err = db.GetEngine(db.DefaultContext).Get(branch) 609 610 return 611} 612 613// RenameBranch rename a branch 614func RenameBranch(repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { 615 ctx, committer, err := db.TxContext() 616 if err != nil { 617 return err 618 } 619 defer committer.Close() 620 621 sess := db.GetEngine(ctx) 622 // 1. update default branch if needed 623 isDefault := repo.DefaultBranch == from 624 if isDefault { 625 repo.DefaultBranch = to 626 _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) 627 if err != nil { 628 return err 629 } 630 } 631 632 // 2. Update protected branch if needed 633 protectedBranch, err := getProtectedBranchBy(sess, repo.ID, from) 634 if err != nil { 635 return err 636 } 637 638 if protectedBranch != nil { 639 protectedBranch.BranchName = to 640 _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) 641 if err != nil { 642 return err 643 } 644 } 645 646 // 3. Update all not merged pull request base branch name 647 _, err = sess.Table(new(PullRequest)).Where("base_repo_id=? AND base_branch=? AND has_merged=?", 648 repo.ID, from, false). 649 Update(map[string]interface{}{"base_branch": to}) 650 if err != nil { 651 return err 652 } 653 654 // 4. do git action 655 if err = gitAction(isDefault); err != nil { 656 return err 657 } 658 659 // 5. insert renamed branch record 660 renamedBranch := &RenamedBranch{ 661 RepoID: repo.ID, 662 From: from, 663 To: to, 664 } 665 err = db.Insert(ctx, renamedBranch) 666 if err != nil { 667 return err 668 } 669 670 return committer.Commit() 671} 672