Skip to content

Make table column branch.name case sensitive #28633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions models/git/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"

"xorm.io/builder"
"xorm.io/xorm/schemas"
)

// ErrBranchNotExist represents an error that branch with such name does not exist.
Expand Down Expand Up @@ -103,7 +105,7 @@ func (err ErrBranchesEqual) Unwrap() error {
type Branch struct {
ID int64
RepoID int64 `xorm:"UNIQUE(s)"`
Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment
Name string `xorm:"UNIQUE(s) NOT NULL"`
CommitID string
CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line)
PusherID int64
Expand All @@ -117,6 +119,20 @@ type Branch struct {
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

// TableCollations is to make the "name" column case-sensitve for MySQL/MSSQL
func (b *Branch) TableCollations() []*schemas.Collation {
if setting.Database.Type.IsMySQL() {
return []*schemas.Collation{
{Column: "name", Name: "utf8mb4_bin"},
}
} else if setting.Database.Type.IsMSSQL() {
return []*schemas.Collation{
{Column: "name", Name: "Latin1_General_CS_AS"},
}
}
return nil
}

func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) {
if b.DeletedBy == nil {
b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID)
Expand Down Expand Up @@ -380,7 +396,11 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
// except the indicate branch
func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) {
branches := make(BranchList, 0, 2)
subQuery := builder.Select("head_branch").From("pull_request").
sqlHeadBranch := "head_branch"
if setting.Database.Type.IsMSSQL() {
sqlHeadBranch = "CAST(head_branch AS nvarchar(255)) COLLATE Latin1_General_CS_AS" // FIXME: a dirty hack for MSSQL collation
}
subQuery := builder.Select(sqlHeadBranch).From("pull_request").
InnerJoin("issue", "issue.id = pull_request.issue_id").
Where(builder.Eq{
"pull_request.head_repo_id": repoID,
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,8 @@ var migrations = []Migration{
NewMigration("Add Index to pull_auto_merge.doer_id", v1_22.AddIndexToPullAutoMergeDoerID),
// v283 -> v284
NewMigration("Add combined Index to issue_user.uid and issue_id", v1_22.AddCombinedIndexToIssueUser),
// v284 -> v285
NewMigration("Alter branch name collation to be case-sensitive", v1_22.AlterBranchNameCollation),
}

// GetCurrentDBVersion returns the current db version
Expand Down
16 changes: 8 additions & 8 deletions models/migrations/v1_22/v283_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import (
"testing"

"code.gitea.io/gitea/models/migrations/base"

"github.com/stretchr/testify/assert"
)

func Test_AddCombinedIndexToIssueUser(t *testing.T) {
type IssueUser struct {
UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID.
IssueID int64 `xorm:"INDEX unique(uid_to_issue)"`
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX"` // User ID.
IssueID int64 `xorm:"INDEX"`
IsRead bool
IsMentioned bool
}

// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0, new(IssueUser))
defer deferable()
if x == nil || t.Failed() {
return
}

if err := AddCombinedIndexToIssueUser(x); err != nil {
t.Fatal(err)
}
assert.NoError(t, AddCombinedIndexToIssueUser(x))
}
29 changes: 29 additions & 0 deletions models/migrations/v1_22/v284.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_22 //nolint

import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"

"xorm.io/xorm"
)

func AlterBranchNameCollation(x *xorm.Engine) error {
if setting.Database.Type.IsMySQL() {
_, err := x.Exec("ALTER TABLE branch MODIFY COLUMN `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL")
return err
} else if setting.Database.Type.IsMSSQL() {
if _, err := x.Exec("DROP INDEX UQE_branch_s ON branch"); err != nil {
log.Error("Failed to drop index UQE_branch_s on branch: %v", err) // ignore this error, in case the index has been dropped in previous migration
}
if _, err := x.Exec("ALTER TABLE branch ALTER COLUMN [name] nvarchar(255) COLLATE Latin1_General_CS_AS NOT NULL"); err != nil {
return err
}
if _, err := x.Exec("CREATE UNIQUE NONCLUSTERED INDEX UQE_branch_s ON branch (repo_id ASC, [name] ASC)"); err != nil {
return err
}
}
return nil
}
25 changes: 25 additions & 0 deletions models/migrations/v1_22/v284_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_22 //nolint

import (
"testing"

"code.gitea.io/gitea/models/migrations/base"

"github.com/stretchr/testify/assert"
)

func TestAlterBranchNameCollation(t *testing.T) {
type Branch struct {
ID int64
RepoID int64 `xorm:"UNIQUE(s)"`
Name string `xorm:"UNIQUE(s) NOT NULL"`
}

x, deferable := base.PrepareTestEnv(t, 0, new(Branch))
defer deferable()

assert.NoError(t, AlterBranchNameCollation(x))
}
12 changes: 12 additions & 0 deletions tests/integration/api_branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ func testAPICreateBranches(t *testing.T, giteaURL *url.URL) {
NewBranch: "branch_2",
ExpectedHTTPStatus: http.StatusCreated,
},
// Trying to create a case-sensitive branch name
{
OldBranch: "new_branch_from_master_1",
NewBranch: "Branch_2",
ExpectedHTTPStatus: http.StatusCreated,
},
// Trying to create a branch with UTF8
{
OldBranch: "master",
NewBranch: "test-👀",
ExpectedHTTPStatus: http.StatusCreated,
},
// Trying to create from a branch which does not exist
{
OldBranch: "does_not_exist",
Expand Down