aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--models/error.go84
-rw-r--r--models/fixtures/branch.yml47
-rw-r--r--models/fixtures/deleted_branch.yml15
-rw-r--r--models/git/branch.go379
-rw-r--r--models/git/branch_list.go132
-rw-r--r--models/git/branch_test.go (renamed from models/git/branches_test.go)44
-rw-r--r--models/git/branches.go197
-rw-r--r--models/git/protected_branch_list.go37
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v1_21/v264.go93
-rw-r--r--models/repo.go2
-rw-r--r--models/user/user.go4
-rw-r--r--modules/context/repo.go35
-rw-r--r--modules/repository/branch.go135
-rw-r--r--modules/repository/init.go6
-rw-r--r--modules/repository/repo.go6
-rw-r--r--options/locale/locale_en-US.ini2
-rw-r--r--routers/api/v1/repo/branch.go83
-rw-r--r--routers/api/v1/repo/file.go6
-rw-r--r--routers/api/v1/repo/patch.go5
-rw-r--r--routers/web/admin/admin.go25
-rw-r--r--routers/web/repo/branch.go213
-rw-r--r--routers/web/repo/cherry_pick.go9
-rw-r--r--routers/web/repo/compare.go17
-rw-r--r--routers/web/repo/editor.go12
-rw-r--r--routers/web/repo/issue.go8
-rw-r--r--routers/web/repo/patch.go5
-rw-r--r--routers/web/repo/pull.go2
-rw-r--r--routers/web/repo/setting_protected_branch.go2
-rw-r--r--services/convert/convert.go8
-rw-r--r--services/migrations/dump.go4
-rw-r--r--services/pull/pull.go4
-rw-r--r--services/pull/temp_repo.go4
-rw-r--r--services/pull/update.go3
-rw-r--r--services/repository/adopt.go11
-rw-r--r--services/repository/branch.go238
-rw-r--r--services/repository/files/patch.go2
-rw-r--r--services/repository/files/update.go2
-rw-r--r--services/repository/fork.go10
-rw-r--r--services/repository/push.go11
-rw-r--r--services/repository/repository.go6
-rw-r--r--templates/admin/dashboard.tmpl4
-rw-r--r--templates/repo/branch/list.tmpl216
-rw-r--r--tests/integration/rename_branch_test.go6
44 files changed, 1414 insertions, 722 deletions
diff --git a/models/error.go b/models/error.go
index 8223f23585..b7bb967b73 100644
--- a/models/error.go
+++ b/models/error.go
@@ -318,90 +318,6 @@ func (err ErrFilePathProtected) Unwrap() error {
return util.ErrPermissionDenied
}
-// __________ .__
-// \______ \____________ ____ ____ | |__
-// | | _/\_ __ \__ \ / \_/ ___\| | \
-// | | \ | | \// __ \| | \ \___| Y \
-// |______ / |__| (____ /___| /\___ >___| /
-// \/ \/ \/ \/ \/
-
-// ErrBranchDoesNotExist represents an error that branch with such name does not exist.
-type ErrBranchDoesNotExist struct {
- BranchName string
-}
-
-// IsErrBranchDoesNotExist checks if an error is an ErrBranchDoesNotExist.
-func IsErrBranchDoesNotExist(err error) bool {
- _, ok := err.(ErrBranchDoesNotExist)
- return ok
-}
-
-func (err ErrBranchDoesNotExist) Error() string {
- return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName)
-}
-
-func (err ErrBranchDoesNotExist) Unwrap() error {
- return util.ErrNotExist
-}
-
-// ErrBranchAlreadyExists represents an error that branch with such name already exists.
-type ErrBranchAlreadyExists struct {
- BranchName string
-}
-
-// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists.
-func IsErrBranchAlreadyExists(err error) bool {
- _, ok := err.(ErrBranchAlreadyExists)
- return ok
-}
-
-func (err ErrBranchAlreadyExists) Error() string {
- return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
-}
-
-func (err ErrBranchAlreadyExists) Unwrap() error {
- return util.ErrAlreadyExist
-}
-
-// ErrBranchNameConflict represents an error that branch name conflicts with other branch.
-type ErrBranchNameConflict struct {
- BranchName string
-}
-
-// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict.
-func IsErrBranchNameConflict(err error) bool {
- _, ok := err.(ErrBranchNameConflict)
- return ok
-}
-
-func (err ErrBranchNameConflict) Error() string {
- return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
-}
-
-func (err ErrBranchNameConflict) Unwrap() error {
- return util.ErrAlreadyExist
-}
-
-// ErrBranchesEqual represents an error that branch name conflicts with other branch.
-type ErrBranchesEqual struct {
- BaseBranchName string
- HeadBranchName string
-}
-
-// IsErrBranchesEqual checks if an error is an ErrBranchesEqual.
-func IsErrBranchesEqual(err error) bool {
- _, ok := err.(ErrBranchesEqual)
- return ok
-}
-
-func (err ErrBranchesEqual) Error() string {
- return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
-}
-
-func (err ErrBranchesEqual) Unwrap() error {
- return util.ErrInvalidArgument
-}
-
// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it.
type ErrDisallowedToMerge struct {
Reason string
diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml
new file mode 100644
index 0000000000..93003049c6
--- /dev/null
+++ b/models/fixtures/branch.yml
@@ -0,0 +1,47 @@
+-
+ id: 1
+ repo_id: 1
+ name: 'foo'
+ commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
+ commit_message: 'first commit'
+ commit_time: 978307100
+ pusher_id: 1
+ is_deleted: true
+ deleted_by_id: 1
+ deleted_unix: 978307200
+
+-
+ id: 2
+ repo_id: 1
+ name: 'bar'
+ commit_id: '62fb502a7172d4453f0322a2cc85bddffa57f07a'
+ commit_message: 'second commit'
+ commit_time: 978307100
+ pusher_id: 1
+ is_deleted: true
+ deleted_by_id: 99
+ deleted_unix: 978307200
+
+-
+ id: 3
+ repo_id: 1
+ name: 'branch2'
+ commit_id: '985f0301dba5e7b34be866819cd15ad3d8f508ee'
+ commit_message: 'make pull5 outdated'
+ commit_time: 1579166279
+ pusher_id: 1
+ is_deleted: false
+ deleted_by_id: 0
+ deleted_unix: 0
+
+-
+ id: 4
+ repo_id: 1
+ name: 'master'
+ commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
+ commit_message: 'Initial commit'
+ commit_time: 1489927679
+ pusher_id: 1
+ is_deleted: false
+ deleted_by_id: 0
+ deleted_unix: 0
diff --git a/models/fixtures/deleted_branch.yml b/models/fixtures/deleted_branch.yml
deleted file mode 100644
index 6a08a78343..0000000000
--- a/models/fixtures/deleted_branch.yml
+++ /dev/null
@@ -1,15 +0,0 @@
--
- id: 1
- repo_id: 1
- name: foo
- commit: 1213212312313213213132131
- deleted_by_id: 1
- deleted_unix: 978307200
-
--
- id: 2
- repo_id: 1
- name: bar
- commit: 5655464564554545466464655
- deleted_by_id: 99
- deleted_unix: 978307200
diff --git a/models/git/branch.go b/models/git/branch.go
new file mode 100644
index 0000000000..adf8b0a78d
--- /dev/null
+++ b/models/git/branch.go
@@ -0,0 +1,379 @@
+// Copyright 2016 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
+)
+
+// ErrBranchNotExist represents an error that branch with such name does not exist.
+type ErrBranchNotExist struct {
+ RepoID int64
+ BranchName string
+}
+
+// IsErrBranchNotExist checks if an error is an ErrBranchDoesNotExist.
+func IsErrBranchNotExist(err error) bool {
+ _, ok := err.(ErrBranchNotExist)
+ return ok
+}
+
+func (err ErrBranchNotExist) Error() string {
+ return fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", err.RepoID, err.BranchName)
+}
+
+func (err ErrBranchNotExist) Unwrap() error {
+ return util.ErrNotExist
+}
+
+// ErrBranchAlreadyExists represents an error that branch with such name already exists.
+type ErrBranchAlreadyExists struct {
+ BranchName string
+}
+
+// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists.
+func IsErrBranchAlreadyExists(err error) bool {
+ _, ok := err.(ErrBranchAlreadyExists)
+ return ok
+}
+
+func (err ErrBranchAlreadyExists) Error() string {
+ return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
+}
+
+func (err ErrBranchAlreadyExists) Unwrap() error {
+ return util.ErrAlreadyExist
+}
+
+// ErrBranchNameConflict represents an error that branch name conflicts with other branch.
+type ErrBranchNameConflict struct {
+ BranchName string
+}
+
+// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict.
+func IsErrBranchNameConflict(err error) bool {
+ _, ok := err.(ErrBranchNameConflict)
+ return ok
+}
+
+func (err ErrBranchNameConflict) Error() string {
+ return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
+}
+
+func (err ErrBranchNameConflict) Unwrap() error {
+ return util.ErrAlreadyExist
+}
+
+// ErrBranchesEqual represents an error that base branch is equal to the head branch.
+type ErrBranchesEqual struct {
+ BaseBranchName string
+ HeadBranchName string
+}
+
+// IsErrBranchesEqual checks if an error is an ErrBranchesEqual.
+func IsErrBranchesEqual(err error) bool {
+ _, ok := err.(ErrBranchesEqual)
+ return ok
+}
+
+func (err ErrBranchesEqual) Error() string {
+ return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
+}
+
+func (err ErrBranchesEqual) Unwrap() error {
+ return util.ErrInvalidArgument
+}
+
+// Branch represents a branch of a repository
+// For those repository who have many branches, stored into database is a good choice
+// for pagination, keyword search and filtering
+type Branch struct {
+ ID int64
+ RepoID int64 `xorm:"UNIQUE(s)"`
+ Name string `xorm:"UNIQUE(s) NOT NULL"`
+ CommitID string
+ CommitMessage string `xorm:"TEXT"`
+ PusherID int64
+ Pusher *user_model.User `xorm:"-"`
+ IsDeleted bool `xorm:"index"`
+ DeletedByID int64
+ DeletedBy *user_model.User `xorm:"-"`
+ DeletedUnix timeutil.TimeStamp `xorm:"index"`
+ CommitTime timeutil.TimeStamp // The commit
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
+}
+
+func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) {
+ if b.DeletedBy == nil {
+ b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID)
+ if user_model.IsErrUserNotExist(err) {
+ b.DeletedBy = user_model.NewGhostUser()
+ err = nil
+ }
+ }
+ return err
+}
+
+func (b *Branch) LoadPusher(ctx context.Context) (err error) {
+ if b.Pusher == nil && b.PusherID > 0 {
+ b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID)
+ if user_model.IsErrUserNotExist(err) {
+ b.Pusher = user_model.NewGhostUser()
+ err = nil
+ }
+ }
+ return err
+}
+
+func init() {
+ db.RegisterModel(new(Branch))
+ db.RegisterModel(new(RenamedBranch))
+}
+
+func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) {
+ var branch Branch
+ has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrBranchNotExist{
+ RepoID: repoID,
+ BranchName: branchName,
+ }
+ }
+ return &branch, nil
+}
+
+func AddBranches(ctx context.Context, branches []*Branch) error {
+ for _, branch := range branches {
+ if _, err := db.GetEngine(ctx).Insert(branch); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, error) {
+ var branch Branch
+ has, err := db.GetEngine(ctx).ID(branchID).Get(&branch)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrBranchNotExist{
+ RepoID: repoID,
+ }
+ }
+ if branch.RepoID != repoID {
+ return nil, ErrBranchNotExist{
+ RepoID: repoID,
+ }
+ }
+ if !branch.IsDeleted {
+ return nil, ErrBranchNotExist{
+ RepoID: repoID,
+ }
+ }
+ return &branch, nil
+}
+
+func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error {
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ branches := make([]*Branch, 0, len(branchIDs))
+ if err := db.GetEngine(ctx).In("id", branchIDs).Find(&branches); err != nil {
+ return err
+ }
+ for _, branch := range branches {
+ if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+}
+
+// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information
+// If it doest not exist, insert a new record into database
+func UpdateBranch(ctx context.Context, repoID int64, branchName, commitID, commitMessage string, pusherID int64, commitTime time.Time) error {
+ cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
+ Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix").
+ Update(&Branch{
+ CommitID: commitID,
+ CommitMessage: commitMessage,
+ PusherID: pusherID,
+ CommitTime: timeutil.TimeStamp(commitTime.Unix()),
+ IsDeleted: false,
+ })
+ if err != nil {
+ return err
+ }
+ if cnt > 0 {
+ return nil
+ }
+
+ return db.Insert(ctx, &Branch{
+ RepoID: repoID,
+ Name: branchName,
+ CommitID: commitID,
+ CommitMessage: commitMessage,
+ PusherID: pusherID,
+ CommitTime: timeutil.TimeStamp(commitTime.Unix()),
+ })
+}
+
+// AddDeletedBranch adds a deleted branch to the database
+func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error {
+ branch, err := GetBranch(ctx, repoID, branchName)
+ if err != nil {
+ return err
+ }
+ if branch.IsDeleted {
+ return nil
+ }
+
+ cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=? AND is_deleted=?", repoID, branchName, false).
+ Cols("is_deleted, deleted_by_id, deleted_unix").
+ Update(&Branch{
+ IsDeleted: true,
+ DeletedByID: deletedByID,
+ DeletedUnix: timeutil.TimeStampNow(),
+ })
+ if err != nil {
+ return err
+ }
+ if cnt == 0 {
+ return fmt.Errorf("branch %s not found or has been deleted", branchName)
+ }
+ return err
+}
+
+func RemoveDeletedBranchByID(ctx context.Context, repoID, branchID int64) error {
+ _, err := db.GetEngine(ctx).Where("repo_id=? AND id=? AND is_deleted = ?", repoID, branchID, true).Delete(new(Branch))
+ return err
+}
+
+// RemoveOldDeletedBranches removes old deleted branches
+func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
+ // Nothing to do for shutdown or terminate
+ log.Trace("Doing: DeletedBranchesCleanup")
+
+ deleteBefore := time.Now().Add(-olderThan)
+ _, err := db.GetEngine(ctx).Where("is_deleted=? AND deleted_unix < ?", true, deleteBefore.Unix()).Delete(new(Branch))
+ if err != nil {
+ log.Error("DeletedBranchesCleanup: %v", err)
+ }
+}
+
+// RenamedBranch provide renamed branch log
+// will check it when a branch can't be found
+type RenamedBranch struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX NOT NULL"`
+ From string
+ To string
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+}
+
+// FindRenamedBranch check if a branch was renamed
+func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) {
+ branch = &RenamedBranch{
+ RepoID: repoID,
+ From: from,
+ }
+ exist, err = db.GetEngine(ctx).Get(branch)
+
+ return branch, exist, err
+}
+
+// RenameBranch rename a branch
+func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) {
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ sess := db.GetEngine(ctx)
+
+ // 1. update branch in database
+ if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
+ Name: to,
+ }); err != nil {
+ return err
+ } else if n <= 0 {
+ return ErrBranchNotExist{
+ RepoID: repo.ID,
+ BranchName: from,
+ }
+ }
+
+ // 2. update default branch if needed
+ isDefault := repo.DefaultBranch == from
+ if isDefault {
+ repo.DefaultBranch = to
+ _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
+ if err != nil {
+ return err
+ }
+ }
+
+ // 3. Update protected branch if needed
+ protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
+ if err != nil {
+ return err
+ }
+
+ if protectedBranch != nil {
+ // there is a protect rule for this branch
+ protectedBranch.RuleName = to
+ _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
+ if err != nil {
+ return err
+ }
+ } else {
+ // some glob protect rules may match this branch
+ protected, err := IsBranchProtected(ctx, repo.ID, from)
+ if err != nil {
+ return err
+ }
+ if protected {
+ return ErrBranchIsProtected
+ }
+ }
+
+ // 4. Update all not merged pull request base branch name
+ _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
+ repo.ID, from, false).
+ Update(map[string]interface{}{"base_branch": to})
+ if err != nil {
+ return err
+ }
+
+ // 5. do git action
+ if err = gitAction(isDefault); err != nil {
+ return err
+ }
+
+ // 6. insert renamed branch record
+ renamedBranch := &RenamedBranch{
+ RepoID: repo.ID,
+ From: from,
+ To: to,
+ }
+ err = db.Insert(ctx, renamedBranch)
+ if err != nil {
+ return err
+ }
+
+ return committer.Commit()
+}
diff --git a/models/git/branch_list.go b/models/git/branch_list.go
new file mode 100644
index 0000000000..da78248c0b
--- /dev/null
+++ b/models/git/branch_list.go
@@ -0,0 +1,132 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "context"
+
+ "code.gitea.io/gitea/models/db"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+type BranchList []*Branch
+
+func (branches BranchList) LoadDeletedBy(ctx context.Context) error {
+ ids := container.Set[int64]{}
+ for _, branch := range branches {
+ if !branch.IsDeleted {
+ continue
+ }
+ ids.Add(branch.DeletedByID)
+ }
+ usersMap := make(map[int64]*user_model.User, len(ids))
+ if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
+ return err
+ }
+ for _, branch := range branches {
+ if !branch.IsDeleted {
+ continue
+ }
+ branch.DeletedBy = usersMap[branch.DeletedByID]
+ if branch.DeletedBy == nil {
+ branch.DeletedBy = user_model.NewGhostUser()
+ }
+ }
+ return nil
+}
+
+func (branches BranchList) LoadPusher(ctx context.Context) error {
+ ids := container.Set[int64]{}
+ for _, branch := range branches {
+ if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher
+ ids.Add(branch.PusherID)
+ }
+ }
+ usersMap := make(map[int64]*user_model.User, len(ids))
+ if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
+ return err
+ }
+ for _, branch := range branches {
+ if branch.PusherID <= 0 {
+ continue
+ }
+ branch.Pusher = usersMap[branch.PusherID]
+ if branch.Pusher == nil {
+ branch.Pusher = user_model.NewGhostUser()
+ }
+ }
+ return nil
+}
+
+const (
+ BranchOrderByNameAsc = "name ASC"
+ BranchOrderByCommitTimeDesc = "commit_time DESC"
+)
+
+type FindBranchOptions struct {
+ db.ListOptions
+ RepoID int64
+ ExcludeBranchNames []string
+ IsDeletedBranch util.OptionalBool
+ OrderBy string
+}
+
+func (opts *FindBranchOptions) Cond() builder.Cond {
+ cond := builder.NewCond()
+ if opts.RepoID > 0 {
+ cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
+ }
+
+ if len(opts.ExcludeBranchNames) > 0 {
+ cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames))
+ }
+ if !opts.IsDeletedBranch.IsNone() {
+ cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()})
+ }
+ return cond
+}
+
+func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) {
+ return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{})
+}
+
+func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session {
+ if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end
+ sess = sess.OrderBy("is_deleted ASC")
+ }
+
+ if opts.OrderBy == "" {
+ opts.OrderBy = BranchOrderByCommitTimeDesc
+ }
+ return sess.OrderBy(opts.OrderBy)
+}
+
+func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) {
+ sess := db.GetEngine(ctx).Where(opts.Cond())
+ if opts.PageSize > 0 && !opts.IsListAll() {
+ sess = db.SetSessionPagination(sess, &opts.ListOptions)
+ }
+ sess = orderByBranches(sess, opts)
+
+ var branches []*Branch
+ return branches, sess.Find(&branches)
+}
+
+func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) {
+ sess := db.GetEngine(ctx).Select("name").Where(opts.Cond())
+ if opts.PageSize > 0 && !opts.IsListAll() {
+ sess = db.SetSessionPagination(sess, &opts.ListOptions)
+ }
+ sess = orderByBranches(sess, opts)
+ var branches []string
+ if err := sess.Table("branch").Find(&branches); err != nil {
+ return nil, err
+ }
+ return branches, nil
+}
diff --git a/models/git/branches_test.go b/models/git/branch_test.go
index 5d18d9525e..bb63660d07 100644
--- a/models/git/branches_test.go
+++ b/models/git/branch_test.go
@@ -11,6 +11,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@@ -18,24 +19,37 @@ import (
func TestAddDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
+ firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
- assert.Error(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID))
- assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "test", "5655464564554545466464656", int64(1)))
+ assert.True(t, firstBranch.IsDeleted)
+ assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.DeletedByID))
+ assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "branch2", int64(1)))
+
+ secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"})
+ assert.True(t, secondBranch.IsDeleted)
+
+ err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.Name, secondBranch.CommitID, secondBranch.CommitMessage, secondBranch.PusherID, secondBranch.CommitTime.AsLocalTime())
+ assert.NoError(t, err)
}
func TestGetDeletedBranches(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- branches, err := git_model.GetDeletedBranches(db.DefaultContext, repo.ID)
+ branches, err := git_model.FindBranches(db.DefaultContext, git_model.FindBranchOptions{
+ ListOptions: db.ListOptions{
+ ListAll: true,
+ },
+ RepoID: repo.ID,
+ IsDeletedBranch: util.OptionalBoolTrue,
+ })
assert.NoError(t, err)
assert.Len(t, branches, 2)
}
func TestGetDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
+ firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
assert.NotNil(t, getDeletedBranch(t, firstBranch))
}
@@ -43,18 +57,18 @@ func TestGetDeletedBranch(t *testing.T) {
func TestDeletedBranchLoadUser(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
- secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2})
+ firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
+ secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2})
branch := getDeletedBranch(t, firstBranch)
assert.Nil(t, branch.DeletedBy)
- branch.LoadUser(db.DefaultContext)
+ branch.LoadDeletedBy(db.DefaultContext)
assert.NotNil(t, branch.DeletedBy)
assert.Equal(t, "user1", branch.DeletedBy.Name)
branch = getDeletedBranch(t, secondBranch)
assert.Nil(t, branch.DeletedBy)
- branch.LoadUser(db.DefaultContext)
+ branch.LoadDeletedBy(db.DefaultContext)
assert.NotNil(t, branch.DeletedBy)
assert.Equal(t, "Ghost", branch.DeletedBy.Name)
}
@@ -63,22 +77,22 @@ func TestRemoveDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
+ firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
err := git_model.RemoveDeletedBranchByID(db.DefaultContext, repo.ID, 1)
assert.NoError(t, err)
unittest.AssertNotExistsBean(t, firstBranch)
- unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2})
+ unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2})
}
-func getDeletedBranch(t *testing.T, branch *git_model.DeletedBranch) *git_model.DeletedBranch {
+func getDeletedBranch(t *testing.T, branch *git_model.Branch) *git_model.Branch {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID)
assert.NoError(t, err)
assert.Equal(t, branch.ID, deletedBranch.ID)
assert.Equal(t, branch.Name, deletedBranch.Name)
- assert.Equal(t, branch.Commit, deletedBranch.Commit)
+ assert.Equal(t, branch.CommitID, deletedBranch.CommitID)
assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID)
return deletedBranch
@@ -146,8 +160,8 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1)
- // Expect no error, and the returned branch is nil.
- assert.NoError(t, err)
+ // Expect error, and the returned branch is nil.
+ assert.Error(t, err)
assert.Nil(t, deletedBranch)
// Now get the deletedBranch with ID of 1 on repo with ID 1.
diff --git a/models/git/branches.go b/models/git/branches.go
deleted file mode 100644
index b94ea32959..0000000000
--- a/models/git/branches.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2016 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package git
-
-import (
- "context"
- "fmt"
- "time"
-
- "code.gitea.io/gitea/models/db"
- repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/timeutil"
-)
-
-// DeletedBranch struct
-type DeletedBranch struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
- Name string `xorm:"UNIQUE(s) NOT NULL"`
- Commit string `xorm:"UNIQUE(s) NOT NULL"`
- DeletedByID int64 `xorm:"INDEX"`
- DeletedBy *user_model.User `xorm:"-"`
- DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"`
-}
-
-func init() {
- db.RegisterModel(new(DeletedBranch))
- db.RegisterModel(new(RenamedBranch))
-}
-
-// AddDeletedBranch adds a deleted branch to the database
-func AddDeletedBranch(ctx context.Context, repoID int64, branchName, commit string, deletedByID int64) error {
- deletedBranch := &DeletedBranch{
- RepoID: repoID,
- Name: branchName,
- Commit: commit,
- DeletedByID: deletedByID,
- }
-
- _, err := db.GetEngine(ctx).Insert(deletedBranch)
- return err
-}
-
-// GetDeletedBranches returns all the deleted branches
-func GetDeletedBranches(ctx context.Context, repoID int64) ([]*DeletedBranch, error) {
- deletedBranches := make([]*DeletedBranch, 0)
- return deletedBranches, db.GetEngine(ctx).Where("repo_id = ?", repoID).Desc("deleted_unix").Find(&deletedBranches)
-}
-
-// GetDeletedBranchByID get a deleted branch by its ID
-func GetDeletedBranchByID(ctx context.Context, repoID, id int64) (*DeletedBranch, error) {
- deletedBranch := &DeletedBranch{}
- has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("id = ?", id).Get(deletedBranch)
- if err != nil {
- return nil, err
- }
- if !has {
- return nil, nil
- }
- return deletedBranch, nil
-}
-
-// RemoveDeletedBranchByID removes a deleted branch from the database
-func RemoveDeletedBranchByID(ctx context.Context, repoID, id int64) (err error) {
- deletedBranch := &DeletedBranch{
- RepoID: repoID,
- ID: id,
- }
-
- if affected, err := db.GetEngine(ctx).Delete(deletedBranch); err != nil {
- return err
- } else if affected != 1 {
- return fmt.Errorf("remove deleted branch ID(%v) failed", id)
- }
-
- return nil
-}
-
-// LoadUser loads the user that deleted the branch
-// When there's no user found it returns a user_model.NewGhostUser
-func (deletedBranch *DeletedBranch) LoadUser(ctx context.Context) {
- user, err := user_model.GetUserByID(ctx, deletedBranch.DeletedByID)
- if err != nil {
- user = user_model.NewGhostUser()
- }
- deletedBranch.DeletedBy = user
-}
-
-// RemoveDeletedBranchByName removes all deleted branches
-func RemoveDeletedBranchByName(ctx context.Context, repoID int64, branch string) error {
- _, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch))
- return err
-}
-
-// RemoveOldDeletedBranches removes old deleted branches
-func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
- // Nothing to do for shutdown or terminate
- log.Trace("Doing: DeletedBranchesCleanup")
-
- deleteBefore := time.Now().Add(-olderThan)
- _, err := db.GetEngine(ctx).Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch))
- if err != nil {
- log.Error("DeletedBranchesCleanup: %v", err)
- }
-}
-
-// RenamedBranch provide renamed branch log
-// will check it when a branch can't be found
-type RenamedBranch struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"INDEX NOT NULL"`
- From string
- To string
- CreatedUnix timeutil.TimeStamp `xorm:"created"`
-}
-
-// FindRenamedBranch check if a branch was renamed
-func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) {
- branch = &RenamedBranch{
- RepoID: repoID,
- From: from,
- }
- exist, err = db.GetEngine(ctx).Get(branch)
-
- return branch, exist, err
-}
-
-// RenameBranch rename a branch
-func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- sess := db.GetEngine(ctx)
- // 1. update default branch if needed
- isDefault := repo.DefaultBranch == from
- if isDefault {
- repo.DefaultBranch = to
- _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
- if err != nil {
- return err
- }
- }
-
- // 2. Update protected branch if needed
- protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
- if err != nil {
- return err
- }
-
- if protectedBranch != nil {
- protectedBranch.RuleName = to
- _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
- if err != nil {
- return err
- }
- } else {
- protected, err := IsBranchProtected(ctx, repo.ID, from)
- if err != nil {
- return err
- }
- if protected {
- return ErrBranchIsProtected
- }
- }
-
- // 3. Update all not merged pull request base branch name
- _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
- repo.ID, from, false).
- Update(map[string]interface{}{"base_branch": to})
- if err != nil {
- return err
- }
-
- // 4. do git action
- if err = gitAction(isDefault); err != nil {
- return err
- }
-
- // 5. insert renamed branch record
- renamedBranch := &RenamedBranch{
- RepoID: repo.ID,
- From: from,
- To: to,
- }
- err = db.Insert(ctx, renamedBranch)
- if err != nil {
- return err
- }
-
- return committer.Commit()
-}
diff --git a/models/git/protected_branch_list.go b/models/git/protected_branch_list.go
index 17fe6d701f..eeb307e245 100644
--- a/models/git/protected_branch_list.go
+++ b/models/git/protected_branch_list.go
@@ -8,7 +8,7 @@ import (
"sort"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
)
@@ -47,19 +47,32 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB
}
// FindAllMatchedBranches find all matched branches
-func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) {
- // FIXME: how many should we get?
- branches, _, err := gitRepo.GetBranchNames(0, 9999999)
- if err != nil {
- return nil, err
- }
- rule := glob.MustCompile(ruleName)
- results := make([]string, 0, len(branches))
- for _, branch := range branches {
- if rule.Match(branch) {
- results = append(results, branch)
+func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) {
+ results := make([]string, 0, 10)
+ for page := 1; ; page++ {
+ brancheNames, err := FindBranchNames(ctx, FindBranchOptions{
+ ListOptions: db.ListOptions{
+ PageSize: 100,
+ Page: page,
+ },
+ RepoID: repoID,
+ IsDeletedBranch: util.OptionalBoolFalse,
+ })
+ if err != nil {
+ return nil, err
+ }
+ rule := glob.MustCompile(ruleName)
+
+ for _, branch := range brancheNames {
+ if rule.Match(branch) {
+ results = append(results, branch)
+ }
+ }
+ if len(brancheNames) < 100 {
+ break
}
}
+
return results, nil
}
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 3e9b348e63..a15b6e4eec 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -509,6 +509,8 @@ var migrations = []Migration{
NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun),
// v263 -> v264
NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable),
+ // v264 -> v265
+ NewMigration("Add branch table", v1_21.AddBranchTable),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_21/v264.go b/models/migrations/v1_21/v264.go
new file mode 100644
index 0000000000..60b7a7acf7
--- /dev/null
+++ b/models/migrations/v1_21/v264.go
@@ -0,0 +1,93 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddBranchTable(x *xorm.Engine) error {
+ type Branch struct {
+ ID int64
+ RepoID int64 `xorm:"UNIQUE(s)"`
+ Name string `xorm:"UNIQUE(s) NOT NULL"`
+ CommitID string
+ CommitMessage string `xorm:"TEXT"`
+ PusherID int64
+ IsDeleted bool `xorm:"index"`
+ DeletedByID int64
+ DeletedUnix timeutil.TimeStamp `xorm:"index"`
+ CommitTime timeutil.TimeStamp // The commit
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ if err := x.Sync(new(Branch)); err != nil {
+ return err
+ }
+
+ if exist, err := x.IsTableExist("deleted_branches"); err != nil {
+ return err
+ } else if !exist {
+ return nil
+ }
+
+ type DeletedBranch struct {
+ ID int64
+ RepoID int64 `xorm:"index UNIQUE(s)"`
+ Name string `xorm:"UNIQUE(s) NOT NULL"`
+ Commit string
+ DeletedByID int64
+ DeletedUnix timeutil.TimeStamp
+ }
+
+ var adminUserID int64
+ has, err := x.Table("user").
+ Select("id").
+ Where("is_admin=?", true).
+ Asc("id"). // Reliably get the admin with the lowest ID.
+ Get(&adminUserID)
+ if err != nil {
+ return err
+ } else if !has {
+ return fmt.Errorf("no admin user found")
+ }
+
+ branches := make([]Branch, 0, 100)
+ if err := db.Iterate(context.Background(), nil, func(ctx context.Context, deletedBranch *DeletedBranch) error {
+ branches = append(branches, Branch{
+ RepoID: deletedBranch.RepoID,
+ Name: deletedBranch.Name,
+ CommitID: deletedBranch.Commit,
+ PusherID: adminUserID,
+ IsDeleted: true,
+ DeletedByID: deletedBranch.DeletedByID,
+ DeletedUnix: deletedBranch.DeletedUnix,
+ })
+ if len(branches) >= 100 {
+ _, err := x.Insert(&branches)
+ if err != nil {
+ return err
+ }
+ branches = branches[:0]
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ if len(branches) > 0 {
+ if _, err := x.Insert(&branches); err != nil {
+ return err
+ }
+ }
+
+ return x.DropTables("deleted_branches")
+}
diff --git a/models/repo.go b/models/repo.go
index 2e0e8af16c..933f7e56a3 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -147,7 +147,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
&repo_model.Collaboration{RepoID: repoID},
&issues_model.Comment{RefRepoID: repoID},
&git_model.CommitStatus{RepoID: repoID},
- &git_model.DeletedBranch{RepoID: repoID},
+ &git_model.Branch{RepoID: repoID},
&git_model.LFSLock{RepoID: repoID},
&repo_model.LanguageStat{RepoID: repoID},
&issues_model.Milestone{RepoID: repoID},
diff --git a/models/user/user.go b/models/user/user.go
index 2077d55f51..6f9c2f5b35 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -1171,9 +1171,9 @@ func GetUserByOpenID(uri string) (*User, error) {
}
// GetAdminUser returns the first administrator
-func GetAdminUser() (*User, error) {
+func GetAdminUser(ctx context.Context) (*User, error) {
var admin User
- has, err := db.GetEngine(db.DefaultContext).
+ has, err := db.GetEngine(ctx).
Where("is_admin=?", true).
Asc("id"). // Reliably get the admin with the lowest ID.
Get(&admin)
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 003309f1b0..e999085251 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -667,13 +667,38 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
}
ctx.Data["Tags"] = tags
- brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
+ branchOpts := git_model.FindBranchOptions{
+ RepoID: ctx.Repo.Repository.ID,
+ IsDeletedBranch: util.OptionalBoolFalse,
+ ListOptions: db.ListOptions{
+ ListAll: true,
+ },
+ }
+ branchesTotal, err := git_model.CountBranches(ctx, branchOpts)
+ if err != nil {
+ ctx.ServerError("CountBranches", err)
+ return
+ }
+
+ // non empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
+ if branchesTotal == 0 { // fallback to do a sync immediately
+ branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
+ if err != nil {
+ ctx.ServerError("SyncRepoBranches", err)
+ return
+ }
+ }
+
+ // FIXME: use paganation and async loading
+ branchOpts.ExcludeBranchNames = []string{ctx.Repo.Repository.DefaultBranch}
+ brs, err := git_model.FindBranchNames(ctx, branchOpts)
if err != nil {
ctx.ServerError("GetBranches", err)
return
}
- ctx.Data["Branches"] = brs
- ctx.Data["BranchesCount"] = len(brs)
+ // always put default branch on the top
+ ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...)
+ ctx.Data["BranchesCount"] = branchesTotal
// If not branch selected, try default one.
// If default branch doesn't exist, fall back to some other branch.
@@ -897,9 +922,9 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
if len(ctx.Params("*")) == 0 {
refName = ctx.Repo.Repository.DefaultBranch
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
- brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
+ brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1)
if err == nil && len(brs) != 0 {
- refName = brs[0]
+ refName = brs[0].Name
} else if len(brs) == 0 {
log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
ctx.Repo.Repository.MarkAsBrokenEmpty()
diff --git a/modules/repository/branch.go b/modules/repository/branch.go
new file mode 100644
index 0000000000..7fd29e3f7d
--- /dev/null
+++ b/modules/repository/branch.go
@@ -0,0 +1,135 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repository
+
+import (
+ "context"
+
+ "code.gitea.io/gitea/models/db"
+ git_model "code.gitea.io/gitea/models/git"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/timeutil"
+)
+
+// SyncRepoBranches synchronizes branch table with repository branches
+func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error) {
+ repo, err := repo_model.GetRepositoryByID(ctx, repoID)
+ if err != nil {
+ return 0, err
+ }
+
+ log.Debug("SyncRepoBranches: in Repo[%d:%s]", repo.ID, repo.FullName())
+
+ gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
+ if err != nil {
+ log.Error("OpenRepository[%s]: %w", repo.RepoPath(), err)
+ return 0, err
+ }
+ defer gitRepo.Close()
+
+ return SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doerID)
+}
+
+func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) {
+ allBranches := container.Set[string]{}
+ {
+ branches, _, err := gitRepo.GetBranchNames(0, 0)
+ if err != nil {
+ return 0, err
+ }
+ log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches)
+ for _, branch := range branches {
+ allBranches.Add(branch)
+ }
+ }
+
+ dbBranches := make(map[string]*git_model.Branch)
+ {
+ branches, err := git_model.FindBranches(ctx, git_model.FindBranchOptions{
+ ListOptions: db.ListOptions{
+ ListAll: true,
+ },
+ RepoID: repo.ID,
+ })
+ if err != nil {
+ return 0, err
+ }
+ for _, branch := range branches {
+ dbBranches[branch.Name] = branch
+ }
+ }
+
+ var toAdd []*git_model.Branch
+ var toUpdate []*git_model.Branch
+ var toRemove []int64
+ for branch := range allBranches {
+ dbb := dbBranches[branch]
+ commit, err := gitRepo.GetBranchCommit(branch)
+ if err != nil {
+ return 0, err
+ }
+ if dbb == nil {
+ toAdd = append(toAdd, &git_model.Branch{
+ RepoID: repo.ID,
+ Name: branch,
+ CommitID: commit.ID.String(),
+ CommitMessage: commit.CommitMessage,
+ PusherID: doerID,
+ CommitTime: timeutil.TimeStamp(commit.Author.When.Unix()),
+ })
+ } else if commit.ID.String() != dbb.CommitID {
+ toUpdate = append(toUpdate, &git_model.Branch{
+ ID: dbb.ID,
+ RepoID: repo.ID,
+ Name: branch,
+ CommitID: commit.ID.String(),
+ CommitMessage: commit.CommitMessage,
+ PusherID: doerID,
+ CommitTime: timeutil.TimeStamp(commit.Author.When.Unix()),
+ })
+ }
+ }
+
+ for _, dbBranch := range dbBranches {
+ if !allBranches.Contains(dbBranch.Name) && !dbBranch.IsDeleted {
+ toRemove = append(toRemove, dbBranch.ID)
+ }
+ }
+
+ log.Trace("SyncRepoBranches[%s]: toAdd: %v, toUpdate: %v, toRemove: %v", repo.FullName(), toAdd, toUpdate, toRemove)
+
+ if len(toAdd) == 0 && len(toRemove) == 0 && len(toUpdate) == 0 {
+ return int64(len(allBranches)), nil
+ }
+
+ if err := db.WithTx(ctx, func(subCtx context.Context) error {
+ if len(toAdd) > 0 {
+ if err := git_model.AddBranches(subCtx, toAdd); err != nil {
+ return err
+ }
+ }
+
+ for _, b := range toUpdate {
+ if _, err := db.GetEngine(subCtx).ID(b.ID).
+ Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted").
+ Update(b); err != nil {
+ return err
+ }
+ }
+
+ if len(toRemove) > 0 {
+ if err := git_model.DeleteBranches(subCtx, repo.ID, doerID, toRemove); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }); err != nil {
+ return 0, err
+ }
+ return int64(len(allBranches)), nil
+}
diff --git a/modules/repository/init.go b/modules/repository/init.go
index f079f72b77..84648f45eb 100644
--- a/modules/repository/init.go
+++ b/modules/repository/init.go
@@ -351,6 +351,12 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
+
+ if !repo.IsEmpty {
+ if _, err := SyncRepoBranches(ctx, repo.ID, u.ID); err != nil {
+ return fmt.Errorf("SyncRepoBranches: %w", err)
+ }
+ }
}
if err = UpdateRepository(ctx, repo, false); err != nil {
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index bcb43f15e1..6a11315cc4 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -151,6 +151,10 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
}
}
+ if _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
+ return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
+ }
+
if !opts.Releases {
// note: this will greatly improve release (tag) sync
// for pull-mirrors with many tags
@@ -169,7 +173,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
}
}
- ctx, committer, err := db.TxContext(db.DefaultContext)
+ ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index be90e5366c..ff59fbc96f 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2660,6 +2660,7 @@ dashboard.delete_repo_archives.started = Delete all repository archives task sta
dashboard.delete_missing_repos = Delete all repositories missing their Git files
dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started.
dashboard.delete_generated_repository_avatars = Delete generated repository avatars
+dashboard.sync_repo_branches = Sync missed branches from git data to databases
dashboard.update_mirrors = Update Mirrors
dashboard.repo_health_check = Health check all repositories
dashboard.check_repo_stats = Check all repository statistics
@@ -2713,6 +2714,7 @@ dashboard.gc_lfs = Garbage collect LFS meta objects
dashboard.stop_zombie_tasks = Stop zombie tasks
dashboard.stop_endless_tasks = Stop endless tasks
dashboard.cancel_abandoned_jobs = Cancel abandoned jobs
+dashboard.sync_branch.started = Branches Sync started
users.user_manage_panel = User Account Management
users.new_account = Create User Account
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index 5336ccb797..4900ecf4d0 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -15,7 +15,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/convert"
@@ -76,7 +78,7 @@ func GetBranch(ctx *context.APIContext) {
return
}
- br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
+ br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
@@ -118,6 +120,37 @@ func DeleteBranch(ctx *context.APIContext) {
branchName := ctx.Params("*")
+ if ctx.Repo.Repository.IsEmpty {
+ ctx.Error(http.StatusForbidden, "", "Git Repository is empty.")
+ return
+ }
+
+ // check whether branches of this repository has been synced
+ totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{
+ RepoID: ctx.Repo.Repository.ID,
+ IsDeletedBranch: util.OptionalBoolFalse,
+ })
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "CountBranches", err)
+ return
+ }
+ if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
+ _, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
+ if err != nil {
+ ctx.ServerError("SyncRepoBranches", err)
+ return
+ }
+ }
+
+ if ctx.Repo.Repository.IsArchived {
+ ctx.Error(http.StatusForbidden, "IsArchived", fmt.Errorf("can not delete branch of an archived repository"))
+ return
+ }
+ if ctx.Repo.Repository.IsMirror {
+ ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository"))
+ return
+ }
+
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
switch {
case git.IsErrBranchNotExist(err):
@@ -203,14 +236,14 @@ func CreateBranch(ctx *context.APIContext) {
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
if err != nil {
- if models.IsErrBranchDoesNotExist(err) {
+ if git_model.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
}
if models.IsErrTagAlreadyExists(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
- } else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
+ } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Error(http.StatusConflict, "", "The branch already exists.")
- } else if models.IsErrBranchNameConflict(err) {
+ } else if git_model.IsErrBranchNameConflict(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
} else {
ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
@@ -236,7 +269,7 @@ func CreateBranch(ctx *context.APIContext) {
return
}
- br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
+ br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
@@ -275,20 +308,38 @@ func ListBranches(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/BranchList"
- var totalNumOfBranches int
+ var totalNumOfBranches int64
var apiBranches []*api.Branch
listOptions := utils.GetListOptions(ctx)
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
+ branchOpts := git_model.FindBranchOptions{
+ ListOptions: listOptions,
+ RepoID: ctx.Repo.Repository.ID,
+ IsDeletedBranch: util.OptionalBoolFalse,
+ }
+ var err error
+ totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "CountBranches", err)
+ return
+ }
+ if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
+ totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
+ if err != nil {
+ ctx.ServerError("SyncRepoBranches", err)
+ return
+ }
+ }
+
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
return
}
- skip, _ := listOptions.GetStartEnd()
- branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
+ branches, err := git_model.FindBranches(ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
@@ -296,11 +347,11 @@ func ListBranches(ctx *context.APIContext) {
apiBranches = make([]*api.Branch, 0, len(branches))
for i := range branches {
- c, err := branches[i].GetCommit()
+ c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
if err != nil {
// Skip if this branch doesn't exist anymore.
if git.IsErrNotExist(err) {
- total--
+ totalNumOfBranches--
continue
}
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
@@ -308,19 +359,17 @@ func ListBranches(ctx *context.APIContext) {
}
branchProtection := rules.GetFirstMatched(branches[i].Name)
- apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
+ apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
}
apiBranches = append(apiBranches, apiBranch)
}
-
- totalNumOfBranches = total
}
- ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
- ctx.SetTotalCountHeader(int64(totalNumOfBranches))
+ ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
+ ctx.SetTotalCountHeader(totalNumOfBranches)
ctx.JSON(http.StatusOK, apiBranches)
}
@@ -580,7 +629,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
}()
}
// FIXME: since we only need to recheck files protected rules, we could improve this
- matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, ruleName)
+ matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
return
@@ -851,7 +900,7 @@ func EditBranchProtection(ctx *context.APIContext) {
}
// FIXME: since we only need to recheck files protected rules, we could improve this
- matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
+ matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
return
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index 2b468d6e73..48f890ee55 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -687,12 +687,12 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
ctx.Error(http.StatusForbidden, "Access", err)
return
}
- if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
+ if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
return
}
- if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
+ if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
return
}
@@ -843,7 +843,7 @@ func DeleteFile(ctx *context.APIContext) {
if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
ctx.Error(http.StatusNotFound, "DeleteFile", err)
return
- } else if models.IsErrBranchAlreadyExists(err) ||
+ } else if git_model.IsErrBranchAlreadyExists(err) ||
models.IsErrFilenameInvalid(err) ||
models.IsErrSHADoesNotMatch(err) ||
models.IsErrCommitIDDoesNotMatch(err) ||
diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go
index 6fbb9e7b3a..d2f055355d 100644
--- a/routers/api/v1/repo/patch.go
+++ b/routers/api/v1/repo/patch.go
@@ -8,6 +8,7 @@ import (
"time"
"code.gitea.io/gitea/models"
+ git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
@@ -91,12 +92,12 @@ func ApplyDiffPatch(ctx *context.APIContext) {
ctx.Error(http.StatusForbidden, "Access", err)
return
}
- if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
+ if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
return
}
- if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
+ if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
return
}
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index 797ba8798d..225a8c6705 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -14,12 +14,15 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/cron"
"code.gitea.io/gitea/services/forms"
+ repo_service "code.gitea.io/gitea/services/repository"
)
const (
@@ -133,12 +136,22 @@ func DashboardPost(ctx *context.Context) {
// Run operation.
if form.Op != "" {
- task := cron.GetTask(form.Op)
- if task != nil {
- go task.RunWithUser(ctx.Doer, nil)
- ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
- } else {
- ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
+ switch form.Op {
+ case "sync_repo_branches":
+ go func() {
+ if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil {
+ log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err)
+ }
+ }()
+ ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started"))
+ default:
+ task := cron.GetTask(form.Op)
+ if task != nil {
+ go task.RunWithUser(ctx.Doer, nil)
+ ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
+ } else {
+ ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
+ }
}
}
if form.From == "monitor" {
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index ea2c01856d..f0282a71b8 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
- issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
@@ -28,32 +27,16 @@ import (
"code.gitea.io/gitea/services/forms"
release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
- files_service "code.gitea.io/gitea/services/repository/files"
)
const (
tplBranch base.TplName = "repo/branch/list"
)
-// Branch contains the branch information
-type Branch struct {
- Name string
- Commit *git.Commit
- IsProtected bool
- IsDeleted bool
- IsIncluded bool
- DeletedBranch *git_model.DeletedBranch
- CommitsAhead int
- CommitsBehind int
- LatestPullRequest *issues_model.PullRequest
- MergeMovedOn bool
-}
-
// Branches render repository branch page
func Branches(ctx *context.Context) {
ctx.Data["Title"] = "Branches"
ctx.Data["IsRepoToolbarBranches"] = true
- ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls()
ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
@@ -68,15 +51,15 @@ func Branches(ctx *context.Context) {
}
pageSize := setting.Git.BranchesRangeSize
- skip := (page - 1) * pageSize
- log.Debug("Branches: skip: %d limit: %d", skip, pageSize)
- defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize)
- if ctx.Written() {
+ defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, page, pageSize)
+ if err != nil {
+ ctx.ServerError("LoadBranches", err)
return
}
+
ctx.Data["Branches"] = branches
- ctx.Data["DefaultBranchBranch"] = defaultBranchBranch
- pager := context.NewPagination(branchesCount, pageSize, page, 5)
+ ctx.Data["DefaultBranchBranch"] = defaultBranch
+ pager := context.NewPagination(int(branchesCount), pageSize, page, 5)
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager
@@ -130,7 +113,7 @@ func RestoreBranchPost(ctx *context.Context) {
if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{
Remote: ctx.Repo.Repository.RepoPath(),
- Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name),
+ Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name),
Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
}); err != nil {
if strings.Contains(err.Error(), "already exists") {
@@ -148,7 +131,7 @@ func RestoreBranchPost(ctx *context.Context) {
&repo_module.PushUpdateOptions{
RefFullName: git.RefNameFromBranch(deletedBranch.Name),
OldCommitID: git.EmptySHA,
- NewCommitID: deletedBranch.Commit,
+ NewCommitID: deletedBranch.CommitID,
PusherID: ctx.Doer.ID,
PusherName: ctx.Doer.Name,
RepoUserName: ctx.Repo.Owner.Name,
@@ -166,180 +149,6 @@ func redirect(ctx *context.Context) {
})
}
-// loadBranches loads branches from the repository limited by page & pageSize.
-// NOTE: May write to context on error.
-func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, int) {
- defaultBranch, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.Repository.DefaultBranch)
- if err != nil {
- if !git.IsErrBranchNotExist(err) {
- log.Error("loadBranches: get default branch: %v", err)
- ctx.ServerError("GetDefaultBranch", err)
- return nil, nil, 0
- }
- log.Warn("loadBranches: missing default branch %s for %-v", ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository)
- }
-
- rawBranches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, limit)
- if err != nil {
- log.Error("GetBranches: %v", err)
- ctx.ServerError("GetBranches", err)
- return nil, nil, 0
- }
-
- rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
- if err != nil {
- ctx.ServerError("FindRepoProtectedBranchRules", err)
- return nil, nil, 0
- }
-
- repoIDToRepo := map[int64]*repo_model.Repository{}
- repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository
-
- repoIDToGitRepo := map[int64]*git.Repository{}
- repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo
-
- var branches []*Branch
- for i := range rawBranches {
- if defaultBranch != nil && rawBranches[i].Name == defaultBranch.Name {
- // Skip default branch
- continue
- }
-
- branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
- if branch == nil {
- return nil, nil, 0
- }
-
- branches = append(branches, branch)
- }
-
- var defaultBranchBranch *Branch
- if defaultBranch != nil {
- // Always add the default branch
- log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name)
- defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
- branches = append(branches, defaultBranchBranch)
- }
-
- if ctx.Repo.CanWrite(unit.TypeCode) {
- deletedBranches, err := getDeletedBranches(ctx)
- if err != nil {
- ctx.ServerError("getDeletedBranches", err)
- return nil, nil, 0
- }
- branches = append(branches, deletedBranches...)
- }
-
- return defaultBranchBranch, branches, totalNumOfBranches
-}
-
-func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules,
- repoIDToRepo map[int64]*repo_model.Repository,
- repoIDToGitRepo map[int64]*git.Repository,
-) *Branch {
- log.Trace("loadOneBranch: '%s'", rawBranch.Name)
-
- commit, err := rawBranch.GetCommit()
- if err != nil {
- ctx.ServerError("GetCommit", err)
- return nil
- }
-
- branchName := rawBranch.Name
- p := protectedBranches.GetFirstMatched(branchName)
- isProtected := p != nil
-
- divergence := &git.DivergeObject{
- Ahead: -1,
- Behind: -1,
- }
- if defaultBranch != nil {
- divergence, err = files_service.CountDivergingCommits(ctx, ctx.Repo.Repository, git.BranchPrefix+branchName)
- if err != nil {
- log.Error("CountDivergingCommits", err)
- }
- }
-
- pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName)
- if err != nil {
- ctx.ServerError("GetLatestPullRequestByHeadInfo", err)
- return nil
- }
- headCommit := commit.ID.String()
-
- mergeMovedOn := false
- if pr != nil {
- pr.HeadRepo = ctx.Repo.Repository
- if err := pr.LoadIssue(ctx); err != nil {
- ctx.ServerError("LoadIssue", err)
- return nil
- }
- if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
- pr.BaseRepo = repo
- } else if err := pr.LoadBaseRepo(ctx); err != nil {
- ctx.ServerError("LoadBaseRepo", err)
- return nil
- } else {
- repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
- }
- pr.Issue.Repo = pr.BaseRepo
-
- if pr.HasMerged {
- baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
- if !ok {
- baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
- if err != nil {
- ctx.ServerError("OpenRepository", err)
- return nil
- }
- defer baseGitRepo.Close()
- repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
- }
- pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
- if err != nil && !git.IsErrNotExist(err) {
- ctx.ServerError("GetBranchCommitID", err)
- return nil
- }
- if err == nil && headCommit != pullCommit {
- // the head has moved on from the merge - we shouldn't delete
- mergeMovedOn = true
- }
- }
- }
-
- isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName
- return &Branch{
- Name: branchName,
- Commit: commit,
- IsProtected: isProtected,
- IsIncluded: isIncluded,
- CommitsAhead: divergence.Ahead,
- CommitsBehind: divergence.Behind,
- LatestPullRequest: pr,
- MergeMovedOn: mergeMovedOn,
- }
-}
-
-func getDeletedBranches(ctx *context.Context) ([]*Branch, error) {
- branches := []*Branch{}
-
- deletedBranches, err := git_model.GetDeletedBranches(ctx, ctx.Repo.Repository.ID)
- if err != nil {
- return branches, err
- }
-
- for i := range deletedBranches {
- deletedBranches[i].LoadUser(ctx)
- branches = append(branches, &Branch{
- Name: deletedBranches[i].Name,
- IsDeleted: true,
- DeletedBranch: deletedBranches[i],
- })
- }
-
- return branches, nil
-}
-
// CreateBranch creates new branch in repository
func CreateBranch(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewBranchForm)
@@ -380,13 +189,13 @@ func CreateBranch(ctx *context.Context) {
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
- if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
+ if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
- if models.IsErrBranchNameConflict(err) {
- e := err.(models.ErrBranchNameConflict)
+ if git_model.IsErrBranchNameConflict(err) {
+ e := err.(git_model.ErrBranchNameConflict)
ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go
index 48bc6959e0..5017d02252 100644
--- a/routers/web/repo/cherry_pick.go
+++ b/routers/web/repo/cherry_pick.go
@@ -9,6 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
+ git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@@ -124,9 +125,9 @@ func CherryPickPost(ctx *context.Context) {
// First lets try the simple plain read-tree -m approach
opts.Content = sha
if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.Doer, form.Revert, opts); err != nil {
- if models.IsErrBranchAlreadyExists(err) {
+ if git_model.IsErrBranchAlreadyExists(err) {
// User has specified a branch that already exists
- branchErr := err.(models.ErrBranchAlreadyExists)
+ branchErr := err.(git_model.ErrBranchAlreadyExists)
ctx.Data["Err_NewBranchName"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
return
@@ -161,9 +162,9 @@ func CherryPickPost(ctx *context.Context) {
ctx.Data["FileContent"] = opts.Content
if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
- if models.IsErrBranchAlreadyExists(err) {
+ if git_model.IsErrBranchAlreadyExists(err) {
// User has specified a branch that already exists
- branchErr := err.(models.ErrBranchAlreadyExists)
+ branchErr := err.(git_model.ErrBranchAlreadyExists)
ctx.Data["Err_NewBranchName"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
return
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index 0ca1f90547..7089c219ad 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -16,6 +16,7 @@ import (
"path/filepath"
"strings"
+ "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
@@ -683,7 +684,13 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
}
defer gitRepo.Close()
- branches, _, err = gitRepo.GetBranchNames(0, 0)
+ branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
+ RepoID: repo.ID,
+ ListOptions: db.ListOptions{
+ ListAll: true,
+ },
+ IsDeletedBranch: util.OptionalBoolFalse,
+ })
if err != nil {
return nil, nil, err
}
@@ -734,7 +741,13 @@ func CompareDiff(ctx *context.Context) {
return
}
- headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0)
+ headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
+ RepoID: ci.HeadRepo.ID,
+ ListOptions: db.ListOptions{
+ ListAll: true,
+ },
+ IsDeletedBranch: util.OptionalBoolFalse,
+ })
if err != nil {
ctx.ServerError("GetBranches", err)
return
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 2fea8a9532..a63b08126c 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -327,10 +327,10 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
} else {
ctx.Error(http.StatusInternalServerError, err.Error())
}
- } else if models.IsErrBranchAlreadyExists(err) {
+ } else if git_model.IsErrBranchAlreadyExists(err) {
// For when a user specifies a new branch that already exists
ctx.Data["Err_NewBranchName"] = true
- if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
+ if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok {
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
} else {
ctx.Error(http.StatusInternalServerError, err.Error())
@@ -529,9 +529,9 @@ func DeleteFilePost(ctx *context.Context) {
} else {
ctx.Error(http.StatusInternalServerError, err.Error())
}
- } else if models.IsErrBranchAlreadyExists(err) {
+ } else if git_model.IsErrBranchAlreadyExists(err) {
// For when a user specifies a new branch that already exists
- if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
+ if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok {
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form)
} else {
ctx.Error(http.StatusInternalServerError, err.Error())
@@ -731,10 +731,10 @@ func UploadFilePost(ctx *context.Context) {
} else if git.IsErrBranchNotExist(err) {
branchErr := err.(git.ErrBranchNotExist)
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form)
- } else if models.IsErrBranchAlreadyExists(err) {
+ } else if git_model.IsErrBranchAlreadyExists(err) {
// For when a user specifies a new branch that already exists
ctx.Data["Err_NewBranchName"] = true
- branchErr := err.(models.ErrBranchAlreadyExists)
+ branchErr := err.(git_model.ErrBranchAlreadyExists)
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form)
} else if git.IsErrPushOutOfDate(err) {
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)
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index a0dd14e314..4f14cc381f 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -785,7 +785,13 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull
return nil
}
- brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
+ brs, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
+ RepoID: ctx.Repo.Repository.ID,
+ ListOptions: db.ListOptions{
+ ListAll: true,
+ },
+ IsDeletedBranch: util.OptionalBoolFalse,
+ })
if err != nil {
ctx.ServerError("GetBranches", err)
return nil
diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go
index efb4662496..5faf9f4fa9 100644
--- a/routers/web/repo/patch.go
+++ b/routers/web/repo/patch.go
@@ -7,6 +7,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
+ git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@@ -94,9 +95,9 @@ func NewDiffPatchPost(ctx *context.Context) {
Content: strings.ReplaceAll(form.Content, "\r", ""),
})
if err != nil {
- if models.IsErrBranchAlreadyExists(err) {
+ if git_model.IsErrBranchAlreadyExists(err) {
// User has specified a branch that already exists
- branchErr := err.(models.ErrBranchAlreadyExists)
+ branchErr := err.(git_model.ErrBranchAlreadyExists)
ctx.Data["Err_NewBranchName"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
return
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index f2a58a35a7..950979a6ed 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -1493,7 +1493,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
"error": err.Error(),
"user_error": errorMessage,
})
- } else if models.IsErrBranchesEqual(err) {
+ } else if git_model.IsErrBranchesEqual(err) {
errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
ctx.Flash.Error(errorMessage)
diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go
index 1a944799c2..ae8a799f06 100644
--- a/routers/web/repo/setting_protected_branch.go
+++ b/routers/web/repo/setting_protected_branch.go
@@ -286,7 +286,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
}
// FIXME: since we only need to recheck files protected rules, we could improve this
- matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
+ matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
if err != nil {
ctx.ServerError("FindAllMatchedBranches", err)
return
diff --git a/services/convert/convert.go b/services/convert/convert.go
index bce0e7ba21..25c89747e3 100644
--- a/services/convert/convert.go
+++ b/services/convert/convert.go
@@ -50,7 +50,7 @@ func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email {
}
// ToBranch convert a git.Commit and git.Branch to an api.Branch
-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) {
+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) {
if bp == nil {
var hasPerm bool
var canPush bool
@@ -65,11 +65,11 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c
if err != nil {
return nil, err
}
- canPush = issues_model.CanMaintainerWriteToBranch(perms, b.Name, user)
+ canPush = issues_model.CanMaintainerWriteToBranch(perms, branchName, user)
}
return &api.Branch{
- Name: b.Name,
+ Name: branchName,
Commit: ToPayloadCommit(ctx, repo, c),
Protected: false,
RequiredApprovals: 0,
@@ -81,7 +81,7 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c
}
branch := &api.Branch{
- Name: b.Name,
+ Name: branchName,
Commit: ToPayloadCommit(ctx, repo, c),
Protected: true,
RequiredApprovals: bp.RequiredApprovals,
diff --git a/services/migrations/dump.go b/services/migrations/dump.go
index cc8518d4a2..729112bcd2 100644
--- a/services/migrations/dump.go
+++ b/services/migrations/dump.go
@@ -642,7 +642,7 @@ func (g *RepositoryDumper) Finish() error {
// DumpRepository dump repository according MigrateOptions to a local directory
func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error {
- doer, err := user_model.GetAdminUser()
+ doer, err := user_model.GetAdminUser(ctx)
if err != nil {
return err
}
@@ -705,7 +705,7 @@ func updateOptionsUnits(opts *base.MigrateOptions, units []string) error {
// RestoreRepository restore a repository from the disk directory
func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string, validation bool) error {
- doer, err := user_model.GetAdminUser()
+ doer, err := user_model.GetAdminUser(ctx)
if err != nil {
return err
}
diff --git a/services/pull/pull.go b/services/pull/pull.go
index f44e690ab7..0f562b9ee3 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -170,7 +170,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
return err
}
if branchesEqual {
- return models.ErrBranchesEqual{
+ return git_model.ErrBranchesEqual{
HeadBranchName: pr.HeadBranch,
BaseBranchName: targetBranch,
}
@@ -338,7 +338,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
for _, pr := range prs {
divergence, err := GetDiverging(ctx, pr)
if err != nil {
- if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
+ if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
} else {
log.Error("GetDiverging: %v", err)
diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go
index 1464707806..db32940e38 100644
--- a/services/pull/temp_repo.go
+++ b/services/pull/temp_repo.go
@@ -11,7 +11,7 @@ import (
"path/filepath"
"strings"
- "code.gitea.io/gitea/models"
+ git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
@@ -181,7 +181,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest)
Run(prCtx.RunOpts()); err != nil {
cancel()
if !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
- return nil, nil, models.ErrBranchDoesNotExist{
+ return nil, nil, git_model.ErrBranchNotExist{
BranchName: pr.HeadBranch,
}
}
diff --git a/services/pull/update.go b/services/pull/update.go
index b977dbdba9..bc8c4a25e5 100644
--- a/services/pull/update.go
+++ b/services/pull/update.go
@@ -7,7 +7,6 @@ import (
"context"
"fmt"
- "code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
@@ -168,7 +167,7 @@ func GetDiverging(ctx context.Context, pr *issues_model.PullRequest) (*git.Diver
log.Trace("GetDiverging[%-v]: compare commits", pr)
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
if err != nil {
- if !models.IsErrBranchDoesNotExist(err) {
+ if !git_model.IsErrBranchNotExist(err) {
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
}
return nil, err
diff --git a/services/repository/adopt.go b/services/repository/adopt.go
index e07ff35041..f95fb5988f 100644
--- a/services/repository/adopt.go
+++ b/services/repository/adopt.go
@@ -12,6 +12,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
+ git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
@@ -146,7 +147,15 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r
}
}
}
- branches, _, _ := gitRepo.GetBranchNames(0, 0)
+
+ branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
+ RepoID: repo.ID,
+ ListOptions: db.ListOptions{
+ ListAll: true,
+ },
+ IsDeletedBranch: util.OptionalBoolFalse,
+ })
+
found := false
hasDefault := false
hasMaster := false
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 4e560786db..11a8b20531 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -10,13 +10,21 @@ import (
"strings"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
+ issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
+ "code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/util"
+ files_service "code.gitea.io/gitea/services/repository/files"
+
+ "xorm.io/builder"
)
// CreateNewBranch creates a new repository branch
@@ -27,7 +35,7 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode
}
if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) {
- return models.ErrBranchDoesNotExist{
+ return git_model.ErrBranchNotExist{
BranchName: oldBranchName,
}
}
@@ -40,16 +48,165 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
- return fmt.Errorf("Push: %w", err)
+ return fmt.Errorf("push: %w", err)
}
return nil
}
-// GetBranches returns branches from the repository, skipping skip initial branches and
-// returning at most limit branches, or all branches if limit is 0.
-func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit int) ([]*git.Branch, int, error) {
- return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit)
+// Branch contains the branch information
+type Branch struct {
+ DBBranch *git_model.Branch
+ IsProtected bool
+ IsIncluded bool
+ CommitsAhead int
+ CommitsBehind int
+ LatestPullRequest *issues_model.PullRequest
+ MergeMovedOn bool
+}
+
+// LoadBranches loads branches from the repository limited by page & pageSize.
+func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, page, pageSize int) (*Branch, []*Branch, int64, error) {
+ defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ branchOpts := git_model.FindBranchOptions{
+ RepoID: repo.ID,
+ IsDeletedBranch: isDeletedBranch,
+ ListOptions: db.ListOptions{
+ Page: page,
+ PageSize: pageSize,
+ },
+ }
+
+ totalNumOfBranches, err := git_model.CountBranches(ctx, branchOpts)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ branchOpts.ExcludeBranchNames = []string{repo.DefaultBranch}
+
+ dbBranches, err := git_model.FindBranches(ctx, branchOpts)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ if err := dbBranches.LoadDeletedBy(ctx); err != nil {
+ return nil, nil, 0, err
+ }
+ if err := dbBranches.LoadPusher(ctx); err != nil {
+ return nil, nil, 0, err
+ }
+
+ rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ repoIDToRepo := map[int64]*repo_model.Repository{}
+ repoIDToRepo[repo.ID] = repo
+
+ repoIDToGitRepo := map[int64]*git.Repository{}
+ repoIDToGitRepo[repo.ID] = gitRepo
+
+ branches := make([]*Branch, 0, len(dbBranches))
+ for i := range dbBranches {
+ branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo)
+ if err != nil {
+ return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
+ }
+
+ branches = append(branches, branch)
+ }
+
+ // Always add the default branch
+ log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name)
+ defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo)
+ if err != nil {
+ return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
+ }
+
+ return defaultBranch, branches, totalNumOfBranches, nil
+}
+
+func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules,
+ repoIDToRepo map[int64]*repo_model.Repository,
+ repoIDToGitRepo map[int64]*git.Repository,
+) (*Branch, error) {
+ log.Trace("loadOneBranch: '%s'", dbBranch.Name)
+
+ branchName := dbBranch.Name
+ p := protectedBranches.GetFirstMatched(branchName)
+ isProtected := p != nil
+
+ divergence := &git.DivergeObject{
+ Ahead: -1,
+ Behind: -1,
+ }
+
+ // it's not default branch
+ if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
+ var err error
+ divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
+ if err != nil {
+ log.Error("CountDivergingCommits: %v", err)
+ }
+ }
+
+ pr, err := issues_model.GetLatestPullRequestByHeadInfo(repo.ID, branchName)
+ if err != nil {
+ return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err)
+ }
+ headCommit := dbBranch.CommitID
+
+ mergeMovedOn := false
+ if pr != nil {
+ pr.HeadRepo = repo
+ if err := pr.LoadIssue(ctx); err != nil {
+ return nil, fmt.Errorf("LoadIssue: %v", err)
+ }
+ if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
+ pr.BaseRepo = repo
+ } else if err := pr.LoadBaseRepo(ctx); err != nil {
+ return nil, fmt.Errorf("LoadBaseRepo: %v", err)
+ } else {
+ repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
+ }
+ pr.Issue.Repo = pr.BaseRepo
+
+ if pr.HasMerged {
+ baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
+ if !ok {
+ baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
+ if err != nil {
+ return nil, fmt.Errorf("OpenRepository: %v", err)
+ }
+ defer baseGitRepo.Close()
+ repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
+ }
+ pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
+ if err != nil && !git.IsErrNotExist(err) {
+ return nil, fmt.Errorf("GetBranchCommitID: %v", err)
+ }
+ if err == nil && headCommit != pullCommit {
+ // the head has moved on from the merge - we shouldn't delete
+ mergeMovedOn = true
+ }
+ }
+ }
+
+ isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName
+ return &Branch{
+ DBBranch: dbBranch,
+ IsProtected: isProtected,
+ IsIncluded: isIncluded,
+ CommitsAhead: divergence.Ahead,
+ CommitsBehind: divergence.Behind,
+ LatestPullRequest: pr,
+ MergeMovedOn: mergeMovedOn,
+ }, nil
}
func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) {
@@ -62,17 +219,17 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
branchRefName := strings.TrimPrefix(refName, git.BranchPrefix)
switch {
case branchRefName == name:
- return models.ErrBranchAlreadyExists{
+ return git_model.ErrBranchAlreadyExists{
BranchName: name,
}
// If branchRefName like a/b but we want to create a branch named a then we have a conflict
case strings.HasPrefix(branchRefName, name+"/"):
- return models.ErrBranchNameConflict{
+ return git_model.ErrBranchNameConflict{
BranchName: branchRefName,
}
// Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
case strings.HasPrefix(name, branchRefName+"/"):
- return models.ErrBranchNameConflict{
+ return git_model.ErrBranchNameConflict{
BranchName: branchRefName,
}
case refName == git.TagPrefix+name:
@@ -101,7 +258,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
- return fmt.Errorf("Push: %w", err)
+ return fmt.Errorf("push: %w", err)
}
return nil
@@ -169,13 +326,28 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return git_model.ErrBranchIsProtected
}
+ rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
+ if err != nil {
+ return fmt.Errorf("GetBranch: %vc", err)
+ }
+
+ if rawBranch.IsDeleted {
+ return nil
+ }
+
commit, err := gitRepo.GetBranchCommit(branchName)
if err != nil {
return err
}
- if err := gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
- Force: true,
+ if err := db.WithTx(ctx, func(ctx context.Context) error {
+ if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
+ return err
+ }
+
+ return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
+ Force: true,
+ })
}); err != nil {
return err
}
@@ -196,3 +368,45 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return nil
}
+
+type BranchSyncOptions struct {
+ RepoID int64
+}
+
+// branchSyncQueue represents a queue to handle branch sync jobs.
+var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions]
+
+func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions {
+ for _, opts := range items {
+ _, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0)
+ if err != nil {
+ log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err)
+ }
+ }
+ return nil
+}
+
+func addRepoToBranchSyncQueue(repoID, doerID int64) error {
+ return branchSyncQueue.Push(&BranchSyncOptions{
+ RepoID: repoID,
+ })
+}
+
+func initBranchSyncQueue(ctx context.Context) error {
+ branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync)
+ if branchSyncQueue == nil {
+ return errors.New("unable to create branch_sync queue")
+ }
+ go graceful.GetManager().RunWithCancel(branchSyncQueue)
+
+ return nil
+}
+
+func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error {
+ if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error {
+ return addRepoToBranchSyncQueue(repo.ID, doerID)
+ }); err != nil {
+ return fmt.Errorf("run sync all branches failed: %v", err)
+ }
+ return nil
+}
diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go
index 19d089b9e4..fdf0b32f1a 100644
--- a/services/repository/files/patch.go
+++ b/services/repository/files/patch.go
@@ -58,7 +58,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
if opts.NewBranch != opts.OldBranch {
existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
if existingBranch != nil {
- return models.ErrBranchAlreadyExists{
+ return git_model.ErrBranchAlreadyExists{
BranchName: opts.NewBranch,
}
}
diff --git a/services/repository/files/update.go b/services/repository/files/update.go
index 01bf2ace00..737f914dd6 100644
--- a/services/repository/files/update.go
+++ b/services/repository/files/update.go
@@ -197,7 +197,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
if opts.NewBranch != opts.OldBranch {
existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
if existingBranch != nil {
- return nil, models.ErrBranchAlreadyExists{
+ return nil, git_model.ErrBranchAlreadyExists{
BranchName: opts.NewBranch,
}
}
diff --git a/services/repository/fork.go b/services/repository/fork.go
index fb93b10f1c..59aa173373 100644
--- a/services/repository/fork.go
+++ b/services/repository/fork.go
@@ -157,7 +157,15 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
if err = repo_module.CreateDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err)
}
- return nil
+
+ gitRepo, err := git.OpenRepository(txCtx, repo.RepoPath())
+ if err != nil {
+ return fmt.Errorf("OpenRepository: %w", err)
+ }
+ defer gitRepo.Close()
+
+ _, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID)
+ return err
})
needsRollbackInPanic = false
if err != nil {
diff --git a/services/repository/push.go b/services/repository/push.go
index e559d3f904..7e7069f580 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -93,7 +93,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
defer gitRepo.Close()
if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
- log.Error("Failed to update size for repository: %v", err)
+ return fmt.Errorf("Failed to update size for repository: %v", err)
}
addTags := make([]string, 0, len(optsList))
@@ -259,8 +259,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
notification.NotifyPushCommits(ctx, pusher, repo, opts, commits)
- if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil {
- log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err)
+ if err = git_model.UpdateBranch(ctx, repo.ID, branch, newCommit.ID.String(), newCommit.CommitMessage, opts.PusherID, newCommit.Committer.When); err != nil {
+ return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err)
}
// Cache for big repository
@@ -273,8 +273,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
// close all related pulls
log.Error("close related pull request failed: %v", err)
}
- if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, opts.OldCommitID, pusher.ID); err != nil {
- log.Warn("AddDeletedBranch: %v", err)
+
+ if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, pusher.ID); err != nil {
+ return fmt.Errorf("AddDeletedBranch %s:%s failed: %v", repo.FullName(), branch, err)
}
}
diff --git a/services/repository/repository.go b/services/repository/repository.go
index 0914a8f6ec..cd3658dcd8 100644
--- a/services/repository/repository.go
+++ b/services/repository/repository.go
@@ -17,6 +17,7 @@ import (
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
@@ -100,7 +101,10 @@ func Init() error {
}
system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath)
system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath())
- return initPushQueue()
+ if err := initPushQueue(); err != nil {
+ return err
+ }
+ return initBranchSyncQueue(graceful.GetManager().ShutdownContext())
}
// UpdateRepository updates a repository
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index 9d503a19b0..1dd920c2dd 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -61,6 +61,10 @@
<td>{{.locale.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
<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>
</tr>
+ <tr>
+ <td>{{.locale.Tr "admin.dashboard.sync_repo_branches"}}</td>
+ <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>
+ </tr>
</tbody>
</table>
</form>
diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl
index d68feb43b7..cf81f46132 100644
--- a/templates/repo/branch/list.tmpl
+++ b/templates/repo/branch/list.tmpl
@@ -22,29 +22,29 @@
{{if .DefaultBranchBranch.IsProtected}}
{{svg "octicon-shield-lock"}}
{{end}}
- <a href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranch}}">{{.DefaultBranch}}</a>
- <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>
+ <a href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
+ <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>
</td>
<td class="right aligned overflow-visible">
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
<button class="btn interact-bg show-create-branch-modal gt-p-3"
data-modal="#create-branch-modal"
- data-branch-from="{{$.DefaultBranch}}"
- data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranch}}"
- data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" ($.DefaultBranch)}}"
+ data-branch-from="{{$.DefaultBranchBranch}}"
+ data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}"
+ data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" ($.DefaultBranchBranch.DBBranch.Name)}}"
>
{{svg "octicon-git-branch"}}
</button>
{{end}}
{{if .EnableFeed}}
- <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranch}}">{{svg "octicon-rss"}}</a>
+ <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-rss"}}</a>
{{end}}
{{if not $.DisableDownloadSourceArchives}}
- <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}">
+ <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranchBranch.DBBranch.Name)}}">
{{svg "octicon-download"}}
<div class="menu">
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
+ <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
+ <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
</div>
</div>
{{end}}
@@ -52,8 +52,8 @@
<button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
data-is-default-branch="true"
data-modal="#rename-branch-modal"
- data-old-branch-name="{{$.DefaultBranch}}"
- data-tooltip-content="{{$.locale.Tr "repo.branch.rename" ($.DefaultBranch)}}"
+ data-old-branch-name="{{$.DefaultBranchBranch}}"
+ data-tooltip-content="{{$.locale.Tr "repo.branch.rename" ($.DefaultBranchBranch.DBBranch.Name)}}"
>
{{svg "octicon-pencil"}}
</button>
@@ -65,7 +65,7 @@
</div>
{{end}}
- {{if gt (len .Branches) 1}}
+ {{if .Branches}}
<h4 class="ui top attached header">
{{.locale.Tr "repo.branches"}}
</h4>
@@ -73,112 +73,110 @@
<table class="ui very basic striped fixed table single line">
<tbody>
{{range .Branches}}
- {{if ne .Name $.DefaultBranch}}
- <tr>
- <td class="six wide">
- {{if .IsDeleted}}
- <s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a></s>
- <p class="info">{{$.locale.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.locale}}</p>
+ <tr>
+ <td class="eight wide">
+ {{if .DBBranch.IsDeleted}}
+ <s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a></s>
+ <p class="info">{{$.locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{TimeSinceUnix .DBBranch.DeletedUnix $.locale}}</p>
+ {{else}}
+ {{if .IsProtected}}
+ {{svg "octicon-shield-lock"}}
+ {{end}}
+ <a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
+ <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>
+ {{end}}
+ </td>
+ <td class="two wide ui">
+ {{if and (not .DBBranch.IsDeleted) $.DefaultBranchBranch}}
+ <div class="commit-divergence">
+ <div class="bar-group">
+ <div class="count count-behind">{{.CommitsBehind}}</div>
+ {{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}}
+ <div class="bar bar-behind" style="width: {{Eval 100 "*" .CommitsBehind "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
+ </div>
+ <div class="bar-group">
+ <div class="count count-ahead">{{.CommitsAhead}}</div>
+ <div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
+ </div>
+ </div>
+ {{end}}
+ </td>
+ <td class="two wide right aligned">
+ {{if not .LatestPullRequest}}
+ {{if .IsIncluded}}
+ <span class="ui orange large label" data-tooltip-content="{{$.locale.Tr "repo.branch.included_desc"}}">
+ {{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}}
+ </span>
+ {{else if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
+ <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .DBBranch.Name}}">
+ <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>
+ </a>
+ {{end}}
+ {{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
+ {{if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
+ <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}">
+ <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>
+ </a>
+ {{end}}
{{else}}
- {{if .IsProtected}}
- {{svg "octicon-shield-lock"}}
+ <a href="{{.LatestPullRequest.Issue.Link}}" class="gt-vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
+ {{if .LatestPullRequest.HasMerged}}
+ <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>
+ {{else if .LatestPullRequest.Issue.IsClosed}}
+ <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>
+ {{else}}
+ <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>
{{end}}
- <a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a>
- <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>
{{end}}
- </td>
- <td class="three wide ui">
- {{if and (not .IsDeleted) $.DefaultBranchBranch}}
- <div class="commit-divergence">
- <div class="bar-group">
- <div class="count count-behind">{{.CommitsBehind}}</div>
- {{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}}
- <div class="bar bar-behind" style="width: {{Eval 100 "*" .CommitsBehind "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
- </div>
- <div class="bar-group">
- <div class="count count-ahead">{{.CommitsAhead}}</div>
- <div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
+ </td>
+ <td class="three wide right aligned overflow-visible">
+ {{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted)}}
+ <button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal"
+ data-branch-from="{{.DBBranch.Name}}"
+ data-branch-from-urlcomponent="{{PathEscapeSegments .DBBranch.Name}}"
+ data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" .DBBranch.Name}}"
+ data-modal="#create-branch-modal" data-name="{{.DBBranch.Name}}"
+ >
+ {{svg "octicon-git-branch"}}
+ </button>
+ {{end}}
+ {{if $.EnableFeed}}
+ <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DBBranch.Name}}">{{svg "octicon-rss"}}</a>
+ {{end}}
+ {{if and (not .DBBranch.IsDeleted) (not $.DisableDownloadSourceArchives)}}
+ <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.DBBranch.Name)}}">
+ {{svg "octicon-download"}}
+ <div class="menu">
+ <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
+ <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
</div>
</div>
- {{end}}
- </td>
- <td class="three wide right aligned">
- {{if not .LatestPullRequest}}
- {{if .IsIncluded}}
- <span class="ui orange large label" data-tooltip-content="{{$.locale.Tr "repo.branch.included_desc"}}">
- {{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}}
+ {{end}}
+ {{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted) (not $.IsMirror)}}
+ <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
+ data-is-default-branch="false"
+ data-old-branch-name="{{.DBBranch.Name}}"
+ data-modal="#rename-branch-modal"
+ data-tooltip-content="{{$.locale.Tr "repo.branch.rename" (.DBBranch.Name)}}"
+ >
+ {{svg "octicon-pencil"}}
+ </button>
+ {{end}}
+ {{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
+ {{if .DBBranch.IsDeleted}}
+ <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)}}">
+ <span class="text blue">
+ {{svg "octicon-reply"}}
</span>
- {{else if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
- <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .Name}}">
- <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>
- </a>
- {{end}}
- {{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
- {{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
- <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}">
- <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>
- </a>
- {{end}}
- {{else}}
- <a href="{{.LatestPullRequest.Issue.Link}}" class="gt-vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
- {{if .LatestPullRequest.HasMerged}}
- <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>
- {{else if .LatestPullRequest.Issue.IsClosed}}
- <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>
- {{else}}
- <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>
- {{end}}
- {{end}}
- </td>
- <td class="three wide right aligned overflow-visible">
- {{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
- <button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal"
- data-branch-from="{{.Name}}"
- data-branch-from-urlcomponent="{{PathEscapeSegments .Name}}"
- data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" .Name}}"
- data-modal="#create-branch-modal" data-name="{{.Name}}"
- >
- {{svg "octicon-git-branch"}}
</button>
- {{end}}
- {{if $.EnableFeed}}
- <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .Name}}">{{svg "octicon-rss"}}</a>
- {{end}}
- {{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}}
- <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}">
- {{svg "octicon-download"}}
- <div class="menu">
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
- </div>
- </div>
- {{end}}
- {{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted) (not $.IsMirror)}}
- <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
- data-is-default-branch="false"
- data-old-branch-name="{{.Name}}"
- data-modal="#rename-branch-modal"
- data-tooltip-content="{{$.locale.Tr "repo.branch.rename" (.Name)}}"
- >
- {{svg "octicon-pencil"}}
+ {{else}}
+ <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}}">
+ {{svg "octicon-trash"}}
</button>
{{end}}
- {{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
- {{if .IsDeleted}}
- <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)}}">
- <span class="text blue">
- {{svg "octicon-reply"}}
- </span>
- </button>
- {{else}}
- <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}}">
- {{svg "octicon-trash"}}
- </button>
- {{end}}
- {{end}}
- </td>
- </tr>
- {{end}}
+ {{end}}
+ </td>
+ </tr>
{{end}}
</tbody>
</table>
diff --git a/tests/integration/rename_branch_test.go b/tests/integration/rename_branch_test.go
index 9a55193ccf..703fc243a4 100644
--- a/tests/integration/rename_branch_test.go
+++ b/tests/integration/rename_branch_test.go
@@ -7,13 +7,19 @@ import (
"net/http"
"testing"
+ git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestRenameBranch(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: 1, Name: "master"})
+
// get branch setting page
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/settings/branches")