Skip to content

Commit 54fe6ea

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: Fix JS error when creating new issue (go-gitea#31383) [skip ci] Updated translations via Crowdin Rename repo_model.SearchOrderByMap to repo_model.OrderByMap (go-gitea#31359) rm const do inline (go-gitea#31360) Allow downloading attachments of draft releases (go-gitea#31369) Fix duplicate sub-path for avatars (go-gitea#31365) [skip ci] Updated translations via Crowdin Add tag protection via rest api go-gitea#17862 (go-gitea#31295)
2 parents e5a9688 + f446e3b commit 54fe6ea

File tree

29 files changed

+986
-107
lines changed

29 files changed

+986
-107
lines changed

custom/conf/app.example.ini

+4
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

+13
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

+28
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

+37-61
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

+4-2
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

+28
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/httplib/url.go

+20-8
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 {

modules/httplib/url_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,22 @@ func TestMakeAbsoluteURL(t *testing.T) {
4646

4747
ctx := context.Background()
4848
assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
49-
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
50-
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
49+
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "foo"))
50+
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
5151
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
5252

5353
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
5454
Host: "user-host",
5555
})
56-
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
56+
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
5757

5858
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
5959
Host: "user-host",
6060
Header: map[string][]string{
6161
"X-Forwarded-Host": {"forwarded-host"},
6262
},
6363
})
64-
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
64+
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
6565

6666
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
6767
Host: "user-host",
@@ -70,7 +70,7 @@ func TestMakeAbsoluteURL(t *testing.T) {
7070
"X-Forwarded-Proto": {"https"},
7171
},
7272
})
73-
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
73+
assert.Equal(t, "https://forwarded-host/foo", MakeAbsoluteURL(ctx, "/foo"))
7474
}
7575

7676
func TestIsCurrentGiteaSiteURL(t *testing.T) {

modules/setting/global.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package setting
5+
6+
// Global settings
7+
var (
8+
// RunUser is the OS user that Gitea is running as. ini:"RUN_USER"
9+
RunUser string
10+
// RunMode is the running mode of Gitea, it only accepts two values: "dev" and "prod".
11+
// Non-dev values will be replaced by "prod". ini: "RUN_MODE"
12+
RunMode string
13+
// IsProd is true if RunMode is not "dev"
14+
IsProd bool
15+
16+
// AppName is the Application name, used in the page title. ini: "APP_NAME"
17+
AppName string
18+
)

modules/setting/server.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ const (
4040
LandingPageLogin LandingPage = "/user/login"
4141
)
4242

43+
// Server settings
4344
var (
44-
// AppName is the Application name, used in the page title.
45-
// It maps to ini:"APP_NAME"
46-
AppName string
4745
// AppURL is the Application ROOT_URL. It always has a '/' suffix
4846
// It maps to ini:"ROOT_URL"
4947
AppURL string
5048
// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
5149
// This value is empty if site does not have sub-url.
5250
AppSubURL string
51+
// UseSubURLPath makes Gitea handle requests with sub-path like "/sub-path/owner/repo/...", to make it easier to debug sub-path related problems without a reverse proxy.
52+
UseSubURLPath bool
5353
// AppDataPath is the default path for storing data.
5454
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
5555
AppDataPath string
@@ -59,8 +59,6 @@ var (
5959
// AssetVersion holds a opaque value that is used for cache-busting assets
6060
AssetVersion string
6161

62-
// Server settings
63-
6462
Protocol Scheme
6563
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
6664
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
@@ -275,9 +273,10 @@ func loadServerFrom(rootCfg ConfigProvider) {
275273
// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
276274
AppURL = strings.TrimRight(appURL.String(), "/") + "/"
277275

278-
// Suburl should start with '/' and end without '/', such as '/{subpath}'.
276+
// AppSubURL should start with '/' and end without '/', such as '/{subpath}'.
279277
// This value is empty if site does not have sub-url.
280278
AppSubURL = strings.TrimSuffix(appURL.Path, "/")
279+
UseSubURLPath = sec.Key("USE_SUB_URL_PATH").MustBool(false)
281280
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
282281

283282
// Check if Domain differs from AppURL domain than update it to AppURL's domain

modules/setting/setting.go

-5
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,7 @@ var (
2525
// AppStartTime store time gitea has started
2626
AppStartTime time.Time
2727

28-
// Other global setting objects
29-
3028
CfgProvider ConfigProvider
31-
RunMode string
32-
RunUser string
33-
IsProd bool
3429
IsWindows bool
3530

3631
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing

0 commit comments

Comments
 (0)