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