Skip to content

Commit 81092d6

Browse files
authored
Merge branch 'main' into quick-approval-action-btn
2 parents 59625a1 + 66ee8f3 commit 81092d6

File tree

19 files changed

+866
-228
lines changed

19 files changed

+866
-228
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
;;

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 {

modules/emoji/emoji_test.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@ package emoji
77
import (
88
"testing"
99

10+
"code.gitea.io/gitea/modules/container"
11+
"code.gitea.io/gitea/modules/setting"
12+
"code.gitea.io/gitea/modules/test"
13+
1014
"github.com/stretchr/testify/assert"
1115
)
1216

13-
func TestDumpInfo(t *testing.T) {
14-
t.Logf("codes: %d", len(codeMap))
15-
t.Logf("aliases: %d", len(aliasMap))
16-
}
17-
1817
func TestLookup(t *testing.T) {
1918
a := FromCode("\U0001f37a")
2019
b := FromCode("🍺")
@@ -24,15 +23,27 @@ func TestLookup(t *testing.T) {
2423
assert.Equal(t, a, b)
2524
assert.Equal(t, b, c)
2625
assert.Equal(t, c, d)
27-
assert.Equal(t, a, d)
2826

2927
m := FromCode("\U0001f44d")
3028
n := FromAlias(":thumbsup:")
3129
o := FromAlias("+1")
3230

3331
assert.Equal(t, m, n)
3432
assert.Equal(t, m, o)
35-
assert.Equal(t, n, o)
33+
34+
defer test.MockVariableValue(&setting.UI.EnabledEmojisSet, container.SetOf("thumbsup"))()
35+
defer globalVarsStore.Store(nil)
36+
globalVarsStore.Store(nil)
37+
a = FromCode("\U0001f37a")
38+
c = FromAlias(":beer:")
39+
m = FromCode("\U0001f44d")
40+
n = FromAlias(":thumbsup:")
41+
o = FromAlias("+1")
42+
assert.Nil(t, a)
43+
assert.Nil(t, c)
44+
assert.NotNil(t, m)
45+
assert.NotNil(t, n)
46+
assert.Nil(t, o)
3647
}
3748

3849
func TestReplacers(t *testing.T) {

modules/markup/html_emoji.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package markup
55

66
import (
77
"strings"
8+
"unicode"
89

910
"code.gitea.io/gitea/modules/emoji"
1011
"code.gitea.io/gitea/modules/setting"
@@ -66,26 +67,31 @@ func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
6667
}
6768
m[0] += start
6869
m[1] += start
69-
7070
start = m[1]
7171

7272
alias := node.Data[m[0]:m[1]]
73-
alias = strings.ReplaceAll(alias, ":", "")
74-
converted := emoji.FromAlias(alias)
75-
if converted == nil {
76-
// check if this is a custom reaction
77-
if _, exist := setting.UI.CustomEmojisMap[alias]; exist {
78-
replaceContent(node, m[0], m[1], createCustomEmoji(ctx, alias))
79-
node = node.NextSibling.NextSibling
80-
start = 0
81-
continue
82-
}
73+
74+
var nextChar byte
75+
if m[1] < len(node.Data) {
76+
nextChar = node.Data[m[1]]
77+
}
78+
if nextChar == ':' || unicode.IsLetter(rune(nextChar)) || unicode.IsDigit(rune(nextChar)) {
8379
continue
8480
}
8581

86-
replaceContent(node, m[0], m[1], createEmoji(ctx, converted.Emoji, converted.Description))
87-
node = node.NextSibling.NextSibling
88-
start = 0
82+
alias = strings.Trim(alias, ":")
83+
converted := emoji.FromAlias(alias)
84+
if converted != nil {
85+
// standard emoji
86+
replaceContent(node, m[0], m[1], createEmoji(ctx, converted.Emoji, converted.Description))
87+
node = node.NextSibling.NextSibling
88+
start = 0 // restart searching start since node has changed
89+
} else if _, exist := setting.UI.CustomEmojisMap[alias]; exist {
90+
// custom reaction
91+
replaceContent(node, m[0], m[1], createCustomEmoji(ctx, alias))
92+
node = node.NextSibling.NextSibling
93+
start = 0 // restart searching start since node has changed
94+
}
8995
}
9096
}
9197

modules/markup/html_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -357,12 +357,9 @@ func TestRender_emoji(t *testing.T) {
357357
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="red question mark">❓</span></p>`)
358358

359359
// should match nothing
360-
test(
361-
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
362-
`<p>2001:0db8:85a3:0000:0000:8a2e:0370:7334</p>`)
363-
test(
364-
":not exist:",
365-
`<p>:not exist:</p>`)
360+
test(":100:200", `<p>:100:200</p>`)
361+
test("std::thread::something", `<p>std::thread::something</p>`)
362+
test(":not exist:", `<p>:not exist:</p>`)
366363
}
367364

368365
func TestRender_ShortLinks(t *testing.T) {

modules/setting/ui.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ var UI = struct {
3333
ReactionsLookup container.Set[string] `ini:"-"`
3434
CustomEmojis []string
3535
CustomEmojisMap map[string]string `ini:"-"`
36+
EnabledEmojis []string
37+
EnabledEmojisSet container.Set[string] `ini:"-"`
3638
SearchRepoDescription bool
3739
OnlyShowRelevantRepos bool
3840
ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"`
@@ -169,4 +171,5 @@ func loadUIFrom(rootCfg ConfigProvider) {
169171
for _, emoji := range UI.CustomEmojis {
170172
UI.CustomEmojisMap[emoji] = ":" + emoji + ":"
171173
}
174+
UI.EnabledEmojisSet = container.SetOf(UI.EnabledEmojis...)
172175
}

routers/web/repo/commit.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -276,20 +276,24 @@ func Diff(ctx *context.Context) {
276276
userName := ctx.Repo.Owner.Name
277277
repoName := ctx.Repo.Repository.Name
278278
commitID := ctx.PathParam("sha")
279-
var (
280-
gitRepo *git.Repository
281-
err error
282-
)
279+
280+
diffBlobExcerptData := &gitdiff.DiffBlobExcerptData{
281+
BaseLink: ctx.Repo.RepoLink + "/blob_excerpt",
282+
DiffStyle: ctx.FormString("style"),
283+
AfterCommitID: commitID,
284+
}
285+
gitRepo := ctx.Repo.GitRepo
286+
var gitRepoStore gitrepo.Repository = ctx.Repo.Repository
283287

284288
if ctx.Data["PageIsWiki"] != nil {
285-
gitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
289+
var err error
290+
gitRepoStore = ctx.Repo.Repository.WikiStorageRepo()
291+
gitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, gitRepoStore)
286292
if err != nil {
287293
ctx.ServerError("Repo.GitRepo.GetCommit", err)
288294
return
289295
}
290-
defer gitRepo.Close()
291-
} else {
292-
gitRepo = ctx.Repo.GitRepo
296+
diffBlobExcerptData.BaseLink = ctx.Repo.RepoLink + "/wiki/blob_excerpt"
293297
}
294298

295299
commit, err := gitRepo.GetCommit(commitID)
@@ -324,7 +328,7 @@ func Diff(ctx *context.Context) {
324328
ctx.NotFound(err)
325329
return
326330
}
327-
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, gitRepo, "", commitID)
331+
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, gitRepoStore, gitRepo, "", commitID)
328332
if err != nil {
329333
ctx.ServerError("GetDiffShortStat", err)
330334
return
@@ -360,6 +364,7 @@ func Diff(ctx *context.Context) {
360364
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID)
361365
ctx.Data["Commit"] = commit
362366
ctx.Data["Diff"] = diff
367+
ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData
363368

364369
if !fileOnly {
365370
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, parentCommitID, commitID)

0 commit comments

Comments
 (0)