Skip to content

Commit 8956513

Browse files
authored
Merge branch 'main' into lunny/fix_shutdown_issue
2 parents bc710be + 897e48d commit 8956513

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1522
-512
lines changed

custom/conf/app.example.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,10 @@ LEVEL = Info
13431343
;; Dont mistake it for Reactions.
13441344
;CUSTOM_EMOJIS = gitea, codeberg, gitlab, git, github, gogs
13451345
;;
1346+
;; Comma separated list of enabled emojis, for example: smile, thumbsup, thumbsdown
1347+
;; Leave it empty to enable all emojis.
1348+
;ENABLED_EMOJIS =
1349+
;;
13461350
;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
13471351
;DEFAULT_SHOW_FULL_NAME = false
13481352
;;

models/git/commit_status.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,21 @@ import (
3030

3131
// CommitStatus holds a single Status of a single Commit
3232
type CommitStatus struct {
33-
ID int64 `xorm:"pk autoincr"`
34-
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
35-
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
36-
Repo *repo_model.Repository `xorm:"-"`
37-
State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
38-
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
39-
TargetURL string `xorm:"TEXT"`
40-
Description string `xorm:"TEXT"`
41-
ContextHash string `xorm:"VARCHAR(64) index"`
42-
Context string `xorm:"TEXT"`
43-
Creator *user_model.User `xorm:"-"`
33+
ID int64 `xorm:"pk autoincr"`
34+
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
35+
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
36+
Repo *repo_model.Repository `xorm:"-"`
37+
State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
38+
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
39+
40+
// TargetURL points to the commit status page reported by a CI system
41+
// If Gitea Actions is used, it is a relative link like "{RepoLink}/actions/runs/{RunID}/jobs{JobID}"
42+
TargetURL string `xorm:"TEXT"`
43+
44+
Description string `xorm:"TEXT"`
45+
ContextHash string `xorm:"VARCHAR(64) index"`
46+
Context string `xorm:"TEXT"`
47+
Creator *user_model.User `xorm:"-"`
4448
CreatorID int64
4549

4650
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
@@ -211,21 +215,45 @@ func (status *CommitStatus) LocaleString(lang translation.Locale) string {
211215

212216
// HideActionsURL set `TargetURL` to an empty string if the status comes from Gitea Actions
213217
func (status *CommitStatus) HideActionsURL(ctx context.Context) {
218+
if _, ok := status.cutTargetURLGiteaActionsPrefix(ctx); ok {
219+
status.TargetURL = ""
220+
}
221+
}
222+
223+
func (status *CommitStatus) cutTargetURLGiteaActionsPrefix(ctx context.Context) (string, bool) {
214224
if status.RepoID == 0 {
215-
return
225+
return "", false
216226
}
217227

218228
if status.Repo == nil {
219229
if err := status.loadRepository(ctx); err != nil {
220230
log.Error("loadRepository: %v", err)
221-
return
231+
return "", false
222232
}
223233
}
224234

225235
prefix := status.Repo.Link() + "/actions"
226-
if strings.HasPrefix(status.TargetURL, prefix) {
227-
status.TargetURL = ""
236+
return strings.CutPrefix(status.TargetURL, prefix)
237+
}
238+
239+
// ParseGiteaActionsTargetURL parses the commit status target URL as Gitea Actions link
240+
func (status *CommitStatus) ParseGiteaActionsTargetURL(ctx context.Context) (runID, jobID int64, ok bool) {
241+
s, ok := status.cutTargetURLGiteaActionsPrefix(ctx)
242+
if !ok {
243+
return 0, 0, false
244+
}
245+
246+
parts := strings.Split(s, "/") // expect: /runs/{runID}/jobs/{jobID}
247+
if len(parts) < 5 || parts[1] != "runs" || parts[3] != "jobs" {
248+
return 0, 0, false
249+
}
250+
251+
runID, err1 := strconv.ParseInt(parts[2], 10, 64)
252+
jobID, err2 := strconv.ParseInt(parts[4], 10, 64)
253+
if err1 != nil || err2 != nil {
254+
return 0, 0, false
228255
}
256+
return runID, jobID, true
229257
}
230258

231259
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc

models/issues/issue_search.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ func applySubscribedCondition(sess *xorm.Session, subscriberID int64) {
476476
),
477477
builder.Eq{"issue.poster_id": subscriberID},
478478
builder.In("issue.repo_id", builder.
479-
Select("id").
479+
Select("repo_id").
480480
From("watch").
481481
Where(builder.And(builder.Eq{"user_id": subscriberID},
482482
builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))),

models/issues/issue_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ func TestIssues(t *testing.T) {
197197
},
198198
[]int64{2},
199199
},
200+
{
201+
issues_model.IssuesOptions{
202+
SubscriberID: 11,
203+
},
204+
[]int64{11, 5, 9, 8, 3, 2, 1},
205+
},
200206
} {
201207
issues, err := issues_model.Issues(t.Context(), &test.Opts)
202208
assert.NoError(t, err)

models/repo/repo.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,6 @@ func RelativePath(ownerName, repoName string) string {
229229
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
230230
}
231231

232-
func RelativeWikiPath(ownerName, repoName string) string {
233-
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
234-
}
235-
236232
// RelativePath should be an unix style path like username/reponame.git
237233
func (repo *Repository) RelativePath() string {
238234
return RelativePath(repo.OwnerName, repo.Name)
@@ -245,12 +241,6 @@ func (sr StorageRepo) RelativePath() string {
245241
return string(sr)
246242
}
247243

248-
// WikiStorageRepo returns the storage repo for the wiki
249-
// The wiki repository should have the same object format as the code repository
250-
func (repo *Repository) WikiStorageRepo() StorageRepo {
251-
return StorageRepo(RelativeWikiPath(repo.OwnerName, repo.Name))
252-
}
253-
254244
// SanitizedOriginalURL returns a sanitized OriginalURL
255245
func (repo *Repository) SanitizedOriginalURL() string {
256246
if repo.OriginalURL == "" {

models/repo/wiki.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package repo
77
import (
88
"context"
99
"fmt"
10-
"path/filepath"
1110
"strings"
1211

1312
user_model "code.gitea.io/gitea/models/user"
@@ -76,12 +75,12 @@ func (repo *Repository) WikiCloneLink(ctx context.Context, doer *user_model.User
7675
return repo.cloneLink(ctx, doer, repo.Name+".wiki")
7776
}
7877

79-
// WikiPath returns wiki data path by given user and repository name.
80-
func WikiPath(userName, repoName string) string {
81-
return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".wiki.git")
78+
func RelativeWikiPath(ownerName, repoName string) string {
79+
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
8280
}
8381

84-
// WikiPath returns wiki data path for given repository.
85-
func (repo *Repository) WikiPath() string {
86-
return WikiPath(repo.OwnerName, repo.Name)
82+
// WikiStorageRepo returns the storage repo for the wiki
83+
// The wiki repository should have the same object format as the code repository
84+
func (repo *Repository) WikiStorageRepo() StorageRepo {
85+
return StorageRepo(RelativeWikiPath(repo.OwnerName, repo.Name))
8786
}

models/repo/wiki_test.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
package repo_test
55

66
import (
7-
"path/filepath"
87
"testing"
98

109
repo_model "code.gitea.io/gitea/models/repo"
1110
"code.gitea.io/gitea/models/unittest"
12-
"code.gitea.io/gitea/modules/setting"
1311

1412
"github.com/stretchr/testify/assert"
1513
)
@@ -23,15 +21,10 @@ func TestRepository_WikiCloneLink(t *testing.T) {
2321
assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS)
2422
}
2523

26-
func TestWikiPath(t *testing.T) {
24+
func TestRepository_RelativeWikiPath(t *testing.T) {
2725
assert.NoError(t, unittest.PrepareTestDatabase())
28-
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
29-
assert.Equal(t, expected, repo_model.WikiPath("user2", "repo1"))
30-
}
3126

32-
func TestRepository_WikiPath(t *testing.T) {
33-
assert.NoError(t, unittest.PrepareTestDatabase())
3427
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
35-
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
36-
assert.Equal(t, expected, repo.WikiPath())
28+
assert.Equal(t, "user2/repo1.wiki.git", repo_model.RelativeWikiPath(repo.OwnerName, repo.Name))
29+
assert.Equal(t, "user2/repo1.wiki.git", repo.WikiStorageRepo().RelativePath())
3730
}

modules/emoji/emoji.go

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"io"
99
"sort"
1010
"strings"
11-
"sync"
11+
"sync/atomic"
12+
13+
"code.gitea.io/gitea/modules/setting"
1214
)
1315

1416
// Gemoji is a set of emoji data.
@@ -23,74 +25,78 @@ type Emoji struct {
2325
SkinTones bool
2426
}
2527

26-
var (
27-
// codeMap provides a map of the emoji unicode code to its emoji data.
28-
codeMap map[string]int
29-
30-
// aliasMap provides a map of the alias to its emoji data.
31-
aliasMap map[string]int
32-
33-
// emptyReplacer is the string replacer for emoji codes.
34-
emptyReplacer *strings.Replacer
35-
36-
// codeReplacer is the string replacer for emoji codes.
37-
codeReplacer *strings.Replacer
38-
39-
// aliasReplacer is the string replacer for emoji aliases.
40-
aliasReplacer *strings.Replacer
41-
42-
once sync.Once
43-
)
28+
type globalVarsStruct struct {
29+
codeMap map[string]int // emoji unicode code to its emoji data.
30+
aliasMap map[string]int // the alias to its emoji data.
31+
emptyReplacer *strings.Replacer // string replacer for emoji codes, used for finding emoji positions.
32+
codeReplacer *strings.Replacer // string replacer for emoji codes.
33+
aliasReplacer *strings.Replacer // string replacer for emoji aliases.
34+
}
4435

45-
func loadMap() {
46-
once.Do(func() {
47-
// initialize
48-
codeMap = make(map[string]int, len(GemojiData))
49-
aliasMap = make(map[string]int, len(GemojiData))
36+
var globalVarsStore atomic.Pointer[globalVarsStruct]
5037

51-
// process emoji codes and aliases
52-
codePairs := make([]string, 0)
53-
emptyPairs := make([]string, 0)
54-
aliasPairs := make([]string, 0)
38+
func globalVars() *globalVarsStruct {
39+
vars := globalVarsStore.Load()
40+
if vars != nil {
41+
return vars
42+
}
43+
// although there can be concurrent calls, the result should be the same, and there is no performance problem
44+
vars = &globalVarsStruct{}
45+
vars.codeMap = make(map[string]int, len(GemojiData))
46+
vars.aliasMap = make(map[string]int, len(GemojiData))
47+
48+
// process emoji codes and aliases
49+
codePairs := make([]string, 0)
50+
emptyPairs := make([]string, 0)
51+
aliasPairs := make([]string, 0)
52+
53+
// sort from largest to small so we match combined emoji first
54+
sort.Slice(GemojiData, func(i, j int) bool {
55+
return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji)
56+
})
5557

56-
// sort from largest to small so we match combined emoji first
57-
sort.Slice(GemojiData, func(i, j int) bool {
58-
return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji)
59-
})
58+
for idx, emoji := range GemojiData {
59+
if emoji.Emoji == "" || len(emoji.Aliases) == 0 {
60+
continue
61+
}
6062

61-
for i, e := range GemojiData {
62-
if e.Emoji == "" || len(e.Aliases) == 0 {
63+
// process aliases
64+
firstAlias := ""
65+
for _, alias := range emoji.Aliases {
66+
if alias == "" {
6367
continue
6468
}
65-
66-
// setup codes
67-
codeMap[e.Emoji] = i
68-
codePairs = append(codePairs, e.Emoji, ":"+e.Aliases[0]+":")
69-
emptyPairs = append(emptyPairs, e.Emoji, e.Emoji)
70-
71-
// setup aliases
72-
for _, a := range e.Aliases {
73-
if a == "" {
74-
continue
75-
}
76-
77-
aliasMap[a] = i
78-
aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji)
69+
enabled := len(setting.UI.EnabledEmojisSet) == 0 || setting.UI.EnabledEmojisSet.Contains(alias)
70+
if !enabled {
71+
continue
7972
}
73+
if firstAlias == "" {
74+
firstAlias = alias
75+
}
76+
vars.aliasMap[alias] = idx
77+
aliasPairs = append(aliasPairs, ":"+alias+":", emoji.Emoji)
8078
}
8179

82-
// create replacers
83-
emptyReplacer = strings.NewReplacer(emptyPairs...)
84-
codeReplacer = strings.NewReplacer(codePairs...)
85-
aliasReplacer = strings.NewReplacer(aliasPairs...)
86-
})
80+
// process emoji code
81+
if firstAlias != "" {
82+
vars.codeMap[emoji.Emoji] = idx
83+
codePairs = append(codePairs, emoji.Emoji, ":"+emoji.Aliases[0]+":")
84+
emptyPairs = append(emptyPairs, emoji.Emoji, emoji.Emoji)
85+
}
86+
}
87+
88+
// create replacers
89+
vars.emptyReplacer = strings.NewReplacer(emptyPairs...)
90+
vars.codeReplacer = strings.NewReplacer(codePairs...)
91+
vars.aliasReplacer = strings.NewReplacer(aliasPairs...)
92+
globalVarsStore.Store(vars)
93+
return vars
8794
}
8895

8996
// FromCode retrieves the emoji data based on the provided unicode code (ie,
9097
// "\u2618" will return the Gemoji data for "shamrock").
9198
func FromCode(code string) *Emoji {
92-
loadMap()
93-
i, ok := codeMap[code]
99+
i, ok := globalVars().codeMap[code]
94100
if !ok {
95101
return nil
96102
}
@@ -102,12 +108,11 @@ func FromCode(code string) *Emoji {
102108
// "alias" or ":alias:" (ie, "shamrock" or ":shamrock:" will return the Gemoji
103109
// data for "shamrock").
104110
func FromAlias(alias string) *Emoji {
105-
loadMap()
106111
if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") {
107112
alias = alias[1 : len(alias)-1]
108113
}
109114

110-
i, ok := aliasMap[alias]
115+
i, ok := globalVars().aliasMap[alias]
111116
if !ok {
112117
return nil
113118
}
@@ -119,15 +124,13 @@ func FromAlias(alias string) *Emoji {
119124
// alias (in the form of ":alias:") (ie, "\u2618" will be converted to
120125
// ":shamrock:").
121126
func ReplaceCodes(s string) string {
122-
loadMap()
123-
return codeReplacer.Replace(s)
127+
return globalVars().codeReplacer.Replace(s)
124128
}
125129

126130
// ReplaceAliases replaces all aliases of the form ":alias:" with its
127131
// corresponding unicode value.
128132
func ReplaceAliases(s string) string {
129-
loadMap()
130-
return aliasReplacer.Replace(s)
133+
return globalVars().aliasReplacer.Replace(s)
131134
}
132135

133136
type rememberSecondWriteWriter struct {
@@ -163,7 +166,6 @@ func (n *rememberSecondWriteWriter) WriteString(s string) (int, error) {
163166

164167
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
165168
func FindEmojiSubmatchIndex(s string) []int {
166-
loadMap()
167169
secondWriteWriter := rememberSecondWriteWriter{}
168170

169171
// A faster and clean implementation would copy the trie tree formation in strings.NewReplacer but
@@ -175,7 +177,7 @@ func FindEmojiSubmatchIndex(s string) []int {
175177
// Therefore we can simply take the index of the second write as our first emoji
176178
//
177179
// FIXME: just copy the trie implementation from strings.NewReplacer
178-
_, _ = emptyReplacer.WriteString(&secondWriteWriter, s)
180+
_, _ = globalVars().emptyReplacer.WriteString(&secondWriteWriter, s)
179181

180182
// if we wrote less than twice then we never "replaced"
181183
if secondWriteWriter.writecount < 2 {

0 commit comments

Comments
 (0)