loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Sync branches into databases (#22743)

Related #14180
Related #25233
Related #22639
Close #19786
Related #12763

This PR will change all the branches retrieve method from reading git
data to read database to reduce git read operations.

- [x] Sync git branches information into database when push git data
- [x] Create a new table `Branch`, merge some columns of `DeletedBranch`
into `Branch` table and drop the table `DeletedBranch`.
- [x] Read `Branch` table when visit `code` -> `branch` page
- [x] Read `Branch` table when list branch names in `code` page dropdown
- [x] Read `Branch` table when list git ref compare page
- [x] Provide a button in admin page to manually sync all branches.
- [x] Sync branches if repository is not empty but database branches are
empty when visiting pages with branches list
- [x] Use `commit_time desc` as the default FindBranch order by to keep
consistent as before and deleted branches will be always at the end.

---------

Co-authored-by: Jason Song <i@wolfogre.com>

authored by

Lunny Xiao
Jason Song
and committed by
GitHub
6e19484f 5a871932

+1414 -722
-84
models/error.go
··· 318 318 return util.ErrPermissionDenied 319 319 } 320 320 321 - // __________ .__ 322 - // \______ \____________ ____ ____ | |__ 323 - // | | _/\_ __ \__ \ / \_/ ___\| | \ 324 - // | | \ | | \// __ \| | \ \___| Y \ 325 - // |______ / |__| (____ /___| /\___ >___| / 326 - // \/ \/ \/ \/ \/ 327 - 328 - // ErrBranchDoesNotExist represents an error that branch with such name does not exist. 329 - type ErrBranchDoesNotExist struct { 330 - BranchName string 331 - } 332 - 333 - // IsErrBranchDoesNotExist checks if an error is an ErrBranchDoesNotExist. 334 - func IsErrBranchDoesNotExist(err error) bool { 335 - _, ok := err.(ErrBranchDoesNotExist) 336 - return ok 337 - } 338 - 339 - func (err ErrBranchDoesNotExist) Error() string { 340 - return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName) 341 - } 342 - 343 - func (err ErrBranchDoesNotExist) Unwrap() error { 344 - return util.ErrNotExist 345 - } 346 - 347 - // ErrBranchAlreadyExists represents an error that branch with such name already exists. 348 - type ErrBranchAlreadyExists struct { 349 - BranchName string 350 - } 351 - 352 - // IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists. 353 - func IsErrBranchAlreadyExists(err error) bool { 354 - _, ok := err.(ErrBranchAlreadyExists) 355 - return ok 356 - } 357 - 358 - func (err ErrBranchAlreadyExists) Error() string { 359 - return fmt.Sprintf("branch already exists [name: %s]", err.BranchName) 360 - } 361 - 362 - func (err ErrBranchAlreadyExists) Unwrap() error { 363 - return util.ErrAlreadyExist 364 - } 365 - 366 - // ErrBranchNameConflict represents an error that branch name conflicts with other branch. 367 - type ErrBranchNameConflict struct { 368 - BranchName string 369 - } 370 - 371 - // IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict. 372 - func IsErrBranchNameConflict(err error) bool { 373 - _, ok := err.(ErrBranchNameConflict) 374 - return ok 375 - } 376 - 377 - func (err ErrBranchNameConflict) Error() string { 378 - return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName) 379 - } 380 - 381 - func (err ErrBranchNameConflict) Unwrap() error { 382 - return util.ErrAlreadyExist 383 - } 384 - 385 - // ErrBranchesEqual represents an error that branch name conflicts with other branch. 386 - type ErrBranchesEqual struct { 387 - BaseBranchName string 388 - HeadBranchName string 389 - } 390 - 391 - // IsErrBranchesEqual checks if an error is an ErrBranchesEqual. 392 - func IsErrBranchesEqual(err error) bool { 393 - _, ok := err.(ErrBranchesEqual) 394 - return ok 395 - } 396 - 397 - func (err ErrBranchesEqual) Error() string { 398 - return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) 399 - } 400 - 401 - func (err ErrBranchesEqual) Unwrap() error { 402 - return util.ErrInvalidArgument 403 - } 404 - 405 321 // ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. 406 322 type ErrDisallowedToMerge struct { 407 323 Reason string
+47
models/fixtures/branch.yml
··· 1 + - 2 + id: 1 3 + repo_id: 1 4 + name: 'foo' 5 + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' 6 + commit_message: 'first commit' 7 + commit_time: 978307100 8 + pusher_id: 1 9 + is_deleted: true 10 + deleted_by_id: 1 11 + deleted_unix: 978307200 12 + 13 + - 14 + id: 2 15 + repo_id: 1 16 + name: 'bar' 17 + commit_id: '62fb502a7172d4453f0322a2cc85bddffa57f07a' 18 + commit_message: 'second commit' 19 + commit_time: 978307100 20 + pusher_id: 1 21 + is_deleted: true 22 + deleted_by_id: 99 23 + deleted_unix: 978307200 24 + 25 + - 26 + id: 3 27 + repo_id: 1 28 + name: 'branch2' 29 + commit_id: '985f0301dba5e7b34be866819cd15ad3d8f508ee' 30 + commit_message: 'make pull5 outdated' 31 + commit_time: 1579166279 32 + pusher_id: 1 33 + is_deleted: false 34 + deleted_by_id: 0 35 + deleted_unix: 0 36 + 37 + - 38 + id: 4 39 + repo_id: 1 40 + name: 'master' 41 + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' 42 + commit_message: 'Initial commit' 43 + commit_time: 1489927679 44 + pusher_id: 1 45 + is_deleted: false 46 + deleted_by_id: 0 47 + deleted_unix: 0
-15
models/fixtures/deleted_branch.yml
··· 1 - - 2 - id: 1 3 - repo_id: 1 4 - name: foo 5 - commit: 1213212312313213213132131 6 - deleted_by_id: 1 7 - deleted_unix: 978307200 8 - 9 - - 10 - id: 2 11 - repo_id: 1 12 - name: bar 13 - commit: 5655464564554545466464655 14 - deleted_by_id: 99 15 - deleted_unix: 978307200
+379
models/git/branch.go
··· 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/log" 15 + "code.gitea.io/gitea/modules/timeutil" 16 + "code.gitea.io/gitea/modules/util" 17 + ) 18 + 19 + // ErrBranchNotExist represents an error that branch with such name does not exist. 20 + type ErrBranchNotExist struct { 21 + RepoID int64 22 + BranchName string 23 + } 24 + 25 + // IsErrBranchNotExist checks if an error is an ErrBranchDoesNotExist. 26 + func IsErrBranchNotExist(err error) bool { 27 + _, ok := err.(ErrBranchNotExist) 28 + return ok 29 + } 30 + 31 + func (err ErrBranchNotExist) Error() string { 32 + return fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", err.RepoID, err.BranchName) 33 + } 34 + 35 + func (err ErrBranchNotExist) Unwrap() error { 36 + return util.ErrNotExist 37 + } 38 + 39 + // ErrBranchAlreadyExists represents an error that branch with such name already exists. 40 + type ErrBranchAlreadyExists struct { 41 + BranchName string 42 + } 43 + 44 + // IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists. 45 + func IsErrBranchAlreadyExists(err error) bool { 46 + _, ok := err.(ErrBranchAlreadyExists) 47 + return ok 48 + } 49 + 50 + func (err ErrBranchAlreadyExists) Error() string { 51 + return fmt.Sprintf("branch already exists [name: %s]", err.BranchName) 52 + } 53 + 54 + func (err ErrBranchAlreadyExists) Unwrap() error { 55 + return util.ErrAlreadyExist 56 + } 57 + 58 + // ErrBranchNameConflict represents an error that branch name conflicts with other branch. 59 + type ErrBranchNameConflict struct { 60 + BranchName string 61 + } 62 + 63 + // IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict. 64 + func IsErrBranchNameConflict(err error) bool { 65 + _, ok := err.(ErrBranchNameConflict) 66 + return ok 67 + } 68 + 69 + func (err ErrBranchNameConflict) Error() string { 70 + return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName) 71 + } 72 + 73 + func (err ErrBranchNameConflict) Unwrap() error { 74 + return util.ErrAlreadyExist 75 + } 76 + 77 + // ErrBranchesEqual represents an error that base branch is equal to the head branch. 78 + type ErrBranchesEqual struct { 79 + BaseBranchName string 80 + HeadBranchName string 81 + } 82 + 83 + // IsErrBranchesEqual checks if an error is an ErrBranchesEqual. 84 + func IsErrBranchesEqual(err error) bool { 85 + _, ok := err.(ErrBranchesEqual) 86 + return ok 87 + } 88 + 89 + func (err ErrBranchesEqual) Error() string { 90 + return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) 91 + } 92 + 93 + func (err ErrBranchesEqual) Unwrap() error { 94 + return util.ErrInvalidArgument 95 + } 96 + 97 + // Branch represents a branch of a repository 98 + // For those repository who have many branches, stored into database is a good choice 99 + // for pagination, keyword search and filtering 100 + type Branch struct { 101 + ID int64 102 + RepoID int64 `xorm:"UNIQUE(s)"` 103 + Name string `xorm:"UNIQUE(s) NOT NULL"` 104 + CommitID string 105 + CommitMessage string `xorm:"TEXT"` 106 + PusherID int64 107 + Pusher *user_model.User `xorm:"-"` 108 + IsDeleted bool `xorm:"index"` 109 + DeletedByID int64 110 + DeletedBy *user_model.User `xorm:"-"` 111 + DeletedUnix timeutil.TimeStamp `xorm:"index"` 112 + CommitTime timeutil.TimeStamp // The commit 113 + CreatedUnix timeutil.TimeStamp `xorm:"created"` 114 + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` 115 + } 116 + 117 + func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) { 118 + if b.DeletedBy == nil { 119 + b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID) 120 + if user_model.IsErrUserNotExist(err) { 121 + b.DeletedBy = user_model.NewGhostUser() 122 + err = nil 123 + } 124 + } 125 + return err 126 + } 127 + 128 + func (b *Branch) LoadPusher(ctx context.Context) (err error) { 129 + if b.Pusher == nil && b.PusherID > 0 { 130 + b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID) 131 + if user_model.IsErrUserNotExist(err) { 132 + b.Pusher = user_model.NewGhostUser() 133 + err = nil 134 + } 135 + } 136 + return err 137 + } 138 + 139 + func init() { 140 + db.RegisterModel(new(Branch)) 141 + db.RegisterModel(new(RenamedBranch)) 142 + } 143 + 144 + func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) { 145 + var branch Branch 146 + has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch) 147 + if err != nil { 148 + return nil, err 149 + } else if !has { 150 + return nil, ErrBranchNotExist{ 151 + RepoID: repoID, 152 + BranchName: branchName, 153 + } 154 + } 155 + return &branch, nil 156 + } 157 + 158 + func AddBranches(ctx context.Context, branches []*Branch) error { 159 + for _, branch := range branches { 160 + if _, err := db.GetEngine(ctx).Insert(branch); err != nil { 161 + return err 162 + } 163 + } 164 + return nil 165 + } 166 + 167 + func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, error) { 168 + var branch Branch 169 + has, err := db.GetEngine(ctx).ID(branchID).Get(&branch) 170 + if err != nil { 171 + return nil, err 172 + } else if !has { 173 + return nil, ErrBranchNotExist{ 174 + RepoID: repoID, 175 + } 176 + } 177 + if branch.RepoID != repoID { 178 + return nil, ErrBranchNotExist{ 179 + RepoID: repoID, 180 + } 181 + } 182 + if !branch.IsDeleted { 183 + return nil, ErrBranchNotExist{ 184 + RepoID: repoID, 185 + } 186 + } 187 + return &branch, nil 188 + } 189 + 190 + func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error { 191 + return db.WithTx(ctx, func(ctx context.Context) error { 192 + branches := make([]*Branch, 0, len(branchIDs)) 193 + if err := db.GetEngine(ctx).In("id", branchIDs).Find(&branches); err != nil { 194 + return err 195 + } 196 + for _, branch := range branches { 197 + if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil { 198 + return err 199 + } 200 + } 201 + return nil 202 + }) 203 + } 204 + 205 + // UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information 206 + // If it doest not exist, insert a new record into database 207 + func UpdateBranch(ctx context.Context, repoID int64, branchName, commitID, commitMessage string, pusherID int64, commitTime time.Time) error { 208 + cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). 209 + Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix"). 210 + Update(&Branch{ 211 + CommitID: commitID, 212 + CommitMessage: commitMessage, 213 + PusherID: pusherID, 214 + CommitTime: timeutil.TimeStamp(commitTime.Unix()), 215 + IsDeleted: false, 216 + }) 217 + if err != nil { 218 + return err 219 + } 220 + if cnt > 0 { 221 + return nil 222 + } 223 + 224 + return db.Insert(ctx, &Branch{ 225 + RepoID: repoID, 226 + Name: branchName, 227 + CommitID: commitID, 228 + CommitMessage: commitMessage, 229 + PusherID: pusherID, 230 + CommitTime: timeutil.TimeStamp(commitTime.Unix()), 231 + }) 232 + } 233 + 234 + // AddDeletedBranch adds a deleted branch to the database 235 + func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { 236 + branch, err := GetBranch(ctx, repoID, branchName) 237 + if err != nil { 238 + return err 239 + } 240 + if branch.IsDeleted { 241 + return nil 242 + } 243 + 244 + cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=? AND is_deleted=?", repoID, branchName, false). 245 + Cols("is_deleted, deleted_by_id, deleted_unix"). 246 + Update(&Branch{ 247 + IsDeleted: true, 248 + DeletedByID: deletedByID, 249 + DeletedUnix: timeutil.TimeStampNow(), 250 + }) 251 + if err != nil { 252 + return err 253 + } 254 + if cnt == 0 { 255 + return fmt.Errorf("branch %s not found or has been deleted", branchName) 256 + } 257 + return err 258 + } 259 + 260 + func RemoveDeletedBranchByID(ctx context.Context, repoID, branchID int64) error { 261 + _, err := db.GetEngine(ctx).Where("repo_id=? AND id=? AND is_deleted = ?", repoID, branchID, true).Delete(new(Branch)) 262 + return err 263 + } 264 + 265 + // RemoveOldDeletedBranches removes old deleted branches 266 + func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { 267 + // Nothing to do for shutdown or terminate 268 + log.Trace("Doing: DeletedBranchesCleanup") 269 + 270 + deleteBefore := time.Now().Add(-olderThan) 271 + _, err := db.GetEngine(ctx).Where("is_deleted=? AND deleted_unix < ?", true, deleteBefore.Unix()).Delete(new(Branch)) 272 + if err != nil { 273 + log.Error("DeletedBranchesCleanup: %v", err) 274 + } 275 + } 276 + 277 + // RenamedBranch provide renamed branch log 278 + // will check it when a branch can't be found 279 + type RenamedBranch struct { 280 + ID int64 `xorm:"pk autoincr"` 281 + RepoID int64 `xorm:"INDEX NOT NULL"` 282 + From string 283 + To string 284 + CreatedUnix timeutil.TimeStamp `xorm:"created"` 285 + } 286 + 287 + // FindRenamedBranch check if a branch was renamed 288 + func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { 289 + branch = &RenamedBranch{ 290 + RepoID: repoID, 291 + From: from, 292 + } 293 + exist, err = db.GetEngine(ctx).Get(branch) 294 + 295 + return branch, exist, err 296 + } 297 + 298 + // RenameBranch rename a branch 299 + func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { 300 + ctx, committer, err := db.TxContext(ctx) 301 + if err != nil { 302 + return err 303 + } 304 + defer committer.Close() 305 + 306 + sess := db.GetEngine(ctx) 307 + 308 + // 1. update branch in database 309 + if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ 310 + Name: to, 311 + }); err != nil { 312 + return err 313 + } else if n <= 0 { 314 + return ErrBranchNotExist{ 315 + RepoID: repo.ID, 316 + BranchName: from, 317 + } 318 + } 319 + 320 + // 2. update default branch if needed 321 + isDefault := repo.DefaultBranch == from 322 + if isDefault { 323 + repo.DefaultBranch = to 324 + _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) 325 + if err != nil { 326 + return err 327 + } 328 + } 329 + 330 + // 3. Update protected branch if needed 331 + protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from) 332 + if err != nil { 333 + return err 334 + } 335 + 336 + if protectedBranch != nil { 337 + // there is a protect rule for this branch 338 + protectedBranch.RuleName = to 339 + _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) 340 + if err != nil { 341 + return err 342 + } 343 + } else { 344 + // some glob protect rules may match this branch 345 + protected, err := IsBranchProtected(ctx, repo.ID, from) 346 + if err != nil { 347 + return err 348 + } 349 + if protected { 350 + return ErrBranchIsProtected 351 + } 352 + } 353 + 354 + // 4. Update all not merged pull request base branch name 355 + _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?", 356 + repo.ID, from, false). 357 + Update(map[string]interface{}{"base_branch": to}) 358 + if err != nil { 359 + return err 360 + } 361 + 362 + // 5. do git action 363 + if err = gitAction(isDefault); err != nil { 364 + return err 365 + } 366 + 367 + // 6. insert renamed branch record 368 + renamedBranch := &RenamedBranch{ 369 + RepoID: repo.ID, 370 + From: from, 371 + To: to, 372 + } 373 + err = db.Insert(ctx, renamedBranch) 374 + if err != nil { 375 + return err 376 + } 377 + 378 + return committer.Commit() 379 + }
+132
models/git/branch_list.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package git 5 + 6 + import ( 7 + "context" 8 + 9 + "code.gitea.io/gitea/models/db" 10 + user_model "code.gitea.io/gitea/models/user" 11 + "code.gitea.io/gitea/modules/container" 12 + "code.gitea.io/gitea/modules/util" 13 + 14 + "xorm.io/builder" 15 + "xorm.io/xorm" 16 + ) 17 + 18 + type BranchList []*Branch 19 + 20 + func (branches BranchList) LoadDeletedBy(ctx context.Context) error { 21 + ids := container.Set[int64]{} 22 + for _, branch := range branches { 23 + if !branch.IsDeleted { 24 + continue 25 + } 26 + ids.Add(branch.DeletedByID) 27 + } 28 + usersMap := make(map[int64]*user_model.User, len(ids)) 29 + if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil { 30 + return err 31 + } 32 + for _, branch := range branches { 33 + if !branch.IsDeleted { 34 + continue 35 + } 36 + branch.DeletedBy = usersMap[branch.DeletedByID] 37 + if branch.DeletedBy == nil { 38 + branch.DeletedBy = user_model.NewGhostUser() 39 + } 40 + } 41 + return nil 42 + } 43 + 44 + func (branches BranchList) LoadPusher(ctx context.Context) error { 45 + ids := container.Set[int64]{} 46 + for _, branch := range branches { 47 + if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher 48 + ids.Add(branch.PusherID) 49 + } 50 + } 51 + usersMap := make(map[int64]*user_model.User, len(ids)) 52 + if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil { 53 + return err 54 + } 55 + for _, branch := range branches { 56 + if branch.PusherID <= 0 { 57 + continue 58 + } 59 + branch.Pusher = usersMap[branch.PusherID] 60 + if branch.Pusher == nil { 61 + branch.Pusher = user_model.NewGhostUser() 62 + } 63 + } 64 + return nil 65 + } 66 + 67 + const ( 68 + BranchOrderByNameAsc = "name ASC" 69 + BranchOrderByCommitTimeDesc = "commit_time DESC" 70 + ) 71 + 72 + type FindBranchOptions struct { 73 + db.ListOptions 74 + RepoID int64 75 + ExcludeBranchNames []string 76 + IsDeletedBranch util.OptionalBool 77 + OrderBy string 78 + } 79 + 80 + func (opts *FindBranchOptions) Cond() builder.Cond { 81 + cond := builder.NewCond() 82 + if opts.RepoID > 0 { 83 + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) 84 + } 85 + 86 + if len(opts.ExcludeBranchNames) > 0 { 87 + cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames)) 88 + } 89 + if !opts.IsDeletedBranch.IsNone() { 90 + cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()}) 91 + } 92 + return cond 93 + } 94 + 95 + func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) { 96 + return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{}) 97 + } 98 + 99 + func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session { 100 + if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end 101 + sess = sess.OrderBy("is_deleted ASC") 102 + } 103 + 104 + if opts.OrderBy == "" { 105 + opts.OrderBy = BranchOrderByCommitTimeDesc 106 + } 107 + return sess.OrderBy(opts.OrderBy) 108 + } 109 + 110 + func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) { 111 + sess := db.GetEngine(ctx).Where(opts.Cond()) 112 + if opts.PageSize > 0 && !opts.IsListAll() { 113 + sess = db.SetSessionPagination(sess, &opts.ListOptions) 114 + } 115 + sess = orderByBranches(sess, opts) 116 + 117 + var branches []*Branch 118 + return branches, sess.Find(&branches) 119 + } 120 + 121 + func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) { 122 + sess := db.GetEngine(ctx).Select("name").Where(opts.Cond()) 123 + if opts.PageSize > 0 && !opts.IsListAll() { 124 + sess = db.SetSessionPagination(sess, &opts.ListOptions) 125 + } 126 + sess = orderByBranches(sess, opts) 127 + var branches []string 128 + if err := sess.Table("branch").Find(&branches); err != nil { 129 + return nil, err 130 + } 131 + return branches, nil 132 + }
-197
models/git/branches.go
··· 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/log" 15 - "code.gitea.io/gitea/modules/timeutil" 16 - ) 17 - 18 - // DeletedBranch struct 19 - type DeletedBranch struct { 20 - ID int64 `xorm:"pk autoincr"` 21 - RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` 22 - Name string `xorm:"UNIQUE(s) NOT NULL"` 23 - Commit string `xorm:"UNIQUE(s) NOT NULL"` 24 - DeletedByID int64 `xorm:"INDEX"` 25 - DeletedBy *user_model.User `xorm:"-"` 26 - DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"` 27 - } 28 - 29 - func init() { 30 - db.RegisterModel(new(DeletedBranch)) 31 - db.RegisterModel(new(RenamedBranch)) 32 - } 33 - 34 - // AddDeletedBranch adds a deleted branch to the database 35 - func AddDeletedBranch(ctx context.Context, repoID int64, branchName, commit string, deletedByID int64) error { 36 - deletedBranch := &DeletedBranch{ 37 - RepoID: repoID, 38 - Name: branchName, 39 - Commit: commit, 40 - DeletedByID: deletedByID, 41 - } 42 - 43 - _, err := db.GetEngine(ctx).Insert(deletedBranch) 44 - return err 45 - } 46 - 47 - // GetDeletedBranches returns all the deleted branches 48 - func GetDeletedBranches(ctx context.Context, repoID int64) ([]*DeletedBranch, error) { 49 - deletedBranches := make([]*DeletedBranch, 0) 50 - return deletedBranches, db.GetEngine(ctx).Where("repo_id = ?", repoID).Desc("deleted_unix").Find(&deletedBranches) 51 - } 52 - 53 - // GetDeletedBranchByID get a deleted branch by its ID 54 - func GetDeletedBranchByID(ctx context.Context, repoID, id int64) (*DeletedBranch, error) { 55 - deletedBranch := &DeletedBranch{} 56 - has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("id = ?", id).Get(deletedBranch) 57 - if err != nil { 58 - return nil, err 59 - } 60 - if !has { 61 - return nil, nil 62 - } 63 - return deletedBranch, nil 64 - } 65 - 66 - // RemoveDeletedBranchByID removes a deleted branch from the database 67 - func RemoveDeletedBranchByID(ctx context.Context, repoID, id int64) (err error) { 68 - deletedBranch := &DeletedBranch{ 69 - RepoID: repoID, 70 - ID: id, 71 - } 72 - 73 - if affected, err := db.GetEngine(ctx).Delete(deletedBranch); err != nil { 74 - return err 75 - } else if affected != 1 { 76 - return fmt.Errorf("remove deleted branch ID(%v) failed", id) 77 - } 78 - 79 - return nil 80 - } 81 - 82 - // LoadUser loads the user that deleted the branch 83 - // When there's no user found it returns a user_model.NewGhostUser 84 - func (deletedBranch *DeletedBranch) LoadUser(ctx context.Context) { 85 - user, err := user_model.GetUserByID(ctx, deletedBranch.DeletedByID) 86 - if err != nil { 87 - user = user_model.NewGhostUser() 88 - } 89 - deletedBranch.DeletedBy = user 90 - } 91 - 92 - // RemoveDeletedBranchByName removes all deleted branches 93 - func RemoveDeletedBranchByName(ctx context.Context, repoID int64, branch string) error { 94 - _, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch)) 95 - return err 96 - } 97 - 98 - // RemoveOldDeletedBranches removes old deleted branches 99 - func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { 100 - // Nothing to do for shutdown or terminate 101 - log.Trace("Doing: DeletedBranchesCleanup") 102 - 103 - deleteBefore := time.Now().Add(-olderThan) 104 - _, err := db.GetEngine(ctx).Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch)) 105 - if err != nil { 106 - log.Error("DeletedBranchesCleanup: %v", err) 107 - } 108 - } 109 - 110 - // RenamedBranch provide renamed branch log 111 - // will check it when a branch can't be found 112 - type RenamedBranch struct { 113 - ID int64 `xorm:"pk autoincr"` 114 - RepoID int64 `xorm:"INDEX NOT NULL"` 115 - From string 116 - To string 117 - CreatedUnix timeutil.TimeStamp `xorm:"created"` 118 - } 119 - 120 - // FindRenamedBranch check if a branch was renamed 121 - func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { 122 - branch = &RenamedBranch{ 123 - RepoID: repoID, 124 - From: from, 125 - } 126 - exist, err = db.GetEngine(ctx).Get(branch) 127 - 128 - return branch, exist, err 129 - } 130 - 131 - // RenameBranch rename a branch 132 - func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { 133 - ctx, committer, err := db.TxContext(ctx) 134 - if err != nil { 135 - return err 136 - } 137 - defer committer.Close() 138 - 139 - sess := db.GetEngine(ctx) 140 - // 1. update default branch if needed 141 - isDefault := repo.DefaultBranch == from 142 - if isDefault { 143 - repo.DefaultBranch = to 144 - _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) 145 - if err != nil { 146 - return err 147 - } 148 - } 149 - 150 - // 2. Update protected branch if needed 151 - protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from) 152 - if err != nil { 153 - return err 154 - } 155 - 156 - if protectedBranch != nil { 157 - protectedBranch.RuleName = to 158 - _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) 159 - if err != nil { 160 - return err 161 - } 162 - } else { 163 - protected, err := IsBranchProtected(ctx, repo.ID, from) 164 - if err != nil { 165 - return err 166 - } 167 - if protected { 168 - return ErrBranchIsProtected 169 - } 170 - } 171 - 172 - // 3. Update all not merged pull request base branch name 173 - _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?", 174 - repo.ID, from, false). 175 - Update(map[string]interface{}{"base_branch": to}) 176 - if err != nil { 177 - return err 178 - } 179 - 180 - // 4. do git action 181 - if err = gitAction(isDefault); err != nil { 182 - return err 183 - } 184 - 185 - // 5. insert renamed branch record 186 - renamedBranch := &RenamedBranch{ 187 - RepoID: repo.ID, 188 - From: from, 189 - To: to, 190 - } 191 - err = db.Insert(ctx, renamedBranch) 192 - if err != nil { 193 - return err 194 - } 195 - 196 - return committer.Commit() 197 - }
+29 -15
models/git/branches_test.go models/git/branch_test.go
··· 11 11 issues_model "code.gitea.io/gitea/models/issues" 12 12 repo_model "code.gitea.io/gitea/models/repo" 13 13 "code.gitea.io/gitea/models/unittest" 14 + "code.gitea.io/gitea/modules/util" 14 15 15 16 "github.com/stretchr/testify/assert" 16 17 ) ··· 18 19 func TestAddDeletedBranch(t *testing.T) { 19 20 assert.NoError(t, unittest.PrepareTestDatabase()) 20 21 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 21 - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) 22 + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) 23 + 24 + assert.True(t, firstBranch.IsDeleted) 25 + assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.DeletedByID)) 26 + assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "branch2", int64(1))) 22 27 23 - assert.Error(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID)) 24 - assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "test", "5655464564554545466464656", int64(1))) 28 + secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"}) 29 + assert.True(t, secondBranch.IsDeleted) 30 + 31 + err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.Name, secondBranch.CommitID, secondBranch.CommitMessage, secondBranch.PusherID, secondBranch.CommitTime.AsLocalTime()) 32 + assert.NoError(t, err) 25 33 } 26 34 27 35 func TestGetDeletedBranches(t *testing.T) { 28 36 assert.NoError(t, unittest.PrepareTestDatabase()) 29 37 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 30 38 31 - branches, err := git_model.GetDeletedBranches(db.DefaultContext, repo.ID) 39 + branches, err := git_model.FindBranches(db.DefaultContext, git_model.FindBranchOptions{ 40 + ListOptions: db.ListOptions{ 41 + ListAll: true, 42 + }, 43 + RepoID: repo.ID, 44 + IsDeletedBranch: util.OptionalBoolTrue, 45 + }) 32 46 assert.NoError(t, err) 33 47 assert.Len(t, branches, 2) 34 48 } 35 49 36 50 func TestGetDeletedBranch(t *testing.T) { 37 51 assert.NoError(t, unittest.PrepareTestDatabase()) 38 - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) 52 + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) 39 53 40 54 assert.NotNil(t, getDeletedBranch(t, firstBranch)) 41 55 } ··· 43 57 func TestDeletedBranchLoadUser(t *testing.T) { 44 58 assert.NoError(t, unittest.PrepareTestDatabase()) 45 59 46 - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) 47 - secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}) 60 + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) 61 + secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2}) 48 62 49 63 branch := getDeletedBranch(t, firstBranch) 50 64 assert.Nil(t, branch.DeletedBy) 51 - branch.LoadUser(db.DefaultContext) 65 + branch.LoadDeletedBy(db.DefaultContext) 52 66 assert.NotNil(t, branch.DeletedBy) 53 67 assert.Equal(t, "user1", branch.DeletedBy.Name) 54 68 55 69 branch = getDeletedBranch(t, secondBranch) 56 70 assert.Nil(t, branch.DeletedBy) 57 - branch.LoadUser(db.DefaultContext) 71 + branch.LoadDeletedBy(db.DefaultContext) 58 72 assert.NotNil(t, branch.DeletedBy) 59 73 assert.Equal(t, "Ghost", branch.DeletedBy.Name) 60 74 } ··· 63 77 assert.NoError(t, unittest.PrepareTestDatabase()) 64 78 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 65 79 66 - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) 80 + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) 67 81 68 82 err := git_model.RemoveDeletedBranchByID(db.DefaultContext, repo.ID, 1) 69 83 assert.NoError(t, err) 70 84 unittest.AssertNotExistsBean(t, firstBranch) 71 - unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}) 85 + unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2}) 72 86 } 73 87 74 - func getDeletedBranch(t *testing.T, branch *git_model.DeletedBranch) *git_model.DeletedBranch { 88 + func getDeletedBranch(t *testing.T, branch *git_model.Branch) *git_model.Branch { 75 89 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 76 90 77 91 deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID) 78 92 assert.NoError(t, err) 79 93 assert.Equal(t, branch.ID, deletedBranch.ID) 80 94 assert.Equal(t, branch.Name, deletedBranch.Name) 81 - assert.Equal(t, branch.Commit, deletedBranch.Commit) 95 + assert.Equal(t, branch.CommitID, deletedBranch.CommitID) 82 96 assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID) 83 97 84 98 return deletedBranch ··· 146 160 147 161 deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1) 148 162 149 - // Expect no error, and the returned branch is nil. 150 - assert.NoError(t, err) 163 + // Expect error, and the returned branch is nil. 164 + assert.Error(t, err) 151 165 assert.Nil(t, deletedBranch) 152 166 153 167 // Now get the deletedBranch with ID of 1 on repo with ID 1.
+25 -12
models/git/protected_branch_list.go
··· 8 8 "sort" 9 9 10 10 "code.gitea.io/gitea/models/db" 11 - "code.gitea.io/gitea/modules/git" 11 + "code.gitea.io/gitea/modules/util" 12 12 13 13 "github.com/gobwas/glob" 14 14 ) ··· 47 47 } 48 48 49 49 // FindAllMatchedBranches find all matched branches 50 - func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) { 51 - // FIXME: how many should we get? 52 - branches, _, err := gitRepo.GetBranchNames(0, 9999999) 53 - if err != nil { 54 - return nil, err 55 - } 56 - rule := glob.MustCompile(ruleName) 57 - results := make([]string, 0, len(branches)) 58 - for _, branch := range branches { 59 - if rule.Match(branch) { 60 - results = append(results, branch) 50 + func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) { 51 + results := make([]string, 0, 10) 52 + for page := 1; ; page++ { 53 + brancheNames, err := FindBranchNames(ctx, FindBranchOptions{ 54 + ListOptions: db.ListOptions{ 55 + PageSize: 100, 56 + Page: page, 57 + }, 58 + RepoID: repoID, 59 + IsDeletedBranch: util.OptionalBoolFalse, 60 + }) 61 + if err != nil { 62 + return nil, err 63 + } 64 + rule := glob.MustCompile(ruleName) 65 + 66 + for _, branch := range brancheNames { 67 + if rule.Match(branch) { 68 + results = append(results, branch) 69 + } 70 + } 71 + if len(brancheNames) < 100 { 72 + break 61 73 } 62 74 } 75 + 63 76 return results, nil 64 77 } 65 78
+2
models/migrations/migrations.go
··· 509 509 NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun), 510 510 // v263 -> v264 511 511 NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable), 512 + // v264 -> v265 513 + NewMigration("Add branch table", v1_21.AddBranchTable), 512 514 } 513 515 514 516 // GetCurrentDBVersion returns the current db version
+93
models/migrations/v1_21/v264.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package v1_21 //nolint 5 + 6 + import ( 7 + "context" 8 + "fmt" 9 + 10 + "code.gitea.io/gitea/models/db" 11 + "code.gitea.io/gitea/modules/timeutil" 12 + 13 + "xorm.io/xorm" 14 + ) 15 + 16 + func AddBranchTable(x *xorm.Engine) error { 17 + type Branch struct { 18 + ID int64 19 + RepoID int64 `xorm:"UNIQUE(s)"` 20 + Name string `xorm:"UNIQUE(s) NOT NULL"` 21 + CommitID string 22 + CommitMessage string `xorm:"TEXT"` 23 + PusherID int64 24 + IsDeleted bool `xorm:"index"` 25 + DeletedByID int64 26 + DeletedUnix timeutil.TimeStamp `xorm:"index"` 27 + CommitTime timeutil.TimeStamp // The commit 28 + CreatedUnix timeutil.TimeStamp `xorm:"created"` 29 + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` 30 + } 31 + 32 + if err := x.Sync(new(Branch)); err != nil { 33 + return err 34 + } 35 + 36 + if exist, err := x.IsTableExist("deleted_branches"); err != nil { 37 + return err 38 + } else if !exist { 39 + return nil 40 + } 41 + 42 + type DeletedBranch struct { 43 + ID int64 44 + RepoID int64 `xorm:"index UNIQUE(s)"` 45 + Name string `xorm:"UNIQUE(s) NOT NULL"` 46 + Commit string 47 + DeletedByID int64 48 + DeletedUnix timeutil.TimeStamp 49 + } 50 + 51 + var adminUserID int64 52 + has, err := x.Table("user"). 53 + Select("id"). 54 + Where("is_admin=?", true). 55 + Asc("id"). // Reliably get the admin with the lowest ID. 56 + Get(&adminUserID) 57 + if err != nil { 58 + return err 59 + } else if !has { 60 + return fmt.Errorf("no admin user found") 61 + } 62 + 63 + branches := make([]Branch, 0, 100) 64 + if err := db.Iterate(context.Background(), nil, func(ctx context.Context, deletedBranch *DeletedBranch) error { 65 + branches = append(branches, Branch{ 66 + RepoID: deletedBranch.RepoID, 67 + Name: deletedBranch.Name, 68 + CommitID: deletedBranch.Commit, 69 + PusherID: adminUserID, 70 + IsDeleted: true, 71 + DeletedByID: deletedBranch.DeletedByID, 72 + DeletedUnix: deletedBranch.DeletedUnix, 73 + }) 74 + if len(branches) >= 100 { 75 + _, err := x.Insert(&branches) 76 + if err != nil { 77 + return err 78 + } 79 + branches = branches[:0] 80 + } 81 + return nil 82 + }); err != nil { 83 + return err 84 + } 85 + 86 + if len(branches) > 0 { 87 + if _, err := x.Insert(&branches); err != nil { 88 + return err 89 + } 90 + } 91 + 92 + return x.DropTables("deleted_branches") 93 + }
+1 -1
models/repo.go
··· 147 147 &repo_model.Collaboration{RepoID: repoID}, 148 148 &issues_model.Comment{RefRepoID: repoID}, 149 149 &git_model.CommitStatus{RepoID: repoID}, 150 - &git_model.DeletedBranch{RepoID: repoID}, 150 + &git_model.Branch{RepoID: repoID}, 151 151 &git_model.LFSLock{RepoID: repoID}, 152 152 &repo_model.LanguageStat{RepoID: repoID}, 153 153 &issues_model.Milestone{RepoID: repoID},
+2 -2
models/user/user.go
··· 1171 1171 } 1172 1172 1173 1173 // GetAdminUser returns the first administrator 1174 - func GetAdminUser() (*User, error) { 1174 + func GetAdminUser(ctx context.Context) (*User, error) { 1175 1175 var admin User 1176 - has, err := db.GetEngine(db.DefaultContext). 1176 + has, err := db.GetEngine(ctx). 1177 1177 Where("is_admin=?", true). 1178 1178 Asc("id"). // Reliably get the admin with the lowest ID. 1179 1179 Get(&admin)
+30 -5
modules/context/repo.go
··· 667 667 } 668 668 ctx.Data["Tags"] = tags 669 669 670 - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) 670 + branchOpts := git_model.FindBranchOptions{ 671 + RepoID: ctx.Repo.Repository.ID, 672 + IsDeletedBranch: util.OptionalBoolFalse, 673 + ListOptions: db.ListOptions{ 674 + ListAll: true, 675 + }, 676 + } 677 + branchesTotal, err := git_model.CountBranches(ctx, branchOpts) 678 + if err != nil { 679 + ctx.ServerError("CountBranches", err) 680 + return 681 + } 682 + 683 + // non empty repo should have at least 1 branch, so this repository's branches haven't been synced yet 684 + if branchesTotal == 0 { // fallback to do a sync immediately 685 + branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) 686 + if err != nil { 687 + ctx.ServerError("SyncRepoBranches", err) 688 + return 689 + } 690 + } 691 + 692 + // FIXME: use paganation and async loading 693 + branchOpts.ExcludeBranchNames = []string{ctx.Repo.Repository.DefaultBranch} 694 + brs, err := git_model.FindBranchNames(ctx, branchOpts) 671 695 if err != nil { 672 696 ctx.ServerError("GetBranches", err) 673 697 return 674 698 } 675 - ctx.Data["Branches"] = brs 676 - ctx.Data["BranchesCount"] = len(brs) 699 + // always put default branch on the top 700 + ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...) 701 + ctx.Data["BranchesCount"] = branchesTotal 677 702 678 703 // If not branch selected, try default one. 679 704 // If default branch doesn't exist, fall back to some other branch. ··· 897 922 if len(ctx.Params("*")) == 0 { 898 923 refName = ctx.Repo.Repository.DefaultBranch 899 924 if !ctx.Repo.GitRepo.IsBranchExist(refName) { 900 - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) 925 + brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1) 901 926 if err == nil && len(brs) != 0 { 902 - refName = brs[0] 927 + refName = brs[0].Name 903 928 } else if len(brs) == 0 { 904 929 log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path) 905 930 ctx.Repo.Repository.MarkAsBrokenEmpty()
+135
modules/repository/branch.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package repository 5 + 6 + import ( 7 + "context" 8 + 9 + "code.gitea.io/gitea/models/db" 10 + git_model "code.gitea.io/gitea/models/git" 11 + repo_model "code.gitea.io/gitea/models/repo" 12 + "code.gitea.io/gitea/modules/container" 13 + "code.gitea.io/gitea/modules/git" 14 + "code.gitea.io/gitea/modules/log" 15 + "code.gitea.io/gitea/modules/timeutil" 16 + ) 17 + 18 + // SyncRepoBranches synchronizes branch table with repository branches 19 + func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error) { 20 + repo, err := repo_model.GetRepositoryByID(ctx, repoID) 21 + if err != nil { 22 + return 0, err 23 + } 24 + 25 + log.Debug("SyncRepoBranches: in Repo[%d:%s]", repo.ID, repo.FullName()) 26 + 27 + gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) 28 + if err != nil { 29 + log.Error("OpenRepository[%s]: %w", repo.RepoPath(), err) 30 + return 0, err 31 + } 32 + defer gitRepo.Close() 33 + 34 + return SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doerID) 35 + } 36 + 37 + func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) { 38 + allBranches := container.Set[string]{} 39 + { 40 + branches, _, err := gitRepo.GetBranchNames(0, 0) 41 + if err != nil { 42 + return 0, err 43 + } 44 + log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches) 45 + for _, branch := range branches { 46 + allBranches.Add(branch) 47 + } 48 + } 49 + 50 + dbBranches := make(map[string]*git_model.Branch) 51 + { 52 + branches, err := git_model.FindBranches(ctx, git_model.FindBranchOptions{ 53 + ListOptions: db.ListOptions{ 54 + ListAll: true, 55 + }, 56 + RepoID: repo.ID, 57 + }) 58 + if err != nil { 59 + return 0, err 60 + } 61 + for _, branch := range branches { 62 + dbBranches[branch.Name] = branch 63 + } 64 + } 65 + 66 + var toAdd []*git_model.Branch 67 + var toUpdate []*git_model.Branch 68 + var toRemove []int64 69 + for branch := range allBranches { 70 + dbb := dbBranches[branch] 71 + commit, err := gitRepo.GetBranchCommit(branch) 72 + if err != nil { 73 + return 0, err 74 + } 75 + if dbb == nil { 76 + toAdd = append(toAdd, &git_model.Branch{ 77 + RepoID: repo.ID, 78 + Name: branch, 79 + CommitID: commit.ID.String(), 80 + CommitMessage: commit.CommitMessage, 81 + PusherID: doerID, 82 + CommitTime: timeutil.TimeStamp(commit.Author.When.Unix()), 83 + }) 84 + } else if commit.ID.String() != dbb.CommitID { 85 + toUpdate = append(toUpdate, &git_model.Branch{ 86 + ID: dbb.ID, 87 + RepoID: repo.ID, 88 + Name: branch, 89 + CommitID: commit.ID.String(), 90 + CommitMessage: commit.CommitMessage, 91 + PusherID: doerID, 92 + CommitTime: timeutil.TimeStamp(commit.Author.When.Unix()), 93 + }) 94 + } 95 + } 96 + 97 + for _, dbBranch := range dbBranches { 98 + if !allBranches.Contains(dbBranch.Name) && !dbBranch.IsDeleted { 99 + toRemove = append(toRemove, dbBranch.ID) 100 + } 101 + } 102 + 103 + log.Trace("SyncRepoBranches[%s]: toAdd: %v, toUpdate: %v, toRemove: %v", repo.FullName(), toAdd, toUpdate, toRemove) 104 + 105 + if len(toAdd) == 0 && len(toRemove) == 0 && len(toUpdate) == 0 { 106 + return int64(len(allBranches)), nil 107 + } 108 + 109 + if err := db.WithTx(ctx, func(subCtx context.Context) error { 110 + if len(toAdd) > 0 { 111 + if err := git_model.AddBranches(subCtx, toAdd); err != nil { 112 + return err 113 + } 114 + } 115 + 116 + for _, b := range toUpdate { 117 + if _, err := db.GetEngine(subCtx).ID(b.ID). 118 + Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted"). 119 + Update(b); err != nil { 120 + return err 121 + } 122 + } 123 + 124 + if len(toRemove) > 0 { 125 + if err := git_model.DeleteBranches(subCtx, repo.ID, doerID, toRemove); err != nil { 126 + return err 127 + } 128 + } 129 + 130 + return nil 131 + }); err != nil { 132 + return 0, err 133 + } 134 + return int64(len(allBranches)), nil 135 + }
+6
modules/repository/init.go
··· 351 351 if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { 352 352 return fmt.Errorf("setDefaultBranch: %w", err) 353 353 } 354 + 355 + if !repo.IsEmpty { 356 + if _, err := SyncRepoBranches(ctx, repo.ID, u.ID); err != nil { 357 + return fmt.Errorf("SyncRepoBranches: %w", err) 358 + } 359 + } 354 360 } 355 361 356 362 if err = UpdateRepository(ctx, repo, false); err != nil {
+5 -1
modules/repository/repo.go
··· 151 151 } 152 152 } 153 153 154 + if _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil { 155 + return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err) 156 + } 157 + 154 158 if !opts.Releases { 155 159 // note: this will greatly improve release (tag) sync 156 160 // for pull-mirrors with many tags ··· 169 173 } 170 174 } 171 175 172 - ctx, committer, err := db.TxContext(db.DefaultContext) 176 + ctx, committer, err := db.TxContext(ctx) 173 177 if err != nil { 174 178 return nil, err 175 179 }
+2
options/locale/locale_en-US.ini
··· 2660 2660 dashboard.delete_missing_repos = Delete all repositories missing their Git files 2661 2661 dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started. 2662 2662 dashboard.delete_generated_repository_avatars = Delete generated repository avatars 2663 + dashboard.sync_repo_branches = Sync missed branches from git data to databases 2663 2664 dashboard.update_mirrors = Update Mirrors 2664 2665 dashboard.repo_health_check = Health check all repositories 2665 2666 dashboard.check_repo_stats = Check all repository statistics ··· 2713 2714 dashboard.stop_zombie_tasks = Stop zombie tasks 2714 2715 dashboard.stop_endless_tasks = Stop endless tasks 2715 2716 dashboard.cancel_abandoned_jobs = Cancel abandoned jobs 2717 + dashboard.sync_branch.started = Branches Sync started 2716 2718 2717 2719 users.user_manage_panel = User Account Management 2718 2720 users.new_account = Create User Account
+66 -17
routers/api/v1/repo/branch.go
··· 15 15 user_model "code.gitea.io/gitea/models/user" 16 16 "code.gitea.io/gitea/modules/context" 17 17 "code.gitea.io/gitea/modules/git" 18 + repo_module "code.gitea.io/gitea/modules/repository" 18 19 api "code.gitea.io/gitea/modules/structs" 20 + "code.gitea.io/gitea/modules/util" 19 21 "code.gitea.io/gitea/modules/web" 20 22 "code.gitea.io/gitea/routers/api/v1/utils" 21 23 "code.gitea.io/gitea/services/convert" ··· 76 78 return 77 79 } 78 80 79 - br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 81 + br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 80 82 if err != nil { 81 83 ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) 82 84 return ··· 118 120 119 121 branchName := ctx.Params("*") 120 122 123 + if ctx.Repo.Repository.IsEmpty { 124 + ctx.Error(http.StatusForbidden, "", "Git Repository is empty.") 125 + return 126 + } 127 + 128 + // check whether branches of this repository has been synced 129 + totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{ 130 + RepoID: ctx.Repo.Repository.ID, 131 + IsDeletedBranch: util.OptionalBoolFalse, 132 + }) 133 + if err != nil { 134 + ctx.Error(http.StatusInternalServerError, "CountBranches", err) 135 + return 136 + } 137 + if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch 138 + _, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) 139 + if err != nil { 140 + ctx.ServerError("SyncRepoBranches", err) 141 + return 142 + } 143 + } 144 + 145 + if ctx.Repo.Repository.IsArchived { 146 + ctx.Error(http.StatusForbidden, "IsArchived", fmt.Errorf("can not delete branch of an archived repository")) 147 + return 148 + } 149 + if ctx.Repo.Repository.IsMirror { 150 + ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository")) 151 + return 152 + } 153 + 121 154 if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { 122 155 switch { 123 156 case git.IsErrBranchNotExist(err): ··· 203 236 204 237 err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName) 205 238 if err != nil { 206 - if models.IsErrBranchDoesNotExist(err) { 239 + if git_model.IsErrBranchNotExist(err) { 207 240 ctx.Error(http.StatusNotFound, "", "The old branch does not exist") 208 241 } 209 242 if models.IsErrTagAlreadyExists(err) { 210 243 ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.") 211 - } else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { 244 + } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { 212 245 ctx.Error(http.StatusConflict, "", "The branch already exists.") 213 - } else if models.IsErrBranchNameConflict(err) { 246 + } else if git_model.IsErrBranchNameConflict(err) { 214 247 ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.") 215 248 } else { 216 249 ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err) ··· 236 269 return 237 270 } 238 271 239 - br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 272 + br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 240 273 if err != nil { 241 274 ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) 242 275 return ··· 275 308 // "200": 276 309 // "$ref": "#/responses/BranchList" 277 310 278 - var totalNumOfBranches int 311 + var totalNumOfBranches int64 279 312 var apiBranches []*api.Branch 280 313 281 314 listOptions := utils.GetListOptions(ctx) 282 315 283 316 if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil { 317 + branchOpts := git_model.FindBranchOptions{ 318 + ListOptions: listOptions, 319 + RepoID: ctx.Repo.Repository.ID, 320 + IsDeletedBranch: util.OptionalBoolFalse, 321 + } 322 + var err error 323 + totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts) 324 + if err != nil { 325 + ctx.Error(http.StatusInternalServerError, "CountBranches", err) 326 + return 327 + } 328 + if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch 329 + totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) 330 + if err != nil { 331 + ctx.ServerError("SyncRepoBranches", err) 332 + return 333 + } 334 + } 335 + 284 336 rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) 285 337 if err != nil { 286 338 ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err) 287 339 return 288 340 } 289 341 290 - skip, _ := listOptions.GetStartEnd() 291 - branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize) 342 + branches, err := git_model.FindBranches(ctx, branchOpts) 292 343 if err != nil { 293 344 ctx.Error(http.StatusInternalServerError, "GetBranches", err) 294 345 return ··· 296 347 297 348 apiBranches = make([]*api.Branch, 0, len(branches)) 298 349 for i := range branches { 299 - c, err := branches[i].GetCommit() 350 + c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name) 300 351 if err != nil { 301 352 // Skip if this branch doesn't exist anymore. 302 353 if git.IsErrNotExist(err) { 303 - total-- 354 + totalNumOfBranches-- 304 355 continue 305 356 } 306 357 ctx.Error(http.StatusInternalServerError, "GetCommit", err) ··· 308 359 } 309 360 310 361 branchProtection := rules.GetFirstMatched(branches[i].Name) 311 - apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 362 + apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) 312 363 if err != nil { 313 364 ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) 314 365 return 315 366 } 316 367 apiBranches = append(apiBranches, apiBranch) 317 368 } 318 - 319 - totalNumOfBranches = total 320 369 } 321 370 322 - ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize) 323 - ctx.SetTotalCountHeader(int64(totalNumOfBranches)) 371 + ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize) 372 + ctx.SetTotalCountHeader(totalNumOfBranches) 324 373 ctx.JSON(http.StatusOK, apiBranches) 325 374 } 326 375 ··· 580 629 }() 581 630 } 582 631 // FIXME: since we only need to recheck files protected rules, we could improve this 583 - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, ruleName) 632 + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName) 584 633 if err != nil { 585 634 ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) 586 635 return ··· 851 900 } 852 901 853 902 // FIXME: since we only need to recheck files protected rules, we could improve this 854 - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) 903 + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName) 855 904 if err != nil { 856 905 ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) 857 906 return
+3 -3
routers/api/v1/repo/file.go
··· 687 687 ctx.Error(http.StatusForbidden, "Access", err) 688 688 return 689 689 } 690 - if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || 690 + if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || 691 691 models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { 692 692 ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) 693 693 return 694 694 } 695 - if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) { 695 + if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { 696 696 ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) 697 697 return 698 698 } ··· 843 843 if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) { 844 844 ctx.Error(http.StatusNotFound, "DeleteFile", err) 845 845 return 846 - } else if models.IsErrBranchAlreadyExists(err) || 846 + } else if git_model.IsErrBranchAlreadyExists(err) || 847 847 models.IsErrFilenameInvalid(err) || 848 848 models.IsErrSHADoesNotMatch(err) || 849 849 models.IsErrCommitIDDoesNotMatch(err) ||
+3 -2
routers/api/v1/repo/patch.go
··· 8 8 "time" 9 9 10 10 "code.gitea.io/gitea/models" 11 + git_model "code.gitea.io/gitea/models/git" 11 12 repo_model "code.gitea.io/gitea/models/repo" 12 13 "code.gitea.io/gitea/modules/context" 13 14 "code.gitea.io/gitea/modules/git" ··· 91 92 ctx.Error(http.StatusForbidden, "Access", err) 92 93 return 93 94 } 94 - if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || 95 + if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || 95 96 models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { 96 97 ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) 97 98 return 98 99 } 99 - if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) { 100 + if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { 100 101 ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) 101 102 return 102 103 }
+19 -6
routers/web/admin/admin.go
··· 14 14 activities_model "code.gitea.io/gitea/models/activities" 15 15 "code.gitea.io/gitea/modules/base" 16 16 "code.gitea.io/gitea/modules/context" 17 + "code.gitea.io/gitea/modules/graceful" 17 18 "code.gitea.io/gitea/modules/json" 19 + "code.gitea.io/gitea/modules/log" 18 20 "code.gitea.io/gitea/modules/setting" 19 21 "code.gitea.io/gitea/modules/updatechecker" 20 22 "code.gitea.io/gitea/modules/web" 21 23 "code.gitea.io/gitea/services/cron" 22 24 "code.gitea.io/gitea/services/forms" 25 + repo_service "code.gitea.io/gitea/services/repository" 23 26 ) 24 27 25 28 const ( ··· 133 136 134 137 // Run operation. 135 138 if form.Op != "" { 136 - task := cron.GetTask(form.Op) 137 - if task != nil { 138 - go task.RunWithUser(ctx.Doer, nil) 139 - ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) 140 - } else { 141 - ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) 139 + switch form.Op { 140 + case "sync_repo_branches": 141 + go func() { 142 + if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil { 143 + log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err) 144 + } 145 + }() 146 + ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started")) 147 + default: 148 + task := cron.GetTask(form.Op) 149 + if task != nil { 150 + go task.RunWithUser(ctx.Doer, nil) 151 + ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) 152 + } else { 153 + ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) 154 + } 142 155 } 143 156 } 144 157 if form.From == "monitor" {
+11 -202
routers/web/repo/branch.go
··· 13 13 14 14 "code.gitea.io/gitea/models" 15 15 git_model "code.gitea.io/gitea/models/git" 16 - issues_model "code.gitea.io/gitea/models/issues" 17 16 repo_model "code.gitea.io/gitea/models/repo" 18 17 "code.gitea.io/gitea/models/unit" 19 18 "code.gitea.io/gitea/modules/base" ··· 28 27 "code.gitea.io/gitea/services/forms" 29 28 release_service "code.gitea.io/gitea/services/release" 30 29 repo_service "code.gitea.io/gitea/services/repository" 31 - files_service "code.gitea.io/gitea/services/repository/files" 32 30 ) 33 31 34 32 const ( 35 33 tplBranch base.TplName = "repo/branch/list" 36 34 ) 37 35 38 - // Branch contains the branch information 39 - type Branch struct { 40 - Name string 41 - Commit *git.Commit 42 - IsProtected bool 43 - IsDeleted bool 44 - IsIncluded bool 45 - DeletedBranch *git_model.DeletedBranch 46 - CommitsAhead int 47 - CommitsBehind int 48 - LatestPullRequest *issues_model.PullRequest 49 - MergeMovedOn bool 50 - } 51 - 52 36 // Branches render repository branch page 53 37 func Branches(ctx *context.Context) { 54 38 ctx.Data["Title"] = "Branches" 55 39 ctx.Data["IsRepoToolbarBranches"] = true 56 - ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch 57 40 ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls() 58 41 ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode) 59 42 ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror ··· 68 51 } 69 52 pageSize := setting.Git.BranchesRangeSize 70 53 71 - skip := (page - 1) * pageSize 72 - log.Debug("Branches: skip: %d limit: %d", skip, pageSize) 73 - defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize) 74 - if ctx.Written() { 54 + defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, page, pageSize) 55 + if err != nil { 56 + ctx.ServerError("LoadBranches", err) 75 57 return 76 58 } 59 + 77 60 ctx.Data["Branches"] = branches 78 - ctx.Data["DefaultBranchBranch"] = defaultBranchBranch 79 - pager := context.NewPagination(branchesCount, pageSize, page, 5) 61 + ctx.Data["DefaultBranchBranch"] = defaultBranch 62 + pager := context.NewPagination(int(branchesCount), pageSize, page, 5) 80 63 pager.SetDefaultParams(ctx) 81 64 ctx.Data["Page"] = pager 82 65 ··· 130 113 131 114 if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{ 132 115 Remote: ctx.Repo.Repository.RepoPath(), 133 - Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name), 116 + Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name), 134 117 Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository), 135 118 }); err != nil { 136 119 if strings.Contains(err.Error(), "already exists") { ··· 148 131 &repo_module.PushUpdateOptions{ 149 132 RefFullName: git.RefNameFromBranch(deletedBranch.Name), 150 133 OldCommitID: git.EmptySHA, 151 - NewCommitID: deletedBranch.Commit, 134 + NewCommitID: deletedBranch.CommitID, 152 135 PusherID: ctx.Doer.ID, 153 136 PusherName: ctx.Doer.Name, 154 137 RepoUserName: ctx.Repo.Owner.Name, ··· 166 149 }) 167 150 } 168 151 169 - // loadBranches loads branches from the repository limited by page & pageSize. 170 - // NOTE: May write to context on error. 171 - func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, int) { 172 - defaultBranch, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.Repository.DefaultBranch) 173 - if err != nil { 174 - if !git.IsErrBranchNotExist(err) { 175 - log.Error("loadBranches: get default branch: %v", err) 176 - ctx.ServerError("GetDefaultBranch", err) 177 - return nil, nil, 0 178 - } 179 - log.Warn("loadBranches: missing default branch %s for %-v", ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository) 180 - } 181 - 182 - rawBranches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, limit) 183 - if err != nil { 184 - log.Error("GetBranches: %v", err) 185 - ctx.ServerError("GetBranches", err) 186 - return nil, nil, 0 187 - } 188 - 189 - rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) 190 - if err != nil { 191 - ctx.ServerError("FindRepoProtectedBranchRules", err) 192 - return nil, nil, 0 193 - } 194 - 195 - repoIDToRepo := map[int64]*repo_model.Repository{} 196 - repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository 197 - 198 - repoIDToGitRepo := map[int64]*git.Repository{} 199 - repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo 200 - 201 - var branches []*Branch 202 - for i := range rawBranches { 203 - if defaultBranch != nil && rawBranches[i].Name == defaultBranch.Name { 204 - // Skip default branch 205 - continue 206 - } 207 - 208 - branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo) 209 - if branch == nil { 210 - return nil, nil, 0 211 - } 212 - 213 - branches = append(branches, branch) 214 - } 215 - 216 - var defaultBranchBranch *Branch 217 - if defaultBranch != nil { 218 - // Always add the default branch 219 - log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name) 220 - defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo) 221 - branches = append(branches, defaultBranchBranch) 222 - } 223 - 224 - if ctx.Repo.CanWrite(unit.TypeCode) { 225 - deletedBranches, err := getDeletedBranches(ctx) 226 - if err != nil { 227 - ctx.ServerError("getDeletedBranches", err) 228 - return nil, nil, 0 229 - } 230 - branches = append(branches, deletedBranches...) 231 - } 232 - 233 - return defaultBranchBranch, branches, totalNumOfBranches 234 - } 235 - 236 - func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules, 237 - repoIDToRepo map[int64]*repo_model.Repository, 238 - repoIDToGitRepo map[int64]*git.Repository, 239 - ) *Branch { 240 - log.Trace("loadOneBranch: '%s'", rawBranch.Name) 241 - 242 - commit, err := rawBranch.GetCommit() 243 - if err != nil { 244 - ctx.ServerError("GetCommit", err) 245 - return nil 246 - } 247 - 248 - branchName := rawBranch.Name 249 - p := protectedBranches.GetFirstMatched(branchName) 250 - isProtected := p != nil 251 - 252 - divergence := &git.DivergeObject{ 253 - Ahead: -1, 254 - Behind: -1, 255 - } 256 - if defaultBranch != nil { 257 - divergence, err = files_service.CountDivergingCommits(ctx, ctx.Repo.Repository, git.BranchPrefix+branchName) 258 - if err != nil { 259 - log.Error("CountDivergingCommits", err) 260 - } 261 - } 262 - 263 - pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName) 264 - if err != nil { 265 - ctx.ServerError("GetLatestPullRequestByHeadInfo", err) 266 - return nil 267 - } 268 - headCommit := commit.ID.String() 269 - 270 - mergeMovedOn := false 271 - if pr != nil { 272 - pr.HeadRepo = ctx.Repo.Repository 273 - if err := pr.LoadIssue(ctx); err != nil { 274 - ctx.ServerError("LoadIssue", err) 275 - return nil 276 - } 277 - if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { 278 - pr.BaseRepo = repo 279 - } else if err := pr.LoadBaseRepo(ctx); err != nil { 280 - ctx.ServerError("LoadBaseRepo", err) 281 - return nil 282 - } else { 283 - repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo 284 - } 285 - pr.Issue.Repo = pr.BaseRepo 286 - 287 - if pr.HasMerged { 288 - baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] 289 - if !ok { 290 - baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) 291 - if err != nil { 292 - ctx.ServerError("OpenRepository", err) 293 - return nil 294 - } 295 - defer baseGitRepo.Close() 296 - repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo 297 - } 298 - pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) 299 - if err != nil && !git.IsErrNotExist(err) { 300 - ctx.ServerError("GetBranchCommitID", err) 301 - return nil 302 - } 303 - if err == nil && headCommit != pullCommit { 304 - // the head has moved on from the merge - we shouldn't delete 305 - mergeMovedOn = true 306 - } 307 - } 308 - } 309 - 310 - isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName 311 - return &Branch{ 312 - Name: branchName, 313 - Commit: commit, 314 - IsProtected: isProtected, 315 - IsIncluded: isIncluded, 316 - CommitsAhead: divergence.Ahead, 317 - CommitsBehind: divergence.Behind, 318 - LatestPullRequest: pr, 319 - MergeMovedOn: mergeMovedOn, 320 - } 321 - } 322 - 323 - func getDeletedBranches(ctx *context.Context) ([]*Branch, error) { 324 - branches := []*Branch{} 325 - 326 - deletedBranches, err := git_model.GetDeletedBranches(ctx, ctx.Repo.Repository.ID) 327 - if err != nil { 328 - return branches, err 329 - } 330 - 331 - for i := range deletedBranches { 332 - deletedBranches[i].LoadUser(ctx) 333 - branches = append(branches, &Branch{ 334 - Name: deletedBranches[i].Name, 335 - IsDeleted: true, 336 - DeletedBranch: deletedBranches[i], 337 - }) 338 - } 339 - 340 - return branches, nil 341 - } 342 - 343 152 // CreateBranch creates new branch in repository 344 153 func CreateBranch(ctx *context.Context) { 345 154 form := web.GetForm(ctx).(*forms.NewBranchForm) ··· 380 189 ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) 381 190 return 382 191 } 383 - if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { 192 + if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { 384 193 ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName)) 385 194 ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) 386 195 return 387 196 } 388 - if models.IsErrBranchNameConflict(err) { 389 - e := err.(models.ErrBranchNameConflict) 197 + if git_model.IsErrBranchNameConflict(err) { 198 + e := err.(git_model.ErrBranchNameConflict) 390 199 ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) 391 200 ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) 392 201 return
+5 -4
routers/web/repo/cherry_pick.go
··· 9 9 "strings" 10 10 11 11 "code.gitea.io/gitea/models" 12 + git_model "code.gitea.io/gitea/models/git" 12 13 "code.gitea.io/gitea/models/unit" 13 14 "code.gitea.io/gitea/modules/base" 14 15 "code.gitea.io/gitea/modules/context" ··· 124 125 // First lets try the simple plain read-tree -m approach 125 126 opts.Content = sha 126 127 if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.Doer, form.Revert, opts); err != nil { 127 - if models.IsErrBranchAlreadyExists(err) { 128 + if git_model.IsErrBranchAlreadyExists(err) { 128 129 // User has specified a branch that already exists 129 - branchErr := err.(models.ErrBranchAlreadyExists) 130 + branchErr := err.(git_model.ErrBranchAlreadyExists) 130 131 ctx.Data["Err_NewBranchName"] = true 131 132 ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form) 132 133 return ··· 161 162 ctx.Data["FileContent"] = opts.Content 162 163 163 164 if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil { 164 - if models.IsErrBranchAlreadyExists(err) { 165 + if git_model.IsErrBranchAlreadyExists(err) { 165 166 // User has specified a branch that already exists 166 - branchErr := err.(models.ErrBranchAlreadyExists) 167 + branchErr := err.(git_model.ErrBranchAlreadyExists) 167 168 ctx.Data["Err_NewBranchName"] = true 168 169 ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form) 169 170 return
+15 -2
routers/web/repo/compare.go
··· 16 16 "path/filepath" 17 17 "strings" 18 18 19 + "code.gitea.io/gitea/models/db" 19 20 git_model "code.gitea.io/gitea/models/git" 20 21 issues_model "code.gitea.io/gitea/models/issues" 21 22 access_model "code.gitea.io/gitea/models/perm/access" ··· 683 684 } 684 685 defer gitRepo.Close() 685 686 686 - branches, _, err = gitRepo.GetBranchNames(0, 0) 687 + branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ 688 + RepoID: repo.ID, 689 + ListOptions: db.ListOptions{ 690 + ListAll: true, 691 + }, 692 + IsDeletedBranch: util.OptionalBoolFalse, 693 + }) 687 694 if err != nil { 688 695 return nil, nil, err 689 696 } ··· 734 741 return 735 742 } 736 743 737 - headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0) 744 + headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ 745 + RepoID: ci.HeadRepo.ID, 746 + ListOptions: db.ListOptions{ 747 + ListAll: true, 748 + }, 749 + IsDeletedBranch: util.OptionalBoolFalse, 750 + }) 738 751 if err != nil { 739 752 ctx.ServerError("GetBranches", err) 740 753 return
+6 -6
routers/web/repo/editor.go
··· 327 327 } else { 328 328 ctx.Error(http.StatusInternalServerError, err.Error()) 329 329 } 330 - } else if models.IsErrBranchAlreadyExists(err) { 330 + } else if git_model.IsErrBranchAlreadyExists(err) { 331 331 // For when a user specifies a new branch that already exists 332 332 ctx.Data["Err_NewBranchName"] = true 333 - if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok { 333 + if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok { 334 334 ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form) 335 335 } else { 336 336 ctx.Error(http.StatusInternalServerError, err.Error()) ··· 529 529 } else { 530 530 ctx.Error(http.StatusInternalServerError, err.Error()) 531 531 } 532 - } else if models.IsErrBranchAlreadyExists(err) { 532 + } else if git_model.IsErrBranchAlreadyExists(err) { 533 533 // For when a user specifies a new branch that already exists 534 - if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok { 534 + if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok { 535 535 ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form) 536 536 } else { 537 537 ctx.Error(http.StatusInternalServerError, err.Error()) ··· 731 731 } else if git.IsErrBranchNotExist(err) { 732 732 branchErr := err.(git.ErrBranchNotExist) 733 733 ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form) 734 - } else if models.IsErrBranchAlreadyExists(err) { 734 + } else if git_model.IsErrBranchAlreadyExists(err) { 735 735 // For when a user specifies a new branch that already exists 736 736 ctx.Data["Err_NewBranchName"] = true 737 - branchErr := err.(models.ErrBranchAlreadyExists) 737 + branchErr := err.(git_model.ErrBranchAlreadyExists) 738 738 ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form) 739 739 } else if git.IsErrPushOutOfDate(err) { 740 740 ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(ctx.Repo.CommitID)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplUploadFile, &form)
+7 -1
routers/web/repo/issue.go
··· 785 785 return nil 786 786 } 787 787 788 - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) 788 + brs, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ 789 + RepoID: ctx.Repo.Repository.ID, 790 + ListOptions: db.ListOptions{ 791 + ListAll: true, 792 + }, 793 + IsDeletedBranch: util.OptionalBoolFalse, 794 + }) 789 795 if err != nil { 790 796 ctx.ServerError("GetBranches", err) 791 797 return nil
+3 -2
routers/web/repo/patch.go
··· 7 7 "strings" 8 8 9 9 "code.gitea.io/gitea/models" 10 + git_model "code.gitea.io/gitea/models/git" 10 11 "code.gitea.io/gitea/models/unit" 11 12 "code.gitea.io/gitea/modules/base" 12 13 "code.gitea.io/gitea/modules/context" ··· 94 95 Content: strings.ReplaceAll(form.Content, "\r", ""), 95 96 }) 96 97 if err != nil { 97 - if models.IsErrBranchAlreadyExists(err) { 98 + if git_model.IsErrBranchAlreadyExists(err) { 98 99 // User has specified a branch that already exists 99 - branchErr := err.(models.ErrBranchAlreadyExists) 100 + branchErr := err.(git_model.ErrBranchAlreadyExists) 100 101 ctx.Data["Err_NewBranchName"] = true 101 102 ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form) 102 103 return
+1 -1
routers/web/repo/pull.go
··· 1493 1493 "error": err.Error(), 1494 1494 "user_error": errorMessage, 1495 1495 }) 1496 - } else if models.IsErrBranchesEqual(err) { 1496 + } else if git_model.IsErrBranchesEqual(err) { 1497 1497 errorMessage := ctx.Tr("repo.pulls.nothing_to_compare") 1498 1498 1499 1499 ctx.Flash.Error(errorMessage)
+1 -1
routers/web/repo/setting_protected_branch.go
··· 286 286 } 287 287 288 288 // FIXME: since we only need to recheck files protected rules, we could improve this 289 - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) 289 + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName) 290 290 if err != nil { 291 291 ctx.ServerError("FindAllMatchedBranches", err) 292 292 return
+4 -4
services/convert/convert.go
··· 50 50 } 51 51 52 52 // ToBranch convert a git.Commit and git.Branch to an api.Branch 53 - func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { 53 + func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName string, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { 54 54 if bp == nil { 55 55 var hasPerm bool 56 56 var canPush bool ··· 65 65 if err != nil { 66 66 return nil, err 67 67 } 68 - canPush = issues_model.CanMaintainerWriteToBranch(perms, b.Name, user) 68 + canPush = issues_model.CanMaintainerWriteToBranch(perms, branchName, user) 69 69 } 70 70 71 71 return &api.Branch{ 72 - Name: b.Name, 72 + Name: branchName, 73 73 Commit: ToPayloadCommit(ctx, repo, c), 74 74 Protected: false, 75 75 RequiredApprovals: 0, ··· 81 81 } 82 82 83 83 branch := &api.Branch{ 84 - Name: b.Name, 84 + Name: branchName, 85 85 Commit: ToPayloadCommit(ctx, repo, c), 86 86 Protected: true, 87 87 RequiredApprovals: bp.RequiredApprovals,
+2 -2
services/migrations/dump.go
··· 642 642 643 643 // DumpRepository dump repository according MigrateOptions to a local directory 644 644 func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error { 645 - doer, err := user_model.GetAdminUser() 645 + doer, err := user_model.GetAdminUser(ctx) 646 646 if err != nil { 647 647 return err 648 648 } ··· 705 705 706 706 // RestoreRepository restore a repository from the disk directory 707 707 func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string, validation bool) error { 708 - doer, err := user_model.GetAdminUser() 708 + doer, err := user_model.GetAdminUser(ctx) 709 709 if err != nil { 710 710 return err 711 711 }
+2 -2
services/pull/pull.go
··· 170 170 return err 171 171 } 172 172 if branchesEqual { 173 - return models.ErrBranchesEqual{ 173 + return git_model.ErrBranchesEqual{ 174 174 HeadBranchName: pr.HeadBranch, 175 175 BaseBranchName: targetBranch, 176 176 } ··· 338 338 for _, pr := range prs { 339 339 divergence, err := GetDiverging(ctx, pr) 340 340 if err != nil { 341 - if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { 341 + if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { 342 342 log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch) 343 343 } else { 344 344 log.Error("GetDiverging: %v", err)
+2 -2
services/pull/temp_repo.go
··· 11 11 "path/filepath" 12 12 "strings" 13 13 14 - "code.gitea.io/gitea/models" 14 + git_model "code.gitea.io/gitea/models/git" 15 15 issues_model "code.gitea.io/gitea/models/issues" 16 16 repo_model "code.gitea.io/gitea/models/repo" 17 17 "code.gitea.io/gitea/modules/git" ··· 181 181 Run(prCtx.RunOpts()); err != nil { 182 182 cancel() 183 183 if !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { 184 - return nil, nil, models.ErrBranchDoesNotExist{ 184 + return nil, nil, git_model.ErrBranchNotExist{ 185 185 BranchName: pr.HeadBranch, 186 186 } 187 187 }
+1 -2
services/pull/update.go
··· 7 7 "context" 8 8 "fmt" 9 9 10 - "code.gitea.io/gitea/models" 11 10 git_model "code.gitea.io/gitea/models/git" 12 11 issues_model "code.gitea.io/gitea/models/issues" 13 12 access_model "code.gitea.io/gitea/models/perm/access" ··· 168 167 log.Trace("GetDiverging[%-v]: compare commits", pr) 169 168 prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) 170 169 if err != nil { 171 - if !models.IsErrBranchDoesNotExist(err) { 170 + if !git_model.IsErrBranchNotExist(err) { 172 171 log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err) 173 172 } 174 173 return nil, err
+10 -1
services/repository/adopt.go
··· 12 12 "strings" 13 13 14 14 "code.gitea.io/gitea/models/db" 15 + git_model "code.gitea.io/gitea/models/git" 15 16 repo_model "code.gitea.io/gitea/models/repo" 16 17 user_model "code.gitea.io/gitea/models/user" 17 18 "code.gitea.io/gitea/modules/container" ··· 146 147 } 147 148 } 148 149 } 149 - branches, _, _ := gitRepo.GetBranchNames(0, 0) 150 + 151 + branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ 152 + RepoID: repo.ID, 153 + ListOptions: db.ListOptions{ 154 + ListAll: true, 155 + }, 156 + IsDeletedBranch: util.OptionalBoolFalse, 157 + }) 158 + 150 159 found := false 151 160 hasDefault := false 152 161 hasMaster := false
+226 -12
services/repository/branch.go
··· 10 10 "strings" 11 11 12 12 "code.gitea.io/gitea/models" 13 + "code.gitea.io/gitea/models/db" 13 14 git_model "code.gitea.io/gitea/models/git" 15 + issues_model "code.gitea.io/gitea/models/issues" 14 16 repo_model "code.gitea.io/gitea/models/repo" 15 17 user_model "code.gitea.io/gitea/models/user" 16 18 "code.gitea.io/gitea/modules/git" 19 + "code.gitea.io/gitea/modules/graceful" 17 20 "code.gitea.io/gitea/modules/log" 18 21 "code.gitea.io/gitea/modules/notification" 22 + "code.gitea.io/gitea/modules/queue" 19 23 repo_module "code.gitea.io/gitea/modules/repository" 24 + "code.gitea.io/gitea/modules/util" 25 + files_service "code.gitea.io/gitea/services/repository/files" 26 + 27 + "xorm.io/builder" 20 28 ) 21 29 22 30 // CreateNewBranch creates a new repository branch ··· 27 35 } 28 36 29 37 if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) { 30 - return models.ErrBranchDoesNotExist{ 38 + return git_model.ErrBranchNotExist{ 31 39 BranchName: oldBranchName, 32 40 } 33 41 } ··· 40 48 if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { 41 49 return err 42 50 } 43 - return fmt.Errorf("Push: %w", err) 51 + return fmt.Errorf("push: %w", err) 44 52 } 45 53 46 54 return nil 47 55 } 48 56 49 - // GetBranches returns branches from the repository, skipping skip initial branches and 50 - // returning at most limit branches, or all branches if limit is 0. 51 - func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit int) ([]*git.Branch, int, error) { 52 - return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit) 57 + // Branch contains the branch information 58 + type Branch struct { 59 + DBBranch *git_model.Branch 60 + IsProtected bool 61 + IsIncluded bool 62 + CommitsAhead int 63 + CommitsBehind int 64 + LatestPullRequest *issues_model.PullRequest 65 + MergeMovedOn bool 66 + } 67 + 68 + // LoadBranches loads branches from the repository limited by page & pageSize. 69 + func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, page, pageSize int) (*Branch, []*Branch, int64, error) { 70 + defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch) 71 + if err != nil { 72 + return nil, nil, 0, err 73 + } 74 + 75 + branchOpts := git_model.FindBranchOptions{ 76 + RepoID: repo.ID, 77 + IsDeletedBranch: isDeletedBranch, 78 + ListOptions: db.ListOptions{ 79 + Page: page, 80 + PageSize: pageSize, 81 + }, 82 + } 83 + 84 + totalNumOfBranches, err := git_model.CountBranches(ctx, branchOpts) 85 + if err != nil { 86 + return nil, nil, 0, err 87 + } 88 + 89 + branchOpts.ExcludeBranchNames = []string{repo.DefaultBranch} 90 + 91 + dbBranches, err := git_model.FindBranches(ctx, branchOpts) 92 + if err != nil { 93 + return nil, nil, 0, err 94 + } 95 + 96 + if err := dbBranches.LoadDeletedBy(ctx); err != nil { 97 + return nil, nil, 0, err 98 + } 99 + if err := dbBranches.LoadPusher(ctx); err != nil { 100 + return nil, nil, 0, err 101 + } 102 + 103 + rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID) 104 + if err != nil { 105 + return nil, nil, 0, err 106 + } 107 + 108 + repoIDToRepo := map[int64]*repo_model.Repository{} 109 + repoIDToRepo[repo.ID] = repo 110 + 111 + repoIDToGitRepo := map[int64]*git.Repository{} 112 + repoIDToGitRepo[repo.ID] = gitRepo 113 + 114 + branches := make([]*Branch, 0, len(dbBranches)) 115 + for i := range dbBranches { 116 + branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo) 117 + if err != nil { 118 + return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err) 119 + } 120 + 121 + branches = append(branches, branch) 122 + } 123 + 124 + // Always add the default branch 125 + log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name) 126 + defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo) 127 + if err != nil { 128 + return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err) 129 + } 130 + 131 + return defaultBranch, branches, totalNumOfBranches, nil 132 + } 133 + 134 + func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules, 135 + repoIDToRepo map[int64]*repo_model.Repository, 136 + repoIDToGitRepo map[int64]*git.Repository, 137 + ) (*Branch, error) { 138 + log.Trace("loadOneBranch: '%s'", dbBranch.Name) 139 + 140 + branchName := dbBranch.Name 141 + p := protectedBranches.GetFirstMatched(branchName) 142 + isProtected := p != nil 143 + 144 + divergence := &git.DivergeObject{ 145 + Ahead: -1, 146 + Behind: -1, 147 + } 148 + 149 + // it's not default branch 150 + if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted { 151 + var err error 152 + divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName) 153 + if err != nil { 154 + log.Error("CountDivergingCommits: %v", err) 155 + } 156 + } 157 + 158 + pr, err := issues_model.GetLatestPullRequestByHeadInfo(repo.ID, branchName) 159 + if err != nil { 160 + return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err) 161 + } 162 + headCommit := dbBranch.CommitID 163 + 164 + mergeMovedOn := false 165 + if pr != nil { 166 + pr.HeadRepo = repo 167 + if err := pr.LoadIssue(ctx); err != nil { 168 + return nil, fmt.Errorf("LoadIssue: %v", err) 169 + } 170 + if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { 171 + pr.BaseRepo = repo 172 + } else if err := pr.LoadBaseRepo(ctx); err != nil { 173 + return nil, fmt.Errorf("LoadBaseRepo: %v", err) 174 + } else { 175 + repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo 176 + } 177 + pr.Issue.Repo = pr.BaseRepo 178 + 179 + if pr.HasMerged { 180 + baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] 181 + if !ok { 182 + baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) 183 + if err != nil { 184 + return nil, fmt.Errorf("OpenRepository: %v", err) 185 + } 186 + defer baseGitRepo.Close() 187 + repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo 188 + } 189 + pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) 190 + if err != nil && !git.IsErrNotExist(err) { 191 + return nil, fmt.Errorf("GetBranchCommitID: %v", err) 192 + } 193 + if err == nil && headCommit != pullCommit { 194 + // the head has moved on from the merge - we shouldn't delete 195 + mergeMovedOn = true 196 + } 197 + } 198 + } 199 + 200 + isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName 201 + return &Branch{ 202 + DBBranch: dbBranch, 203 + IsProtected: isProtected, 204 + IsIncluded: isIncluded, 205 + CommitsAhead: divergence.Ahead, 206 + CommitsBehind: divergence.Behind, 207 + LatestPullRequest: pr, 208 + MergeMovedOn: mergeMovedOn, 209 + }, nil 53 210 } 54 211 55 212 func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) { ··· 62 219 branchRefName := strings.TrimPrefix(refName, git.BranchPrefix) 63 220 switch { 64 221 case branchRefName == name: 65 - return models.ErrBranchAlreadyExists{ 222 + return git_model.ErrBranchAlreadyExists{ 66 223 BranchName: name, 67 224 } 68 225 // If branchRefName like a/b but we want to create a branch named a then we have a conflict 69 226 case strings.HasPrefix(branchRefName, name+"/"): 70 - return models.ErrBranchNameConflict{ 227 + return git_model.ErrBranchNameConflict{ 71 228 BranchName: branchRefName, 72 229 } 73 230 // Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict 74 231 case strings.HasPrefix(name, branchRefName+"/"): 75 - return models.ErrBranchNameConflict{ 232 + return git_model.ErrBranchNameConflict{ 76 233 BranchName: branchRefName, 77 234 } 78 235 case refName == git.TagPrefix+name: ··· 101 258 if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { 102 259 return err 103 260 } 104 - return fmt.Errorf("Push: %w", err) 261 + return fmt.Errorf("push: %w", err) 105 262 } 106 263 107 264 return nil ··· 169 326 return git_model.ErrBranchIsProtected 170 327 } 171 328 329 + rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName) 330 + if err != nil { 331 + return fmt.Errorf("GetBranch: %vc", err) 332 + } 333 + 334 + if rawBranch.IsDeleted { 335 + return nil 336 + } 337 + 172 338 commit, err := gitRepo.GetBranchCommit(branchName) 173 339 if err != nil { 174 340 return err 175 341 } 176 342 177 - if err := gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ 178 - Force: true, 343 + if err := db.WithTx(ctx, func(ctx context.Context) error { 344 + if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { 345 + return err 346 + } 347 + 348 + return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ 349 + Force: true, 350 + }) 179 351 }); err != nil { 180 352 return err 181 353 } ··· 196 368 197 369 return nil 198 370 } 371 + 372 + type BranchSyncOptions struct { 373 + RepoID int64 374 + } 375 + 376 + // branchSyncQueue represents a queue to handle branch sync jobs. 377 + var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions] 378 + 379 + func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions { 380 + for _, opts := range items { 381 + _, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0) 382 + if err != nil { 383 + log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err) 384 + } 385 + } 386 + return nil 387 + } 388 + 389 + func addRepoToBranchSyncQueue(repoID, doerID int64) error { 390 + return branchSyncQueue.Push(&BranchSyncOptions{ 391 + RepoID: repoID, 392 + }) 393 + } 394 + 395 + func initBranchSyncQueue(ctx context.Context) error { 396 + branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync) 397 + if branchSyncQueue == nil { 398 + return errors.New("unable to create branch_sync queue") 399 + } 400 + go graceful.GetManager().RunWithCancel(branchSyncQueue) 401 + 402 + return nil 403 + } 404 + 405 + func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error { 406 + if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error { 407 + return addRepoToBranchSyncQueue(repo.ID, doerID) 408 + }); err != nil { 409 + return fmt.Errorf("run sync all branches failed: %v", err) 410 + } 411 + return nil 412 + }
+1 -1
services/repository/files/patch.go
··· 58 58 if opts.NewBranch != opts.OldBranch { 59 59 existingBranch, err := gitRepo.GetBranch(opts.NewBranch) 60 60 if existingBranch != nil { 61 - return models.ErrBranchAlreadyExists{ 61 + return git_model.ErrBranchAlreadyExists{ 62 62 BranchName: opts.NewBranch, 63 63 } 64 64 }
+1 -1
services/repository/files/update.go
··· 197 197 if opts.NewBranch != opts.OldBranch { 198 198 existingBranch, err := gitRepo.GetBranch(opts.NewBranch) 199 199 if existingBranch != nil { 200 - return nil, models.ErrBranchAlreadyExists{ 200 + return nil, git_model.ErrBranchAlreadyExists{ 201 201 BranchName: opts.NewBranch, 202 202 } 203 203 }
+9 -1
services/repository/fork.go
··· 157 157 if err = repo_module.CreateDelegateHooks(repoPath); err != nil { 158 158 return fmt.Errorf("createDelegateHooks: %w", err) 159 159 } 160 - return nil 160 + 161 + gitRepo, err := git.OpenRepository(txCtx, repo.RepoPath()) 162 + if err != nil { 163 + return fmt.Errorf("OpenRepository: %w", err) 164 + } 165 + defer gitRepo.Close() 166 + 167 + _, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID) 168 + return err 161 169 }) 162 170 needsRollbackInPanic = false 163 171 if err != nil {
+6 -5
services/repository/push.go
··· 93 93 defer gitRepo.Close() 94 94 95 95 if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { 96 - log.Error("Failed to update size for repository: %v", err) 96 + return fmt.Errorf("Failed to update size for repository: %v", err) 97 97 } 98 98 99 99 addTags := make([]string, 0, len(optsList)) ··· 259 259 260 260 notification.NotifyPushCommits(ctx, pusher, repo, opts, commits) 261 261 262 - if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil { 263 - log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) 262 + if err = git_model.UpdateBranch(ctx, repo.ID, branch, newCommit.ID.String(), newCommit.CommitMessage, opts.PusherID, newCommit.Committer.When); err != nil { 263 + return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err) 264 264 } 265 265 266 266 // Cache for big repository ··· 273 273 // close all related pulls 274 274 log.Error("close related pull request failed: %v", err) 275 275 } 276 - if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, opts.OldCommitID, pusher.ID); err != nil { 277 - log.Warn("AddDeletedBranch: %v", err) 276 + 277 + if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, pusher.ID); err != nil { 278 + return fmt.Errorf("AddDeletedBranch %s:%s failed: %v", repo.FullName(), branch, err) 278 279 } 279 280 } 280 281
+5 -1
services/repository/repository.go
··· 17 17 system_model "code.gitea.io/gitea/models/system" 18 18 "code.gitea.io/gitea/models/unit" 19 19 user_model "code.gitea.io/gitea/models/user" 20 + "code.gitea.io/gitea/modules/graceful" 20 21 "code.gitea.io/gitea/modules/log" 21 22 "code.gitea.io/gitea/modules/notification" 22 23 repo_module "code.gitea.io/gitea/modules/repository" ··· 100 101 } 101 102 system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) 102 103 system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath()) 103 - return initPushQueue() 104 + if err := initPushQueue(); err != nil { 105 + return err 106 + } 107 + return initBranchSyncQueue(graceful.GetManager().ShutdownContext()) 104 108 } 105 109 106 110 // UpdateRepository updates a repository
+4
templates/admin/dashboard.tmpl
··· 61 61 <td>{{.locale.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td> 62 62 <td class="text right"><button type="submit" class="ui green button" name="op" value="delete_generated_repository_avatars">{{svg "octicon-play"}} {{.locale.Tr "admin.dashboard.operation_run"}}</button></td> 63 63 </tr> 64 + <tr> 65 + <td>{{.locale.Tr "admin.dashboard.sync_repo_branches"}}</td> 66 + <td class="text right"><button type="submit" class="ui green button" name="op" value="sync_repo_branches">{{svg "octicon-play"}} {{.locale.Tr "admin.dashboard.operation_run"}}</button></td> 67 + </tr> 64 68 </tbody> 65 69 </table> 66 70 </form>
+107 -109
templates/repo/branch/list.tmpl
··· 22 22 {{if .DefaultBranchBranch.IsProtected}} 23 23 {{svg "octicon-shield-lock"}} 24 24 {{end}} 25 - <a href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranch}}">{{.DefaultBranch}}</a> 26 - <p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.Commit.ID.String}}">{{ShortSha .DefaultBranchBranch.Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.Commit.CommitMessage .RepoLink .Repository.ComposeMetas}}</span> · {{.locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.Commit.Committer.When .locale}}</p> 25 + <a href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a> 26 + <p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage .RepoLink .Repository.ComposeMetas}}</span> · {{.locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.DBBranch.CommitTime.AsTime .locale}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "Context" $.Context "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p> 27 27 </td> 28 28 <td class="right aligned overflow-visible"> 29 29 {{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}} 30 30 <button class="btn interact-bg show-create-branch-modal gt-p-3" 31 31 data-modal="#create-branch-modal" 32 - data-branch-from="{{$.DefaultBranch}}" 33 - data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranch}}" 34 - data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" ($.DefaultBranch)}}" 32 + data-branch-from="{{$.DefaultBranchBranch}}" 33 + data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}" 34 + data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" ($.DefaultBranchBranch.DBBranch.Name)}}" 35 35 > 36 36 {{svg "octicon-git-branch"}} 37 37 </button> 38 38 {{end}} 39 39 {{if .EnableFeed}} 40 - <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranch}}">{{svg "octicon-rss"}}</a> 40 + <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-rss"}}</a> 41 41 {{end}} 42 42 {{if not $.DisableDownloadSourceArchives}} 43 - <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}"> 43 + <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranchBranch.DBBranch.Name)}}"> 44 44 {{svg "octicon-download"}} 45 45 <div class="menu"> 46 - <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a> 47 - <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a> 46 + <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a> 47 + <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a> 48 48 </div> 49 49 </div> 50 50 {{end}} ··· 52 52 <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal" 53 53 data-is-default-branch="true" 54 54 data-modal="#rename-branch-modal" 55 - data-old-branch-name="{{$.DefaultBranch}}" 56 - data-tooltip-content="{{$.locale.Tr "repo.branch.rename" ($.DefaultBranch)}}" 55 + data-old-branch-name="{{$.DefaultBranchBranch}}" 56 + data-tooltip-content="{{$.locale.Tr "repo.branch.rename" ($.DefaultBranchBranch.DBBranch.Name)}}" 57 57 > 58 58 {{svg "octicon-pencil"}} 59 59 </button> ··· 65 65 </div> 66 66 {{end}} 67 67 68 - {{if gt (len .Branches) 1}} 68 + {{if .Branches}} 69 69 <h4 class="ui top attached header"> 70 70 {{.locale.Tr "repo.branches"}} 71 71 </h4> ··· 73 73 <table class="ui very basic striped fixed table single line"> 74 74 <tbody> 75 75 {{range .Branches}} 76 - {{if ne .Name $.DefaultBranch}} 77 - <tr> 78 - <td class="six wide"> 79 - {{if .IsDeleted}} 80 - <s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a></s> 81 - <p class="info">{{$.locale.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.locale}}</p> 76 + <tr> 77 + <td class="eight wide"> 78 + {{if .DBBranch.IsDeleted}} 79 + <s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a></s> 80 + <p class="info">{{$.locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{TimeSinceUnix .DBBranch.DeletedUnix $.locale}}</p> 81 + {{else}} 82 + {{if .IsProtected}} 83 + {{svg "octicon-shield-lock"}} 84 + {{end}} 85 + <a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a> 86 + <p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.locale.Tr "org.repo_updated"}} {{TimeSince .DBBranch.CommitTime.AsTime $.locale}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "Context" $.Context "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p> 87 + {{end}} 88 + </td> 89 + <td class="two wide ui"> 90 + {{if and (not .DBBranch.IsDeleted) $.DefaultBranchBranch}} 91 + <div class="commit-divergence"> 92 + <div class="bar-group"> 93 + <div class="count count-behind">{{.CommitsBehind}}</div> 94 + {{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}} 95 + <div class="bar bar-behind" style="width: {{Eval 100 "*" .CommitsBehind "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div> 96 + </div> 97 + <div class="bar-group"> 98 + <div class="count count-ahead">{{.CommitsAhead}}</div> 99 + <div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div> 100 + </div> 101 + </div> 102 + {{end}} 103 + </td> 104 + <td class="two wide right aligned"> 105 + {{if not .LatestPullRequest}} 106 + {{if .IsIncluded}} 107 + <span class="ui orange large label" data-tooltip-content="{{$.locale.Tr "repo.branch.included_desc"}}"> 108 + {{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}} 109 + </span> 110 + {{else if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} 111 + <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .DBBranch.Name}}"> 112 + <button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button> 113 + </a> 114 + {{end}} 115 + {{else if and .LatestPullRequest.HasMerged .MergeMovedOn}} 116 + {{if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} 117 + <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}"> 118 + <button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button> 119 + </a> 120 + {{end}} 82 121 {{else}} 83 - {{if .IsProtected}} 84 - {{svg "octicon-shield-lock"}} 122 + <a href="{{.LatestPullRequest.Issue.Link}}" class="gt-vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a> 123 + {{if .LatestPullRequest.HasMerged}} 124 + <a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label purple large label gt-vm">{{svg "octicon-git-merge" 16 "gt-mr-2"}}{{$.locale.Tr "repo.pulls.merged"}}</a> 125 + {{else if .LatestPullRequest.Issue.IsClosed}} 126 + <a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label red large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.closed_title"}}</a> 127 + {{else}} 128 + <a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label green large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.open_title"}}</a> 85 129 {{end}} 86 - <a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a> 87 - <p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .Commit.ID.String}}">{{ShortSha .Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.locale.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.locale}}</p> 88 130 {{end}} 89 - </td> 90 - <td class="three wide ui"> 91 - {{if and (not .IsDeleted) $.DefaultBranchBranch}} 92 - <div class="commit-divergence"> 93 - <div class="bar-group"> 94 - <div class="count count-behind">{{.CommitsBehind}}</div> 95 - {{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}} 96 - <div class="bar bar-behind" style="width: {{Eval 100 "*" .CommitsBehind "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div> 97 - </div> 98 - <div class="bar-group"> 99 - <div class="count count-ahead">{{.CommitsAhead}}</div> 100 - <div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div> 131 + </td> 132 + <td class="three wide right aligned overflow-visible"> 133 + {{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted)}} 134 + <button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal" 135 + data-branch-from="{{.DBBranch.Name}}" 136 + data-branch-from-urlcomponent="{{PathEscapeSegments .DBBranch.Name}}" 137 + data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" .DBBranch.Name}}" 138 + data-modal="#create-branch-modal" data-name="{{.DBBranch.Name}}" 139 + > 140 + {{svg "octicon-git-branch"}} 141 + </button> 142 + {{end}} 143 + {{if $.EnableFeed}} 144 + <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DBBranch.Name}}">{{svg "octicon-rss"}}</a> 145 + {{end}} 146 + {{if and (not .DBBranch.IsDeleted) (not $.DisableDownloadSourceArchives)}} 147 + <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.DBBranch.Name)}}"> 148 + {{svg "octicon-download"}} 149 + <div class="menu"> 150 + <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a> 151 + <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a> 101 152 </div> 102 153 </div> 103 - {{end}} 104 - </td> 105 - <td class="three wide right aligned"> 106 - {{if not .LatestPullRequest}} 107 - {{if .IsIncluded}} 108 - <span class="ui orange large label" data-tooltip-content="{{$.locale.Tr "repo.branch.included_desc"}}"> 109 - {{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}} 154 + {{end}} 155 + {{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted) (not $.IsMirror)}} 156 + <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal" 157 + data-is-default-branch="false" 158 + data-old-branch-name="{{.DBBranch.Name}}" 159 + data-modal="#rename-branch-modal" 160 + data-tooltip-content="{{$.locale.Tr "repo.branch.rename" (.DBBranch.Name)}}" 161 + > 162 + {{svg "octicon-pencil"}} 163 + </button> 164 + {{end}} 165 + {{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}} 166 + {{if .DBBranch.IsDeleted}} 167 + <button class="btn interact-bg gt-p-3 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DBBranch.ID}}&name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.restore" (.DBBranch.Name)}}"> 168 + <span class="text blue"> 169 + {{svg "octicon-reply"}} 110 170 </span> 111 - {{else if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} 112 - <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .Name}}"> 113 - <button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button> 114 - </a> 115 - {{end}} 116 - {{else if and .LatestPullRequest.HasMerged .MergeMovedOn}} 117 - {{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} 118 - <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}"> 119 - <button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button> 120 - </a> 121 - {{end}} 122 - {{else}} 123 - <a href="{{.LatestPullRequest.Issue.Link}}" class="gt-vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a> 124 - {{if .LatestPullRequest.HasMerged}} 125 - <a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label purple large label gt-vm">{{svg "octicon-git-merge" 16 "gt-mr-2"}}{{$.locale.Tr "repo.pulls.merged"}}</a> 126 - {{else if .LatestPullRequest.Issue.IsClosed}} 127 - <a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label red large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.closed_title"}}</a> 128 - {{else}} 129 - <a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label green large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.open_title"}}</a> 130 - {{end}} 131 - {{end}} 132 - </td> 133 - <td class="three wide right aligned overflow-visible"> 134 - {{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}} 135 - <button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal" 136 - data-branch-from="{{.Name}}" 137 - data-branch-from-urlcomponent="{{PathEscapeSegments .Name}}" 138 - data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" .Name}}" 139 - data-modal="#create-branch-modal" data-name="{{.Name}}" 140 - > 141 - {{svg "octicon-git-branch"}} 142 171 </button> 143 - {{end}} 144 - {{if $.EnableFeed}} 145 - <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .Name}}">{{svg "octicon-rss"}}</a> 146 - {{end}} 147 - {{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}} 148 - <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}"> 149 - {{svg "octicon-download"}} 150 - <div class="menu"> 151 - <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a> 152 - <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a> 153 - </div> 154 - </div> 155 - {{end}} 156 - {{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted) (not $.IsMirror)}} 157 - <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal" 158 - data-is-default-branch="false" 159 - data-old-branch-name="{{.Name}}" 160 - data-modal="#rename-branch-modal" 161 - data-tooltip-content="{{$.locale.Tr "repo.branch.rename" (.Name)}}" 162 - > 163 - {{svg "octicon-pencil"}} 172 + {{else}} 173 + <button class="btn interact-bg gt-p-3 delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.delete" (.DBBranch.Name)}}" data-name="{{.DBBranch.Name}}"> 174 + {{svg "octicon-trash"}} 164 175 </button> 165 176 {{end}} 166 - {{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}} 167 - {{if .IsDeleted}} 168 - <button class="btn interact-bg gt-p-3 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID}}&name={{.DeletedBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.restore" (.Name)}}"> 169 - <span class="text blue"> 170 - {{svg "octicon-reply"}} 171 - </span> 172 - </button> 173 - {{else}} 174 - <button class="btn interact-bg gt-p-3 delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.delete" (.Name)}}" data-name="{{.Name}}"> 175 - {{svg "octicon-trash"}} 176 - </button> 177 - {{end}} 178 - {{end}} 179 - </td> 180 - </tr> 181 - {{end}} 177 + {{end}} 178 + </td> 179 + </tr> 182 180 {{end}} 183 181 </tbody> 184 182 </table>
+6
tests/integration/rename_branch_test.go
··· 7 7 "net/http" 8 8 "testing" 9 9 10 + git_model "code.gitea.io/gitea/models/git" 10 11 repo_model "code.gitea.io/gitea/models/repo" 11 12 "code.gitea.io/gitea/models/unittest" 13 + "code.gitea.io/gitea/tests" 12 14 13 15 "github.com/stretchr/testify/assert" 14 16 ) 15 17 16 18 func TestRenameBranch(t *testing.T) { 19 + defer tests.PrepareTestEnv(t)() 20 + 21 + unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: 1, Name: "master"}) 22 + 17 23 // get branch setting page 18 24 session := loginUser(t, "user2") 19 25 req := NewRequest(t, "GET", "/user2/repo1/settings/branches")