code.gitea.io/gitea@v1.21.7/models/git/branch.go (about) 1 // Copyright 2016 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package git 5 6 import ( 7 "context" 8 "fmt" 9 "time" 10 11 "code.gitea.io/gitea/models/db" 12 repo_model "code.gitea.io/gitea/models/repo" 13 user_model "code.gitea.io/gitea/models/user" 14 "code.gitea.io/gitea/modules/git" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/timeutil" 17 "code.gitea.io/gitea/modules/util" 18 19 "xorm.io/builder" 20 ) 21 22 // ErrBranchNotExist represents an error that branch with such name does not exist. 23 type ErrBranchNotExist struct { 24 RepoID int64 25 BranchName string 26 } 27 28 // IsErrBranchNotExist checks if an error is an ErrBranchDoesNotExist. 29 func IsErrBranchNotExist(err error) bool { 30 _, ok := err.(ErrBranchNotExist) 31 return ok 32 } 33 34 func (err ErrBranchNotExist) Error() string { 35 return fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", err.RepoID, err.BranchName) 36 } 37 38 func (err ErrBranchNotExist) Unwrap() error { 39 return util.ErrNotExist 40 } 41 42 // ErrBranchAlreadyExists represents an error that branch with such name already exists. 43 type ErrBranchAlreadyExists struct { 44 BranchName string 45 } 46 47 // IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists. 48 func IsErrBranchAlreadyExists(err error) bool { 49 _, ok := err.(ErrBranchAlreadyExists) 50 return ok 51 } 52 53 func (err ErrBranchAlreadyExists) Error() string { 54 return fmt.Sprintf("branch already exists [name: %s]", err.BranchName) 55 } 56 57 func (err ErrBranchAlreadyExists) Unwrap() error { 58 return util.ErrAlreadyExist 59 } 60 61 // ErrBranchNameConflict represents an error that branch name conflicts with other branch. 62 type ErrBranchNameConflict struct { 63 BranchName string 64 } 65 66 // IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict. 67 func IsErrBranchNameConflict(err error) bool { 68 _, ok := err.(ErrBranchNameConflict) 69 return ok 70 } 71 72 func (err ErrBranchNameConflict) Error() string { 73 return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName) 74 } 75 76 func (err ErrBranchNameConflict) Unwrap() error { 77 return util.ErrAlreadyExist 78 } 79 80 // ErrBranchesEqual represents an error that base branch is equal to the head branch. 81 type ErrBranchesEqual struct { 82 BaseBranchName string 83 HeadBranchName string 84 } 85 86 // IsErrBranchesEqual checks if an error is an ErrBranchesEqual. 87 func IsErrBranchesEqual(err error) bool { 88 _, ok := err.(ErrBranchesEqual) 89 return ok 90 } 91 92 func (err ErrBranchesEqual) Error() string { 93 return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) 94 } 95 96 func (err ErrBranchesEqual) Unwrap() error { 97 return util.ErrInvalidArgument 98 } 99 100 // Branch represents a branch of a repository 101 // For those repository who have many branches, stored into database is a good choice 102 // for pagination, keyword search and filtering 103 type Branch struct { 104 ID int64 105 RepoID int64 `xorm:"UNIQUE(s)"` 106 Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment 107 CommitID string 108 CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line) 109 PusherID int64 110 Pusher *user_model.User `xorm:"-"` 111 IsDeleted bool `xorm:"index"` 112 DeletedByID int64 113 DeletedBy *user_model.User `xorm:"-"` 114 DeletedUnix timeutil.TimeStamp `xorm:"index"` 115 CommitTime timeutil.TimeStamp // The commit 116 CreatedUnix timeutil.TimeStamp `xorm:"created"` 117 UpdatedUnix timeutil.TimeStamp `xorm:"updated"` 118 } 119 120 func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) { 121 if b.DeletedBy == nil { 122 b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID) 123 if user_model.IsErrUserNotExist(err) { 124 b.DeletedBy = user_model.NewGhostUser() 125 err = nil 126 } 127 } 128 return err 129 } 130 131 func (b *Branch) LoadPusher(ctx context.Context) (err error) { 132 if b.Pusher == nil && b.PusherID > 0 { 133 b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID) 134 if user_model.IsErrUserNotExist(err) { 135 b.Pusher = user_model.NewGhostUser() 136 err = nil 137 } 138 } 139 return err 140 } 141 142 func init() { 143 db.RegisterModel(new(Branch)) 144 db.RegisterModel(new(RenamedBranch)) 145 } 146 147 func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) { 148 var branch Branch 149 has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch) 150 if err != nil { 151 return nil, err 152 } else if !has { 153 return nil, ErrBranchNotExist{ 154 RepoID: repoID, 155 BranchName: branchName, 156 } 157 } 158 return &branch, nil 159 } 160 161 func AddBranches(ctx context.Context, branches []*Branch) error { 162 for _, branch := range branches { 163 if _, err := db.GetEngine(ctx).Insert(branch); err != nil { 164 return err 165 } 166 } 167 return nil 168 } 169 170 func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, error) { 171 var branch Branch 172 has, err := db.GetEngine(ctx).ID(branchID).Get(&branch) 173 if err != nil { 174 return nil, err 175 } else if !has { 176 return nil, ErrBranchNotExist{ 177 RepoID: repoID, 178 } 179 } 180 if branch.RepoID != repoID { 181 return nil, ErrBranchNotExist{ 182 RepoID: repoID, 183 } 184 } 185 if !branch.IsDeleted { 186 return nil, ErrBranchNotExist{ 187 RepoID: repoID, 188 } 189 } 190 return &branch, nil 191 } 192 193 func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error { 194 return db.WithTx(ctx, func(ctx context.Context) error { 195 branches := make([]*Branch, 0, len(branchIDs)) 196 if err := db.GetEngine(ctx).In("id", branchIDs).Find(&branches); err != nil { 197 return err 198 } 199 for _, branch := range branches { 200 if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil { 201 return err 202 } 203 } 204 return nil 205 }) 206 } 207 208 // UpdateBranch updates the branch information in the database. 209 func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) (int64, error) { 210 return db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). 211 Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix"). 212 Update(&Branch{ 213 CommitID: commit.ID.String(), 214 CommitMessage: commit.Summary(), 215 PusherID: pusherID, 216 CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), 217 IsDeleted: false, 218 }) 219 } 220 221 // AddDeletedBranch adds a deleted branch to the database 222 func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { 223 branch, err := GetBranch(ctx, repoID, branchName) 224 if err != nil { 225 return err 226 } 227 if branch.IsDeleted { 228 return nil 229 } 230 231 cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=? AND is_deleted=?", repoID, branchName, false). 232 Cols("is_deleted, deleted_by_id, deleted_unix"). 233 Update(&Branch{ 234 IsDeleted: true, 235 DeletedByID: deletedByID, 236 DeletedUnix: timeutil.TimeStampNow(), 237 }) 238 if err != nil { 239 return err 240 } 241 if cnt == 0 { 242 return fmt.Errorf("branch %s not found or has been deleted", branchName) 243 } 244 return err 245 } 246 247 func RemoveDeletedBranchByID(ctx context.Context, repoID, branchID int64) error { 248 _, err := db.GetEngine(ctx).Where("repo_id=? AND id=? AND is_deleted = ?", repoID, branchID, true).Delete(new(Branch)) 249 return err 250 } 251 252 // RemoveOldDeletedBranches removes old deleted branches 253 func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { 254 // Nothing to do for shutdown or terminate 255 log.Trace("Doing: DeletedBranchesCleanup") 256 257 deleteBefore := time.Now().Add(-olderThan) 258 _, err := db.GetEngine(ctx).Where("is_deleted=? AND deleted_unix < ?", true, deleteBefore.Unix()).Delete(new(Branch)) 259 if err != nil { 260 log.Error("DeletedBranchesCleanup: %v", err) 261 } 262 } 263 264 // RenamedBranch provide renamed branch log 265 // will check it when a branch can't be found 266 type RenamedBranch struct { 267 ID int64 `xorm:"pk autoincr"` 268 RepoID int64 `xorm:"INDEX NOT NULL"` 269 From string 270 To string 271 CreatedUnix timeutil.TimeStamp `xorm:"created"` 272 } 273 274 // FindRenamedBranch check if a branch was renamed 275 func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { 276 branch = &RenamedBranch{ 277 RepoID: repoID, 278 From: from, 279 } 280 exist, err = db.GetEngine(ctx).Get(branch) 281 282 return branch, exist, err 283 } 284 285 // RenameBranch rename a branch 286 func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) { 287 ctx, committer, err := db.TxContext(ctx) 288 if err != nil { 289 return err 290 } 291 defer committer.Close() 292 293 sess := db.GetEngine(ctx) 294 295 var branch Branch 296 exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch) 297 if err != nil { 298 return err 299 } else if !exist || branch.IsDeleted { 300 return ErrBranchNotExist{ 301 RepoID: repo.ID, 302 BranchName: from, 303 } 304 } 305 306 // 1. update branch in database 307 if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ 308 Name: to, 309 }); err != nil { 310 return err 311 } else if n <= 0 { 312 return ErrBranchNotExist{ 313 RepoID: repo.ID, 314 BranchName: from, 315 } 316 } 317 318 // 2. update default branch if needed 319 isDefault := repo.DefaultBranch == from 320 if isDefault { 321 repo.DefaultBranch = to 322 _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) 323 if err != nil { 324 return err 325 } 326 } 327 328 // 3. Update protected branch if needed 329 protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from) 330 if err != nil { 331 return err 332 } 333 334 if protectedBranch != nil { 335 // there is a protect rule for this branch 336 protectedBranch.RuleName = to 337 _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) 338 if err != nil { 339 return err 340 } 341 } else { 342 // some glob protect rules may match this branch 343 protected, err := IsBranchProtected(ctx, repo.ID, from) 344 if err != nil { 345 return err 346 } 347 if protected { 348 return ErrBranchIsProtected 349 } 350 } 351 352 // 4. Update all not merged pull request base branch name 353 _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?", 354 repo.ID, from, false). 355 Update(map[string]any{"base_branch": to}) 356 if err != nil { 357 return err 358 } 359 360 // 5. do git action 361 if err = gitAction(ctx, isDefault); err != nil { 362 return err 363 } 364 365 // 6. insert renamed branch record 366 renamedBranch := &RenamedBranch{ 367 RepoID: repo.ID, 368 From: from, 369 To: to, 370 } 371 err = db.Insert(ctx, renamedBranch) 372 if err != nil { 373 return err 374 } 375 376 return committer.Commit() 377 } 378 379 // FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created 380 // except the indicate branch 381 func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) { 382 branches := make(BranchList, 0, 2) 383 subQuery := builder.Select("head_branch").From("pull_request"). 384 InnerJoin("issue", "issue.id = pull_request.issue_id"). 385 Where(builder.Eq{ 386 "pull_request.head_repo_id": repoID, 387 "issue.is_closed": false, 388 }) 389 err := db.GetEngine(ctx). 390 Where("pusher_id=? AND is_deleted=?", userID, false). 391 And("name <> ?", excludeBranchName). 392 And("repo_id = ?", repoID). 393 And("commit_time >= ?", time.Now().Add(-time.Hour*6).Unix()). 394 NotIn("name", subQuery). 395 OrderBy("branch.commit_time DESC"). 396 Limit(2). 397 Find(&branches) 398 return branches, err 399 }