diff --git a/go.mod b/go.mod index 29a17b16abf57..5cd618a26d4de 100644 --- a/go.mod +++ b/go.mod @@ -120,7 +120,7 @@ require ( mvdan.cc/xurls/v2 v2.5.0 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 xorm.io/builder v0.3.12 - xorm.io/xorm v1.3.3-0.20230623150031-18f8e7a86c75 + xorm.io/xorm v1.3.3-0.20230701034009-d29fe4993351 ) require ( diff --git a/go.sum b/go.sum index 9c6250c1016c0..44326986978d5 100644 --- a/go.sum +++ b/go.sum @@ -1887,5 +1887,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1: xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM= xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= -xorm.io/xorm v1.3.3-0.20230623150031-18f8e7a86c75 h1:ReBAlO50dCIXCWF8Gbi0ZRa62AGAwCJNCPaUNUa7JSg= -xorm.io/xorm v1.3.3-0.20230623150031-18f8e7a86c75/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw= +xorm.io/xorm v1.3.3-0.20230701034009-d29fe4993351 h1:zjbf38FKz2hcqIspgMXYhwhGfy4iXZ/7YBqyv9ZNz8I= +xorm.io/xorm v1.3.3-0.20230701034009-d29fe4993351/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw= diff --git a/models/git/branch.go b/models/git/branch.go index d57b72719ce80..b44709d162a8f 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -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. @@ -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 @@ -117,6 +119,22 @@ type Branch struct { UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } +// TableCollations git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment +// so we need to set the collation to case-sensitive which is utf8mb4_bin for mysql and Latin1_General_CS_AS for mssql +// the function is supported by xorm +func (b *Branch) TableCollations() []*schemas.Collation { + if setting.Database.Type.IsMySQL() { + return []*schemas.Collation{ + {Name: "utf8mb4_bin", Column: "name"}, + } + } else if setting.Database.Type.IsMSSQL() { + return []*schemas.Collation{ + {Name: "Latin1_General_CS_AS", Column: "name"}, + } + } + return nil +} + func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) { if b.DeletedBy == nil { b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 6599cb9cda3eb..3ade5aeaab718 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -515,6 +515,8 @@ var migrations = []Migration{ NewMigration("Alter Actions Artifact table", v1_21.AlterActionArtifactTable), // v266 -> v267 NewMigration("Reduce commit status", v1_21.ReduceCommitStatus), + // v267 -> v268 + NewMigration("Change Branch name column collation to support case sensitive", v1_21.BranchColumnNameCollation), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_21/v265_test.go b/models/migrations/v1_21/v265_test.go new file mode 100644 index 0000000000000..37aaf938a49e3 --- /dev/null +++ b/models/migrations/v1_21/v265_test.go @@ -0,0 +1,29 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/assert" +) + +func Test_BranchColumnNameCollation(t *testing.T) { + type Branch struct { + ID int64 + RepoID int64 `xorm:"Unique(s)"` + Name string `xorm:"Unique(s) NOT NULL"` + } + + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(Branch)) + defer deferable() + if x == nil || t.Failed() { + return + } + + assert.NoError(t, BranchColumnNameCollation(x)) +} diff --git a/models/migrations/v1_21/v267.go b/models/migrations/v1_21/v267.go new file mode 100644 index 0000000000000..376a2f39f07d6 --- /dev/null +++ b/models/migrations/v1_21/v267.go @@ -0,0 +1,21 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" +) + +func BranchColumnNameCollation(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() { + _, err := x.Exec("ALTER TABLE [branch] ALTER COLUMN [name] nvarchar(255) COLLATE Latin1_General_CS_AS NOT NULL;") + return err + } + return nil +} diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index dd81ec22ddc6a..96f20a148dfed 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -135,12 +135,30 @@ func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { NewBranch: "branch_2", ExpectedHTTPStatus: http.StatusCreated, }, + // Trying to create a Case insensive branch name + { + OldBranch: "new_branch_from_master_1", + NewBranch: "Branch_2", + ExpectedHTTPStatus: http.StatusCreated, + }, // Trying to create from a branch which does not exist { OldBranch: "does_not_exist", NewBranch: "new_branch_from_non_existent", ExpectedHTTPStatus: http.StatusNotFound, }, + // Trying to create a branch with utf8 + { + OldBranch: "master", + NewBranch: "主版本", + ExpectedHTTPStatus: http.StatusCreated, + }, + // Trying to create a branch with utf8 + /*{ + OldBranch: "master", + NewBranch: "My ❤️", + ExpectedHTTPStatus: http.StatusCreated, + },*/ } for _, test := range testCases { session := ctx.Session