Skip to content

Commit 123a027

Browse files
committed
Merge remote-tracking branch 'origin/main' into zzc/dev/org_profile_2
2 parents d3f415d + 0f09c22 commit 123a027

File tree

42 files changed

+1582
-320
lines changed

Some content is hidden

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

42 files changed

+1582
-320
lines changed

custom/conf/app.example.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ RUN_USER = ; git
8181
;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
8282
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
8383
;;
84+
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
85+
;; DO NOT USE IT IN PRODUCTION!!!
86+
;USE_SUB_URL_PATH = false
87+
;;
8488
;; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
8589
;STATIC_URL_PREFIX =
8690
;;

models/git/protected_tag.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ func GetProtectedTagByID(ctx context.Context, id int64) (*ProtectedTag, error) {
110110
return tag, nil
111111
}
112112

113+
// GetProtectedTagByNamePattern gets protected tag by name_pattern
114+
func GetProtectedTagByNamePattern(ctx context.Context, repoID int64, pattern string) (*ProtectedTag, error) {
115+
tag := &ProtectedTag{NamePattern: pattern, RepoID: repoID}
116+
has, err := db.GetEngine(ctx).Get(tag)
117+
if err != nil {
118+
return nil, err
119+
}
120+
if !has {
121+
return nil, nil
122+
}
123+
return tag, nil
124+
}
125+
113126
// IsUserAllowedToControlTag checks if a user can control the specific tag.
114127
// It returns true if the tag name is not protected or the user is allowed to control it.
115128
func IsUserAllowedToControlTag(ctx context.Context, tags []*ProtectedTag, tagName string, userID int64) (bool, error) {

models/repo/avatar_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package repo
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/db"
10+
"code.gitea.io/gitea/modules/setting"
11+
"code.gitea.io/gitea/modules/test"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestRepoAvatarLink(t *testing.T) {
17+
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
18+
defer test.MockVariableValue(&setting.AppSubURL, "")()
19+
20+
repo := &Repository{ID: 1, Avatar: "avatar.png"}
21+
link := repo.AvatarLink(db.DefaultContext)
22+
assert.Equal(t, "https://localhost/repo-avatars/avatar.png", link)
23+
24+
setting.AppURL = "https://localhost/sub-path/"
25+
setting.AppSubURL = "/sub-path"
26+
link = repo.AvatarLink(db.DefaultContext)
27+
assert.Equal(t, "https://localhost/sub-path/repo-avatars/avatar.png", link)
28+
}

models/repo/search.go

Lines changed: 37 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,72 +5,48 @@ package repo
55

66
import "code.gitea.io/gitea/models/db"
77

8-
// Strings for sorting result
9-
const (
10-
// only used for repos
11-
SearchOrderByAlphabetically db.SearchOrderBy = "owner_name ASC, name ASC"
12-
SearchOrderByAlphabeticallyReverse db.SearchOrderBy = "owner_name DESC, name DESC"
13-
SearchOrderBySize db.SearchOrderBy = "size ASC"
14-
SearchOrderBySizeReverse db.SearchOrderBy = "size DESC"
15-
SearchOrderByGitSize db.SearchOrderBy = "git_size ASC"
16-
SearchOrderByGitSizeReverse db.SearchOrderBy = "git_size DESC"
17-
SearchOrderByLFSSize db.SearchOrderBy = "lfs_size ASC"
18-
SearchOrderByLFSSizeReverse db.SearchOrderBy = "lfs_size DESC"
19-
// alias as also used elsewhere
20-
SearchOrderByLeastUpdated db.SearchOrderBy = db.SearchOrderByLeastUpdated
21-
SearchOrderByRecentUpdated db.SearchOrderBy = db.SearchOrderByRecentUpdated
22-
SearchOrderByOldest db.SearchOrderBy = db.SearchOrderByOldest
23-
SearchOrderByNewest db.SearchOrderBy = db.SearchOrderByNewest
24-
SearchOrderByID db.SearchOrderBy = db.SearchOrderByID
25-
SearchOrderByIDReverse db.SearchOrderBy = db.SearchOrderByIDReverse
26-
SearchOrderByStars db.SearchOrderBy = db.SearchOrderByStars
27-
SearchOrderByStarsReverse db.SearchOrderBy = db.SearchOrderByStarsReverse
28-
SearchOrderByForks db.SearchOrderBy = db.SearchOrderByForks
29-
SearchOrderByForksReverse db.SearchOrderBy = db.SearchOrderByForksReverse
30-
)
31-
32-
// SearchOrderByMap represents all possible search order
33-
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
8+
// OrderByMap represents all possible search order
9+
var OrderByMap = map[string]map[string]db.SearchOrderBy{
3410
"asc": {
35-
"alpha": SearchOrderByAlphabetically,
36-
"created": SearchOrderByOldest,
37-
"updated": SearchOrderByLeastUpdated,
38-
"size": SearchOrderBySize,
39-
"git_size": SearchOrderByGitSize,
40-
"lfs_size": SearchOrderByLFSSize,
41-
"id": SearchOrderByID,
42-
"stars": SearchOrderByStars,
43-
"forks": SearchOrderByForks,
11+
"alpha": "owner_name ASC, name ASC",
12+
"created": db.SearchOrderByOldest,
13+
"updated": db.SearchOrderByLeastUpdated,
14+
"size": "size ASC",
15+
"git_size": "git_size ASC",
16+
"lfs_size": "lfs_size ASC",
17+
"id": db.SearchOrderByID,
18+
"stars": db.SearchOrderByStars,
19+
"forks": db.SearchOrderByForks,
4420
},
4521
"desc": {
46-
"alpha": SearchOrderByAlphabeticallyReverse,
47-
"created": SearchOrderByNewest,
48-
"updated": SearchOrderByRecentUpdated,
49-
"size": SearchOrderBySizeReverse,
50-
"git_size": SearchOrderByGitSizeReverse,
51-
"lfs_size": SearchOrderByLFSSizeReverse,
52-
"id": SearchOrderByIDReverse,
53-
"stars": SearchOrderByStarsReverse,
54-
"forks": SearchOrderByForksReverse,
22+
"alpha": "owner_name DESC, name DESC",
23+
"created": db.SearchOrderByNewest,
24+
"updated": db.SearchOrderByRecentUpdated,
25+
"size": "size DESC",
26+
"git_size": "git_size DESC",
27+
"lfs_size": "lfs_size DESC",
28+
"id": db.SearchOrderByIDReverse,
29+
"stars": db.SearchOrderByStarsReverse,
30+
"forks": db.SearchOrderByForksReverse,
5531
},
5632
}
5733

58-
// SearchOrderByFlatMap is similar to SearchOrderByMap but use human language keywords
34+
// OrderByFlatMap is similar to OrderByMap but use human language keywords
5935
// to decide between asc and desc
60-
var SearchOrderByFlatMap = map[string]db.SearchOrderBy{
61-
"newest": SearchOrderByMap["desc"]["created"],
62-
"oldest": SearchOrderByMap["asc"]["created"],
63-
"leastupdate": SearchOrderByMap["asc"]["updated"],
64-
"reversealphabetically": SearchOrderByMap["desc"]["alpha"],
65-
"alphabetically": SearchOrderByMap["asc"]["alpha"],
66-
"reversesize": SearchOrderByMap["desc"]["size"],
67-
"size": SearchOrderByMap["asc"]["size"],
68-
"reversegitsize": SearchOrderByMap["desc"]["git_size"],
69-
"gitsize": SearchOrderByMap["asc"]["git_size"],
70-
"reverselfssize": SearchOrderByMap["desc"]["lfs_size"],
71-
"lfssize": SearchOrderByMap["asc"]["lfs_size"],
72-
"moststars": SearchOrderByMap["desc"]["stars"],
73-
"feweststars": SearchOrderByMap["asc"]["stars"],
74-
"mostforks": SearchOrderByMap["desc"]["forks"],
75-
"fewestforks": SearchOrderByMap["asc"]["forks"],
36+
var OrderByFlatMap = map[string]db.SearchOrderBy{
37+
"newest": OrderByMap["desc"]["created"],
38+
"oldest": OrderByMap["asc"]["created"],
39+
"leastupdate": OrderByMap["asc"]["updated"],
40+
"reversealphabetically": OrderByMap["desc"]["alpha"],
41+
"alphabetically": OrderByMap["asc"]["alpha"],
42+
"reversesize": OrderByMap["desc"]["size"],
43+
"size": OrderByMap["asc"]["size"],
44+
"reversegitsize": OrderByMap["desc"]["git_size"],
45+
"gitsize": OrderByMap["asc"]["git_size"],
46+
"reverselfssize": OrderByMap["desc"]["lfs_size"],
47+
"lfssize": OrderByMap["asc"]["lfs_size"],
48+
"moststars": OrderByMap["desc"]["stars"],
49+
"feweststars": OrderByMap["asc"]["stars"],
50+
"mostforks": OrderByMap["desc"]["forks"],
51+
"fewestforks": OrderByMap["asc"]["forks"],
7652
}

models/user/avatar.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,11 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
8989
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
9090
}
9191

92-
// AvatarLink returns the full avatar url with http host. TODO: refactor it to a relative URL, but it is still used in API response at the moment
92+
// AvatarLink returns the full avatar url with http host.
93+
// TODO: refactor it to a relative URL, but it is still used in API response at the moment
9394
func (u *User) AvatarLink(ctx context.Context) string {
94-
return httplib.MakeAbsoluteURL(ctx, u.AvatarLinkWithSize(ctx, 0))
95+
relLink := u.AvatarLinkWithSize(ctx, 0) // it can't be empty
96+
return httplib.MakeAbsoluteURL(ctx, relLink)
9597
}
9698

9799
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data

models/user/avatar_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package user
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/db"
10+
"code.gitea.io/gitea/modules/setting"
11+
"code.gitea.io/gitea/modules/test"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestUserAvatarLink(t *testing.T) {
17+
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
18+
defer test.MockVariableValue(&setting.AppSubURL, "")()
19+
20+
u := &User{ID: 1, Avatar: "avatar.png"}
21+
link := u.AvatarLink(db.DefaultContext)
22+
assert.Equal(t, "https://localhost/avatars/avatar.png", link)
23+
24+
setting.AppURL = "https://localhost/sub-path/"
25+
setting.AppSubURL = "/sub-path"
26+
link = u.AvatarLink(db.DefaultContext)
27+
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
28+
}

modules/base/natural_sort.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,67 @@
44
package base
55

66
import (
7+
"unicode/utf8"
8+
79
"golang.org/x/text/collate"
810
"golang.org/x/text/language"
911
)
1012

13+
func naturalSortGetRune(str string, pos int) (r rune, size int, has bool) {
14+
if pos >= len(str) {
15+
return 0, 0, false
16+
}
17+
r, size = utf8.DecodeRuneInString(str[pos:])
18+
if r == utf8.RuneError {
19+
r, size = rune(str[pos]), 1 // if invalid input, treat it as a single byte ascii
20+
}
21+
return r, size, true
22+
}
23+
24+
func naturalSortAdvance(str string, pos int) (end int, isNumber bool) {
25+
end = pos
26+
for {
27+
r, size, has := naturalSortGetRune(str, end)
28+
if !has {
29+
break
30+
}
31+
isCurRuneNum := '0' <= r && r <= '9'
32+
if end == pos {
33+
isNumber = isCurRuneNum
34+
end += size
35+
} else if isCurRuneNum == isNumber {
36+
end += size
37+
} else {
38+
break
39+
}
40+
}
41+
return end, isNumber
42+
}
43+
1144
// NaturalSortLess compares two strings so that they could be sorted in natural order
1245
func NaturalSortLess(s1, s2 string) bool {
46+
// There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997
47+
// text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997
48+
// So we need to handle the number parts by ourselves
1349
c := collate.New(language.English, collate.Numeric)
14-
return c.CompareString(s1, s2) < 0
50+
pos1, pos2 := 0, 0
51+
for pos1 < len(s1) && pos2 < len(s2) {
52+
end1, isNum1 := naturalSortAdvance(s1, pos1)
53+
end2, isNum2 := naturalSortAdvance(s2, pos2)
54+
part1, part2 := s1[pos1:end1], s2[pos2:end2]
55+
if isNum1 && isNum2 {
56+
if part1 != part2 {
57+
if len(part1) != len(part2) {
58+
return len(part1) < len(part2)
59+
}
60+
return part1 < part2
61+
}
62+
} else {
63+
if cmp := c.CompareString(part1, part2); cmp != 0 {
64+
return cmp < 0
65+
}
66+
}
67+
pos1, pos2 = end1, end2
68+
}
69+
return len(s1) < len(s2)
1570
}

modules/base/natural_sort_test.go

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,36 @@ import (
1010
)
1111

1212
func TestNaturalSortLess(t *testing.T) {
13-
test := func(s1, s2 string, less bool) {
14-
assert.Equal(t, less, NaturalSortLess(s1, s2), "s1=%q, s2=%q", s1, s2)
13+
testLess := func(s1, s2 string) {
14+
assert.True(t, NaturalSortLess(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
15+
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
1516
}
16-
test("v1.20.0", "v1.2.0", false)
17-
test("v1.20.0", "v1.29.0", true)
18-
test("v1.20.0", "v1.20.0", false)
19-
test("abc", "bcd", true)
20-
test("a-1-a", "a-1-b", true)
21-
test("2", "12", true)
22-
test("a", "ab", true)
23-
24-
test("A", "b", true)
25-
test("a", "B", true)
26-
27-
test("cafe", "café", true)
28-
test("café", "cafe", false)
29-
test("caff", "café", false)
17+
testEqual := func(s1, s2 string) {
18+
assert.False(t, NaturalSortLess(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
19+
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
20+
}
21+
22+
testEqual("", "")
23+
testLess("", "a")
24+
testLess("", "1")
25+
26+
testLess("v1.2", "v1.2.0")
27+
testLess("v1.2.0", "v1.10.0")
28+
testLess("v1.20.0", "v1.29.0")
29+
testEqual("v1.20.0", "v1.20.0")
30+
31+
testLess("a", "A")
32+
testLess("a", "B")
33+
testLess("A", "b")
34+
testLess("A", "ab")
35+
36+
testLess("abc", "bcd")
37+
testLess("a-1-a", "a-1-b")
38+
testLess("2", "12")
39+
40+
testLess("cafe", "café")
41+
testLess("café", "caff")
42+
43+
testLess("A-2", "A-11")
44+
testLess("0.txt", "1.txt")
3045
}

modules/httplib/url.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,16 @@ func getForwardedHost(req *http.Request) string {
5757
return req.Header.Get("X-Forwarded-Host")
5858
}
5959

60-
// GuessCurrentAppURL tries to guess the current full URL by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
60+
// GuessCurrentAppURL tries to guess the current full app URL (with sub-path) by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
6161
func GuessCurrentAppURL(ctx context.Context) string {
62+
return GuessCurrentHostURL(ctx) + setting.AppSubURL + "/"
63+
}
64+
65+
// GuessCurrentHostURL tries to guess the current full host URL (no sub-path) by http headers, there is no trailing slash.
66+
func GuessCurrentHostURL(ctx context.Context) string {
6267
req, ok := ctx.Value(RequestContextKey).(*http.Request)
6368
if !ok {
64-
return setting.AppURL
69+
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
6570
}
6671
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
6772
// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
@@ -74,20 +79,27 @@ func GuessCurrentAppURL(ctx context.Context) string {
7479
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
7580
reqScheme := getRequestScheme(req)
7681
if reqScheme == "" {
77-
return setting.AppURL
82+
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
7883
}
7984
reqHost := getForwardedHost(req)
8085
if reqHost == "" {
8186
reqHost = req.Host
8287
}
83-
return reqScheme + "://" + reqHost + setting.AppSubURL + "/"
88+
return reqScheme + "://" + reqHost
8489
}
8590

86-
func MakeAbsoluteURL(ctx context.Context, s string) string {
87-
if IsRelativeURL(s) {
88-
return GuessCurrentAppURL(ctx) + strings.TrimPrefix(s, "/")
91+
// MakeAbsoluteURL tries to make a link to an absolute URL:
92+
// * If link is empty, it returns the current app URL.
93+
// * If link is absolute, it returns the link.
94+
// * Otherwise, it returns the current host URL + link, the link itself should have correct sub-path (AppSubURL) if needed.
95+
func MakeAbsoluteURL(ctx context.Context, link string) string {
96+
if link == "" {
97+
return GuessCurrentAppURL(ctx)
98+
}
99+
if !IsRelativeURL(link) {
100+
return link
89101
}
90-
return s
102+
return GuessCurrentHostURL(ctx) + "/" + strings.TrimPrefix(link, "/")
91103
}
92104

93105
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {

0 commit comments

Comments
 (0)